# Wafer # Serverless Source: https://docs.wafer.ai/serverless.md Access Wafer models through OpenAI-compatible and Anthropic-compatible APIs. Add credits, create an API key, and pay per token. ## Get an API key Sign in at [app.wafer.ai](https://app.wafer.ai) to add credits and create an API key. Store your key securely. ## Connect | Setting | Value | |---|---| | Base URL | `https://pass.wafer.ai/v1` | | Authentication | `Authorization: Bearer ` | Anthropic-style clients can send the key as `x-api-key: ` instead. ## Send a request ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [ {"role": "user", "content": "Hello!"} ], "max_tokens": 128 }' ``` ## Choose an API Chat Completions is the primary API. Every model supports it, and it has the widest feature set. Responses and Messages are compatibility APIs for clients that already speak those formats. | API | Endpoint | Use it for | | --- | -------- | ---------- | | [Chat Completions](/serverless/chat-completions) | `POST /v1/chat/completions` | New integrations and OpenAI SDK clients. Streaming, tools, structured outputs, vision, and reasoning. | | [Responses](/serverless/responses) | `POST /v1/responses` | Clients built on the OpenAI Responses API, such as Codex. Stateless: no stored responses. | | [Messages](/serverless/messages) | `POST /v1/messages` | Anthropic SDK clients and Claude Code. | To use an SDK, point the OpenAI SDK's base URL at `https://pass.wafer.ai/v1`, or the Anthropic SDK's base URL at `https://pass.wafer.ai`, and pass your Wafer API key. ## Available models List available models and their capabilities: ```bash curl -sS "https://pass.wafer.ai/v1/models" \ -H "Authorization: Bearer " ``` Use a model's `id` in your requests. See [Models and Capabilities](/serverless/models) for context limits, per-API capability flags, reasoning efforts, and pricing. ## Zero Data Retention Every request uses Zero Data Retention by default, with no header required. See [Zero Data Retention](/serverless/zero-data-retention) for details and model support. ## Reference - [Errors and Rate Limits](/serverless/errors): error format, error codes, retries, and `429` handling. - [Usage and Billing](/serverless/usage): token usage in responses, prompt caching, and billing. ## Support For help, contact [support@wafer.ai](mailto:support@wafer.ai). Include the request ID if available. Never share your API key. # Models and Capabilities Source: https://docs.wafer.ai/serverless/models.md `GET https://pass.wafer.ai/v1/models` is the source of truth for which models are available and what each one supports. Read capabilities from the catalog at runtime instead of hard-coding them. Models, limits, and prices change as the catalog evolves. ## List models ```bash curl -sS "https://pass.wafer.ai/v1/models" \ -H "Authorization: Bearer " ``` The model list is public, so the API key is optional. With your key, pricing reflects your account's rates. An invalid key returns `401`. The response is an OpenAI-compatible model list. Use a model's `id` as the `model` field in requests: ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "Hello!"}], "max_tokens": 128 }' ``` `GET /v1/models` is the only model route. There is no per-model `GET /v1/models/{id}`. ## Model card Each entry carries the standard OpenAI fields plus a `wafer` object. This is an abridged card: ```json { "id": "GLM-5.3", "object": "model", "created": 1790793167, "owned_by": "wafer", "max_model_len": 1048576, "zdr_supported": true, "wafer": { "display_name": "GLM-5.3", "context_length": 1048576, "max_output_tokens": null, "capabilities": { "vision": false, "tools": true, "reasoning": true, "chat_completions": { "supported": true, "streaming": true, "tools": true, "tool_streaming": true, "json_object": true, "json_schema": true, "json_schema_refs": true, "tools_with_response_format": true, "n": true, "regex": false, "grammar": false }, "responses": { "supported": true, "streaming": true, "tools": true, "text_format": ["text", "json_object", "json_schema"], "raw_json_schema_text": true }, "messages": { "supported": true, "streaming": true, "tools": true, "tool_streaming": true, "vision": false, "reasoning": true }, "zdr": { "supported": true, "same_capabilities": true }, "reasoning_effort": { "efforts": ["none", "low", "high", "max"], "default": "low" } }, "pricing": { "currency": "usd", "input_cents_per_million": 22, "output_cents_per_million": 339, "cache_read_cents_per_million": 18, "cache_read_microcents_per_million": 17750000 } } } ``` Ignore fields you don't recognize. Wafer adds fields to the card over time. `created` is the time of the request, not a model release date. ## Limits | Field | Meaning | | ----- | ------- | | `max_model_len` | Context window in tokens: prompt tokens plus generated tokens. A request that doesn't fit returns `400` with code `context_length_exceeded`. | | `wafer.context_length` | Same value as `max_model_len`. | | `wafer.max_output_tokens` | Maximum generated tokens per request when the model has a separate output cap. `null` means output is bounded only by the context window. A larger `max_tokens` is lowered to this cap. | See [Output length](/serverless/chat-completions#output-length) for how `max_tokens` defaults and limits apply. ## Capability flags The top-level flags summarize the model: | Flag | Meaning | | ---- | ------- | | `wafer.capabilities.vision` | Accepts image input. Also exposed as `supports_vision: true` at the top level of the card. | | `wafer.capabilities.tools` | Supports function calling. | | `wafer.capabilities.reasoning` | Can return reasoning separately from the final answer. | | `zdr_supported` | Supports Zero Data Retention, which every request uses. A model without it returns `422` with code `model_zdr_not_supported`. See [Zero Data Retention](/serverless/zero-data-retention). | Per-API flags tell you what each endpoint supports for that model. Branch on these when you use structured outputs, streaming tools, or constrained decoding. | Flag | Meaning | | ---- | ------- | | `chat_completions.streaming` | `stream: true` is supported. | | `chat_completions.tools` | `tools` and `tool_choice` are supported. | | `chat_completions.tool_streaming` | Tool calls stream as incremental argument deltas. | | `chat_completions.json_object` | `response_format: {"type": "json_object"}` is supported. | | `chat_completions.json_schema` | `response_format: {"type": "json_schema", ...}` is supported. | | `chat_completions.json_schema_refs` | Local `$ref` references (`#/$defs/...`, `#/definitions/...`) in schemas are inlined for you. | | `chat_completions.tools_with_response_format` | `tools` and `response_format` can be sent in the same request. See [Structured outputs](/serverless/chat-completions#structured-outputs). | | `chat_completions.n` | `n > 1` is supported. Otherwise `n > 1` returns `unsupported_feature`. | | `chat_completions.regex` | Top-level `regex` constrained decoding is supported. Otherwise it returns `unsupported_regex`. | | `chat_completions.grammar` | `response_format: {"type": "grammar", ...}` is supported. Otherwise it returns `unsupported_response_format`. | | `responses.text_format` | Accepted `text.format.type` values on `/v1/responses`. | | `responses.raw_json_schema_text` | JSON-schema output is returned as raw JSON text, without Markdown fences. | | `messages.*` | The same meanings for `/v1/messages`. | | `zdr.same_capabilities` | The model supports the same features under ZDR, which every request uses. | ## Reasoning efforts `wafer.capabilities.reasoning_effort` describes the reasoning controls for the model: - `efforts` lists the effort tiers the model supports. `none` turns reasoning off; see the [reasoning warning](/serverless/chat-completions#reasoning) for models where it can't fully. - `default` is the effort applied when a request doesn't set one. Some models reason by default. Requests can use `none`, `low`, `medium`, `high`, `xhigh`, or `max` on any reasoning model. A value the model doesn't list in `efforts` runs at a nearby listed tier; for example, `medium` runs as `low` on a model that lists `none`, `low`, `high`, and `max`. On Chat Completions, any other value, including `minimal`, returns `400` with code `unsupported_value`. On Responses, an unrecognized effort turns reasoning off. See [Reasoning](/serverless/chat-completions#reasoning) for the request fields on each API. ## Pricing `wafer.pricing` is the price Wafer bills for the model, in US cents per million tokens. Without an API key, the card shows list prices. With your key, it shows your account's rates. - `input_cents_per_million` for uncached prompt tokens. - `output_cents_per_million` for generated tokens, including reasoning tokens. - `cache_read_cents_per_million` for prompt tokens served from the prompt cache, rounded to whole cents. `cache_read_microcents_per_million` is the exact value (1 cent = 1,000,000 microcents). - `input_microcents_per_million` and `output_microcents_per_million` appear only when that rate is not a whole number of cents. Each is the exact billed rate, and the matching `*_cents_per_million` field is that rate rounded to whole cents. For example, $0.0095 per million input tokens is `input_microcents_per_million: 950000` with `input_cents_per_million: 1`. See [Usage and Billing](/serverless/usage) for how token counts are reported. ## Model retirement When a model is scheduled for retirement, its card gains `wafer.deprecation_date`, a `YYYY-MM-DD` date on which Wafer plans to retire the model. After a model is retired, requests to it fail with `model_not_found`. The field is omitted for models with no scheduled retirement, so you can check for it when you list models and plan migrations ahead of time. ## Model IDs Model IDs are case-insensitive: `GLM-5.3` and `glm-5.3` select the same model. An unknown model returns `404` with code `model_not_found`. Print a quick capability summary with `jq`: ```bash curl -sS "https://pass.wafer.ai/v1/models" \ -H "Authorization: Bearer " | jq -r '.data[] | [.id, .max_model_len, .wafer.capabilities.vision, .wafer.capabilities.reasoning_effort.default] | @tsv' ``` # Zero Data Retention Source: https://docs.wafer.ai/serverless/zero-data-retention.md Every Wafer Serverless request uses Zero Data Retention (ZDR) by default, with no header required. ## Default ZDR You don't need a header or an account setting to get ZDR, and no setting turns it off. A standard request is a ZDR request: ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [ {"role": "user", "content": "Hello!"} ], "max_tokens": 128 }' ``` ## The `Wafer-ZDR` header Earlier integrations sent this header to require ZDR: ```http Wafer-ZDR: required ``` The header is no longer needed. It is still accepted for compatibility, and sending it doesn't change how the request is handled. Any other value, including an empty one, is rejected with HTTP `400` and code `invalid_zdr_header`. Remove the header or set it to `required`. ## Model support Wafer serves a model only if it supports ZDR. Its card in `GET https://pass.wafer.ai/v1/models` advertises `zdr_supported: true`. A request for a model that doesn't support ZDR is rejected with HTTP `422` and code `model_zdr_not_supported`. # Chat Completions Source: https://docs.wafer.ai/serverless/chat-completions.md `POST https://pass.wafer.ai/v1/chat/completions` is the primary Wafer Serverless API. It follows the OpenAI Chat Completions format, so OpenAI SDKs and OpenAI-compatible tools work by setting the base URL to `https://pass.wafer.ai/v1`. ## Request ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [ {"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What is Wafer Serverless?"} ], "max_tokens": 1024 }' ``` ## Request fields | Field | Type | Notes | | ----- | ---- | ----- | | `model` | string | Required. A model ID from `GET /v1/models`. Case-insensitive. | | `messages` | array | Required. `system`, `user`, `assistant`, and `tool` messages. `content` is a string or an array of `text` and `image_url` parts. | | `max_tokens` | integer | Maximum generated tokens, including reasoning tokens. `max_completion_tokens` is also accepted; if you send both, `max_tokens` wins. See [Output length](#output-length). | | `temperature` | number | Sampling temperature. | | `top_p` | number | Nucleus sampling. | | `top_k` | integer | Sample from the top K tokens. | | `min_p` | number | Minimum token probability relative to the top token. | | `frequency_penalty` | number | Penalize tokens by frequency. | | `presence_penalty` | number | Penalize tokens that already appeared. | | `repetition_penalty` | number | Multiplicative repetition penalty. | | `stop` | string or array | Stop sequences: at most 16, each at most 512 characters. | | `seed` | integer | Sampling seed. | | `n` | integer | Number of choices. `n > 1` requires `wafer.capabilities.chat_completions.n`. | | `logprobs`, `top_logprobs` | boolean, integer | Return token log probabilities. `top_logprobs` is 0 to 20. Not available on every model. | | `stream` | boolean | Stream server-sent events. See [Streaming](#streaming). | | `tools` | array | Function tools. See [Tools](#tools). | | `tool_choice` | string or object | `auto`, `none`, `required`, or `{"type": "function", "function": {"name": ...}}`. | | `parallel_tool_calls` | boolean | Allow several tool calls in one response. | | `response_format` | object | JSON mode or JSON Schema. See [Structured outputs](#structured-outputs). | | `reasoning_effort` | string | Reasoning effort. See [Reasoning](#reasoning). | Wafer doesn't reject unrecognized fields, but they may have no effect. **Sampling fields on GLM models.** `GLM-5.3` and `GLM-5.3-Flash` ignore `frequency_penalty`. `GLM-5.2` ignores `temperature`, `top_p`, `presence_penalty`, and `frequency_penalty` and uses the model's own defaults. Every other model honors these fields. `temperature` and `top_p` behave the same way on Responses and Messages; the penalties are Chat Completions fields. `top_k`, `min_p`, `repetition_penalty`, `seed`, and `stop` reach every model. ## Output length - `max_tokens` bounds generated tokens, including reasoning tokens. - When you omit `max_tokens`, Wafer applies a default: the model's `wafer.max_output_tokens` when it has one, otherwise 65,536 tokens or half the context window, whichever is smaller. - A `max_tokens` above the model's `wafer.max_output_tokens` is lowered to that cap. A `max_tokens` larger than the whole context window is ignored. - If the prompt plus `max_tokens` doesn't fit the context window, Chat Completions retries once without `max_tokens`; Responses and Messages return `400` with code `context_length_exceeded`. - On `DeepSeek-V4-Flash-0731-Fast` and `DeepSeek-V4-Pro`, Wafer adds up to 24,576 tokens of room for reasoning when reasoning is on, so `completion_tokens` can exceed `max_tokens`. ## Response ```json { "id": "c3a7ac6de84a4e9eade77ab63a27a1ca", "object": "chat.completion", "created": 1790793243, "model": "GLM-5.3", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Wafer Serverless is pay-per-token access to Wafer-hosted models.", "reasoning_content": null, "tool_calls": null }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 22, "completion_tokens": 16, "total_tokens": 38, "prompt_tokens_details": {"cached_tokens": 0}, "completion_tokens_details": {"reasoning_tokens": 0} } } ``` `finish_reason` is `stop`, `length` (hit `max_tokens`, or generation was stopped because the model started repeating itself), or `tool_calls`. Responses can include extra fields beyond the OpenAI format; ignore fields you don't use. See [Usage and Billing](/serverless/usage) for the `usage` fields. ## Streaming Set `"stream": true` to receive server-sent events. Each event is a `data:` line with a `chat.completion.chunk` JSON object, followed by a blank line. The stream ends with `data: [DONE]`. The transcript below is abridged; real chunks carry a few more fields, such as `logprobs`. ```bash curl -N -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "Count from 1 to 5."}], "max_tokens": 256, "stream": true }' ``` ```text data: {"id":"724b7b08675f40d7aa75c80d5fe51cf5","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} data: {"id":"724b7b08675f40d7aa75c80d5fe51cf5","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[{"index":0,"delta":{"content":"1, 2, 3, 4, 5"},"finish_reason":null}]} data: {"id":"724b7b08675f40d7aa75c80d5fe51cf5","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: {"id":"724b7b08675f40d7aa75c80d5fe51cf5","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[],"usage":{"prompt_tokens":11,"completion_tokens":10,"total_tokens":21,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":0}}} data: [DONE] ``` - The first chunk sets `delta.role`. - Text arrives in `delta.content`. Reasoning text arrives in `delta.reasoning_content`. - The chunk with a non-null `finish_reason` ends the choice. - A final chunk with an empty `choices` array carries `usage`. Wafer sends it on every successful stream; you don't need `stream_options`. - Tool calls stream as deltas. See [Streaming tool calls](#streaming-tool-calls). If an error happens after the stream starts, see [Errors during streaming](/serverless/errors#errors-during-streaming). ## Tools Define tools with a JSON Schema for their parameters. `parameters` must be an object schema (`"type": "object"`). ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "What is the weather in Paris?"}], "tools": [{ "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a city.", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"] } } }], "tool_choice": "auto", "max_tokens": 1024 }' ``` When the model calls a tool, `finish_reason` is `tool_calls` and the message carries `tool_calls`: ```json { "role": "assistant", "content": "", "tool_calls": [ { "id": "call_c5b76a367ad9406c8354ae14", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\": \"Paris\"}"} } ] } ``` `arguments` is a JSON string. Run the tool, then send the assistant message and a `tool` message with the matching `tool_call_id`: ```json { "model": "GLM-5.3", "messages": [ {"role": "user", "content": "What is the weather in Paris?"}, {"role": "assistant", "content": null, "tool_calls": [ {"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\":\"Paris\"}"}} ]}, {"role": "tool", "tool_call_id": "call_1", "content": "18C and sunny"} ], "tools": [{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}] } ``` A model can return several tool calls in one message. Match results to calls by `id`. ### Streaming tool calls With `stream: true`, the first delta for a tool call carries its `id`, `type`, and `function.name`. Later deltas with the same `index` carry fragments of `function.arguments`. Concatenate the fragments until `finish_reason` is `tool_calls`. ```text data: {"id":"106ed1a9d8294689a7d9e3f5487f50df","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[{"index":0,"delta":{"tool_calls":[{"id":"call_2887db464fdc4968a012f396","index":0,"type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]} data: {"id":"106ed1a9d8294689a7d9e3f5487f50df","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\": "}}]},"finish_reason":null}]} data: {"id":"106ed1a9d8294689a7d9e3f5487f50df","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Paris\"}"}}]},"finish_reason":null}]} data: {"id":"106ed1a9d8294689a7d9e3f5487f50df","object":"chat.completion.chunk","created":1790793243,"model":"GLM-5.3","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]} ``` ## Structured outputs Use `response_format` to constrain output to JSON. Check the model's `wafer.capabilities.chat_completions` flags in [the model catalog](/serverless/models#capability-flags). JSON mode returns a valid JSON object. Tell the model in the prompt what JSON to produce: ```json {"response_format": {"type": "json_object"}} ``` JSON Schema constrains the output to your schema: ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "Invent a fictional person."}], "response_format": { "type": "json_schema", "json_schema": { "name": "person", "strict": true, "schema": { "type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer"}}, "required": ["name", "age"], "additionalProperties": false } } }, "max_tokens": 512 }' ``` The JSON is returned as a string in `message.content`. Parse it before use. - Local references (`#/$defs/...` and `#/definitions/...`) are inlined for you, so schemas generated by Pydantic, Zod, or MCP servers work as-is. Remote, unresolvable, recursive, or oversized references return `400` with one of the `json_schema_refs_*` [error codes](/serverless/errors#request-validation-400). - Schema keywords that can't be enforced during decoding, such as `if`/`then`/`else`, `not`, `uniqueItems`, `multipleOf`, and `format`, are dropped from the constraint. Validate the output if you rely on them. - `tools` and `response_format` can be sent in the same request. With `tool_choice: "auto"`, the schema constrains the reply, so the model answers in JSON rather than calling a tool. To get a tool call, set `tool_choice` to `required` or a named function; `response_format` is then ignored. - On models with `chat_completions.regex: true`, a top-level `regex` field constrains output to a regular expression. On models with `chat_completions.grammar: true`, `response_format: {"type": "grammar", ...}` constrains output to a grammar. Other models reject these fields instead of ignoring them. ## Vision Models whose catalog card has `wafer.capabilities.vision: true` accept images as `image_url` content parts. Send the image inline as a base64 data URI (recommended) or as a public `https://` URL: ```json { "model": "Kimi-K3", "messages": [{ "role": "user", "content": [ {"type": "text", "text": "What is in this image?"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,"}} ] }], "max_tokens": 1024 } ``` - Wafer fetches `https://` image URLs itself. If the image can't be fetched (for example, the host blocks the request), the request fails with `400` code `model_request_rejected`. URLs that point to private networks or local paths return `400` code `unsupported_value`. Data URIs avoid fetch failures. - Use common image formats such as PNG or JPEG. - Image tokens count toward `prompt_tokens` and the context window. - The whole request body, including base64 images, must be under 50 MB. - Send images only to vision models. Images, video and audio sent to other models are removed from the request without an error. - Video and audio sent to a vision model that doesn't accept them fail with `400` code `model_request_rejected`, whether they are URLs or inline data. The same applies to `/v1/messages`, `/v1/messages/count_tokens` and `/v1/responses`. ## Reasoning Reasoning models can think before answering. Reasoning text is returned separately from the answer in `message.reasoning_content` (streaming: `delta.reasoning_content`), and reasoning tokens are reported in `usage.completion_tokens_details.reasoning_tokens`. Control reasoning with any of these equivalent fields: | Field | Values | | ----- | ------ | | `reasoning_effort` | `none`, `low`, `medium`, `high`, `xhigh`, or `max` | | `reasoning` | `{"effort": ""}` with the same effort values | | `thinking` | `{"type": "enabled"}` turns reasoning on; `{"type": "disabled"}` turns it off. | Each model lists its supported efforts and its default in `wafer.capabilities.reasoning_effort` in [the model catalog](/serverless/models#reasoning-efforts). An effort the model doesn't list runs at a nearby listed tier. An unknown value returns `400` with code `unsupported_value`. When a request doesn't set reasoning, the model's default applies, and some models reason by default. Send `reasoning_effort: "none"` to turn reasoning off. On some models, notably `GLM-5.3` and `GLM-5.3-Flash`, `none` doesn't fully stop reasoning. The model can still spend output tokens reasoning before it answers. That reasoning isn't returned and isn't counted in `reasoning_tokens`, but it counts toward `max_tokens` and `completion_tokens`. With a small `max_tokens`, the answer can be cut off or empty. Leave headroom in `max_tokens`, or use `low` to see the reasoning. ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "Is 391 prime?"}], "reasoning_effort": "high", "max_tokens": 4096 }' ``` Reasoning tokens count toward `max_tokens` and bill as output tokens. Give reasoning requests enough `max_tokens` for the reasoning and the answer. If `finish_reason` is `length` with empty `content`, raise `max_tokens` or lower the effort. ## Zero Data Retention Every request uses Zero Data Retention by default, with no header required. See [Zero Data Retention](/serverless/zero-data-retention). # Responses Source: https://docs.wafer.ai/serverless/responses.md `POST https://pass.wafer.ai/v1/responses` accepts OpenAI Responses API requests. Use it for clients that are built on the Responses API, such as Codex. For new integrations, prefer [Chat Completions](/serverless/chat-completions), which has the widest feature set. The Responses API on Wafer is **stateless**. Wafer doesn't store responses, so every request must carry the full conversation in `input`. ## Request ```bash curl -sS "https://pass.wafer.ai/v1/responses" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "instructions": "Answer in one sentence.", "input": "What is Wafer Serverless?", "max_output_tokens": 1024 }' ``` ## Supported fields | Field | Notes | | ----- | ----- | | `model` | Required. A model ID from `GET /v1/models`. | | `input` | Required. A string, or an array of input items (see below). | | `instructions` | System instructions. | | `max_output_tokens` | Maximum generated tokens, **including reasoning tokens**. | | `reasoning.effort` | Reasoning effort. See [Reasoning](#reasoning). | | `text.format` | Structured output: `{"type": "text"}`, `{"type": "json_object"}`, or `{"type": "json_schema", "name": ..., "schema": ..., "strict": ...}`. | | `tools` | Function tools: `{"type": "function", "name": ..., "description": ..., "parameters": {...}}`. A `web_search` tool is exposed to the model as a function tool that your client must execute. Other hosted tools return `400` with code `invalid_tool`. | | `tool_choice` | `auto`, `none`, `required`, or `{"type": "function", "name": ...}`. | | `parallel_tool_calls` | Allow several function calls in one response. | | `temperature`, `top_p`, `seed` | Sampling controls. Some models use fixed sampling; see the [note on sampling](/serverless/chat-completions#request-fields). | | `stream` | Stream server-sent events. See [Streaming](#streaming). | `previous_response_id` returns `400` with code `unsupported_parameter`. `store`, `background`, `include`, `truncation`, and `metadata` are accepted but have no effect: every request runs synchronously and nothing is stored. There is no `GET /v1/responses/{id}`. ## Input items Each item in an `input` array needs an explicit `type`: ```json [ {"type": "message", "role": "user", "content": "What's the weather in Paris?"}, {"type": "function_call", "call_id": "call_1", "name": "get_weather", "arguments": "{\"city\":\"Paris\"}"}, {"type": "function_call_output", "call_id": "call_1", "output": "18C and sunny"} ] ``` - Supported item types are `message`, `function_call`, and `function_call_output`. `reasoning` items and some client-internal item types are accepted and skipped. Other item types return `400` with code `unsupported_input_item`, and so does an item without `type`, including the short `{"role": ..., "content": ...}` form. - Message roles are `user`, `assistant`, `system`, and `developer`. - Message `content` is a string or an array of `input_text` (or `output_text`) parts. Image input isn't supported on `/v1/responses`: `input_image` parts are removed without an error, so the model never sees them. Use [Chat Completions](/serverless/chat-completions#vision) or [Messages](/serverless/messages#vision) for vision models. ## Response ```json { "id": "resp_606be8bc5a4c4b0ea3a8a597d4f78c31", "object": "response", "created_at": 1790793623, "model": "GLM-5.3", "status": "completed", "output": [ { "type": "message", "id": "msg_22a75da7bd1148af9d9e6a99c39e8255", "status": "completed", "role": "assistant", "content": [ {"type": "output_text", "text": "It's 18C and sunny in Paris.", "annotations": []} ] } ], "usage": {"input_tokens": 336, "output_tokens": 17, "total_tokens": 353}, "parallel_tool_calls": true } ``` Function calls appear in `output` as `{"type": "function_call", "id": ..., "call_id": ..., "name": ..., "arguments": ...}` items. Send the result back as a `function_call_output` item with the same `call_id`. When generation stops early, `status` is `incomplete` and `incomplete_details.reason` says why: `max_output_tokens` or `content_filter`. ## Reasoning - `reasoning: {"effort": "low" | "medium" | "high" | "xhigh" | "max"}` turns reasoning on at that effort. See [Reasoning efforts](/serverless/models#reasoning-efforts) for how efforts map to each model's tiers. - `reasoning: {"effort": "none"}` turns reasoning off. So does any other effort value, such as `minimal`, and a `reasoning` object without an effort, such as `{"summary": "auto"}`. Some models can still reason briefly; see the [reasoning warning](/serverless/chat-completions#reasoning). - Without `reasoning`, the model's default applies (`wafer.capabilities.reasoning_effort.default`). Reasoning text is not returned on `/v1/responses`, but reasoning tokens count toward `max_output_tokens` and `usage.output_tokens`. If `max_output_tokens` is small and the model reasons, the budget can run out before any answer text: the response is `incomplete` with an empty `output`. Leave room for reasoning in `max_output_tokens`. `reasoning: {"effort": "none"}` avoids this on most models, but not fully on `GLM-5.3` and `GLM-5.3-Flash` (see the [reasoning warning](/serverless/chat-completions#reasoning)). ## Structured outputs ```bash curl -sS "https://pass.wafer.ai/v1/responses" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "input": "Invent a fictional person.", "reasoning": {"effort": "none"}, "text": { "format": { "type": "json_schema", "name": "person", "strict": true, "schema": { "type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer"}}, "required": ["name", "age"], "additionalProperties": false } } }, "max_output_tokens": 256 }' ``` The JSON arrives as raw text in the `output_text` part, without Markdown fences. When streaming with `json_schema`, the whole JSON arrives in one `response.output_text.delta` event just before the item completes. ## Streaming With `"stream": true`, Wafer sends server-sent events. Each event has an `event:` line and a `data:` line with a JSON payload whose `type` matches the event name. | Event | When | | ----- | ---- | | `response.created` | First event. The response object with `status: "in_progress"`. | | `response.output_item.added` | A message or function-call item starts. | | `response.content_part.added` | A text part starts in a message item. | | `response.output_text.delta` | Text delta in `delta`. | | `response.function_call_arguments.delta` | Function-call argument delta in `delta`. | | `response.function_call_arguments.done` | Complete arguments in `arguments`. | | `response.output_item.done` | The finished item. | | `response.completed` | Last event. The full response, including `usage`. | | `response.failed` | Last event instead of `response.completed` when generation fails. The response has `"status": "failed"` and an `error` with `code` and `message`. | ```text event: response.created data: {"type":"response.created","response":{"id":"resp_2bbf637026614d0d8c4546325632c8f7","object":"response","created_at":1790793263,"model":"GLM-5.3","status":"in_progress","output":[],"parallel_tool_calls":true,"usage":{"input_tokens":0,"output_tokens":0,"total_tokens":0}}} event: response.output_item.added data: {"type":"response.output_item.added","output_index":0,"item":{"type":"message","id":"msg_fb3a72729c7d446497a00184c346b811","status":"in_progress","role":"assistant","content":[]}} event: response.content_part.added data: {"type":"response.content_part.added","item_id":"msg_fb3a72729c7d446497a00184c346b811","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}} event: response.output_text.delta data: {"type":"response.output_text.delta","item_id":"msg_fb3a72729c7d446497a00184c346b811","output_index":0,"content_index":0,"delta":"Four"} event: response.output_item.done data: {"type":"response.output_item.done","output_index":0,"item":{"type":"message","id":"msg_fb3a72729c7d446497a00184c346b811","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Four","annotations":[]}]}} event: response.completed data: {"type":"response.completed","response":{"id":"resp_2bbf637026614d0d8c4546325632c8f7","object":"response","created_at":1790793263,"model":"GLM-5.3","status":"completed","output":[{"type":"message","id":"msg_fb3a72729c7d446497a00184c346b811","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Four","annotations":[]}]}],"parallel_tool_calls":true,"usage":{"input_tokens":22,"output_tokens":36,"total_tokens":58}}} ``` Build your final result from `response.completed` or from the `.done` events. Wafer doesn't emit `response.in_progress`, `response.output_text.done`, `response.content_part.done`, or reasoning events, and events carry no `sequence_number`. The stream ends after `response.completed` or `response.failed`; there is no `[DONE]` line. If the connection closes before `response.completed` or `response.failed`, the request failed. See [Errors during streaming](/serverless/errors#errors-during-streaming). # Messages Source: https://docs.wafer.ai/serverless/messages.md `POST https://pass.wafer.ai/v1/messages` accepts Anthropic Messages API requests. Use it with the Anthropic SDKs, Claude Code, and other Anthropic-format clients. Set the client's base URL to `https://pass.wafer.ai`; the client appends `/v1/messages`. ## Request ```bash curl -sS "https://pass.wafer.ai/v1/messages" \ -H "x-api-key: " \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "max_tokens": 1024, "system": "Answer in one sentence.", "messages": [ {"role": "user", "content": "What is Wafer Serverless?"} ] }' ``` Authenticate with `x-api-key` or `Authorization: Bearer`. The `anthropic-version` and `anthropic-beta` headers are accepted and ignored. Use a Wafer model ID from `GET /v1/models` as `model`. Anthropic model names such as `claude-...` are not available. ## Supported fields | Field | Notes | | ----- | ----- | | `model` | Required. A model ID from `GET /v1/models`. | | `max_tokens` | Required. Must be a positive integer. | | `messages` | Required. `user` and `assistant` turns with string content or content blocks: `text`, `image`, `tool_use`, `tool_result`, and `thinking` (from earlier assistant turns). `document` (PDF) blocks are not supported. | | `system` | System prompt, as a string or an array of `text` blocks. | | `tools` | Tool definitions with `name`, `description`, and `input_schema`. Anthropic server tools (web search, bash, text editor, computer use) are exposed to the model as regular tools that your client must execute. | | `tool_choice` | `{"type": "auto"}`, `{"type": "none"}`, or `{"type": "tool", "name": ...}` to force a specific tool. `{"type": "any"}` is accepted but behaves like `auto`. | | `thinking` | `{"type": "enabled"}` turns reasoning on. See [Thinking](#thinking). | | `output_config.effort` | Reasoning effort when thinking is on: `low`, `medium`, `high`, or `max`. Other values are ignored. | | `stop_sequences` | Stop generation at any of these strings: at most 16, each at most 512 characters. | | `temperature`, `top_p`, `top_k` | Sampling controls. Some models use fixed sampling; see the [note on sampling](/serverless/chat-completions#request-fields). | | `stream` | Stream server-sent events. See [Streaming](#streaming). | `metadata` and `cache_control` are accepted and ignored. Prompt caching is automatic; see [Prompt caching](/serverless/usage#prompt-caching). ## Response ```json { "id": "msg_953d3af2bec34ee6a5a29c455601af21", "type": "message", "role": "assistant", "model": "GLM-5.3", "content": [ {"type": "text", "text": "It's 18C and sunny in Paris."} ], "stop_reason": "end_turn", "usage": { "input_tokens": 338, "output_tokens": 29, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 256 } } ``` `stop_reason` is `end_turn`, `max_tokens`, `stop_sequence`, or `tool_use`. `usage.input_tokens` counts all prompt tokens, **including** `cache_read_input_tokens`. This differs from Anthropic's API. See [Usage and Billing](/serverless/usage#messages-usage). ## Thinking On `/v1/messages`, reasoning is **off** unless you enable it, whatever the model's default on other APIs. ```json { "model": "GLM-5.3", "max_tokens": 4096, "thinking": {"type": "enabled", "budget_tokens": 2048}, "output_config": {"effort": "high"}, "messages": [{"role": "user", "content": "Is 391 prime?"}] } ``` - `thinking: {"type": "enabled"}` turns reasoning on. Set `output_config.effort` to choose the effort; without it, the effort depends on the model. - `budget_tokens` is accepted but ignored. Use `output_config.effort` and `max_tokens` to control reasoning length. - Reasoning is returned as `thinking` content blocks before the answer. Their `signature` is an empty string. - You can pass earlier `thinking` blocks back in assistant turns. - On `GLM-5.3` and `GLM-5.3-Flash`, reasoning can't be fully turned off. With thinking off, the model can still spend output tokens reasoning that isn't returned; they count toward `max_tokens` and `usage.output_tokens`. See the [reasoning warning](/serverless/chat-completions#reasoning). ## Tools When the model calls a tool, the response contains a `tool_use` block and `stop_reason` is `tool_use`. Send the result back in a `tool_result` block: ```json { "model": "GLM-5.3", "max_tokens": 1024, "tools": [ { "name": "get_weather", "description": "Get the current weather for a city.", "input_schema": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"] } } ], "messages": [ {"role": "user", "content": "What's the weather in Paris?"}, {"role": "assistant", "content": [ {"type": "tool_use", "id": "toolu_1", "name": "get_weather", "input": {"city": "Paris"}} ]}, {"role": "user", "content": [ {"type": "tool_result", "tool_use_id": "toolu_1", "content": "18C and sunny"} ]} ] } ``` ## Vision On models whose catalog card has `wafer.capabilities.messages.vision: true`, send images as base64 `image` blocks: ```json { "type": "image", "source": {"type": "base64", "media_type": "image/png", "data": ""} } ``` `url` image sources are also accepted; Wafer fetches them, and an image that can't be fetched fails the request with `400` code `model_request_rejected`. Base64 avoids fetch failures. Images sent to models without vision support fail with `400` code `model_request_rejected`, and so do video and audio blocks sent to a model that doesn't accept them. ## Count tokens `POST /v1/messages/count_tokens` takes the same body without `max_tokens` and returns the prompt token count: ```bash curl -sS "https://pass.wafer.ai/v1/messages/count_tokens" \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "Hello there"}] }' ``` ```json {"input_tokens": 6} ``` If the exact count isn't available, the response is an estimate and carries the header `x-wafer-input-tokens-estimated: true`. ## Streaming With `"stream": true`, Wafer sends Anthropic-format server-sent events: `message_start`, then `content_block_start`, `content_block_delta`, and `content_block_stop` for each content block, then `message_delta` with `stop_reason` and final `usage`, then `message_stop`. Text arrives as `text_delta` deltas. Reasoning arrives in `thinking` blocks as `thinking_delta` deltas when thinking is on. Tool input arrives as `input_json_delta` deltas; concatenate `partial_json` to get the full input. Wafer doesn't send `ping` or `signature_delta` events. ```text event: message_start data: {"type":"message_start","message":{"id":"msg_b9f3b97e1c8a43439bf3c8ccd73e4205","type":"message","role":"assistant","content":[],"model":"GLM-5.3","usage":{"input_tokens":0,"output_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}} event: content_block_start data: {"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"toolu_01A09q90qw90lq917835lq9","name":"get_weather","input":{}}} event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{"}} event: content_block_delta data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\"city\": \"Paris\"}"}} event: content_block_stop data: {"type":"content_block_stop","index":0} event: message_delta data: {"type":"message_delta","delta":{"stop_reason":"tool_use"},"usage":{"input_tokens":278,"output_tokens":54,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}} event: message_stop data: {"type":"message_stop"} ``` `message_start` carries zero usage. Read final usage from `message_delta`. If generation fails after the stream starts, Wafer sends an `error` event and ends the stream without `message_stop`. If the connection closes before `message_stop`, the request failed too. See [Errors during streaming](/serverless/errors#errors-during-streaming). ## Errors Errors on `/v1/messages` and `/v1/messages/count_tokens` use Anthropic's error format, with Wafer's `code` and `request_id` added: ```json { "type": "error", "error": { "type": "invalid_request_error", "message": "tools[0].name is required", "code": "missing_tool_name", "request_id": "20b6009020a3" } } ``` Anthropic SDKs pick the exception class from the HTTP status. In your own code, branch on the HTTP status, then on `error.code`. See [Errors and Rate Limits](/serverless/errors) for the codes. # Errors and Rate Limits Source: https://docs.wafer.ai/serverless/errors.md ## Error format Failed requests return a JSON body with an `error` object: ```json { "error": { "message": "The request exceeded Qwen3.8-27B's context window (max_model_len=262144). Reduce the prompt length, compact your conversation, or lower max_tokens.", "type": "invalid_request_error", "param": null, "code": "context_length_exceeded", "model": "Qwen3.8-27B", "context_length_limit": 262144, "request_id": "fd025fb80f35" } } ``` | Field | Meaning | | ----- | ------- | | `type` | Error category. | | `code` | The specific reason. Branch on this in your code. | | `message` | Human-readable description. Don't parse it; the wording can change. | | `param` | The request field at fault, when known. | | `request_id` | Wafer's ID for the request. Same value as the `x-request-id` response header. | Some codes add extra fields, listed below. New codes may be added over time, so handle unknown codes by HTTP status. OpenAI and Anthropic SDKs choose their exception class from the HTTP status. On `/v1/messages` and `/v1/messages/count_tokens`, errors use Anthropic's wrapper instead: `{"type": "error", "error": {"type": ..., "message": ..., "code": ..., "request_id": ...}}`, with the same codes and extra fields. See [Messages errors](/serverless/messages#errors). ## Request IDs Responses carry an `x-request-id` header with Wafer's ID for the request, a 12-character hex string. Log it, and include it when you contact support. If you send your own `x-request-id` header, Wafer records it with the request, but the response header carries Wafer's ID. A few `429` responses from Wafer's front proxy have no request ID. ## Status codes | Status | `type` | Retry? | Meaning | | ------ | ------ | ------ | ------- | | `400` | `invalid_request_error` | No | The request is invalid for this API or model. Fix it before retrying. | | `401` | `authentication_error` | No | Missing or invalid API key. | | `402` | `insufficient_credits` | After top-up | Your credit balance is too low for the request. | | `403` | `permission_error` | No | Your key can't use this model. | | `404` | `not_found_error`, `invalid_request_error` | No | Unknown path or model. | | `413` | `invalid_request_error` | No | Request body over 50 MiB (code `request_body_too_large`, with `limit_bytes`). | | `422` | `invalid_request_error` | No | The model doesn't support ZDR, which every request requires. | | `429` | `rate_limit_error` | Yes | Temporary: a model is at capacity, a concurrency limit was hit, or a transient failure happened on Wafer's side. | Wafer usually returns transient server-side failures as `429` with a `Retry-After` header rather than `5xx`, so SDK retry logic for rate limits covers them. Treat any `5xx` you do receive as retryable too. ## Error codes ### Authentication and access - `missing_api_key` (401): No `Authorization: Bearer ` or `x-api-key` header. - `invalid_api_key` (401): The key doesn't exist or was revoked. - `model_not_allowed` (403): The model exists, but your key's type or account can't use it. - `model_not_found` (404): Unknown `model`, or a model your account doesn't have access to. The message lists the available models; `GET /v1/models` lists the models visible to your key. - `not_found` (404): The path isn't a Wafer Serverless route. ### Request validation (400) - `invalid_json_request`: The request body isn't valid JSON, or isn't a JSON object. - `missing_model`: The request has no `model`. - `context_length_exceeded`: The request doesn't fit the model's context window. Extra fields: `model`, `context_length_limit`, and sometimes `suggested_models` (models with larger windows). Shorten the prompt or switch models. - `unsupported_value`: A field has a value the model doesn't accept, such as an unknown `reasoning_effort`. `param` names the field. - `unsupported_feature`: The model doesn't support a requested feature, such as `n > 1`. `param` names the field. - `integer_above_max_value`, `integer_below_min_value`, `array_above_max_length`, `string_above_max_length`: A field is outside its limit, such as `top_logprobs` above 20, or more than 16 `stop` sequences. `param` names the field. - `unsupported_parameter`: The API doesn't support the field, such as `previous_response_id` on `/v1/responses`. - `unsupported_response_format`: The model doesn't support the `response_format` type. - `unsupported_regex`: The model doesn't support top-level `regex`. - `json_schema_refs_unsupported`, `json_schema_refs_unresolved`, `json_schema_refs_recursive`, `json_schema_refs_too_deep`, `json_schema_refs_too_large`: A schema has `$ref` references that can't be inlined. Inline them yourself or simplify the schema. - `tool_schema_invalid`: A tool's `parameters` isn't a JSON Schema object with `"type": "object"`. - `missing_tool_name`, `duplicate_tool_name`: A tool has no name, or two tools share a name. - `tool_choice_unknown_tool`: `tool_choice` names a tool that isn't in `tools`. - `missing_tool_call_id`, `orphan_tool_message`: A `tool` message has no `tool_call_id`, or its ID doesn't match a tool call in an earlier assistant message. - `unsupported_input_item`, `unsupported_content_type`, `unsupported_block_type`: An input item, content part, or content block type isn't supported on this API. - `invalid_zdr_header`: `Wafer-ZDR` is set to a value other than `required`. The header isn't needed, since every request is ZDR; remove it or set it to `required`. - `model_request_rejected`: The model rejected the request, for example because an image couldn't be fetched or was sent to a model without vision, or because the request carries video or audio the model doesn't accept. Check the request before retrying. This code keeps the status the model returned, usually `400`. Other `invalid_*`, `missing_*`, and `empty_*` codes describe a malformed field; `param` or `message` identifies it. ### Credits (402) - `insufficient_credits`: Your balance is below the estimated cost of the request. Extra fields: `credits_available_cents` and `credits_required_cents_estimate`. Add credits at [app.wafer.ai](https://app.wafer.ai), then retry. - `member_spend_limit`: A spend limit set on your account for this member has been reached. The credit check happens before the request runs. The estimate assumes the request generates its full `max_tokens` (times `n`). If you omit `max_tokens`, the estimate uses the default (see [Output length](/serverless/chat-completions#output-length)), which can be tens of cents to over a dollar per request. With a low balance, set a smaller `max_tokens`. ### ZDR (422) - `model_zdr_not_supported`: The model doesn't support ZDR, which every request requires. Choose another model. See [Zero Data Retention](/serverless/zero-data-retention). ### Rate limits and capacity (429) - `server_overloaded`: The model is temporarily at capacity, or a transient failure happened while serving the request. Retry after `Retry-After`. - `concurrency_limit_exceeded`: Your account has too many requests in flight. Retry when an earlier request finishes. - `model_service_unavailable`, `model_request_timeout`, `auth_backend_unavailable`, `auth_state_stale`, `edge_at_capacity`: Transient failures. Retry after `Retry-After`. - `rate_limited`: An unexpected error on Wafer's side. Retry after `Retry-After`; if it repeats, contact support with the request ID. ## Rate limits Wafer doesn't publish fixed requests-per-minute or tokens-per-minute limits. Besides transient failures, two things return `429`: - **Model capacity.** When a model is busy, Wafer sheds new requests with `server_overloaded` instead of queueing them for a long time. This protects latency for requests already running and usually clears within seconds. - **Account concurrency.** An account can have a limit on concurrent in-flight requests. Exceeding it returns `concurrency_limit_exceeded`. `429` responses include `Retry-After` (seconds). Capacity and concurrency `429`s generated by Wafer's API also include `RateLimit-Reset` with the same value. Successful responses carry no rate-limit headers. If you need guaranteed throughput, contact [support@wafer.ai](mailto:support@wafer.ai). ## Retries - Retry `429` after the `Retry-After` delay, using exponential backoff with jitter and a cap on attempts. The OpenAI and Anthropic SDKs do this by default. - Don't retry other `4xx` errors unchanged. Fix the request first. For `402`, add credits first. - Wafer already retries some failures internally before sending the first byte, so a `429` means those retries didn't succeed. - Requests that fail before generation starts are not charged. A stream that fails after it starts may be charged for tokens already generated. ## Errors during streaming Once a stream has started, the HTTP status is already `200`, so errors arrive in the stream or as a dropped connection. - **Chat Completions:** an error arrives as a `data:` line with an `error` object instead of a chunk, for example `data: {"error": {"message": "...", "type": "rate_limit_error", "param": null, "code": "server_overloaded"}}`. It can still be followed by `data: [DONE]`, so `[DONE]` alone doesn't mean success. Treat any `error` line as a failed request. - **Messages:** an `event: error` event carries `{"type": "error", "error": {"type": "overloaded_error" or "api_error", "message": ..., "code": ...}}`, and the stream ends without `message_stop`. - **Responses:** a `response.failed` event carries the response with `"status": "failed"` and `error: {"code": ..., "message": ...}`, and the stream ends without `response.completed`. - The `code` in these events can be one of the codes in [Rate limits and capacity](#rate-limits-and-capacity-429), `model_request_rejected`, or `invalid_stream_response` (the model returned a malformed stream). `server_overloaded` (`overloaded_error` on Messages) and `invalid_stream_response` are safe to retry. - **All APIs:** a stream that ends without its final event (`data: [DONE]`, `message_stop`, or `response.completed`) failed; retry the request. ## Timeouts - A non-streaming request that runs longer than about 15 minutes (longer on some models) can fail with `server_overloaded`. - A stream can drop, without an error event, if no data arrives for about 15 minutes. Use streaming for long generations. For non-streaming requests with large reasoning outputs, set your client timeout to at least 15 minutes. ## Request example This request fails with `unsupported_value` because `reasoning_effort` isn't a supported value: ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "Hi"}], "reasoning_effort": "turbo" }' ``` ```json { "error": { "message": "reasoning_effort 'turbo' is not supported for this model (supported: none, low, high, max)", "type": "invalid_request_error", "param": "reasoning_effort", "code": "unsupported_value", "request_id": "561a1a2e0e36" } } ``` # Usage and Billing Source: https://docs.wafer.ai/serverless/usage.md Every successful response reports token usage, including streaming responses. Use it to track spend per request. For account-level usage and cost over time, use the [usage API](#account-usage-api) or the dashboard at [app.wafer.ai](https://app.wafer.ai). ## Chat Completions usage ```bash curl -sS "https://pass.wafer.ai/v1/chat/completions" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "GLM-5.3", "messages": [{"role": "user", "content": "What is 17 * 3?"}], "max_tokens": 512 }' ``` The response carries a `usage` object: ```json { "usage": { "prompt_tokens": 4508, "completion_tokens": 20, "total_tokens": 4528, "prompt_tokens_details": {"cached_tokens": 4352}, "completion_tokens_details": {"reasoning_tokens": 16} } } ``` | Field | Meaning | | ----- | ------- | | `prompt_tokens` | All prompt tokens, including cached tokens. | | `prompt_tokens_details.cached_tokens` | Prompt tokens served from the prompt cache. A subset of `prompt_tokens`. | | `completion_tokens` | All generated tokens, including reasoning tokens. | | `completion_tokens_details.reasoning_tokens` | Generated tokens spent on returned reasoning. A subset of `completion_tokens`. On `GLM-5.3` and `GLM-5.3-Flash` with reasoning set to `none`, reasoning that isn't returned is counted in `completion_tokens` but not here; see the [reasoning warning](/serverless/chat-completions#reasoning). | | `total_tokens` | `prompt_tokens + completion_tokens`. | Successful streams end with a usage chunk before `data: [DONE]`. You don't need to set `stream_options`. See [Streaming](/serverless/chat-completions#streaming). ## Responses usage `/v1/responses` returns `usage.input_tokens`, `usage.output_tokens`, and `usage.total_tokens`. `output_tokens` includes reasoning tokens. `input_tokens_details.cached_tokens` is included when there are cached tokens, and non-streaming responses can include `output_tokens_details.reasoning_tokens`. In streams, usage is on the `response.completed` event. ## Messages usage `/v1/messages` returns `usage.input_tokens`, `usage.output_tokens`, `usage.cache_read_input_tokens`, and `usage.cache_creation_input_tokens`. In streams, final usage is on the `message_delta` event. On Wafer, `input_tokens` counts **all** prompt tokens, including `cache_read_input_tokens`. Anthropic's own API excludes cached tokens from `input_tokens`. Subtract `cache_read_input_tokens` from `input_tokens` to get uncached prompt tokens. `cache_creation_input_tokens` is always `0`: Wafer has no separate cache-write charge. ## Prompt caching Prompt caching is automatic. When a request starts with the same tokens as a recent request, Wafer reuses the cached prefix. You don't need to send `cache_control` or any other field. Cached tokens are reported as `prompt_tokens_details.cached_tokens` (Chat Completions), `input_tokens_details.cached_tokens` (Responses), or `cache_read_input_tokens` (Messages), and bill at the cache-read rate. Cache hits aren't guaranteed. To get more cache hits, keep the stable part of the prompt (system prompt, tool definitions, long documents) at the start and put changing content at the end. ## How requests are billed Serverless is prepaid. Each request is charged against your credit balance at the rates in the model's `wafer.pricing` card (see [Pricing](/serverless/models#pricing)): - Uncached prompt tokens (prompt tokens minus cached tokens) at `input_microcents_per_million` when present, otherwise `input_cents_per_million`. - Cached prompt tokens at `cache_read_microcents_per_million`. - Generated tokens, including reasoning tokens, at `output_microcents_per_million` when present, otherwise `output_cents_per_million`. Each of the three amounts is rounded to the nearest microcent (one millionth of a cent) per request, with halves rounded up. On some models, generated tokens can exceed `max_tokens` when reasoning is on; see [Output length](/serverless/chat-completions#output-length). Call `GET /v1/models` with your API key to see your account's rates. Without a key, the cards show list prices. Requests rejected before generation starts (authentication, validation, and rate-limit errors) are not charged. A stream that fails after it starts may be charged for tokens already generated. `/v1/messages/count_tokens` is free. Before a request runs, Wafer checks that your balance covers its maximum possible cost. When it doesn't, Wafer returns `402` with code `insufficient_credits`. See [Credits](/serverless/errors#credits-402). ## Account usage API `GET https://api.wafer.ai/v1/usage` returns your account's requests, tokens, and billed cost over time. Authenticate with any API key on the account. The report covers every key on the account for that key's endpoint. ```bash curl -sS "https://api.wafer.ai/v1/usage?starting_at=2026-09-01&ending_at=2026-10-01&bucket_width=1d" \ -H "Authorization: Bearer " ``` | Parameter | Meaning | | --------- | ------- | | `starting_at` | Required. Start of the window: an RFC 3339 timestamp or a `YYYY-MM-DD` date (UTC). | | `ending_at` | End of the window, exclusive. Defaults to the end of the current bucket. | | `bucket_width` | `1d` (default, up to 93 buckets) or `1h` (up to 168 buckets). `starting_at` and `ending_at` must fall on a UTC day or hour boundary to match. | | `group_by` | `model` (default) or `api_key`. | The response has one entry in `data` per bucket, including empty ones, and `totals` for the whole window. This excerpt of the response to the request above shows only the first of its 30 daily buckets, so `totals` is larger than that one bucket: ```json { "object": "usage_report", "bucket_width": "1d", "group_by": "model", "cost_status": "billed", "currency": "usd", "totals": {"requests": 1520, "input_tokens": 9120000, "cache_read_tokens": 6100000, "output_tokens": 412000, "cost_usd": 3.418}, "data": [ { "starting_at": "2026-09-01T00:00:00Z", "ending_at": "2026-09-02T00:00:00Z", "requests": 52, "input_tokens": 310000, "cache_read_tokens": 205000, "output_tokens": 14000, "cost_usd": 0.1137, "results": [ {"model": "GLM-5.3", "requests": 40, "input_tokens": 280000, "cache_read_tokens": 190000, "output_tokens": 12000, "cost_usd": 0.0981}, {"model": "DeepSeek-V4.1-Flash", "requests": 12, "input_tokens": 30000, "cache_read_tokens": 15000, "output_tokens": 2000, "cost_usd": 0.0156} ] } ] } ``` - `input_tokens` includes `cache_read_tokens`, as in per-request usage. - `cost_usd` is what each request was charged at your account's rates, before invoice-level discounts or credits. Cost history starts on 2026-06-16; earlier buckets report `cost_usd` as `null`. - Cost is tracked per model, not per key. With `group_by=api_key`, each key's `cost_usd` is `null`; the bucket and window totals still include cost. - Usage under a model name that can't be shown is grouped under `unavailable`. - `cost_status` is `billed` when cost is reported, `not_applicable` for plans that aren't billed per token, and `unavailable` when cost can't be reported for this account (contact support). - Tokens are bucketed by request time and cost by billing time, so daily figures near midnight UTC can differ slightly. The most recent minutes may not be included yet. ## Dashboard The dashboard at [app.wafer.ai](https://app.wafer.ai) shows your credit balance and usage, and is where you add credits. To track individual requests, use the `usage` object in each response and the `x-request-id` response header to correlate requests with your logs.