Bearer keys, the scopes they carry, and what no key can do.
Getting a key
Keys are created in the workspace, under Developers. The plaintext is shown once, at creation, and is not recoverable afterwards — store it before you close the dialog. Creating, rotating and revoking a key each write an audit row, readable through the audit endpoint below. A request with no key, or with a key TAVONEL did not issue, is refused with 401 and the code AUTH_REQUIRED: that is the first error most integrations see, and it means the credential rather than the request.
POST/developer/keys
Creates a workspace-scoped key with an explicit scope set. The plaintext `token` is in this response and nowhere else: only its SHA-256 digest is stored. A free evaluation cannot create keys and is answered 402 TRIAL_FEATURE_NOT_INCLUDED; the same refusal meets a free evaluation approving a remote MCP connection, which mints a key (see `x-tavonel-remote-mcp`). Where trial AI access is enabled, a free evaluation may hold one live read-only key (the remote MCP scopes) -- hand-made or minted by a remote MCP connection -- and a spent evaluation bound is answered 402 TRIAL_LIMIT_REACHED.
# Browser session only. A developer API key is refused on this route.
curl -sS -X POST https://tavonel.com/api/developer/keys \
-H "Authorization: Bearer $TAVONEL_SESSION_JWT" \
-H "content-type: application/json" \
-d '{ "name": "<name>", "scopes": [ "documents:read" ], "expiresInDays": "<expiresInDays>" }'
# Browser session only. A developer API key is refused on this route.
import os
import requests
body = {
"name": "<name>",
"scopes": ["documents:read"],
"expiresInDays": "<expiresInDays>"
}
response = requests.request(
"POST",
"https://tavonel.com/api/developer/keys",
headers={"Authorization": "Bearer " + os.environ["TAVONEL_SESSION_JWT"]},
json=body,
timeout=30,
)
response.raise_for_status()
print(response.json())
// Browser session only. A developer API key is refused on this route.
const response = await fetch("https://tavonel.com/api/developer/keys", {
method: "POST",
headers: { authorization: `Bearer ${process.env.TAVONEL_SESSION_JWT}`, "content-type": "application/json" },
body: JSON.stringify({ "name": "<name>", "scopes": [ "documents:read" ], "expiresInDays": "<expiresInDays>" }),
});
if (!response.ok) throw new Error(`${response.status} ${(await response.json()).code}`);
console.log(await response.json());
The key's record and its one-time plaintext token. Store the token now; it is not shown again.
400
INVALID_JSON — The body was not parseable JSON. API_KEY_INPUT_INVALID — The rotation body did not validate.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
402
TRIAL_FEATURE_NOT_INCLUDED — The operation is not in the free evaluation: creating, rotating or revoking an API key, and approving a remote MCP connection (which mints a key), among them. TRIAL_LIMIT_REACHED — A free-evaluation bound is spent: its AI-access window, page, World or daily query bound, which Ask in the browser and World activation also read, or, with trial AI access enabled, its one live API key (hand-made, or minted by approving a remote MCP connection).
403
PILOT_ACCESS_REQUIRED — The credential is valid and the workspace it names is not admitted to TAVONEL. AUTHORIZATION_CHANGED_RETRY — The caller's authorization changed while the request was in flight, so the request was abandoned rather than finished under a permission that may no longer hold.
413
REQUEST_TOO_LARGE — The JSON body exceeded the route's bound.
503
DEVELOPER_STORE_NOT_CONFIGURED — The developer store is not configured. API_KEY_CREATE_FAILED — The replacement key could not be written, so nothing was revoked either. DEVELOPER_STORE_BINDING_INVALID — A stored row did not validate, so it was refused rather than returned partially.
Sending the key
Send the key as a bearer token. Keys are workspace-scoped and carry an explicit scope set; a request outside its scopes is refused with 403 and API_SCOPE_REQUIRED rather than silently returning less.
Request upload capabilities and register document versions.
collections:read
Read compile jobs, corpora and compiled packages.
collections:compile
Start compiles, answer blockers, cancel a run.
collections:download
Download the signed knowledge package.
worlds:read
Read the active World, its objects, relations and evidence.
ask:read
Grounded answers and hybrid retrieval over the active World.
connections:read
Read connection state and cursors.
connections:write
Create and revoke connections.
connections:sync
Advance a connection cursor and collect what changed.
How often you may call
Every scoped request consumes one unit of a per-minute allowance held per key and per scope. The window is a clock minute rather than a rolling one, so an allowance that is spent is free again at the top of the next minute. The per-scope numbers are on the Billing and limits page, printed from the same values the authorizer enforces.
NoteOver the allowance the answer is 429 with API_RATE_LIMITED. No Retry-After header is sent on that code today and no X-RateLimit-* headers are published — waiting for the next clock minute is sufficient by construction, and documenting a header we do not send would be worse than documenting the window. The separate hourly allowance on World activation, rollback and retrieval-index rebuild answers ACTIVATION_RATE_LIMITED and does carry Retry-After.
Rotating a key, and reading the audit trail
Rotation is atomic: a replacement key is created, the source key is revoked and an audit event is written, or none of the three happened. There is no window in which the old key is dead and no replacement exists. Both operations take a signed-in browser session — a developer API key cannot call them, which is the same boundary activation sits behind.
POST/developer/keys/{id}/rotate
Atomically creates a replacement key, revokes the source key and writes an audit event. Plaintext is returned once and is not recoverable afterwards. Either all three happened or none did — there is no state where the old key is revoked and no replacement exists.
# Browser session only. A developer API key is refused on this route.
curl -sS -X POST https://tavonel.com/api/v1/developer/keys/{id}/rotate \
-H "Authorization: Bearer $TAVONEL_SESSION_JWT" \
-H "content-type: application/json" \
-d '{ "name": "<name>", "scopes": [ "documents:read" ], "expiresInDays": "<expiresInDays>" }'
# Browser session only. A developer API key is refused on this route.
import os
import requests
body = {
"name": "<name>",
"scopes": ["documents:read"],
"expiresInDays": "<expiresInDays>"
}
response = requests.request(
"POST",
"https://tavonel.com/api/v1/developer/keys/{id}/rotate",
headers={"Authorization": "Bearer " + os.environ["TAVONEL_SESSION_JWT"]},
json=body,
timeout=30,
)
response.raise_for_status()
print(response.json())
// Browser session only. A developer API key is refused on this route.
const response = await fetch("https://tavonel.com/api/v1/developer/keys/{id}/rotate", {
method: "POST",
headers: { authorization: `Bearer ${process.env.TAVONEL_SESSION_JWT}`, "content-type": "application/json" },
body: JSON.stringify({ "name": "<name>", "scopes": [ "documents:read" ], "expiresInDays": "<expiresInDays>" }),
});
if (!response.ok) throw new Error(`${response.status} ${(await response.json()).code}`);
console.log(await response.json());
The replacement key's record, its one-time plaintext token, and replacedKeyId, the key now revoked. Store the token now; it is not shown again.
400
API_KEY_ID_INVALID — The key id was not a UUID. API_KEY_INPUT_INVALID — The rotation body did not validate. INVALID_JSON — The body was not parseable JSON.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
402
TRIAL_FEATURE_NOT_INCLUDED — The operation is not in the free evaluation: creating, rotating or revoking an API key, and approving a remote MCP connection (which mints a key), among them. TRIAL_LIMIT_REACHED — A free-evaluation bound is spent: its AI-access window, page, World or daily query bound, which Ask in the browser and World activation also read, or, with trial AI access enabled, its one live API key (hand-made, or minted by approving a remote MCP connection).
403
PILOT_ACCESS_REQUIRED — The credential is valid and the workspace it names is not admitted to TAVONEL. AUTHORIZATION_CHANGED_RETRY — The caller's authorization changed while the request was in flight, so the request was abandoned rather than finished under a permission that may no longer hold.
404
API_KEY_NOT_FOUND — No such key in this workspace.
413
REQUEST_TOO_LARGE — The JSON body exceeded the route's bound.
503
API_KEY_ROTATE_FAILED — The rotation could not be completed. DEVELOPER_STORE_NOT_CONFIGURED — The developer store is not configured. DEVELOPER_STORE_BINDING_INVALID — A stored row did not validate, so it was refused rather than returned partially.
GET/developer/audit
The tenant-scoped developer and connector audit trail, newest first, bounded by `limit`. Every key create, rotate and revoke writes a row here or does not happen.
# Browser session only. A developer API key is refused on this route.
curl -sS -X GET https://tavonel.com/api/v1/developer/audit \
-H "Authorization: Bearer $TAVONEL_SESSION_JWT"
# Browser session only. A developer API key is refused on this route.
import os
import requests
response = requests.request(
"GET",
"https://tavonel.com/api/v1/developer/audit",
headers={"Authorization": "Bearer " + os.environ["TAVONEL_SESSION_JWT"]},
timeout=30,
)
response.raise_for_status()
print(response.json())
// Browser session only. A developer API key is refused on this route.
const response = await fetch("https://tavonel.com/api/v1/developer/audit", {
method: "GET",
headers: { authorization: `Bearer ${process.env.TAVONEL_SESSION_JWT}` },
});
if (!response.ok) throw new Error(`${response.status} ${(await response.json()).code}`);
console.log(await response.json());
Status
Response
200
{ code: OK, apiVersion, events }, newest first.
400
AUDIT_LIMIT_INVALID — The audit limit was outside its range.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
403
PILOT_ACCESS_REQUIRED — The credential is valid and the workspace it names is not admitted to TAVONEL. AUTHORIZATION_CHANGED_RETRY — The caller's authorization changed while the request was in flight, so the request was abandoned rather than finished under a permission that may no longer hold.
503
DEVELOPER_AUDIT_READ_FAILED — The audit trail could not be read. DEVELOPER_STORE_NOT_CONFIGURED — The developer store is not configured. DEVELOPER_STORE_BINDING_INVALID — A stored row did not validate, so it was refused rather than returned partially.
What no key can do
NoteActivation, rollback and destructive workspace actions are human-session-only. There is no scope that grants them, which is why you will not find one in this table.
API version 2026-09-02.1 · reviewed 11 September 2026