On this page
API keys
Your organization's keys are yours to see and yours to kill. Listing shows you what exists; revoking takes effect on the revoked key's very next request. Neither call needs us, and neither has an office-hours dependency — which is the whole point, because the moment you need to revoke a key is never a convenient one.
Keys are still issued by us; see Access. What changed is that you no longer have to ask in order to look at them or to stop one.
:org is your organization's slug or its org_... id. A key can only address its own organization: any other value answers 404, exactly as an organization that does not exist would.
A key has leaked. Do this now.
# 1. find its id
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
"$DOCSARY_API/v1/orgs/acme/keys"
# 2. kill it
curl -s -X DELETE "$DOCSARY_API/v1/orgs/acme/keys/key_9f2c..." \
-H "authorization: Bearer $DOCSARY_KEY"
The revoked key is dead on its next request. Nothing else is affected: your other keys keep working, your sites keep serving, and no content is touched. Then ask us for a replacement key and update whatever was holding the old one.
If the leaked key is the only key you have, use it to revoke itself — that is allowed however narrow its scopes are. See Revoking the key you are holding.
List keys
GET /v1/orgs/:org/keys
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
"$DOCSARY_API/v1/orgs/acme/keys"
{
"org": "acme",
"keys": [
{
"id": "key_9f2c1d0a4b6e8f1c2d0a4b6e8f1c2d0a",
"name": "ci",
"scopes": "write",
"created_at": 1756300000000,
"last_used_at": 1756399000000,
"revoked_at": null,
"revoked": false
},
{
"id": "key_3a7b5c9d1e2f3a7b5c9d1e2f3a7b5c9d",
"name": "docs-linter",
"scopes": "read",
"created_at": 1756200000000,
"last_used_at": null,
"revoked_at": 1756350000000,
"revoked": true
}
]
}
Newest first. A read-only key may list — looking is what a read-only key is for.
| Field | Meaning |
|---|---|
id |
The key_... id. This is what you pass to revoke, and it is safe to write down |
name |
The name the key was issued under |
scopes |
write or read — see Conventions |
created_at |
Millisecond timestamp of issue |
last_used_at |
Millisecond timestamp of the key's most recent request, or null if it has never been used |
revoked_at |
Millisecond timestamp of revocation, or null |
revoked |
true once revoked. A revoked key stays listed |
Revoked keys stay in the list, with the time they died. That is deliberate: the question you usually have is "is the thing still calling us the key I killed last week?", and a list that quietly forgot cannot answer it.
last_used_at is the useful column. A key with no last_used_at months after issue is a key nothing depends on, which makes it free to revoke. A key you thought was retired but which has a recent timestamp is worth understanding before you kill it.
What a listing never contains
Not the key. Keys are stored only as a hash of the token, and the token itself is stored nowhere at all — it exists in exactly one response, the one that issued it. Nothing here can return it, including by mistake.
Not the hash either. The stored hash is what a request is checked against, so publishing it would turn this endpoint into a way to test guesses offline. It is never returned in any field.
A lost key cannot be recovered, only replaced. Revoke it and ask for a new one.
Revoke a key
DELETE /v1/orgs/:org/keys/:id
curl -s -X DELETE "$DOCSARY_API/v1/orgs/acme/keys/key_9f2c..." \
-H "authorization: Bearer $DOCSARY_KEY"
{
"revoked": true,
"self": false,
"key": {
"id": "key_9f2c1d0a4b6e8f1c2d0a4b6e8f1c2d0a",
"name": "ci",
"scopes": "write",
"created_at": 1756300000000,
"last_used_at": 1756399000000,
"revoked_at": 1756400000000,
"revoked": true
}
}
Immediate. Every request checks the key against the store as it arrives, so the revoked key gets 401 on its next call. There is no cache to wait out and no propagation window.
Irreversible. A revoked key cannot be un-revoked. Replacing it means issuing a new one.
Idempotent. Revoking an already-revoked key answers 200 with "already_revoked": true, not an error. Re-running the command you ran in a panic should not make you wonder whether the first run worked.
Revoking a key changes no content. Sites keep serving, pages are untouched, and your other keys keep working.
Who may revoke
| Caller | May revoke |
|---|---|
| A write-capable key | Any key in its own organization |
| A read-only key | Only itself |
Revocation is a change, so a read-only key does not get to make it on somebody else's behalf — with one deliberate exception below.
Revoking the key you are holding
Any key may revoke itself, whatever its scopes. If the credential that leaked is the read-only one, and it is the only one you have to hand, you can still stop it:
# whoami tells you which key you are holding
curl -s -H "authorization: Bearer $DOCSARY_KEY" "$DOCSARY_API/v1/whoami"
{ "kind": "org", "org": { "slug": "acme" }, "scopes": "read", "key_id": "key_3a7b..." }
Passing that key_id to the revoke call works, and the response says so:
{
"revoked": true,
"self": true,
"note": "That was the key this request authenticated with. It is dead now."
}
The exception is safe because a key destroying only itself cannot be turned against anything else. The next request that key makes will fail, so make it the last thing you do with it.
Errors
| Response | Cause |
|---|---|
401 |
No token, or the token has already been revoked |
403 containing read-only |
A read-only key tried to revoke a different key |
404 {"error":"key not found"} |
No such key id in this organization — including a real key id belonging to somebody else |
404 {"error":"org not found"} |
The :org value is not your organization |
Both 404s are the same answer another organization's real slug gets, so neither can be used to find out what exists.
Good practice
One key per consumer. A key for CI, a key for the docs linter, a key for the script on your laptop. Then revoking the one that leaked costs you exactly one thing to re-issue instead of taking down everything at once.
Read-only wherever writing is not needed. A linter, a status check, a dashboard: all of them only look. Ask for read scope and the blast radius of that key leaking is a 401 on a mutation.
Check last_used_at occasionally. It is the cheapest audit there is, and it turns "we should tidy up our keys some day" into a two-minute job with an obvious answer.
Revoke first, investigate afterwards. Revocation costs nothing, breaks nothing else, and cannot be got wrong in a way that matters. Working out how the key got out is a slower job and does not need to block the fast one.