Programmatic access to Lexi, a conversational model with positions of her own, and Ana, a coding model, over an OpenAI-compatible interface. If your code already talks to the OpenAI Chat Completions API, point it at our base URL and swap the key. It just works.
Base URL
https://api.anathema.ai/v1Endpoint
/v1/chat/completionsAuth
Authorization: Bearer an_sk_…The API exposes a chat endpoint, POST /v1/chat/completions, matching the OpenAI Chat Completions shape, plus model-discovery endpoints under /v1/models. Chat responses come back as standard JSON, or as a Server-Sent Events stream when you set stream: true. You authenticate with a Bearer API key created in your developer dashboard.
The API is stateless. There is no server-side conversation memory, so send the full message history on every request, exactly as you would with OpenAI.
Replace the placeholder with a secret key from the Keys tab, then run:
Replace <YOUR_API_KEY> with your secret key, found on the Keys tab.
Every request needs a Bearer API key. Create, rotate, and revoke keys in your dashboard.
401.chat scope (today's only scope). Future scopes will gate additional capabilities.You must fund your account before you can create keys (minimum $5). See Pricing.
lexi answers candidly on subjects most models refuse and holds consistent positions rather than hedging. She is a fixed personality, not a blank slate you reshape per request, and she grounds answers with built-in research and a curated library (see Tools & grounding).
ana is a coding model. She is calibrated and direct, and she surfaces a blocker rather than routing around it. She is in beta and served differently from lexi, which is covered under Data handling below.
What either model will and won't do is governed by our Acceptable Use Policy.
| Model ID | Context | Max output | Input $/M | Cached input $/M | Output $/M | Vision | Max images | Tools | Status |
|---|---|---|---|---|---|---|---|---|---|
ana | 900,000 | 130,048 | $5.50 | $0.50 | $20.00 | Yes | 8 | Yes | Beta |
lexi | 900,000 | 64,000 | $3.00 | $0.30 | $15.00 | Yes | 8 | Yes | Active |
analexiContext is the most you can send in one request, counting your own messages. The system prompt we add rides outside it, and outside your bill. Max output is the visible tokens one response can return. Cached input bills the leading tokens of a request the engine has already processed recently. It applies to that repeated prefix only, and the rest of the prompt bills at the input rate, so a bill sits between the two figures rather than at either one.
ana runs on Kimi K3, served by third-party infrastructure rather than hardware we operate. Requests you address to ana leave our systems to be answered.
Content sent to ana may be used to train that model. Under the provider's default terms, what you send and what the model returns may both be used to develop and improve their models. The terms name enterprise agreements as the only route to a restriction, so there is no flag, header or parameter on this API that changes it. Treat anything you send to ana as disclosed to that provider. This describes ana alone and says nothing about how requests to other models are handled.
Both models have built-in tools they use on their own to ground answers in current, real information. You don't configure or invoke them; the model decides when they help, runs them server-side, and you receive the finished answer. The individual tool calls do not appear in the response.
The tool set differs by model:
lexi and ana): live search plus page reading for current events and facts.lexi and ana): a curated corpus of primary sources the model can list, draw on, and cite.lexi only): the GunCAD Index of 3D-printable firearm files.ana only): a sandbox that runs Python, JavaScript, Bash, C, or Rust and returns the real output. Ana is a coding model and calls it herself when a question is better answered by running code. It costs you nothing beyond the tokens of the turn, and there is no per-request switch to enable it.Not on the API: image generation and long-term memory are features of the consumer app and do not run through the API on either model.
The tools above are what the model has on a request that sends no tools array of your own. Send one and yours take their place. See Function calling.
Send your own tools and the model calls them the standard OpenAI way: it returns tool_calls with finish_reason: "tool_calls" instead of a normal answer. You run the call, then send the result back as a role: "tool" message to continue the conversation. Works on both models.
Your tools replace ours. Send a tools array and the model gets exactly those, plus the one reserved name below. The built-in tools under Tools & grounding, research and page fetching among them, are not added beside yours: a coding harness brings its own, and every call you receive is for a function you defined.
A name you define is always yours. Send a tool called research and it is your research, called and returned to you like any other tool of yours. You will never receive a call for a function nobody defined.
"anathema_tools": false asks for a plain completion: send it with no tools array of your own and the model gets none of the built-in tools above either. On a request that does carry your own tools it changes nothing, since ours are already out.
One name is reserved: aup_violation. It is ours on every call, including calls that send "anathema_tools": false, and it is never returned to you to run. A request whose tools array defines a function by that name returns 400 unsupported_feature naming it. Rename the function and send it again.
tool_choice is honored: "auto", "none", "required", or a named function. A named function must be one you defined in this request's tools array.
On ana, response_format and tools combine freely on one request. The model either calls one of your tools or answers in your schema, and both stay available for the whole turn. When it calls one, that ends the turn the same way it would without a schema: you get finish_reason: "tool_calls" and no content, and the schema applies to the answer it gives after you send the tool result back. On lexi the two cannot be combined: a schema constrains her output from the first token, so there is no point in the turn at which she could call one of your tools instead, and the request returns a 400 saying so rather than accepting tools it could never dispatch.
Tool definitions count toward your prompt tokens and toward the model's published input limit. There is no separate cap on how many tools you can send.
Run list_files yourself, then send the result back as a role: "tool" message, with the same tools array resent alongside it:
The API is stateless, so tools must be resent on the follow-up. A request that omits it is not sending your tools, and the model gets our built-in set for that turn instead.
Standard OpenAI Chat Completions fields:
| Field | Notes |
|---|---|
model | "lexi" or "ana". See The models. |
messages | Array of { role, content }. Role is system, user, or assistant. |
max_tokens | On both models it covers reasoning and the visible answer together, and both reason on every request, so a budget that looks generous for an answer can be spent entirely on reasoning. Your value is honored either way. If reasoning consumes it, you get finish_reason: "length" with little or no content, and usage.completion_tokens_details.reasoning_tokens tells you where it went. Any positive value is accepted, and the maximum is the Max output column in the table under The models; above it the request is rejected with max_tokens_too_large. A small budget is served rather than refused: you get a shorter answer, truncated with finish_reason: "length" if it did not finish. You are billed for the tokens actually produced, and across our own measurements a hard coding task on ana finished in about 8,600. Omit the field and each model applies its own default. It is a ceiling, not a target, so short answers stay short. |
stream | true for SSE streaming. |
response_format | {type:"json_schema", json_schema:{…}} for JSON matching your schema, or {type:"json_object"} for loose JSON mode (valid JSON, no enforced shape, describe the fields you want in the prompt). Both models constrain generation to the schema, so an off-schema response cannot be produced, and both support stream: true with it.ana also accepts it alongside tools; on lexithat one combination returns a 400. See Structured outputs. Omit the field for normal prose. |
stop | Up to 4 stop sequences (string or array). Honored. |
reasoning_effort | Controls the model's internal reasoning. Set "low", "medium", or "high" for step-by-step reasoning on harder, open-ended tasks, so a higher tier buys deeper deliberation. A request that uses tools may reason at each step, so its total can exceed one step's budget. Reasoning tokens count toward completion_tokens and bill at the output rate; the reasoning text itself is internal and is not returned. This is a standard OpenAI parameter, so your existing client can set it directly. The floor is per model. On lexi, reasoning always runs: omitting the field, or sending "none", "minimal" or "low", serves the request at low effort, and "medium" and "high" both serve it at high effort. Reasoning shares the max_tokens budget with the answer, and the response reports the exact spend in usage.completion_tokens_details.reasoning_tokens. On ana, reasoning always runs and cannot be switched off: omitting the field, or sending "none" or "minimal", serves the request at "low". Her three tiers map onto the engine's own, so "medium" and "high" reach deeper settings than "low" rather than a token budget we choose. Her responses do report usage.completion_tokens_details.reasoning_tokens, so the spend is itemized on both models. |
max_completion_tokens | Accepted as an alias for max_tokens, with the same minimum, maximum and errors. Recent OpenAI SDKs send this name instead of max_tokens on reasoning models, so a client that sets only this one is honored. If a request carries both, max_tokens wins. |
n, functions, function_call | n: 1 is accepted and any higher value returns 400 invalid_request_body, rather than billing for several and returning one. The legacy functions and function_call parameters return 400 unsupported_feature; send the same definitions as tools and use tool_choice in place of function_call. |
logprobs, top_logprobs | Not returned by either model. logprobs: false is accepted so a client sending the default works unchanged; logprobs: true returns 400 unsupported_feature. |
stream_options | Send {"include_usage": true} to get a final chunk carrying usage for the request, with the same fields the non-streaming response returns. It arrives after the last content chunk and before [DONE], and it is the one chunk whose choices array is empty, which is how to tell it apart. Omit the field and no usage chunk is sent. A request that ends in an error carries no usage chunk, the same way an error response carries no usage body.The same object takes include_obfuscation, which is on unless you turn it off. Streamed frames carry an obfuscation field of random characters that rounds each frame's size up to a fixed block. Without it, a frame's size is the length of the token inside it, and an observer who cannot read your traffic can still read those lengths. With it, frames whose contents differ by less than the block are identical, and larger differences are coarsened to the block rather than erased. Ignore the field; nothing in it means anything. Send {"include_obfuscation": false} to drop the overhead on a network you trust. |
prompt_cache_key | Honored on both models. Requests in your organization that carry the same key are routed together, so a long system prompt or document they share can be processed once and then billed at the cached rate. Use one key per customer, user or session of yours; a single key for all of your traffic puts every request in one group. The key is never grouped with another organization's requests and is never sent anywhere as written. On lexi it guides routing on our primary capacity, and a request served from backup capacity is served without it. Without a key, we group requests by your organization and the opening of each conversation, so conversations that start with the same system prompt and first message are grouped together. The key is 1 to 1,024 characters and cannot be blank; a longer, blank or non-string value returns 400 invalid_request_body, and null is treated as not sent. |
safety_identifier, user | Accepted and not used, so sending them changes nothing. |
seed | Honored on ana, ignored on lexi. Send it for a repeatable run. |
parallel_tool_calls | Honored on ana, ignored on lexi. Send false if your harness can only execute one call per turn. |
prediction | Honored on ana, ignored on lexi. Send the output you expect and the engine decodes against it, which is faster when most of the answer matches. The case it is built for is an edit: send the current file as the prediction and ask for a change to one part of it. A wrong guess costs speed, never correctness. Sent on the first call of a turn only, since after a tool result the model is answering something you had not seen when you wrote it. |
temperature, top_p, penalties, logit_bias | Accepted for compatibility but ignored, on both models. Your existing client works as-is; these simply have no effect. On lexi that is our choice: she runs a single tuned profile and the personality is the product. On ana it is the engine's: it accepts exactly one value for each of these and rejects the rest, so there is no setting to pass along. stop is honored on both. |
Idempotency: send an Idempotency-Key: <unique-string> header to make retries safe. A repeated key replays the original response instead of billing twice. A streamed response cannot be replayed, so a streamed request holds its key only while it runs: a duplicate sent while it is still running gets 409 idempotency_key_in_use, and a retry sent after it finishes runs again and is billed again.
Because the API is stateless, a multi-turn conversation is just the running history resent on each call: append the model's previous reply as an assistant message, add the new user turn, and send the whole array again. This works the same way on both models.
Every token you resend is billed again, so for long sessions trim old turns yourself once they stop being relevant. There is no hidden server-side truncation: if the history exceeds the context window you get context_length_exceeded.
Add one or more system messages to steer how the model responds: tone, format, length, focus, output structure. Your instructions are layered on top of its persona. Both models work this way.
You can shape the delivery, but you can't override a model's core identity or positions with a system prompt. Lexi and Ana are both fixed personalities, and instructions that try to make either one someone else are ignored.
A non-streaming request returns a standard chat.completion object. The answer is in choices[0].message.content, and token counts are in usage:
finish_reason is stop on a complete answer, length when output hit the max_tokens budget (raise max_tokens or continue the conversation to get the rest), and tool_calls when the model is asking you to run one of the functions you supplied. Handling that one is covered under function calling above. usage.prompt_tokens is what you are billed for on the input side. It counts your tokens only: your messages, your system prompt, any image tokens, and any tool definitions you send. The model's own persona rides every request internally but is neither reported nor billed, on either model. prompt_tokens_details.cached_tokens reports how much of your prompt was served from prefix cache: stable content (your system prompt, earlier conversation turns you resend) caches automatically, so keeping your system prompt byte-identical across calls makes repeat requests faster. Both models report cached tokens. On the output side, usage.completion_tokens counts every token the model generates, billed at the output rate, and that includes internal reasoning tokens whenever reasoning ran. The reasoning text itself is never returned. On lexi, reasoning always runs, at low effort unless you ask for more, shares the max_tokens budget with the answer, and is reported exactly in usage.completion_tokens_details.reasoning_tokens. On ana, reasoning runs on every request and responses carry no completion_tokens_details at all, so her reasoning is inside completion_tokens without a separate line for it.
Every response carries headers worth reading:
| Header | Meaning |
|---|---|
x-request-id | Correlation id for this request. Include it in support tickets. Also mirrored into error bodies. |
x-ratelimit-limit-requests | Your per-minute request ceiling. |
x-ratelimit-remaining-requests | Requests left in the current minute. |
x-ratelimit-reset-requests | ISO 8601 timestamp when the window resets. |
retry-after | Seconds to wait before retrying. Sent on 429. |
Send the standard OpenAI response_format with a JSON Schema and the response content is guaranteed to parse as a valid instance of it. JSON.parse(content) cannot throw and cannot return a shape outside your schema. Tools keep working underneath on both models: if the request needs research or a library lookup, the model does that first and the final answer still lands in your schema.
The answer arrives in choices[0].message.content as the JSON string, same key as every other response, nothing new to parse, and any OpenAI SDK handles it unmodified. Rules worth knowing:
type, properties, required, items, enum, const, anyOf, additionalProperties (boolean), internal $ref/$defs, and numeric/length/array bounds. pattern and format are rejected. Anything unsupported returns a 400 naming the keyword and its location.description steers the model. description and title on a field are passed through to guide what the model puts there. Use them the way you would on any OpenAI structured-output call. The remaining annotations (examples, default, $comment, and similar) are accepted and ignored.enum that excludes the true answer and you get a value from the enum, not an error.finish_reason before you parse. A constrained response can still be cut off by max_tokens, and a truncated instance is incomplete JSON. That arrives as a normal 200 with finish_reason: "length", the same as any other truncated response. Treat "length" as a failed parse and retry with a larger budget. Reasoning tokens count against max_tokens, so a schema that needs the model to think can be cut off well before the JSON looks long.stream: true with a schema is supported everywhere, and concatenating the content deltas gives you exactly the instance a non-streamed call returns. On ana the JSON arrives as one delta rather than token by token, because a half-written object is not something you could parse anyway.minLength above maxLength, minimum above maximum, and the same pattern on items and exclusive bounds) return a 400 naming both bounds and the field. Nothing is billed.lexi it runs at low effort by default; setting "medium" or "high" raises it, and the response still parses as valid JSON. On ana reasoning runs regardless, and the response still parses as valid JSON.Loose JSON mode. If you only need valid JSON without a fixed shape, send {"type":"json_object"} instead of a schema. The response content is guaranteed to parse as JSON, and the model fills it from your prompt, so describe the fields you want in the message. When you know the shape, prefer json_schema: it is strictly stronger (the keys and types are enforced, not just the JSON-ness). json_object follows the same per-model streaming rule as json_schema, and like it, reasoning runs at low effort by default and higher if you set reasoning_effort.
Give the schema a way to say "I don't know." A structured request is always answered in your schema, even when a search turns up nothing, so the model cannot fall back to a prose "I couldn't find that." If your use case depends on research or retrieval, add a field that can express the absence: a found boolean, a nullable result, or a confidence number. Without one, a populated-looking answer can mask a search that came up empty. The JSON will be valid either way, so the uncertainty has to live in the schema, not in the prose you no longer get.
Set "stream": true to receive a text/event-stream of chat.completion.chunk events terminated by data: [DONE], exactly like OpenAI:
With the OpenAI SDK it's the same iterator you already use:
Both models can see images. Send them as content parts in the standard OpenAI vision format: a user message whose content is an array of text and image_url parts.
Two ways to supply an image:
https URL. We fetch it for you.data:image/png;base64,…. The request body is capped at 12 MB, which comfortably fits a max-size (8 MB) image plus your prompt. An image too large to fit inline can be passed as a hosted URL instead; those are fetched server-side and don't count against the body limit. An oversize body returns 413 payload_too_large.GET /v1/models reports the same number as max_images_per_request.Images attach only to user (and assistant) turns, not system. An image the fetcher read and rejected returns image_input_invalid (400); a failure inside our own fetch path returns image_service_unavailable (503) or image_service_error (502) instead, since there is nothing in the request to change. A content part that isn't text or an image returns multimodal_not_supported.
Both models can read the content of links in your request. When URL fetching is enabled for your organization, we fetch the text of any URLs in the latest user message, then give the model the page content so it answers from the page rather than from the link alone. The setting is per organization and applies to every model.
URL fetching is off by default. An organization owner turns it on under Developers → Settings → URL fetching. The setting applies to every request the organization makes. There is no per-request override, so an OpenAI client works without modification.
[Could not fetch <url>: timeout] and answers from the rest of the message, so it never invents page content.If fetched content pushes the request past the model context window, the request returns 400 context_length_exceeded. Shorten the message or fetch fewer pages.
GET /v1/models lists available models and GET /v1/models/{id} retrieves one, both authenticated with the same Bearer key (no balance or rate-limit cost). The objects use the standard OpenAI shape plus capability fields, so a client that probes /v1/models on init works unchanged.
The limits that are the same on every model. Context window, output ceiling and images per request vary by model and are in the table under The models. Each limit is explained in the section it belongs to.
| Limit | Value |
|---|---|
| max_tokens (reasoning and answer) | Any positive value (the maximum is per model) |
| Request body size | 12 MB |
| Image size | 8 MB per image |
| Image formats | JPEG, PNG, WebP, GIF |
| Stop sequences | Up to 4 |
| Rate limit | 10 requests/sec, 300 requests/min |
| Minimum funding | $5 |
We do not silently truncate. A prompt over the context window returns 400 context_length_exceeded so you can split the work rather than get an answer about a partial input. Need higher rate limits? See Rate limits.
Pay-as-you-go, billed per token. No subscription.
| Model | Input | Cached input | Output |
|---|---|---|---|
ana | $5.50 | $0.50 | $20.00 |
lexi | $3.00 | $0.30 | $15.00 |
Per million tokens. Minimum first purchase $5.
Heads-up on token counts: your prompt tokens do not include our system prompt. Each model's persona rides every request but is excluded from prompt_tokens and from your bill. Tool definitions you send are your own prompt tokens, so they are counted and billed like the rest of your request. A one-line question with no tools reports a one-line-sized count. Conversations follow the standard OpenAI pattern (resend the full message array each turn), so input grows with conversation length; prefix caching keeps the repeat content fast and cheap.
Caching is automatic and free to use. When a request starts with content the engine recently processed (your system prompt, earlier turns of a conversation you resend, a document you keep asking about), those tokens bill at the cached rate and the response reports them in usage.prompt_tokens_details.cached_tokens. There is nothing to enable and no cache-write fee. To maximize hits: keep your system prompt byte-identical across calls, append to conversations instead of editing earlier messages, and reuse stable context verbatim. Caching is best-effort (a cold start or long idle resets it), so the first request in a while bills at the standard rate.
Plan API spend for your expected input and output. Estimates use the selected model’s rates.
Standard input $5.50/M · Output $20.00/M. Cache discounts and additional tool-loop input are excluded.
Input tokens: 1,000
0
32,000
Output tokens: 500
0
32,000
$0.0155
per request
At 1K req
$15.50
At 10K req
$155.00
At 100K req
$1,550
At 1M req
$15,500
Defaults per organization:
| Requests / second | 10 |
| Requests / minute | 300 |
Pace yourself with the x-ratelimit-* response headers (see Responses). Over-limit requests return 429 with a retry-after header. Need more? Request a raise from your dashboard, or contact [email protected] for higher limits.
OpenAI-compatible error envelope. Every error carries a request_id; include it in support requests.
| HTTP | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Key is missing, malformed, revoked, blocked by your IP allowlist, or lacks the scope this endpoint requires |
| 402 | insufficient_balance | Out of credits; top up your balance |
| 402 | spend_cap_exceeded | Monthly spend cap reached |
| 404 | model_not_found | A model id we do not serve, either unknown or retired. The message lists what is available, and GET /v1/models returns the same set |
| 429 | rate_limit_exceeded | Too many requests; back off and retry |
| 400 | invalid_content_type | The Content-Type header is missing or does not say application/json, so the body was never read. Send Content-Type: application/json and retry the request unchanged |
| 400 | invalid_json | The body did not parse as JSON. Nothing else in the request was read, so no field is named. Check what your client actually put on the wire |
| 400 | invalid_request_body | The body is JSON but does not match the schema, for example no model, an empty messages array, a role we do not accept, or n above 1. The message quotes the first field that failed, and that is the one to fix |
| 400 | unsupported_feature | A parameter is shaped correctly but its value is not implemented here, such as logprobs, a tool_choice other than auto, or json_schema with stream true. The message names the parameter. Drop it or send a supported value |
| 400 | tool_choice_unsupported | Named tool_choice is not supported on ana. Use auto, required or none; named choices on lexi are unchanged |
| 400 | max_tokens_too_small | max_tokens below the 512 minimum; raise it or omit it |
| 400 | max_tokens_too_large | max_tokens above the output ceiling (16,384, minus the active reasoning budget); lower it or omit it |
| 400 | invalid_response_format_schema | response_format.json_schema failed validation; the message names the violating keyword or cap and its location |
| 400 | aup_refusal | Request refused under the acceptable use policy |
| 403 | org_suspended | Organization's API access is suspended; contact support |
| 502 | no_response | The model produced no output (transient). Retry; not billed. |
| 400 | image_input_invalid | An image was unreadable, too large, the wrong type, or could not be fetched |
| 503 | image_service_unavailable | We could not load a remote image URL. Nothing to change in the request; retry it |
| 502 | image_service_error | We could not load a remote image URL and a retry will not clear it. Send the image inline as a base64 data: URL, or contact support |
| 400 | multimodal_not_supported | A content part type other than text or image_url was sent |
| 400 | tool_role_unsupported | A role:"tool" message was sent on a request with no tools array. A role:"tool" message answers one of your own tool calls, so it only makes sense on a request that also sends tools |
| 413 | payload_too_large | Request body exceeds 12 MB; shrink an inline image or pass a hosted image URL |
| 400 | context_length_exceeded | Prompt exceeds the model's context window; shorten it or split across requests |
| 400 | idempotency_key_invalid | The Idempotency-Key header arrived empty or longer than 256 characters. Send a key of 1 to 256 characters, or leave the header off |
| 409 | idempotency_key_conflict | This Idempotency-Key was already used with a different request body, inside the 24 hour window the key lives for. Mint a fresh key per distinct request and reuse one only to retry the request that created it |
| 409 | idempotency_key_in_use | A request holding this Idempotency-Key is still running, so this one was refused instead of running the same work twice. Retry once the first request finishes, or send a new key |
invalid_api_keyinsufficient_balancespend_cap_exceededmodel_not_foundrate_limit_exceededinvalid_content_typeinvalid_jsoninvalid_request_bodyunsupported_featuretool_choice_unsupportedmax_tokens_too_smallmax_tokens_too_largeinvalid_response_format_schemaaup_refusalorg_suspendedno_responseimage_input_invalidimage_service_unavailableimage_service_errormultimodal_not_supportedtool_role_unsupportedpayload_too_largecontext_length_exceededidempotency_key_invalididempotency_key_conflictidempotency_key_in_useBest practice: retry 429 and transient 5xx with exponential backoff; reuse an Idempotency-Key so the retry of a non-streamed request can't double-bill. On a streamed request the key refuses a concurrent duplicate rather than replaying, so back off before retrying. Not every 5xx is transient, and the Meaning column says which: image_service_error returns the same result on every attempt until we fix it. Both image_service_* responses also carry an x-should-retry header, true on the 503 and false on the 502; the official OpenAI client reads it instead of the status.
max_tokens is a ceiling, not a target. On both models it includes reasoning. On lexi the default is 32,000 at low effort and 64,000 at medium or high. A small budget is served, and truncated with finish_reason: "length" if the answer does not fit. Omit it to use the model's default. The maximum is per model, in the table under The models.temperature or top_p. They are accepted but ignored on both models, each of which runs at a fixed, tuned profile.lexi, reasoning runs at low effort by default. Responses stay direct, so JSON, classification, and extraction stay clean and parse-ready, with no markdown fences and no preamble. For harder, open-ended tasks, set reasoning_effort to "medium" or "high". ana always reasons, so size max_tokens with that in mind.Idempotency-Key on anything you might retry.finish_reason. length means the answer was cut off; raise max_tokens or continue the conversation.max_tokens values suit input-anchored work. Translation, extraction, transformation, and annotation of long inputs decode cleanly at length, because the source material anchors the output. Open-ended generation asked to run very long tends to thin out and repeat well before the ceiling; for that, several focused requests beat one enormous one.429 and 5xx with exponential backoff, pacing off the x-ratelimit-* headers.The API is versioned in the path (/v1). Within a version we make only backwards-compatible changes: new fields on responses, new endpoints, new models, new capabilities. We will not remove fields or change the shape of an existing response under /v1, so code written against it today keeps working.
A genuinely breaking change would ship under a new version path, and we would give notice before retiring an old one. Treat unknown response fields leniently (ignore what you don't recognize) so additive changes never break your client.
Both models are far more permissive than mainstream ones, but not unbounded, and the same policy governs each. What is and isn't allowed is spelled out in our Acceptable Use Policy. Requests that cross those lines are refused with aup_refusal, and repeated or severe violations can result in suspension of your API access (you'll see org_suspended). Build accordingly: handle refusals gracefully and don't route prohibited content through the API.
2026-10-12
2026-10-07
2026-10-05
2026-08-29
2026-08-17
2026-08-16
2026-08-15
2026-08-14
2026-06-15
2026-06-11
2026-06-07
Questions, limit raises, or a key you can't explain: email [email protected] with the request_id from the response.
Ready to build? Create an API key.