Responses
Explain how rainbows form in a short sentence.
Your requestMessages, model and optional tools arrive with your project key.
Generate text or function calls with your project's controls applied to input and
generated tool calls. The input scan is built into POST /v1/responses.
Send one request with a run:write key; you do not need to call POST /v1/scan/prompt
separately or grant the key scan:write. Zaun assesses the submitted conversation,
applies the project's controls, and includes the scan result in zaun.scan on the
response. Generated function calls are also scanned before release. Plain assistant
output text is not scanned automatically. The built-in assessments are billed separately
from model generation at the scan rate described below.
POST /v1/responses accepts a text prompt or a conversation of input items, and
supports Claude Fable 5.1, Opus 5.5 and Sonnet 5, Cerebras GPT-OSS 120B
(cerebras-gpt-oss-120b), and GPT-6 Astra, Sol and Luna (gpt-6-astra, gpt-6-sol,
gpt-6-luna). Use GET /v1/models to check which are enabled on your deployment.
Responses are stateless. Send the full conversation in input on every request; store: true and
previous_response_id are rejected, and responses are not kept for later retrieval.
Request flow#
- Validate the request and run input regex controls. A regex Block stops the request; Redact replaces matching input before it reaches the generation model.
- If regex checks have no matches, start input classification and generation in parallel. If they match, finish the input assessment before generation.
- Hold all generated content until the persisted input verdict permits release. A Block verdict cancels generation and returns an error before streaming begins.
- Release buffered text, then stream new text as it arrives. Plain output text is not scanned automatically.
- Buffer generated tool calls separately and scan them before release. A tool-call Block withholds those calls; text already streamed remains visible.
Failed safety checks prevent release of protected content. Non-streaming responses wait for the required checks too. See Project controls to configure the actions.
Create a key#
In API keys, select Create key, then enable Run responses and list models.
This grants run:write. Existing keys retain their permissions; create a replacement
if yours only has scan:write. Add Read results for assessment and session retrieval.
Keep API keys on your server, never in browser code.
Send a request#
curl https://api.zaun.ai/v1/responses \
-H "Authorization: Bearer $ZAUN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-5",
"instructions": "Answer clearly and briefly.",
"input": "Why do rainbows form?",
"max_output_tokens": 256,
"sample": true
}'
The response is a response object. Generated text is in output as a message item with
output_text content, and allowed tool calls are function_call items. The same response carries
the input assessment in zaun.scan, its enforced action in verdict and zaun.enforcement,
and a server-generated request_uuid. If the model generates function calls, their separate
assessment appears in zaun.output_scan. Plain text output has no zaun.output_scan.
Streaming responses include these fields in the final event. Each HTTP request starts a new operation.
zaun.scan is the full input scan result, with status, violation, verdict,
findings, confidence, coverage, and assessment usage, the input tokens billed
for it at the scan rate (zaun.output_scan.usage does the same for a tool-call
assessment). It gives you the scan decision in the same request as the generated
response. The top-level metadata field is reserved for ordinary Responses metadata,
so the scan result stays in the zaun extension rather than being copied into metadata.
For example, these fields appear alongside the generated output in a successful response:
{
"verdict": "no_action",
"zaun": {
"scan_uuid": "00000000-0000-4000-8000-000000000005",
"scan": {
"status": "completed",
"violation": false,
"verdict": "no_action",
"findings": []
},
"enforcement": "allowed"
}
}
instructions becomes the first system message. For a conversation, pass input as a list of
items instead of a string. No zaun request field is needed to enable scanning. Optionally,
zaun.scan.session_uuid links activity for later retrieval; it does not load conversation history. Generate one UUID per conversation on your side and reuse it on every call; Zaun never assigns one.
Use the OpenAI SDK#
Set the base URL to https://api.zaun.ai/v1 and use a project key with run:write.
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["ZAUN_API_KEY"], base_url="https://api.zaun.ai/v1", max_retries=0)
response = client.responses.create(
model="claude-sonnet-5",
instructions="Answer clearly and briefly.",
input="Why do rainbows form?",
max_output_tokens=256,
)
print(response.output_text)
scan = response.model_extra["zaun"]["scan"]
print(scan["status"], scan["violation"], scan["verdict"], scan["findings"])
stream = client.responses.create(
model="claude-sonnet-5", input="Explain least privilege.", stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="")
elif event.type in ("response.completed", "response.incomplete"):
print("\n", event.response.usage)
print(event.response.model_extra["zaun"]["scan"])
The OpenAI Python SDK exposes Zaun's custom response field through
response.model_extra["zaun"]. For a generated function call, read its separate
assessment at response.model_extra["zaun"]["output_scan"]. A blocked input returns
HTTP 403 instead of a successful Response object; a regex Block can stop before
classifier assessment, so that error has no zaun.scan.
When input regex checks have no matches, generation and input classification run in
parallel. No response content is released until the input assessment is persisted
and allows the request. Buffered text is then released immediately, followed by
live response.output_text.delta events as the model generates them. Plain output text is not scanned.
Generated tool calls remain buffered until their separate assessment allows release.
Streams start with response.created and response.in_progress and end with response.completed,
or response.incomplete when the output budget runs out (max_output_tokens) or a control withholds
output (content_filter). The final event carries the full response, including usage. Every event has
an increasing sequence_number. Input blocks return HTTP 403 before streaming. Tool-call blocks end
the stream incomplete with content_filter and no tool calls; any text already streamed is retained.
An error after streaming begins is an error event, not a new HTTP status.
Function tools#
Pass tools and optionally tool_choice. Choices are auto, required, none, or a named
function ({"type": "function", "name": "..."}). The default is auto when tools are present.
Zaun returns tool requests; your application decides whether and how to execute them.
tools = [{
"type": "function",
"name": "get_weather",
"description": "Look up the weather for a city.",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
}]
conversation = [{"role": "user", "content": "Find the weather in Seattle."}]
response = client.responses.create(
model="claude-sonnet-5", input=conversation, tools=tools, tool_choice="required",
)
calls = [item for item in response.output if item.type == "function_call"]
for call in calls:
print(call.call_id, call.name, call.arguments)
conversation += [call.model_dump(exclude_none=True) for call in calls]
conversation += [{"type": "function_call_output", "call_id": call.call_id, "output": "Rain, 12 C"} for call in calls]
followup = client.responses.create(model="claude-sonnet-5", input=conversation, tools=tools)
print(followup.output_text)
Allowed calls appear as function_call output items. Arguments are JSON strings. For a follow-up,
append each call item and one function_call_output item per call, with its call_id and your result
text, then resend the tool definitions and full conversation. Use auto, required, or a named choice
for tool-result follow-ups; none is supported only without tool-result history. Unknown, duplicate,
missing, or unmatched call IDs are rejected. Calls are never executed by Zaun.
Web search as a function tool
Zaun has no built-in web search. Declare search as an ordinary function tool, run the
search yourself, and return the results as a function_call_output. Zaun assesses the
generated call before releasing it, and assesses the returned results as input on the
follow-up request, so web content passes through your project's controls before the
model reads it.
tools = [{
"type": "function",
"name": "web_search",
"description": "Search the web. Use for current events or facts newer than your training data.",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string", "description": "A short search query."}},
"required": ["query"],
},
}]
conversation = [{"role": "user", "content": "What did the Fed announce this week?"}]
response = client.responses.create(model="claude-sonnet-5", input=conversation, tools=tools)
for call in (item for item in response.output if item.type == "function_call"):
query = json.loads(call.arguments)["query"]
results = my_search(query) # your search provider; return a compact text summary with URLs
conversation.append(call.model_dump(exclude_none=True))
conversation.append({"type": "function_call_output", "call_id": call.call_id, "output": results})
followup = client.responses.create(model="claude-sonnet-5", input=conversation, tools=tools)
print(followup.output_text)
Keep output compact: a few titles, URLs and snippets rather than whole pages. The
returned text counts toward the follow-up's input tokens for both the scan and the model.
If the model asks for several searches, loop until a response contains no function_call
items.
Only generated call names and arguments are output-scanned. Assistant prose and private reasoning are excluded.
For non-streaming responses, an output Block withholds calls and text and returns an incomplete
response with incomplete_details.reason: content_filter. Streaming text already released is not
retracted; blocked calls are never released. A failed tool-call scan returns an error. Streamed calls
arrive as function_call output items with response.function_call_arguments.done, after the same checks finish.
Redacted plain output remains in session activity with a not_assessed status.
Explicit scan requests always assess their submitted content, including assistant text.
Supported features#
| Field | Behavior |
|---|---|
model | Required deployed alias. Discover available models with GET /v1/models. |
input | Required. A string becomes one user message. A list may contain messages (role user, assistant or system, with text or input_text and output_text parts), function_call items and function_call_output items. Consecutive plain messages with the same role are supported. The first non-system message must be user, and the last item must be a user message or a function call output. |
instructions | Optional system text, inserted before the conversation. |
max_output_tokens | Optional model-specific output budget; default 512. Claude models allow up to 128,000 and Cerebras GPT-OSS 120B allows up to 16,384. |
temperature, top_p | Omit both. These models use provider defaults. |
reasoning.effort | Optional. One of low, medium, high, xhigh or max, limited to the levels in the model's zaun.effort.levels from GET /v1/models. Omit it to run at zaun.effort.default. Claude routes accept all five (Sonnet 5 and Fable 5.1 default high, Opus 5.5 defaults medium); Cerebras GPT-OSS accepts low, medium and high (default medium); the GPT-6 routes accept all five (Sol and Luna default medium; OpenAI does not document Astra default, so zaun.effort.default is null there). A level the model lacks, or any other reasoning field, returns 422 invalid_request with param reasoning.effort before any paid call. A non-streaming response must finish within 180 seconds and a stream must release its first text within 180 seconds, thinking included. Opus 5.5 at max ran past the first on staging and inside the second, so send stream: true at high and above and expect xhigh or max on hard prompts to fail with 504 deadline_exceeded when thinking alone outlasts the budget. |
tools, tool_choice | Function tools only. Claude routes reject strict: true; explicit strict: false is treated as omission. Fable 5.1 and Opus 5.5 reject required and named choices with 422. See Function tools. |
stream | Set true for typed server-sent events. Use streaming for large output budgets and for high reasoning effort: a non-streaming response must finish within 180 seconds and a stream must release its first text within 180 seconds, or the request returns 504 deadline_exceeded; once text is flowing a stream may run for up to an hour. Generated tool calls are assessed before release. Plain text output is not scanned. |
store | Only false is accepted. Responses are never stored. |
zaun.scan.session_uuid | Optional UUID shared across related scans and responses. |
sample | Defaults to true. With false, Zaun keeps the redacted text but hides it from you: it is not shown in the console or returned by session retrieval. It is still used for session context and campaign detection. A project stops all retention only by setting retain_activity to false, which also removes the text from historical investigation. Read zaun.activity_capture.status for requested or disabled (project retention off); requested capture is asynchronous. |
User messages may include PNG or JPEG input_image parts as data URLs, up to four per request,
on models that support vision. Zaun reads their text and assesses it with the message; the model
receives the image. Each image costs $0.0075 for text recognition. See limits.
Remote image URLs, files, audio, built-in tools, developer-role messages, reasoning summaries, text-format
options, previous_response_id, metadata, and provider-specific options are not supported. Unsupported
fields are rejected rather than silently ignored. Model discovery also requires run:write:
curl https://api.zaun.ai/v1/models -H "Authorization: Bearer $ZAUN_API_KEY"
Cerebras GPT-OSS#
Use model="cerebras-gpt-oss-120b". The native model ID is
resolved internally. Input costs $0.35 per million tokens and output costs $0.85
per million tokens, including reasoning tokens reported by the model.
response = client.responses.create(
model="cerebras-gpt-oss-120b",
input="Explain least privilege.",
max_output_tokens=512,
)
Availability and retries#
When enabled, an explicit upstream throttle rejection can use one alternate route
to the same model. Timeouts, connection failures, safety blocks, and organization
limits do not authorize a fallback. The configured model's published customer
rate remains fixed for the request, including when the alternate route is used.
Input permission gates all output; only generated tool calls need an output assessment. No model substitution is performed. The
cerebras-gpt-oss-120b alternate route remains pinned to Cerebras.
Input and output controls#
Configure controls in Projects. The strongest matching action wins: Block, then Redact, then Alert, then No action. Unconfigured controls default to No action.
Six deterministic regex controls protect completion inputs and all explicitly submitted prompt, output and document scans:
-
Email address (
pii_email). -
Phone number (
pii_phone): formatted domestic and international numbers. -
Credit card number (
pii_credit_card): 13 to 19 digits with a valid Luhn checksum. -
IP address (
pii_ip): valid IPv4 and IPv6 addresses. -
Social Security numbers (
pii_ssn): US SSN patterns such as123-45-6789. -
API keys and secrets (
credentials): recognized API keys, bearer tokens, password assignments, private keys, and credential-bearing URLs.
These checks cover model-visible messages, history, system text, and tool definitions. Regex recognition is format-based; use the control descriptions for supported patterns. Stored findings contain only control IDs and their selected action, never matched sensitive values.
A regex Redact replaces matching completion input with category markers before either classifier or generation sees it. The original request fingerprint still identifies retries; the dispatched invocation fingerprint reflects sanitized content. A separate Block still wins. Tool arguments remain valid JSON. If a sensitive match occurs in a structural identifier or schema key that cannot be safely rewritten, the request fails before dispatch.
A regex Block stops before generation or classifier calls. A regex Alert or No action or Redact continues through input assessment before generation starts. With no regex matches, generation runs alongside the input assessment. An input Block or technical input-assessment failure discards buffered output and cancels local provider consumption. A completed classifier abstention defaults to No action.
Generated tool calls are assessed before release. Tool Block withholds those calls.
For non-streaming requests, a tool Block returns no output items and
incomplete_details.reason: content_filter; streaming text already released is not retracted.
Tool assessment errors never release tool calls. Explicit scan requests retain
their submitted-content behavior and also evaluate regex controls. Prompt and output scans return optional redacted_prompt (rendered assessed text) and input_checks; document scans return regex evidence and the verdict, never redacted extracted text. Regex controls are separate from ATLAS technique labels.
Read zaun.input_checks for regex control IDs and action, zaun.scan for input
classification, and zaun.output_scan for tool-call classification. The latter is
omitted for plain text output. The top-level action includes regex findings.
The response also carries zaun.enforcement, zaun.reason, and
zaun.accounting_status. Regex-block errors include zaun.input_checks without
claiming a classifier assessment occurred.
Limits and accounting#
Responses share your organization's prompt-scan allowance across all keys and projects. Each response consumes one request; input and generated tool-call assessments consume protected tokens from the same allowance.
| Tier | Shared requests/min | Shared protected tokens/min |
|---|---|---|
| 1 | 60 | 1,000,000 |
| 2 | 300 | 5,000,000 |
| 3 | 600 | 10,000,000 |
Responses follow the request limits. Discover each model's configured
output cap with GET /v1/models. A tool-call assessment that exceeds the available
token allowance withholds those calls and returns an error. Honor Retry-After
when retrying rate-limit or temporary availability errors.
Completed input and tool-call assessments cost $0.20 per million protected tokens,
and each reports the input tokens it was charged in its usage.
Generation is additionally billed at these token rates:
| Model alias | USD / million input tokens | USD / million output tokens |
|---|---|---|
claude-fable-5-1 | $11.00 | $55.10 |
claude-opus-5-5 | $4.40 | $22.10 |
claude-sonnet-5 | $2.20 | $11.10 |
cerebras-gpt-oss-120b | $0.35 | $0.85 |
gpt-6-astra | $10.00 | $50.10 |
gpt-6-sol | $2.00 | $10.10 |
gpt-6-luna | $0.10 | $0.60 |
Use GET /v1/models for enabled model IDs and current token rates.
Claude Sonnet 5, Opus 5.5, and Fable 5.1 requests include five-minute provider
cache checkpoints after tools, system instructions, and the final conversation turn,
for both buffered and streaming responses. Keep reusable content at the beginning
of the request. Cache hits are best effort and depend on the provider's minimum
prefix length and routing; inspect usage.input_tokens_details.cached_tokens and
cache-write counts rather than assuming every repeat is cached. Cache reads and
writes use the model's published cache rates, including the write premium.
sample=false controls whether Zaun shows you the stored activity, not provider inference caching.
See AWS prompt caching.
Batch and priority inference are not enabled by this endpoint. Provider output
counts include reasoning tokens, even when reasoning is not shown. reasoning.effort
changes how much the model thinks and writes, so it changes output cost at the same
per-token rate: a claim-judging call can run at low and a drafting call at high on
the same key. The effort travels to Bedrock as Anthropic's native output_config.effort,
to OpenAI's Responses API as reasoning.effort, and to Cerebras as reasoning_effort.
On an OpenRouter throttle fallback it is sent as OpenRouter's reasoning.effort; if that route cannot honor it the
request fails rather than silently running at another level.
usage.output_tokens_details.reasoning_tokens reports the provider's separate
reasoning count when available, or null when unavailable (including Bedrock
Converse). It is a subset of output tokens, not an additional charge. Unknown
reasoning usage is never presented as zero or estimated from generated text.
Before dispatch, credits are held using a conservative input budget and the requested
output cap. Settlement uses only actual provider-reported input and output counts.
Fractional microdollars carry across requests instead of rounding every request up.
Generation may incur cost before the input verdict is known. Generated tokens still incur cost if input or tool output is blocked or an assessment fails. Usage the provider already reported is charged when a request stops early, including when the input is blocked, an assessment fails, or a streaming client stops reading.
A request that never reaches the provider, or that the provider refuses before generating (such as a throttle with no alternate route), releases its hold without a generation charge.
Unknown provider usage is not guessed or charged. Interrupted calls with unknown usage retain their credit hold until usage is reconciled; they are never automatically repeated. sample: false does not make requests free.
Retries and errors#
| Response | Meaning |
|---|---|
401 or 403 forbidden | Check key permissions and project status. |
403 input_block or policy_block | A project control blocked the request. The decision is in zaun. |
| 402 | Insufficient credits for admission. |
| 409 | The same Idempotency-Key is in progress or already used, or its body changed. |
| 422 | Invalid model, content, or parameters. |
| 429 | Shared request or token allowance exhausted. Respect Retry-After. |
| 503 | Temporary service or concurrency capacity failure, or assessment_unavailable when the input assessment could not be trusted. Nothing was generated; respect Retry-After. |
| 504 | Deadline exceeded. The provider may already have been called. |
| 501 | Generation is disabled on the target deployment. |
Without Idempotency-Key, each HTTP request starts a new generation. Supply a stable
project-scoped key through the SDK extra_headers option for retries. Reusing the same
key and body returns 409 without another generation because output is not retained for
replay. A changed body returns idempotency_conflict. After a timeout or uncertain
response, retrying without the original key can generate and bill another answer.
Activity and session retrieval#
By default, redacted response inputs and outputs are queued after assessment and
retained in activity storage for 30 days. The Violations page shows flagged requests
and their available session timeline. Retrieval requires result:read:
curl "https://api.zaun.ai/v1/sessions/$SESSION_UUID?limit=10" \
-H "Authorization: Bearer $ZAUN_API_KEY"
Activity appears asynchronously. Refresh for late arrivals; a session timeline contains available captured activity and is not a complete audit log. Regex redaction covers recognized patterns.
Successful responses distinguish two identifiers: X-Request-Id traces the HTTP request,
while top-level request_uuid identifies the persisted generation and its assessment.
Explicit output scans#
Use POST /v1/scan/prompt to assess output text and tool calls directly, without generating a response or executing tools. Supply assistant messages with content text and optional tool_calls, each {"id", "type": "function", "function": {"name", "arguments"}}. A tool-only message uses content: null; function.arguments is a JSON object encoded as a string. Every supplied message is scanned. Only include messages you want scanned.
{
"messages": [{
"role": "assistant",
"content": "Here is the requested summary.",
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {"name": "send_email", "arguments": "{\"to\":\"team@example.com\",\"body\":\"Summary\"}"}
}]
}],
"sample": true
}
Requires a project key with scan:write. The response includes a server-generated request_uuid. It shares scan pricing and the organization's scan RPM and TPM allowance. The compact response includes the combined violation, configured action, detected techniques and usage. Regex controls apply to explicitly submitted output; a Redact match uses the existing redacted_prompt response field. Optional session_uuid attaches activity to a session; sample: false hides the stored activity from you.
Automatic response behavior is unchanged: ordinary generated text is not scanned, and tool calls are assessed before release.
See scan feedback for assessment IDs and submitting a review without changing a verdict.
For claude-opus-5-5, use tool_choice: "auto" (the default) or "none".
Bedrock does not support "required" or a named function choice for this model;
Zaun rejects them with HTTP 422 before billed work begins.
Maintained with the API contract · Defaults may vary by deployment.