# Actions and API  Your application comes bundled with its own **auto-generated API**, which can be called from both your **frontend and backend**. This API includes a **GraphQL endpoint** as well as **type-safe JavaScript and React clients**, making it easy to interact with your backend in different contexts. **Calling the API** * **In the frontend**: The API can be called directly using the JavaScript/React client. * **In the backend**: The API is accessible within server-side functions, including Gadget actions. This automatically generated API includes [documentation](https://docs.gadget.dev/api/example-app/development/) exclusive to your Gadget application. These pages provide a more detailed overview of how to use your automatically generated API. **Using the API in Backend Actions** Gadget actions accept an `api` as an argument for your run function. This `api` object is an instance of the generated JavaScript client for your Gadget application. It works the same way in an action as it does elsewhere and has all the same functions documented in the [API Reference](https://docs.gadget.dev/api/example-app/development). You can use the `api` object to fetch other data in an action with `findOne`, `findMany`, `findById`, and other find actions: ```typescript // example: send a notification to the user who owns the record if they've opted in import twilio from "../../twilio-client"; export const run: ActionRun = async ({ api, record }) => { const creator = await api.user.findOne(record.creatorId, { select: { phoneNumber: true, wantsNotifications: true }, // Selecting only necessary fields for performance }); if (creator?.wantsNotifications) { await twilio.sendSMS({ to: creator.phoneNumber, from: "+11112223333", body: `Notification: ${record.title} was updated`, }); } }; ``` Or, you can use the `api` object to change other data in your application through `create`, `update`, and `delete` actions: ```typescript // example: save an audit log record when this record changes export const run: ActionRun = async ({ api, record }) => { await api.auditLog.create({ data: { action: "UPDATE", recordId: record.id, changes: record.changes, }, }); }; ``` ## Public API vs Internal API  Your application has two different levels of API: the Public API and the Internal API. The Public API runs the high-level actions you've defined in your app, and the Internal API runs low-level operations that change the database. Depending on what you're trying to accomplish with your action, it is sometimes appropriate to use the [Public API](https://docs.gadget.dev/api/example-app/development) of your application via `api.someModel.create`, and sometimes it's appropriate to use the [Internal API](https://docs.gadget.dev/api/example-app/development/internal-api) via `api.internal.someModel.create`. Generally, the **Public API** should be preferred. The Public API for a Gadget application performs all the steps of an action, including checking permissions, performing data validation, and executing the `run` and `onSuccess` function. The Public API can be accessed in both the backend and the frontend of your application. For example, you may choose to use the Public API to perform a CRUD action on a small batch of data. ```typescript await api.blogPost.create({ title: "My First Post", content: "This is my first post", date: new Date(), }); ``` Sometimes, though, you may want to skip all the steps of the Public API, and just interact with the database. The Internal API does just this. Unlike the Public API, the Internal API only performs database interactions, and skips any permission-checking, most validations (except required and unique), or custom code in your `run` and `onSuccess` functions. The Internal API can be used by running `api.internal.someModel.`. You can think of the Internal API like raw SQL statements in other frameworks where you might run explicit `INSERT` or `UPDATE` statements that skip over the other bits of the framework. The Internal API is significantly faster and is often used for building high-volume scripts that need to import data or touch many records quickly. ```typescript await api.internal.blogPost.bulkCreate([ { title: "post1", content: "Hello world", date: "2024-04-01" }, ]); ``` However, because the Internal API skips important logic for actions, it is generally not recommended for use unless you have an explicit reason. Additionally, the Internal API can only be accessed in the backend of your application, not in the frontend. ## actAsSession and actAsAdmin  [Roles](https://docs.gadget.dev/guides/access-control#roles) assigned to users and their sessions limit what APIs and database interactions are available. `api.actAsSession` and `api.actAsAdmin` let you choose whether code runs with session scoped access or admin access. With session scoped access, only actions the session has permission to access can be run. With admin access, all actions, including the [internal API](https://docs.gadget.dev/api/example-app/development/internal-api), can be run, regardless of the permissions of the current session. The default depends on runtime context. Model actions, global actions, and backend HTTP routes default to admin access. React Router and Remix request handlers, and requests made from the frontend, default to session scoped access. Use `api.actAsSession` when you want role filters and permissions to apply to actions. Use `api.actAsAdmin` when you need elevated access, such as running internal API operations from a session scoped context. ### actAsSession examples  For example, you might want to grant signed-in users on your blog access to exclusive posts. Unauthenticated users should not see these posts. In this case, using `api.actAsSession` would be useful: ```typescript export const run: ActionRun = async ({ api }) => { // assume that this action is called from an unauthenticated session // by default api has admin privileges, so this will be all posts const allPosts = await api.post.findMany(); // if we only want published posts, using api we need to filter for them const publishedPosts = await api.post.findMany({ filter: { published: { equals: true, }, }, }); // assume that there is a filter on the unauthenticated role's read access to post // in this case publishedPosts will be the same as unauthenticatedAccessiblePosts const unauthenticatedAccessiblePosts = await api.actAsSession.post.findMany(); }; ``` `api.actAsSession` is only available when your action or route has been triggered by a call from a client that is using session authentication. Gadget uses session authentication by default for clients created in the browser, but for clients authenticating with an API key, `.actAsSession` will throw an error. You can use the action context to know if there is a session available: ```typescript export const run: ActionRun = async ({ api, session }) => { if (session) { const widgets = await api.actAsSession.widget.findMany(); } }; ``` ### actAsAdmin example  You can enable admin access with `api.actAsAdmin`. This is useful in server contexts that default to session-scoped access, such as React Router and Remix request handlers, when you need to run admin operations like skipping tenancy checks or bypassing role-based access control. ```typescript export async function loader({ context }) { // get all widgets, not just widgets accessible to the current session const widgets = await context.api.actAsAdmin.widget.findMany(); return { widgets }; } ``` ## Infinite loops  Invoking actions within actions can cause infinite loops, so care must be taken to avoid this. If you invoke the currently underway action from within itself, your `run` function will run again, potentially invoking the action again in a chain. Or, if you invoke an action in model B from model A, but model B invokes an action in model A, you can create a chain of action invocations that never ends. Action chains like this can show up in the logs as timeouts or errors that happen at an arbitrary point in an action. To avoid infinite loops, you must break the loop somehow. There are a couple of options for this: * Use `record.changed('someField')` to only run a nested action some of the time when data you care about has changed * Use the Internal API instead of the Public API (`api.internal.someModel.update` instead of `api.someModel.update`) to make changes, skipping the action that re-triggers the loop. Calling third-party APIs from your actions is common, but it's important to be aware that third-party APIs may trigger subsequent actions in your Gadget app. For example, if you have a Gadget app connected to a Shopify store with a connected `shopifyProduct` model, every time Shopify sends the app a `product/updated` webhook, Gadget will run the `update` action. If the business logic inside the `update` action makes API calls back to Shopify to update the product again (say to update the tags of a product in response to a change in the other fields), the API call back to Shopify will trigger a second webhook sent back to Gadget. Without care, this can cause an infinite loop of actions triggering webhooks that retrigger the actions. See for strategies to break this infinite loop. ## Bulk actions  Gadget supports running many instances of the same action on a model with bulk actions. Bulk actions are automatically generated for model-scoped actions by Gadget. Unlike your CRUD actions, these are not visible in the model's actions folder. ### Types of bulk actions  Bulk actions can be categorized under one of four types: * `bulkCreate`: use to create many records * `bulkUpdate`: use to update many records * `bulkDelete`: use to delete many records * `bulkCustomAction`: use for any custom model-scoped actions you define For example, if you had a `user` model, you could use `api.user.bulkCreate`, `api.user.bulkDelete`, and `api.user.bulkUpdate` upon creation of the model. If you added a model-scoped action to `user`, such as `incrementLoginCount`, you could also call `api.user.bulkIncrementLoginCount` without having to create this action yourself. ### Invoking a bulk action  You can invoke an action in bulk by calling the `.bulk` function: ```typescript const widgets = await api.widget.bulkCreate([ { name: "foo" }, { name: "bar" }, { name: "baz" }, ]); ``` Each model's actions are all available in bulk, including custom actions: ```typescript const widgets = await api.widget.bulkUpdate([ { id: "1", name: "bar" }, { id: "2", name: "baz" }, ]); // delete 3 widgets in bulk await api.widget.bulkDelete(["1", "2", "3"]); // run a custom publish action in bulk await api.widget.bulkPublish([ { id: "1", published: true }, { id: "2", published: true }, ]); ``` To run bulk actions in the background, see [Bulk enqueuing](https://docs.gadget.dev/guides/actions/background#bulk-enqueuing).