What API keys are for
An API key authenticates requests you make to Traceten directly, outside the dashboard UI. Six things use one:- The server SDKs. Node, Python, and Go all require a key and send it on every ingestion request. See the server SDK overview for what the key buys you there.
- The data deletion and access endpoints used to fulfill GDPR/CCPA requests.
- The read API. Stats, breakdowns, sessions, revenue, detection, AI crawlers, sites, events, sources, goals and funnels.
- The management API. Creating sites, minting keys, registering webhooks, and reading your account and team.
- The CLI. The same surface from a terminal or a CI job. It reads the key from stdin or
TRACETEN_TOKENand never takes one on the command line. - The MCP server. The same surface, exposed to AI apps as tools. It uses the key you give it and nothing else, so a key’s permissions are exactly what a connected assistant can do. Claude, Claude Code, Codex, ChatGPT and VS Code can also connect with no key at all. See below.
- Which sites it reaches. One site, or every site on the account.
- What it is allowed to do. See permissions.
GET /v1/team; you cannot change it. See Team and roles.
Creating a key
- Go to Settings → API keys in the dashboard.
- Click Create key, give it a name, and optionally scope it to one site.
- Tick the permissions this key needs. Read analytics is selected by default; everything else is off until you turn it on.
- Copy the new key. It is masked on screen: Copy copies the full key without showing it, and the eye button reveals it. It is available exactly once. Traceten stores only a hash, never the plaintext, so it cannot be recovered later.
Permissions
A key carries only the permissions you tick. A request to an endpoint the key is not permitted to use is refused, even though the key itself is valid.
Permissions are not hierarchical.
privacy:delete does not include stats:read, config:write does not include ingest:write or credentials:write, and privacy:delete does not include privacy:read. Grant each one you need.
Reading a visitor’s data and erasing it are separate permissions because answering an access request is routine and erasing is irreversible. A support tool that only needs to answer “what do you hold about me” should carry privacy:read alone.
Which endpoints need which permission
Two read endpoints take no key at all, whatever its permissions:
GET /v1/conversions and GET /v1/sources. They authenticate with a dashboard session only, and a key gets 401. Each of those pages says so where the endpoint is documented, and the CLI has no command for them.
Listing credentials is the one read that needs a write permission, and it is deliberate: a list of every key on the account, with its prefix, permissions, expiry and site scope, is a target list rather than a metric. A leaked read-only key should not produce one.
This applies to API keys only. In the dashboard, any member can still see the account’s keys under Settings → API keys, and bot tokens stay admin-only there, exactly as before. The table above describes what an API key needs.
A key scoped to a single site cannot use either listing at all, because the rows name the account’s other sites.
Defaults, and what happened to existing keys
- New keys default to
stats:readonly. A key that can only read cannot damage anything, so that is where we start you. - The key created automatically with a new site carries
stats:readandingest:write, because the install instructions point the server SDKs at it. - Keys created before permissions existed were given all of them, including
privacy:readandprivacy:delete. Only keys created from now on start narrow. If an older key is deployed somewhere that never needed to touch visitor data, replace it with a narrower one. credentials:writewas added later, and every key that already heldconfig:writewas given it automatically. Nothing you had stopped working, and you do not need to re-mint. A key created from now on getscredentials:writeonly if you tick it.
You must re-mint every existing key
We now record which team member created each key, so that when a key is used to create a site or issue another credential we can check whether that person is still an administrator. Existing keys were created before we stored that, and the value cannot be reconstructed after the fact. We had two options: treat “no recorded creator” as “allow”, which would leave the exact gap this change closes, or revoke and reissue. We revoked. What to do:- Sign in and go to Settings → API keys. Every previous key is listed under revoked.
- Create a replacement for each one, ticking the same permissions and the same site scope. The new key is shown once.
- Deploy the replacements everywhere the old key was used: server SDK configuration, CI secrets, scheduled jobs, and anything calling the API directly.
tk_live_ API keys were revoked.
If you use the API to mint keys, note that the calling key must itself be valid, so the first replacement has to be created in the dashboard.
Minting credentials with a key
Five endpoints return a new secret, and all five needcredentials:write: POST /v1/sites (which hands back an API key for the new site), POST /v1/account/api-keys, POST /v1/account/bot-tokens, POST /v1/sites/{siteId}/webhooks and POST /v1/sites/{siteId}/webhooks/{webhookId}/rotate-secret.
credentials:write is separate from config:write so that a key which manages your goals and funnels cannot also mint keys. A permission you can withdraw should not be able to issue one you then have to hunt down.
A key can never mint a credential more capable than itself. Every permission the new secret will carry has to be one the calling key already holds. If it is not, the request is refused with 403 and the response names the missing permission in required_scope:
config:write too, even though it is a read. A list of every key on the account (prefix, name, permissions, expiry, site pin) is a target list, not a metric, and a leaked read-only key should not produce one.
That is why POST /v1/sites needs three permissions rather than one: the site it creates comes with an API key carrying stats:read and ingest:write, so the caller must hold those too. And POST /v1/account/bot-tokens needs ingest:write because a bot token writes crawl reports.
We refuse rather than quietly issuing a narrower secret. A key that silently lacks a permission does not fail at the moment you create it, it fails days later in an unrelated part of your system, and the 403 here tells you exactly what to fix.
Omitting scopes on POST /v1/account/api-keys mints a stats:read key. That default is checked against the caller as well, so not asking is not a way around the rule.
Connecting an AI app instead of using a key
The MCP server can also be reached by connecting your Traceten account from an AI app, with no key at all. That connection is a different kind of credential and follows different rules:- Only apps we have approved can connect. Claude, Claude Code, Codex, ChatGPT and VS Code are approved today; any other app is refused before your sign-in page loads, until we add it. An app you have never heard of cannot put a Traceten sign-in in front of your team. Each entry also fixes which permissions that app may request.
- It acts as the person who approved it, in the organization they chose on the sign-in screen, with their own site access. It cannot reach another organization, and it never widens what that person can already do.
- It can carry
stats:readandconfig:write, and nothing else.ingest:write,privacy:read,privacy:deleteandcredentials:writeare not offered. So a connected app cannot send events, cannot touch a visitor’s personal data, and cannot mint an API key, a bot token or a webhook secret. The permission for that is deliberately withheld, because a connection you can stop by removing one person should not be able to leave behind a credential that outlives them. Both credential listings are refused to it as well; revoking a key is still reachable, but only for an account-wide admin and only for a key id it was already given. - It cannot be revoked, it does not expire on its own, and membership is the only thing that ends it. Access tokens are JWTs and cannot be withdrawn once issued, and every connection also holds a refresh token it uses to replace its own token, so waiting achieves nothing. Every request, read or write, re-checks the approving person’s live organization membership, so removing them stops the connection within about 30 seconds. The 24-hour access-token lifetime bounds a token that has leaked on its own, not a live connection.
- Writes additionally re-check the token with our identity provider before anything is changed, and are refused if we cannot get an answer. Reads skip that round-trip, which is what keeps them fast.
Who is behind a key
Creating a site, minting a credential and revoking one are admin actions. When you make one of those calls with an API key, we look up the person who created that key and check their current role in your Clerk organisation. The consequences are worth knowing before you build against it:- If that person is demoted from admin, their key stops working on those endpoints on the next call.
- If they are removed from the organisation, or their user is deleted, every key they created is revoked, so it stops working on every endpoint. You do not have to remember to revoke it. See when the person who created a key leaves.
- If they are an admin restricted to specific sites, their key cannot create sites or mint credentials at all. Restricted admins are not account-wide admins.
- A key scoped to a single site cannot create sites or mint account-level credentials, whoever made it.
- Reads are unaffected. Only the admin endpoints do this check.
503 and error: "clerk_unavailable". Retry it. Nothing was changed. We refuse rather than assume the role still holds, because letting a demoted admin’s key through is worse than a retry.
How to authenticate
Include your API key as a Bearer token in theAuthorization header:
Security
- Do not include API keys in client-side code. Put them in a server-side environment variable.
- Keys are one-way hashed before storage. If the database were compromised, your key would not be exposed as plaintext.
- Rotate a key by creating the new one first, deploying it, then revoking the old one. Revocation is immediate for the account API. On the ingestion path it takes effect within about a minute, because each edge location caches a verification result for 60 seconds. One exception, and we would rather state it than hide it: if our database is unreachable at that moment, an edge location that was already using the key may keep honouring it for up to about fifteen minutes more. That grace window exists so a database blip cannot silently drop your events, and it only ever applies to a key that location had already verified. A key it has never seen is always rejected.
- Scope keys to a single site when you can. An account-wide key can act on every site in the account; a site-scoped key is limited to one. A site-scoped key presented for a different site is rejected the same way an invalid key is.
-
Grant the fewest permissions that work. A key used only to read your numbers should carry only
stats:read. If that key leaks, it cannot send fake events or erase anything.
Error responses
Authentication errors return401. The account API responds:
401, so the response cannot be used to probe which keys exist.
Missing permission
A valid key sent to an endpoint it is not permitted to use returns403, and names the permission it needs:
401s above on purpose. You have already proved you hold the key, so telling you what it is missing gives away nothing you could not read off your own settings page, and leaving you to guess would be unhelpful.
What happens depends on which endpoint you called. One group does not return an error at all.
The last row is the one to read twice.
/v1/events and /v1/conversions are shared with the browser snippet, which can never hold a secret, so a key there is optional and the endpoint never rejects one. If your key is missing the ingest:write permission, the request still succeeds. Nothing in the response says otherwise.
What you lose is rate-limit isolation. A verified key is metered on its own bucket; an ignored one falls back to the bucket shared with all traffic sent under your public site id, which anyone who can read your page source can flood. Your events still arrive, and your headroom is no longer yours.
If you want a missing permission to be loud, send to /v1/server/events and /v1/server/conversions instead. Same payloads, same behaviour otherwise, but a key is mandatory and a bad one is a 401. The server SDKs already use these.
Not found vs forbidden
When you ask about a site your key cannot reach, you get404, not 403. That is the same answer as a site that does not exist. This is deliberate: distinguishing the two would let one leaked key enumerate every site id we host. Dashboard sessions get 403 in the same situation, because a signed-in user is already known to us.
If Traceten cannot check your key at all (a dependency is unavailable), /v1/server/events and /v1/server/conversions return 503 with { "error": "unavailable" } rather than 401. That is a retryable condition, not an authentication failure. /v1/events and /v1/conversions follow the last row of the table above instead: the request is accepted as anonymous, exactly as it is when a key is ignored.
Requests are rate-limited. If you exceed the limit you will receive a 429, so back off and retry.
On the ingestion path the limit is measured against a bucket tied to your key, sized the same as your plan’s per-site allowance and separate from it. Traffic sent under your public site id cannot consume the quota your authenticated calls rely on.
