Content Delivery API

CDA Technical Limits & Rate Limits

Content Delivery API (GraphQL) Only

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,000 bytes). 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:

  1. Your query's x-complexity HTTP header is under the maximum GraphQL query complexity limit (10,000,000). Requests that exceed this limit will return a HTTP 200 with a JSON errors object from our GraphQL server with a message like "Query has complexity of X, which exceeds max complexity of 10000000".

  2. The x-cacheable-on-cdn HTTP header should return true. 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.

Pro tip: Use datocms/cda-client to automatically handle rate limits

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

  1. 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.

  2. 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 after x-ratelimit-reset: 1 second.

  3. 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 after x-ratelimit-reset: 35 seconds.

  4. 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.

  5. 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.

CategoryHTTP HeaderValueNotes
Complexityx-complexity<integer>

This query's GraphQL complexity
Your request's current GraphQL complexity
Complexityx-max-complexity10000000

Max complexity allowed
The maximum complexity allowed, 10,000,000 by default.
Cacheabilitycf-cache-statusHIT or MISS or DYNAMIC or BYPASS

Whether 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)
Cacheabilityx-cacheable-on-cdntrue or falseWhether 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.
Cacheabilityx-cacheable-on-cdn-query-length-limit<integer>/12000

Compressed 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 Limitsx-ratelimit-limit40 or 1000

Which rate limit bucket you're currently closer to.
40: You're closer to the per-second rate limit

1000: You're closer to the per-minute rate limit
Rate Limitsx-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 Limitsx-ratelimit-reset <integer>

Seconds until next bucket refill
1 for the per-second limit

< 60 for the per-minute limit

This header is only added when rate-limited, i.e., accompanying a HTTP 429 Too many requests status code.
Miscellaneousx-request-idUUIDv4

Internal 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

  1. Very rarely, extreme load on our origin servers can also cause separate 429 Too many requests errors, 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.

  2. 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.

  3. If you get a cf-cache-status: BYPASS on a HTTP 200 OK, this is probably because you are getting a GraphQL error from our origin server (those errors return a 200 with a JSON body you must parse to see the actual GraphQL error). See CDA: GraphQL Query Errors for more information.

Last updated: