CDA Technical Limits & Rate Limits
These limits apply only to the Content Delivery API, our read-only GraphQL API.
For other API limits, see:
Overview
To ensure system integrity and performance for all our customers, the Content Delivery API imposes 3 basic technical limits on your queries:
Complexity: How difficult your query is for our origin server to serve, in terms of its GraphQL complexity (max
10,000,000). Queries that exceed this limit will return an error.Cacheability: Your query request size, in bytes, indicating whether our CDN can cache it (max
12,000bytes). Uncached or uncacheable requests are subject to rate limits (see below).Concurrency: How many active, uncached requests our origin server can process for your project at once (normally
40), shared between all of your requests that miss the cache. This primarily affects slow, long-running queries.
Whenever you make a CDA query, it first goes through our CDN, Cloudflare. Per data center, we will serve the query straight from cache if possible, warm up the cache if needed, or tell you that this query can never be cached (because it's too long). Once it hits our origin server, we also check for its complexity and concurrency, process it, then return either a successful result or an error.
To make sure your queries succeed and aren't limited, please make sure:
Your query's
x-complexityHTTP header is under the maximum GraphQL query complexity limit (10,000,000). Requests that exceed this limit will return aHTTP 200with a JSONerrorsobject from our GraphQL server with a message like "Query has complexity of X, which exceeds max complexity of 10000000".The
x-cacheable-on-cdnHTTP header should returntrue. Requests that exceed this limit are too long for the CDN to cache and will always be dynamic and rate-limited. Consider breaking it down into smaller, separate queries instead.
If all of the above are OK, then the bulk of your CDA responses should include a HTTP header of cf-cache-status: HIT, indicating a successful cache hit. For these cache hits, you don't have to worry about rate limits at all (because they only apply to uncached responses).
For troubleshooting, there is a table further down this page with relevant HTTP headers.
Rate Limits for Uncached CDA Requests
Cached requests (see above) are not subject to a rate limit.
Uncached requests must stay within two rate limits, both active simultaneously:
40 requests/second per API token
1000 requests/min per API token
The rate limits are per API token. If your frontend uses the same API token across many pages, or in a big parallel build, every request made with the same token counts towards the same pool.
If you use our official CDA client ( Using the JavaScript CDA client ), it will automatically handle our rate limits for you.
This is great for simpler use cases, but please note that it does not account for concurrency (parallel Next.js build workers, Promise.all() over many simultaneous requests, etc.).
Rate Limit Examples
If you use our official CDA client ( Using the JavaScript CDA client ) to make your CDA requests, we handle this for you automatically, and you don't have to worry about the other examples.
You make 60 requests at once and hit the per-second limit immediately. The last 20 requests will be rejected with a
HTTP 429 Too many requests. You should retry again afterx-ratelimit-reset: 1second.You sustain 40 requests/second and stay within the per-second limit. But after 25 seconds, you will hit the per-minute limit. Remaining requests are rejected with a
429. You should retry again afterx-ratelimit-reset: 35seconds.You sustain 40 requests/second across two worker threads, 20/sec each: Same outcome as above. The rate limit is per API token, not per client.
You sustain 16 requests/second, in total across that API token, and stay under both limits indefinitely. Well done!
Project-wide Concurrency Limit
Separate from the per-token rate limits above, there is also a per-project limit of 40 concurrent requests, shared between all your API tokens. "Concurrent" means that those requests are actively being processed by our origin server at the same time, regardless of when we received them.
Whereas rate limits measure only the frequency of incoming requests; concurrency measures how many are still being processed at the same time — their overlapping durations, in other words.
An exaggerated example: If you send us 16 requests/second, you will stay comfortably within the rate limits. But if each of those were a separate, slow and uncached query taking 10+ seconds to look up, then within 3 seconds you'll hit the concurrency limit. The first 40 requests will keep processing for the next 7+ seconds, while the last 8 requests will be rejected with a 429.
Normally, concurrency is not something the typical project or use case ever needs to worry about. It primarily affects only very slow, long-running queries that are sent at or near the same exact time, AND that are all uncached and hit our origin server. In practice, this combination of factors is extremely rare, and most of our customers never encounter it.
We don't have separate error codes or HTTP headers for concurrency exceptions; they just look like regular rate limiting, and you can respond to them the same way: by simply waiting longer and respecting the x-ratelimit-reset header.
However, if you suspect you are regularly hitting the concurrency limit, please first check your query and its filters, variables, and relationships and see if any could be simplified. If you need further help, please contact DatoCMS Support and we can help you further troubleshoot it on a case-by-case basis.
HTTP Headers for CDA Responses
CDA responses include the following HTTP headers to help you stay within limits and diagnose caching and rate limit issues.
| Category | HTTP Header | Value | Notes |
|---|---|---|---|
| Complexity | x-complexity | <integer>This query's GraphQL complexity | Your request's current GraphQL complexity |
| Complexity | x-max-complexity | 10000000Max complexity allowed | The maximum complexity allowed, 10,000,000 by default. |
| Cacheability | cf-cache-status | HIT or MISS or DYNAMIC or BYPASSWhether the CDN can cache this query | HIT indicates a cache hit, the desired behavior. No rate limits apply to this query.MISS indicates a cache miss, usually because it was the first time this particular request hit that data center. Repeat the same query/request and it should become a HIT next time.DYNAMIC means this query can NEVER be cached and will ALWAYS be subject to rate limits. May be due excess complexity, the query length limit (see below), or other error states.BYPASS is a special state that means our origin server told the CDN not to cache it. This should only happen on error states, either on the 429 themselves, OR sometimes on a 200 if there is a GraphQL error from our origin server. (See https://www.datocms.com/docs/content-delivery-api/errors#graphql-query-errors) |
| Cacheability | x-cacheable-on-cdn | true or false | Whether the request body is short enough to ever be cacheable. This is the boolean summary of the following header, x-cacheable-on-cdn-query-length-limit. |
| Cacheability | x-cacheable-on-cdn-query-length-limit | <integer>/12000Compressed query size, in bytes, out of allowed max | The actual calculation that determines if the previous header, x-cacheable-on-cdn, can ever be true.This is your GZIP-compressed & base64-encoded query size + our overhead, in bytes. The result must stay within 12,000 bytes (12KB decimal). |
| Rate Limits | x-ratelimit-limit | 40 or 1000Which rate limit bucket you're currently closer to. | 40: You're closer to the per-second rate limit1000: You're closer to the per-minute rate limit |
| Rate Limits | x-ratelimit-remaining | <integer>Requests remaining in closest bucket | How many requests you have remaining in the current bucket (per-second or per-minute), depending on x-ratelimit-limit. |
| Rate Limits | x-ratelimit-reset | <integer>Seconds until next bucket refill | 1 for the per-second limit< 60 for the per-minute limitThis header is only added when rate-limited, i.e., accompanying a HTTP 429 Too many requests status code. |
| Miscellaneous | x-request-id | UUIDv4Internal ID that helps us troubleshoot specific requests | Please include this in support requests that refer to a specific GraphQL query. |
Billing considerations
Caching does not impact billing or quota calculations.
1 request = 1 API call billed against your quota, regardless of whether it's cached or cacheable.
Free plans that exceed CDA limits will be temporarily suspended until the next month. Paid plans that exceed their included CDA quota will be charged per-request overages (in chunks). See Pricing for details.
Notes
Very rarely, extreme load on our origin servers can also cause separate
429 Too many requestserrors, altogether separate from your query. You are very unlikely to ever experience this, and the remedy is the same as any other rate limit; please try again in a few seconds.Invalidations happen automatically. When content in your project is changed, our system calculates the relevant queries to invalidate. For more manual control, you may wish to consider Cache Tags.
If you get a
cf-cache-status: BYPASSon aHTTP 200 OK, this is probably because you are getting a GraphQL error from our origin server (those errors return a200with a JSON body you must parse to see the actual GraphQL error). See CDA: GraphQL Query Errors for more information.