Rate limits, errors and the stability policy
Rate limits
Requests are budgeted per key and per org, per minute; the tighter bucket wins.
Every API-key response carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Resetheaders. An exhausted budget returns429with aRetry-Afterheader.A separate daily spend guardrail also returns
429when the organization's cost cap is exceeded.
Errors
Errors use the envelope {"detail": ...} (body-validation errors return a list of field errors). Every response carries an X-Request-ID header — quote it in support requests and the exact call can be traced.
Status | Trigger |
|---|---|
| Missing / invalid / expired / revoked API key |
| Key missing the required scope; an org key on a personal-only endpoint (or vice versa); or the agent isn't API-enabled |
| Document / Creation / agent / conversation not found (or not accessible) |
| Invalid request body (missing field, bad enum value) |
| Rate limit exhausted, or the organization's daily spend guardrail exceeded |
| Upstream retrieval/model failure (the raw cause is never echoed) |
| A dependency is not configured on the server |
Match on status codes, never on message strings — messages may be reworded at any time.
Stability policy
The public developer API is the stable contract for third-party integrations: scripts, Zapier/n8n/Make automations, the browser extension, MCP-adjacent tooling, and anything a customer builds against an sk-live-… API key. It lives under /api/v1 (backend/app/api/routes/v1_* + the automation subtree), authenticated with API keys.
It is distinct from /api/client/v1, the JWT-authenticated first-party client surface (client-api-policy.md). The two share service-layer code; they never share handlers by accident.
The compatibility promise
Code written against /api/v1 today keeps working. Concretely:
Additive-only. New fields, new endpoints, new optional parameters, new enum values: fine, at any time, without notice. Removing or renaming fields/endpoints, changing types or semantics, tightening validation on existing inputs, narrowing a scope an endpoint accepts: breaking — these require a new versioned path (
/api/v2/...for the affected resource) and a deprecation window of 12 months on the v1 spelling.Alias windows. When a request-field spelling is unified (e.g.
effort/modejoininganswer_effort/answer_modeon the per-agent answer endpoint), the old spelling is kept as a permanent alias — aliases on this surface do not expire.Tolerant readers expected. Clients must ignore unknown response fields; new fields appear without a version bump. The server never starts requiring a new request field from existing callers.
Scopes only widen. An endpoint may start accepting additional scopes or key types (e.g.
GET /v1/agentsacceptingretrieveas well asanswer); it never drops one it accepted.Deprecation process. A deprecated endpoint or field is (a) marked in the changelog and the OpenAPI description, (b) kept fully functional through the window above, and (c) announced through the discovery document (
GET /api/v1) before removal. Nothing is removed silently.
Error contract
Errors use the FastAPI envelope
{"detail": ...}; status codes and their triggers are documented in api-v1-reference.md.Every response carries an
X-Request-IDheader for tracing; quote it in support requests.The first-party client error-code registry (
ClientErrorCodeinshared/types/python.py) applies to/api/client/v1, not this surface;/api/v1errors are matched on status code, never on message strings (messages may be reworded at any time).
Rate limits
Requests are budgeted per key and per org, per minute; the tighter bucket wins. Limits are org-configurable server settings.
Every API-key response carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Resetheaders. An exhausted budget returns429withRetry-After.A separate daily spend guardrail also returns
429when the org's cost cap is exceeded.
Contract visibility
The reviewable public contract is the committed
docs/v1-openapi.json, exported byscripts/export-v1-openapi.pyfrom the same build (app.core.openapi_export.build_v1_spec) that servesGET /api/v1/openapi.json. A drift test (test_v1_openapi_committed.py) fails CI when the committed spec is stale, so every contract change is visible in the PR diff.GET /api/v1(the discovery document) lists every top-level endpoint with its scope + key type and is drift-tested against the live route table (test_v1_meta.py).Human-readable docs: api-v1-reference.md, the generated Help Center articles under developer-help-center/, and the served Swagger UI at
GET /api/v1/docs.
Changelog
Additions and deprecations are recorded per release in api-v1-changelog.md.