Three behaviours of the Integration API will shape your integration whether you plan for them or not: what a key can reach, how fast it can call, and how long a revoked key keeps working. This page also states plainly what the API does not do, so you find out here rather than at 2am.
A key reaches only the rows it created. That is the whole rule, and it is enforced in one place for every route on this surface.
A row belongs to your integration when both of these hold:
its owner is your integration — derived from the stable integration_id, not from the key material, so it survives credential rotation; and
its team matches the team your key carries.
Everything else is out of reach:
Row
Reachable?
Created by this key’s integration, in this key’s team
Yes
Created by a human in the same tenant, private
No
Created by a human in the same tenant, shared tenant-wide
No
Created by this integration but in a different team
No
Created by a different integration
No
In a different tenant
No
Pre-existing, with no recorded owner
No
The factory routes narrow this further: a flow-mode session your key genuinely owns is still a 404 on /api/v1/factory/assistants/{id}, because a flow row is not an assistant. It is reachable on /api/v1/sessions/{id}.
120 requests back-to-back before the sustained rate binds
Over the limit
429 rate_limited with a Retry-After header
Retry-After is a whole number of seconds until the next token is available, minimum 1. Honour it — retrying sooner just spends another refused request.
The bucket is shared across every route on this surface. A knowledge sync and an assistant publish draw from the same 60/minute.
A newly seen key starts with a full burst of 120, which is enough for a modest initial sync in one go. Size a bulk load around the sustained rate, not the burst.
The reverse direction is quicker. A key the admin rejects is remembered as invalid for only 10 seconds, so a newly minted key becomes usable within about ten seconds even if something called with it before it existed.
Both windows are ceilings, not schedules: any request after the cached entry expires re-checks the admin.
Every outcome that is not “the admin says this key is active” refuses the request. There is no fallback that lets a request through unverified, and no stale positive is served when the admin is unreachable.
Outcome
Response
Admin does not recognise the key
401 invalid_api_key
Admin resolves it, status is not active
403 integration_disabled
Admin resolves it, status is one this build does not recognise
403 integration_disabled
Admin unreachable, timed out, rate-limited, erroring, or undecodable
503 integration_auth_unavailable
No admin attached to this deployment
503 integration_auth_unavailable
Treat 503 integration_auth_unavailable as retryable with backoff; it says nothing about your key. Treat 401 and 403 as terminal until a human intervenes.
Verify the key’s scope with GET /api/v1/knowledge/whoami and assert the tenant and team your code expects.
Persist every id you create. Ownership means you cannot rediscover a row you did not create.
Match on error.code, never on error.message. Messages are English fallbacks and may change.
Treat every 404 as “not reachable” — do not branch on missing versus forbidden.
Back off on 429 using Retry-After, and size bulk work against 60 requests/minute rather than the 120 burst.
Retry 503 integration_auth_unavailable with backoff; do not retry 401 or 403.
Document the 60-second revocation window in your own runbook, so an on-call engineer is not testing whether revocation worked.
Fail your build when a publish reports unreachableTools unless you have deliberately accepted it — and default the field first (published.unreachableTools ?? []), because it is omitted on a clean publish rather than sent empty.
Keep the key on your server. No CORS, and no reason to ship a tenant-wide credential to a browser.