Limits and errors
Use this guide to size requests and handle API responses. The Limits tab in your organization’s Settings shows its current tier, model pricing and allowances. Request and token budgets are shared across projects and API keys in the organization.
Request limits#
| Limit | Value | Behavior |
|---|---|---|
| JSON HTTP request body | 1 MiB = 1,048,576 bytes | 413 payload_too_large, including streamed bodies. POST /v1/scan/prompt and POST /v1/responses allow 20 MiB so that image data URLs fit. |
| Document multipart body | 11 MiB = 11,534,336 bytes | 413 payload_too_large; includes file and form overhead. |
| Document count and file size | One file, 10 MiB = 10,485,760 bytes | 413 above the byte cap before a provider call; multiple files are invalid. |
| Document formats | pdf, csv, doc, docx, xls, xlsx, html, txt, md | Bytes determine the format: Office container signatures or UTF-8/content signatures for text. Unsupported formats return 415. Upload bytes, not a URL. |
| Messages per prompt | 1 to 64 | Empty or oversized lists return 422 invalid_request. |
| Characters per message | 131,072 across combined text parts and serialized tool calls | 422 invalid_request above the cap. Consecutive plain messages with the same role are supported. Text recognized in images does not count. |
| Message content | Text strings or text parts; POST /v1/scan/prompt and POST /v1/responses also accept image parts in user messages | Audio, file, and other part types return 422 unsupported_content. Images in non-user turns, sent to a model without vision, or sent where image input is not enabled return 422 unsupported_content. |
| Images per prompt scan or completion | 4 per request, 3 MiB each decoded, PNG or JPEG data URLs | Remote URLs and other formats return 422 unsupported_content; a malformed data URL, a signature that does not match the declared format, or an oversized image returns 422 invalid_request. Recognized text per image is capped at 32,768 characters (422 input_too_long). See Scan an image. |
| Metadata | 4 KiB = 4,096 bytes, 32 top-level keys, depth 2 | 422 for oversized, overly nested, or reserved fields, and for a channel of hook, ingest or import, which only Zaun sets; never sent to models. |
| Project application context | 4,000 characters | Invalid policy publication is rejected. |
| Usage query interval | At most 90 days; default preceding 7 days | RFC 3339 from inclusive, to exclusive; invalid, empty, or reversed intervals return 422. |
| Violations listing | Interval at most 90 days, default the preceding 7; 1 to 100 rows a page; 1 to 100 count groups; 8 values per list filter; timelines 1 to 100 scans a page | since and until take RFC 3339 or a duration such as 24h. Anything outside these bounds, or an unknown filter value, returns 422. |
| PDF pages | 20 | Split larger PDFs before uploading. |
POST /v1/responses time to first output | 180 seconds. Non-streaming: the whole response, including input scan, generation, tool-call scan and accounting. Streaming: until the first text is released, which includes the input scan and any private thinking before the first word | 504 deadline_exceeded; generation the provider already ran is still charged. Stream long generations so only the first text has to arrive in time. At reasoning.effort xhigh or max, a model can think past 180 seconds before its first word, which fails even on a stream. |
| Extracted document text | 2 MiB UTF-8 | Reduce or split documents whose extracted text exceeds this limit. |
Use asynchronous document scans for background processing and poll the returned
status URL. See Document scans. For completions, use
GET /v1/models to discover the model-specific output cap for each enabled model.
Omitted completion output budgets default to 512 tokens.
Hook limits#
POST /v1/hooks/claude-code takes the same key and scan:write scope as a prompt scan.
Claude Code's generated hook settings give each call a 10-second timeout.
| Limit | Value | Behavior |
|---|---|---|
| Answer deadline | 8 seconds | A scan still running then is answered 200 with allow and the message "Zaun did not answer in time". The scan finishes and is recorded. |
| Prompt or tool call size | 131,072 characters, counted as a scan counts them | A larger prompt or tool call is refused with a block in a 200 reply, never with an error, because an error lets a client that fails open skip the check. Split it into smaller steps. A tool result over the limit is scanned up to it. |
| Request body | 20 MiB = 20,971,520 bytes | 413 payload_too_large. |
| Request allowance | Shared with prompt scans | A hook call takes one prompt-scan admission and the same protected tokens, so a fleet's hooks and direct scans draw on one allowance per project and organization. When it is spent the call returns 429 rate_limited. |
| Payload | session_id and hook_event_name are required; tool_input and tool_response nest at most 128 levels | 422 invalid_request. |
Telemetry ingestion limits#
These apply to POST /v1/ingest/otlp/v1/logs and /v1/ingest/otlp/v1/traces, which accept
OpenTelemetry exports authenticated by an ingest key. Over any byte, record, count or nesting
limit the whole export is refused and counted on the key's source. A long text value is the
exception: it is kept at both ends and marked as partially captured, because exporters
do not retry a 413 and one long pasted prompt would otherwise drop the rest of its batch.
| Limit | Value | Behavior |
|---|---|---|
| Export as sent | 4 MiB = 4,194,304 bytes | 413 payload_too_large, compressed or not. |
| Export after gzip | 32 MiB, and at most ten times its compressed size above 4 MiB | 413 payload_too_large. Send Content-Encoding: gzip or no encoding; other encodings return 415. |
| Records per export | 10,000 log records, spans and span events | 413 payload_too_large. Lower the exporter's batch size. |
| Attribute values per export | 200,000, nested values included | 413 payload_too_large. |
| Resource and scope groups per export | 1,000 | 413 payload_too_large. Exporters send a handful. |
| Decoded attribute text per export | 64 MiB, counting the copies of a span's attributes in each of its events | 413 payload_too_large. |
| Attribute nesting | 8 levels of arrays and key-value lists | 413 payload_too_large. |
| Attribute text value or log body | 64 KiB = 65,536 bytes | The export is accepted. A longer value keeps its first 32 KiB and its last 32 KiB, each cut on a UTF-8 character boundary, with a short note between them saying how many bytes were left out, and the event is marked as partially captured. Zaun assesses the text it kept, and the middle is not stored, so keep long content under the limit where it matters. Nested values and the text of a structured body are cut the same way. |
| Attribute key | 64 KiB = 65,536 bytes | 413 payload_too_large for the whole export. A key is a name, not content. |
| Identifier attributes | 256 bytes for session, prompt and tool call ids, user.id, the event sequence, and the service name and version | A longer value is accepted but not used as an identifier, so events carry no session, prompt or tool call id from it. |
| Content type | application/x-protobuf or application/json | 415 unsupported_media_type; a body that does not decode returns 422 invalid_request. |
| Event time | From the project's result retention ago to five minutes ahead | Records outside it are dropped alone; the 200 response reports them as OTLP partial success. |
| Decoding capacity | Eight exports decoding at once per server | 503 capacity with Retry-After; exporters retry. |
POST /v1/ingest/otlp/v1/metrics authenticates and discards metrics with 200.
Telemetry has its own allowances, separate from scan and completion allowances: a telemetry burst never spends the organization's prompt requests or tokens.
| Allowance | Per project | Per organization |
|---|---|---|
| Export requests per minute | 3,000 | 6,000 |
| Bytes as sent per minute | 256 MiB | 512 MiB |
| Events per day | 2,000,000 | 5,000,000 |
Over any of them the export is refused with 429 rate_limited and Retry-After, which
OpenTelemetry exporters honor; a request over the daily event allowance is refused whole and
nothing from it is stored. Metrics exports are discarded but still count as requests, so turn
metrics off in exporters that do not need them. An administrator can turn a project's telemetry off with
PUT /v1/admin/projects/{id}/ingest; its ingest keys are then refused with 401 before the body
is read.
Organization allowances#
Protected tokens are the tokens Zaun scans: submitted messages and tool calls, extracted document text, and completion inputs and generated tool calls. They are separate from model generation tokens.
| Tier | Scan/completion requests/min | Document requests/min | Scan/completion protected tokens/min | Document protected tokens/min |
|---|---|---|---|---|
| 1 | 60 | 15 | 1,000,000 | 3,000,000 |
| 2 | 300 | 75 | 5,000,000 | 15,000,000 |
| 3 | 600 | 150 | 10,000,000 | 30,000,000 |
Both request and token allowances must permit the request. Creating additional projects or keys does not increase the organization's allowance. Only Zaun administrators can change tiers. Contact support to discuss higher limits.
Scanning costs $0.20 per million protected tokens assessed for the first time. In a
session, tokens assessed on an earlier call and read back by the classifier cost $0.02
per million (usage.cached_input_tokens). A result the classifier could not read, which
completes as a violation for review, is not billed. Document scanning costs
$1.00 per million protected tokens. Each image in a prompt scan or completion
costs $0.0075 for text recognition, on top of the protected tokens its recognized
text adds. Completion generation is billed separately at the model rates shown in
Models. Prices do not vary by tier.
Prepaid packs: the $1,000 credit pack costs $800 (save 20%). It adds $1,000 of credit that never expires and pays for scans and generation. At the pack rate that is $0.16 per million protected tokens. Organization admins buy packs on the Billing tab in Settings. A refund of a pack takes back the credit it paid for, in proportion: a partial refund takes back the same share of the pack's credit. Any amount the pack's own credit no longer covers comes out of your other credit first, and only then shows as a negative balance.
Each submission consumes a request admission, including retries. Idempotent scan replays do not repeat assessment token usage or billing. Protected tokens exclude internal classifier instructions and repeated window overlap. Completion input and generated tool-call assessments share the scan token allowance; plain output text is not automatically scanned. Completed scans with detections are billable.
Images are charged when the scan or completion assessment that read them completes, the same as its tokens; failed or inconclusive assessments charge neither. Every image in a request is read and charged, including images resent in a session conversation, so send each image once. Idempotent replays do not charge images again.
Edge limits#
Before a request reaches the organization allowances above, the load balancer applies coarse limits over five-minute windows:
| What is counted | Limit per 5 minutes |
|---|---|
Requests with one API key (Authorization: Bearer, or x-zaun-ingest-key on ingest routes) | 10,000 |
| Requests with no key, per source address | 10,000 |
| All requests from one source address | 100,000 |
A gateway or proxy that forwards many keys from one address is limited per key, up to the
per-address ceiling. A request over an edge limit gets a 403 from the load balancer, not the
API's error envelope; wait for the window to pass before retrying.
Retry guidance#
For HTTP 429, wait for Retry-After before retrying. A request exceeding the full
token allowance must be reduced or its organization's allowance increased.
For temporary 503 errors, honor Retry-After and retry with backoff.
Retry a prompt scan with the same Idempotency-Key and body: a completed scan replays its stored result
and is not billed again, including a session scan's new and cached tokens. A scan still running returns
409 request_in_progress; wait and retry with the same key. An attempt that failed was never
charged; retry it with a new key. A POST without a key starts a new request. Use the returned
scan_uuid (scans) or request_uuid (completions) to retrieve accepted work.
See Responses for generation-specific retry behavior.
Error catalogue#
API errors use this envelope. For errors, error.request_uuid is also returned as
X-Request-Id; retain it when contacting support. Successful generation responses use
request_uuid for the persisted operation and X-Request-Id for the HTTP trace. A correlation ID does not
promise a retrievable result if persistence failed. Zaun always generates X-Request-Id; an
X-Request-Id you send comes back unchanged as X-Client-Request-Id.
{
"error": {
"code": "invalid_request",
"message": "The request does not match the operation schema.",
"request_uuid": "00000000-0000-4000-8000-000000000001"
}
}
| Code | HTTP status | Meaning and next action |
|---|---|---|
invalid_request | 422 | Invalid input, unknown fields or invalid metadata. Correct the request before retrying. |
unsupported_content | 422 | A message contains a part this endpoint, turn or model does not accept. Send text, send PNG or JPEG data URLs in user messages to POST /v1/scan/prompt or to a vision model on POST /v1/responses, or upload a supported document; remote resources are not fetched. |
input_too_long | 422 | The input exceeds a supported token limit. Reduce or split the input. |
idempotency_conflict | 409 | An Idempotency-Key was reused with a different body (scan, document, response or administrative operation). Send a new key for new content. |
request_in_progress | 409 | The original request is still running. Honor Retry-After and retry with the same key and body. |
not_found | 404 | Resource or route is absent, deleted, or outside this project. Check the endpoint, ID, and key's project. |
result_expired | 410 | The result has expired. Submit a new request only if you intend a new assessment. |
capacity | 503 | Processing is temporarily at capacity. Honor Retry-After and retry with backoff. |
model_unavailable | 503 | Classification or generation is temporarily unavailable. Retry with backoff; contact support if it persists. |
extraction_unavailable | 503 | Document extraction or image text recognition is temporarily unavailable. Retry with backoff. Nothing was dispatched to a model. |
result_not_persisted | 500 | The result and usage could not be saved. No retrievable result is promised; contact support if persistent. |
document_unreadable | 422 | The bytes could not be read before any provider work: an Office file the native parser rejected and the model then declined. Non-UTF-8 bytes that match no binary signature return 415 unsupported_media_type instead. A model refusal on the document-model path is instead reported inside an HTTP 200 body with status: failed and error.stage: document_model. Submit a readable supported variant. |
no_text_extracted | 422 | The upload contains no text to assess (blank native text). An empty model transcription is instead reported inside an HTTP 200 body with status: failed, stages.document_model: invalid_output and error.stage: document_model. Submit readable text or another document representation. |
deadline_exceeded | 504 | The operation exceeded its deadline. Retry with the original key or reduce new work. The reserved batch contract carries item failures inside HTTP 200. |
insufficient_credits | 402 | Add organization credits before submitting more scans. |
key_daily_cap | 402 | The key's settled spend today reached its daily cap. zaun.resets_at and Retry-After give the next UTC midnight, when the key works again; raise the cap with a new key if the work must continue today. The cap counts assessments, not generation on POST /v1/responses, and calls running at the same time can overshoot it by what they settle. If Zaun cannot read the key's spending counter, it keeps admitting the key within a bound: all of Zaun's servers together admit at most half of the cap beyond what the key had already spent, never past the cap, plus calls already in flight, and then answer 402. A server that restarts during the outage starts its share again. The spend settled meanwhile is added to the counter when the counter is back, and a key already known to be at its cap stays refused. |
billing_unavailable | 503 | Billing setup requires attention. Contact support. |
database_unavailable | 503 | Authentication or result access is unavailable. Honor Retry-After and retry with backoff; stale authorization is not used. |
unauthorized | 401 | Missing, invalid, expired, or revoked credential, or scans disabled for the project/organization. Check the key and project status with your operator. |
rate_limited | 429 | Organization request or token allowance exhausted. Honor Retry-After. X-RateLimit-Remaining reports remaining request admissions for the current minute. |
operation_not_implemented | 501 | This build reserves the operation. Batch scanning returns this code; use a supported operation. |
payload_too_large | 413 | HTTP body or file byte cap exceeded. Reduce the upload or split the input. |
unsupported_media_type | 415 | The uploaded document format is unsupported. Convert it to an accepted format. |
reference_not_ready | 503 | Classification setup is not ready. Contact support with the request ID. |
key_already_created | 409 | Key creation already committed and the secret cannot be returned again. If lost, ask the operator to revoke the returned key ID and issue a replacement. |
forbidden | 403 | Recognized credential lacks the required scope or credential class. Use a correctly scoped key; customer keys cannot administer the service. Never returned for a content decision. |
policy_block | 403 | The project's policy blocked the assessed input of a completion. The body carries the assessment in zaun with enforcement: blocked and reason: policy_block. Review the finding; retrying the same input returns the same decision. |
assessment_unavailable | 503 | A completion's input or output assessment did not complete, so nothing was generated or released. Not a block: zaun.enforcement reports blocked only as the gate's fail-closed decision, with reason: incomplete_assessment or assessment_mismatch. Honor Retry-After and retry; contact support with the request ID if it persists. |
input_block | 403 | An input regex control blocked a completion before any classifier or model call. zaun.input_checks names the controls. Remove or redact the matched content, or change the control's action. |
precondition_failed | 412 | Published configuration changed. The operator must read its current version and retry publication with the matching If-Match. |
principal_lookup_unavailable | 503 | Finding a user by email is not available. Filter by a hash exactly as results report it. Retrying does not help; contact support with the request ID. |
management_disabled | 503 | Administrative access is unavailable. Contact support. |
disabled | 409 | Organization or project is disabled. Enable it before issuing new keys. |
method_not_allowed | 405 | The path exists but does not accept this method. Use the method documented in OpenAPI. |
The API reference lists the error codes returned by each operation.
Classification failures return errors. A completed classifier that abstains
returns status: inconclusive, detection_verdict: unknown, and verdict: no_action.
Benign PDF, DOC, DOCX, XLS and XLSX documents always take this shape over HTTP 200,
with coverage.extraction: uncertain: parser success is not a clearance guarantee, so
binary extraction can never clear a document, but it is not an error either. A detected
attack in such a document still reports detection_verdict: violation. Native TXT, MD,
CSV and HTML can report complete coverage and status: completed.
See project controls for the action verdict and completion enforcement contract.
Results and usage#
Result reads and deletes are restricted to the key's project. Expired results return 410;
missing or deleted results return 404. Deleting a result does not erase usage
or billing history. Revoked keys retain historical attribution.
GET /v1/usage requires usage:read. Use UTC RFC 3339 from (inclusive) and to
(exclusive). Day is the only grouping. Empty intervals return zero
totals. Invalid ranges are rejected. Each day reports what billing charged:
input_tokens at the scan rate, cached_input_tokens at the cached rate,
output_tokens for generated text, and calls, the number of billed requests.
Shared scan windows#
Zaun assesses long inputs in overlapping windows and combines findings before applying project controls. Original assessed text is counted once for the token allowance. Chunking does not remove request limits. Incomplete assessments do not establish benign clearance; failed tool-call checks withhold the generated calls.
Prompt scans assess all supplied messages, including assistant text and tool calls, under the same protected-token limits and pricing. See Project controls for actions.
Maintained with the API contract · Defaults may vary by deployment.