# Rate limits  To ensure fair use across the platform, Gadget limits the number of requests an app can run in a given time window. Each environment of each application has independent rate limits. Rate limits can be raised by upgrading your plan, or contacting Gadget support to discuss custom limits. Each environment is subject to two limits: * a , which limits the total number of requests processed by an environment per 10-second window * a , which limits the total number of seconds spent running database queries by an environment per 10-second window Rate limits are different for each environment, and production environments have much higher rate limits than development environments. ## Request rate limit  The request rate limit is enforced at your app's API layer, and is enforced for all requests to the API. This includes requests made from the outside world, like from your app's frontend, as well as requests made from your app's backend code. The request rate limit is enforced using a , which means that the environment can burst up to the request rate limit, but will be throttled if it exceeds the request rate limit. Each request your application makes to your app's routes, Public API, or Internal API will consume one unit of the request rate limit. This includes internal operations made within your app's code. For example, if you run this model action: ```typescript import { applyParams, save, ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ api, record, params }) => { await applyParams(record, params); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ api, record, params }) => { // find and delete all children of the record const children = await api.child.findMany({ parent: record.id, }); for (const child of children) { await api.child.delete(child.id); } }; ``` This action will consume: * 1 unit of the request rate limit for the request to start the action * 1 unit of the request rate limit for the `save` call to persist the record, as it calls the Internal API to persist the record * 1 unit of the request rate limit for the `findMany` call to find the child records, as it calls the Public API to find the child records * 1 unit of the request rate limit for _each_ child record that is deleted, as it calls the Public API to delete the child record ### Request rate limit exemptions  Some requests are not subject to the request rate limit, and have no direct impact on your app's rate limit usage. These requests are not rate-limited: * webhook requests made by external systems like Shopify or BigCommerce to Gadget's plugins. This includes: * requests to the `/api/webhooks/shopify` endpoint * requests to the `/api/webhooks/bigcommerce` endpoint * requests made to enqueue background actions via `api.enqueue`. * requests to content served via your app's CDN. This includes: * all requests to `app-assets.gadget.dev` for frontend JS and CSS assets * all requests to `storage.gadget.dev` for files stored in file fields * all requests to paths like `/api/assets/...` for frontend JS and CSS assets * for development environments, requests to content served via your app's Vite dev server. This includes: * vite JS and CSS assets served at paths like `/.vite/...` and `/@vite/...` * requests to the `/api/webhooks/shopify` endpoint * requests to the `/api/webhooks/bigcommerce` endpoint * all requests to `app-assets.gadget.dev` for frontend JS and CSS assets * all requests to `storage.gadget.dev` for files stored in file fields * all requests to paths like `/api/assets/...` for frontend JS and CSS assets * vite JS and CSS assets served at paths like `/.vite/...` and `/@vite/...` Other requests are subject to the request rate limit, and will be throttled if the environment exceeds the request rate limit. ### Background action rate limit consumption  Background actions consume one unit of request rate limit when they start, and then consume request rate limit units for each request they make to the Public or Internal API for your application. This means actions have the same rate limit consumption regardless of whether they are run as a foreground request like `api.someAction()`, or as a background request, for example `api.enqueue(api.someAction)`. Foreground requests are synchronous requests that run immediately, while background requests are asynchronous actions that run after being enqueued. See the [billing guide](https://docs.gadget.dev/guides/account-and-billing#surge-compute) for how these categories affect surge compute eligibility. ### Background action throttling  Gadget automatically throttles background actions so they don't consume your environment's entire rate limit. If you enqueue more actions than your environment's rate limit allows, Gadget will adjust the max concurrency for your app to consume most, but not all, of your rate limit. This way, your queues make the most progress possible, but you shouldn't see extra 429 errors or excessive retries. Occasionally, if the nature of your app's running background actions changes abruptly, Gadget's rate limit system may not be able to adjust your background concurrency immediately to compensate. When this happens, running background actions may temporarily exhaust the full rate limit. All clients will get 429 errors when this happens. Gadget's built-in API client, the `api` object, will automatically retry these 429 errors from rate limit exhaustion up to 5 times, with a delay between retries, so there will be a short delay in action completion, but no errors. Other clients making foreground requests, such as external API calls or HTTP route requests, may need to implement the same retry logic. Note that the request rate limit impacts the _execution_ of background actions, not the _enqueuing_ of background actions. Enqueueing background actions is not rate-limited. ```typescript // will charge 1 unit of the request rate limit for running the action await api.someAction(); // will not charge any rate limit for enqueueing the action await api.enqueue(api.someAction); ``` ### Large request body costs  Requests with large payloads consume more rate limit capacity than normal requests. This helps ensure platform stability by preventing large data transfers from exhausting your application's resources. **How it works:** * Requests with payloads under **10MB** cost **1 unit** (the standard cost) * Requests with payloads over **10MB** cost **1 unit + 1 unit per MB over 10MB** For example: | Payload size | Rate limit cost | | --- | --- | | 5MB | 1 unit | | 10MB | 1 unit | | 15MB | 6 units | | 25MB | 16 units | | 50MB | 41 units | This cost applies to all requests to your application's API, including requests that enqueue background actions with large payloads. Large request payloads are often a sign that data could be structured more efficiently. Consider using [direct file uploads](https://docs.gadget.dev/guides/models/storing-files#direct-uploads-using-tokens) instead of sending file contents in API request bodies. **Example of a problematic pattern:** ```typescript // Avoid: Embedding file contents directly in the request const xmlContent = generateLargeXMLFile(products); await api.feedFile.update(feedFileId, { file: { // This base64-encodes the entire file and sends it through the API base64: Buffer.from(xmlContent).toString("base64"), fileName: "product-feed.xml", mimeType: "application/xml", }, }); ``` #### Recommended approach: Direct uploads  Instead of embedding large file contents in API requests, use Gadget's direct file upload feature. This uploads files directly to cloud storage, bypassing the API payload limits entirely. ```typescript // Better: Use direct uploads for large files const xmlContent = generateLargeXMLFile(products); // 1. Get a direct upload token const { url, token } = await api.getDirectUploadToken(); // 2. Upload directly to cloud storage (no size limit through the API!) await fetch(url, { method: "PUT", headers: { "Content-Type": "application/xml", }, body: xmlContent, }); // 3. Associate the uploaded file with your record (tiny payload) await api.feedFile.update(feedFileId, { file: { directUploadToken: token, fileName: "product-feed.xml", mimeType: "application/xml", }, }); ``` Direct uploads support files up to **10GB** and do not consume your request rate limit for the file transfer itself. See the [direct uploads documentation](https://docs.gadget.dev/guides/models/storing-files#direct-uploads-using-tokens) for more details on implementing this pattern. #### Handling large JSON data  If you are storing large amounts of JSON data (not files), consider these alternatives: * **Use batch processing**: Break large operations into smaller chunks using [background actions with concurrency control](https://docs.gadget.dev/guides/actions/background#queuing-and-concurrency-control) * **Paginate data**: Instead of fetching or sending all data at once, implement pagination to work with smaller chunks ## Database rate limit  The database rate limit is enforced at the database layer, and is enforced for all database queries run by an environment. The database rate limit ensures no one application can use more than it's fair share of the available database resources. It is enforced using a , which means that the environment can burst up to the database rate limit, but will be throttled if it exceeds the database rate limit. Database rate limit consumption is measured in milliseconds, and is charged for the duration of the database query. For each database query that your application runs, Gadget times the query, and deducts that duration from the database rate limit. Dedicated databases are available on enterprise Gadget plans, and are exempt from any database rate limits. There are no queries that are exempt from the database rate limit. ## Limiting 429 errors from rate limit exhaustion  If you are seeing too many 429 errors from rate limit exhaustion, you must reduce the load on your application to lower your rate limit consumption, or upgrade your plan to raise your rate limits. You can reduce the request rate limit load on your application by: * reducing the number of requests your frontend application makes to your app's routes, Public API, or Internal API * merging multiple requests into a single request by using the `bulk` actions, custom global actions, or HTTP routes * enqueueing actions to run in the background, where they are throttled to consume only a portion of your rate limit * enqueueing actions using a [concurrency queue](https://docs.gadget.dev/guides/actions/background#queuing-and-concurrency-control), where you can control the maximum concurrency of actions that can run at the same time * using [direct file uploads](https://docs.gadget.dev/guides/models/storing-files#direct-uploads-using-tokens) instead of embedding large file contents in API requests, which avoids the You can reduce the database rate limit load on your application by: * excluding computed fields from your `select:` parameters when loading records * reducing the number of computed fields selected or computed view queries your application makes, as these are often the biggest consumers of database rate limit * pre-aggregating data in model fields to reduce the number of read-time computations your application makes ## Leaky bucket algorithm  All Gadget rate limits use a leaky bucket algorithm to define capacity. The leaky bucket is a metaphor for a bucket that has a fixed size, is filled by requests, and is drained at a fixed rate. More requests can only be made if there is room in the bucket. For example: * Your environment's app has access to a bucket. It can hold, say, 60 "marbles" (requests). * Each API request that arrives adds a marble into the bucket. * Each second, a marble is removed from the bucket (if there are any). This is the drain rate -- it restores capacity for more marbles. * If the bucket gets full, you get a `429 Rate Limit Exceeded` error, and you must wait for more bucket capacity to become available. This model ensures that apps that manage API calls responsibly can maintain capacity to make bursts of requests when needed. For example, if you average 20 requests ("marbles") per second but suddenly need to make 30 requests all at once, you can still do so without hitting your rate limit.