Errors
The Strata HTTP API returns errors as RFC 7807 problem documents. Every non-2xx response uses the same shape, so you can write one handler for the whole /v1/* surface.
Response shape
All errors return Content-Type: application/problem+json and a JSON body with these fields:
| Field | Type | Description |
|---|---|---|
type | string | A URI identifying the error class, e.g. https://api.strata.kronisys.com/errors/rate_limit_exceeded. The slug after /errors/ is the stable identifier — branch on it. |
title | string | Short human summary. Safe to log. |
status | integer | HTTP status code, mirrors the response status. |
Some errors add extra fields for context — for example a 403 model_not_allowed includes requested_model and allowed_models, and a 429 includes retry_after, used, and limit. Treat any field beyond type/title/status as optional and tolerate its absence.
TipBranch on the
typeURI — match the stable slug after/errors/, not the HTTP status code ortitle. Thetitlestring may change; the type URI will not.
Error catalog
The type field is always the full URI https://api.strata.kronisys.com/errors/<slug>. The catalog lists the <slug> for readability.
| Status | Slug | When you get it |
|---|---|---|
| 400 | invalid_request | Body failed validation — required field missing, wrong shape, or unparsable. |
| 400 | unsafe_image_ref | A document-generator spec referenced a local file path or a disallowed image source. Only inline data: URIs and public https:// URLs are accepted. |
| 400 | invalid_cursor | The cursor you passed to GET /v1/audit is not one this API issued. Restart the walk without it, or resume from the last next_cursor you received. |
| 400 | content_filtered | The AI provider's content filter blocked the request. Do not retry it unchanged: the same request is blocked again. |
| 400 | content_filtered_after_side_effect | The request was blocked after an action in it had already run (an email sent, a file saved). Check the result before sending anything again, so nothing runs twice. Never retry it automatically. |
| 401 | missing_token | No Authorization: Bearer header was sent. |
| 401 | invalid_token | Token is malformed, unknown, expired, or revoked. Strata deliberately does not distinguish these — issue or rotate a key from Organization → API Keys. |
| 402 | subscription_required | Your organization has no active Strata subscription. An admin can start one in Billing. |
| 403 | insufficient_scope | Authenticated, but the key was not granted the scope this endpoint requires. Includes required_scope and granted_scopes. |
| 403 | trial_no_api_access | Your organization is on a self-serve trial. The API is not available during a trial — add a payment plan. |
| 403 | key_actor_revoked | The user (or connection bot) the key acts as is no longer active, so the key was disabled with them. |
| 403 | model_not_allowed | The requested model is not in the key's allowed models, or your organization has disabled it in the Model Catalog. Includes requested_model and allowed_models. |
| 403 | org_suspended | The key's organization is suspended. Contact your admin. |
| 403 | api_access_disabled | An admin flipped the organization's API kill switch off. Every key in the org is refused until an admin re-enables programmatic access on Organization → API Keys — this is reversible, unlike revoking a key. |
| 403 | forbidden | A valid key that isn't linked to an organization (an edge case seen on GET /v1/usage and GET /v1/audit). |
| 404 | not_found | Resource (or /v1 route) does not exist or is not visible to this key's organization. |
| 404 | conversation_not_found | The conversation_id you referenced does not exist for this organization. |
| 405 | method_not_allowed | A write verb (POST/PUT/PATCH/DELETE) was sent to GET /v1/audit, which is read-only. The response carries an Allow: GET header. |
| 409 | request_in_progress | A request carrying the same Idempotency-Key is still being processed. Wait for it to finish, then retry to fetch the cached result. |
| 413 | payload_too_large | Request body or uploaded file exceeds the endpoint limit (the JSON body cap is 1 MB; file uploads have their own cap). |
| 413 | spec_too_large | A document-generator spec exceeded the generator's own size limit. Split the document or trim the spec. |
| 422 | spec_invalid | A document-generator (/v1/excel, /v1/pdf, /v1/pptx, /v1/docx) rejected the spec you sent. |
| 429 | rate_limit_exceeded | The key's hourly request cap was exceeded. Honor Retry-After. |
| 429 | ip_rate_limit_exceeded | A coarse per-IP guardrail was tripped (before the key was even resolved). Honor Retry-After. |
| 429 | usage_limit_exceeded | A usage limit the web app, Teams, and agents share was hit: the actor's hourly rate limit, the organization's shared usage pool, its monthly AI spend ceiling or credit allowance, or the monthly spend limit of the member the key acts as (an API service account has none). Includes unit, used, limit, period, resets_at, pct, can_request_increase and admin_notified. Honor Retry-After. |
| 429 | model_allowance_exhausted | The requested model's per-model allowance is used up for this cycle. Includes requested_model and unit. Honor Retry-After (900s). If the org configured a cheaper fallback model, the request succeeds on it instead — see Model fallback. |
| 429 | budget_exhausted | The key's monthly token budget — or the organization's token pool — is fully consumed. Resets on the organization's billing-cycle reset day. |
| 500 | internal_error | Strata encountered an unexpected failure. Safe to retry. |
| 500 | engine_returned_empty | A document generator produced an empty file. Safe to retry; if it persists, simplify the spec. |
| 500 | extension_config_error | The key's extension tools could not be safely initialized for this request. Retry; if it persists, check the actor's extension connections. |
| 503 | budget_check_failed | Budget enforcement is briefly unavailable (a transient backend hiccup). Strata fails closed rather than spend uncapped. Retry with backoff. |
| 503 | entitlement_check_failed | Strata could not confirm your organization's API entitlement. It fails closed rather than assume access. Retry shortly. |
| 503 | policy_lookup_failed | The organization's model/mode policy could not be read for this request. Strata fails closed. Retry with backoff. |
| 503 | retention_policy_unavailable | GET /v1/audit could not read the organization's audit-retention policy. Strata fails closed rather than guess a window. Retry shortly. |
| 503 | unavailable | A transient backend outage on GET /v1/usage or GET /v1/audit. Retry with backoff. |
NoteToken-budget exhaustion returns
429, not403. Thebudget_exhaustedbody reportsused(committed tokens since the cycle reset),in_flight(estimated tokens reserved by currently-running requests), andlimit. Both the per-key budget and the shared organization token pool are enforced on the same call.
Examples
401 invalid_token
{
"type": "https://api.strata.kronisys.com/errors/invalid_token",
"title": "The provided API key is invalid, expired, or revoked.",
"status": 401
}
403 model_not_allowed
{
"type": "https://api.strata.kronisys.com/errors/model_not_allowed",
"title": "Model \"claude-opus\" is not currently allowed by this organization's policy. Pick an allowed model or have an admin update the org's allowed models.",
"status": 403,
"requested_model": "claude-opus",
"allowed_models": ["gpt-5.4", "gpt-5.4-mini", "claude-sonnet"]
}
429 rate_limit_exceeded
{
"type": "https://api.strata.kronisys.com/errors/rate_limit_exceeded",
"title": "API key rate limit exceeded (1000/1000 per hour). Retry after 42s.",
"status": 429,
"retry_after": 42,
"used": 1000,
"limit": 1000
}
A 429 rate-limit response also includes the Retry-After (seconds) and X-RateLimit-Reset (Unix epoch) headers, so you know exactly when to try again. Every successful response also carries X-RateLimit-Limit and X-RateLimit-Remaining.
Model governance and errors
The set of models a key may call is the intersection of your organization's allowed models, the key's allowed_models, and any role or per-user limits (including roles conferred by the actor's groups). Because your organization curates which models it enables from the central Model Catalog, a model that worked yesterday can return 403 model_not_allowed today if an admin disabled it — even if the key's own allowed_models still lists it. The organization's policy is always authoritative and is re-checked on every request. A request that names a retired model is checked as a request for its replacement, so a 403 model_not_allowed for it carries the replacement's id in requested_model, and a success carries a strata-model-retired header with the id you sent.
Separate from that allow-list, the org may cap how much of a given model each actor may use. When the requested model's allowance is exhausted, one of two things happens:
- If no fallback is configured, the call returns
429 model_allowance_exhaustedwithrequested_modeland aRetry-After. - If the org configured a cheaper fallback model, the call succeeds on the fallback rather than failing. The response's
modelfield names the model that actually ran, and astrata-model-fallbackresponse header names the model you asked for. Read both when you need to know exactly which model produced a reply.
TipDon't hard-code model IDs in retry logic. On a
403 model_not_allowed, readallowed_modelsfrom the response body and fall back to one of those, or surface the error to whoever provisioned the key.
How to handle errors
Build one error handler that reads type and decides what to do.
Retry on 5xx and 503
500, 503 budget_check_failed, 503 entitlement_check_failed, and 503 policy_lookup_failed are transient. Retry with exponential backoff (for example: 1s, 2s, 4s, 8s, cap at 30s) and a small amount of jitter. Cap total retries at 3–5 attempts.
KeyOn the non-streaming
POST /v1/chat, send anIdempotency-Keyheader on retries. Strata caches the first result for 24 hours and replays it on a repeat with the same key, so a network-timeout retry never creates a duplicate conversation or double-bills. While the first request is still running, a retry returns409 request_in_progress— wait, then retry to fetch the cached result. Idempotency is not applied to streaming requests; streaming clients recover by reconnecting, not re-POSTing.
Back off on 429
When you receive a 429, wait for the duration in Retry-After (or until the timestamp in X-RateLimit-Reset) before sending another request with the same key. A budget_exhausted 429 will not clear until the organization's billing cycle resets, so back off long rather than retry tightly. Spreading bursts across multiple keys is fine; hammering a throttled key is not.
Don't retry most 4xx
400, 401, 402, 403, 404, 413, and 422 will never succeed on retry without changing the request. Surface them to your caller or your logs, fix the underlying cause (bad payload, missing scope, disallowed model, expired key, rejected spec), then send a new request. The exception is 409 request_in_progress, which clears once the in-flight request completes.
Log the request context
Log the type, title, and any extra fields (such as allowed_models or required_scope) on every failure. Include them in any support request — they let the Kronisys Inc. team trace the exact problem without asking you to reproduce it.