The Delivery API is anonymous by design: any caller can read what a site publishes. A Delivery API key is how a caller says who it is. Each key is counted on its own and held to its own per-minute limit, so one front end that misbehaves is refused while every other caller keeps being served.
These are not the keys under Settings → Configuration → API Keys. Those are secrets this installation sends to other services, such as an AI vendor. Delivery API keys are what other systems send to this installation.
Sending a key
Send it in the X-Cendia-Api-Key header on every call under /api. That covers the content
endpoints, GraphQL and site search.
curl -H "X-Cendia-Api-Key: cdk_…" https://www.example.com/api/en-US/pages/about
const response = await fetch(`${cms}/api/graphql`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Cendia-Api-Key": process.env.CENDIA_API_KEY!,
},
body: JSON.stringify({ query }),
});
There is no query-string form. A URL ends up in logs and browser history, and a key in one would end up there too.
Keep the key on the server that calls the API: a BFF, a build step, a server component. A key in a browser's script is a key anyone can read.
What a caller is answered
| The request | The answer |
|---|---|
| Sends a key this site issued | Served, and counted against that key |
| Sends a key this site never issued, or one that has been revoked | 401 with { "error": "…" } |
| Sends a key that has used its limit for the minute | 429 with a Retry-After header. Other callers are not affected |
| Sends no key, on a website that does not require one | Served, and counted as "No key sent" |
| Sends no key, on a website that requires one | 401 |
A refused request is not counted against the key. Pages the website renders itself are not the
API, and nothing here applies to them. Neither does the click a rendered search page reports to
/api/search/{id}/click: that comes from a visitor's browser, which has no key to send.
Issuing keys
An administrator manages keys under Settings → Delivery API Keys:
- New Key makes a key and shows it once. Only its last four characters are kept in a form you can read; the rest is stored as a SHA-256 hash.
- Edit renames a key or changes its limit. The key itself does not change.
- Revoke stops a key working from the next request. It cannot be turned back on. Its counts are kept.
- Delete is offered only for a revoked key, and removes it with its counts.
The page shows each key's requests this month and last month (UTC calendar months) and the last day it was used, with the same figures for calls that sent no key.
Limits
A key's limit is requests a minute, counted over a sliding minute. A key with no limit of its own uses the installation's default:
{
"Cendia": {
"DeliveryKeyRateLimitPerMinute": 600
}
}
The default is 0, which means no limit for such keys.
The limit is counted per instance, like every limiter in the platform. Behind a load balancer of three instances, a key can make up to three times its limit.
Calls that send no key are not under any key's limit. They are under the per-address limit,
Cendia:GlobalRateLimitPerIp, when that is turned on. A BFF is one address for every visitor, so
that limit cannot tell one front end from another. A key can.
Requiring a key
Under Require an API key on the same page, tick a website to refuse calls to its /api that
send no key. The website is the one the request's host belongs to, falling back to the website
that owns *, the same way site search decides it.
Before you turn it on, make sure every front end that calls that website's API sends a key.
While it is on, every answer from that website's API carries Vary: X-Cendia-Api-Key, so a shared
cache in front of the site does not hand a keyed caller's answer to one with no key.
Counting
Counts are the requests that reached the installation. Delivery API answers are cacheable
(Cache-Control: public, max-age=60), so a call a CDN or the caller's own HTTP cache answers is
not counted, and is not under the key's limit either.
Counts are kept in memory and written a few seconds after the first request that has not been written yet. An idle API writes nothing. Each instance writes only what it served and the database adds them up, so the totals are the installation's across every instance. A crash can lose a few seconds of counting.
Counts are by UTC day in cms_DeliveryKeyUsage. Going over a monthly allowance is the hosting
platform's to report (roadmap u.3), and an allowance alerts. It never cuts a site off.