# Direct LLM Gateway Use Wiro models through OpenAI Chat Completions, OpenAI Responses, Anthropic Messages. The gateway uses the same Wiro task, project-access, team-policy, and billing system as the Run API. OpenAI-compatible clients use base URL: ```text https://llm.wiro.ai/v1 ``` Clients that append `/v1` themselves, including Anthropic SDKs and Claude Code, use origin `https://llm.wiro.ai`. Asynchronous Run, Task, the original Wiro File API, and WebSocket remain on `https://api.wiro.ai/v1` and `wss://socket.wiro.ai/v1`. The Direct LLM Gateway also provides OpenAI/Anthropic-compatible `/v1/files` routes backed by that same authenticated project storage. Direct LLM responses never expose Wiro Run/Sync `segments`. Each route returns only its selected protocol's native JSON and streaming events: OpenAI Chat chunks, OpenAI Responses events, or Anthropic Messages events. OpenClaw and Hermes receive the native transport they configure. In this release, `stream: true` provides protocol-compatible SSE ordering after the provider request completes and the authenticated structured turn is persisted. Do not interpret first-event timing as live provider token latency. Model IDs use lowercase `owner/model` form. Do not send a provider-only model name: discover the exact ID and protocol capabilities first. ## Choose an endpoint - `POST /v1/Run/{owner}/{project}` remains the asynchronous task-creation endpoint on `api.wiro.ai`. - `POST /v1/Run/{owner}/{project}/sync` is the generic finite-model wait on `api.wiro.ai`; see [Run a Model](/docs/run-a-model). It is not a Direct LLM protocol. - `GET /v1/models` lists verified `llm-tool-call` models available in the authenticated project/team context. `GET /v1/chat/models` is an equivalent compatibility alias. - `GET /v1/models/{owner}/{model}` returns one model's exact gateway contract. - `POST`, `GET`, and `DELETE /v1/files` compatibility routes use the authenticated project's existing Wiro file storage. - `POST /v1/chat/completions` uses OpenAI Chat JSON and SSE. - `POST /v1/responses` uses OpenAI Responses JSON and official named SSE events. - `POST /v1/messages` uses Anthropic Messages JSON and SSE. - `POST /v1/messages/count_tokens` estimates Anthropic input tokens before a run. - `GET /v1/generation?id=...` returns project-authorized generation metadata. The asynchronous Run route returns `taskid` and `socketaccesstoken` immediately; retrieve its result through Task Detail, WebSocket, or a callback. The generic `/sync` wait is an additional method on `api.wiro.ai`; it does not replace asynchronous Run. Direct LLM routes remain separate on `llm.wiro.ai`. Training and realtime models are not accepted by `/sync` or the direct LLM gateway. Use their existing training or realtime APIs. ## Authentication ### Project credentials Model discovery/detail, the protocol routes, and `/generation` all use the authenticated project's credentials. Standard compatible clients should send a project Bearer credential: - API Key Only project: `Authorization: Bearer YOUR_API_KEY` - Signature project: `Authorization: Bearer YOUR_API_KEY:YOUR_API_SECRET` The Signature Bearer value may instead be the base64 encoding of `YOUR_API_KEY:YOUR_API_SECRET`. The same endpoints also accept `x-api-key` containing either `YOUR_API_KEY` or `YOUR_API_KEY:YOUR_API_SECRET`. Direct LLM routes do not accept the Run API's `x-nonce`/`x-signature` HMAC trio. If `Authorization` and `x-api-key` are sent together, both must identify the same complete project credential. A mismatched key or secret, or a malformed Bearer credential returns `401`; Wiro does not silently choose one credential or fall back to another project. Never expose a project key or secret in browser or mobile application code. Anthropic Messages requires `x-api-key`. Send `YOUR_API_KEY` for an API Key Only project or `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project. Generic `/sync` waits stay on `api.wiro.ai` and use normal Run headers. ### Session identifier `session_id` is the only public session field. It is optional on Chat Completions and Anthropic Messages, and must contain 1–128 letters, digits, `.`, `_`, `:`, or `-`. Reuse the same value for related turns. Wiro maps it to a project-scoped internal session; it does not replace the protocol's normal client-history requirements. Responses uses `previous_response_id` instead, and `/v1/messages/count_tokens` does not accept `session_id`. ## Connect an agent Point a compatible client at `llm.wiro.ai`. OpenAI-compatible clients use a project Bearer credential. Anthropic-compatible clients put the same project credential in `x-api-key`: `YOUR_API_KEY` for API Key Only or `YOUR_API_KEY:YOUR_API_SECRET` for Signature. Every client below needs the exact lowercase `owner/model` ID. Two places give it to you: - [The catalog, filtered to gateway models](https://wiro.ai/models?categories=llm-tool-call) — browse them, open one, and copy the ID from its page. - `GET /v1/models` — the same set as JSON, scoped to what your project and team can actually run. `GET /v1/models/{owner}/{model}` then reports that model's routes, modalities, tool support and token limits. Generic finite-model waits stay on [Run a Model](/docs/run-a-model). ### Cursor Cursor reaches the gateway through its OpenAI base URL override, which it appends `/chat/completions` to. Point it at the normal `/v1` base. 1. **Open the model settings** — Command Palette (`Cmd+Shift+P`, `Ctrl+Shift+P` on Windows) → `Cursor Settings: Models`. 2. **View All Models** — the settings pane lists only the models Cursor ships with. `View All Models`, at the bottom, opens the full list where custom ones live. 3. **+ Add Custom Model** — type the exact lowercase `owner/model` ID — the same IDs VS Code takes, listed below. Repeat for each model you want. Cursor shows whatever you type in its picker, so a parenthesised label is read and discarded at either end: `(Wiro) claude/sonnet-5` and `claude/sonnet-5 (Wiro)` both resolve to `claude/sonnet-5`. Leading keeps every Wiro model together in the picker. ![Cursor's custom model list showing four Wiro models, each labelled (Wiro AI), with the Add Custom Model button below them.](css/cursor-custom-llm.png) 4. **OpenAI API Key** — put the project credential in the `OpenAI API Key` field; that is the field Cursor uses for the overridden endpoint. `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project, `YOUR_API_KEY` alone for API Key Only. 5. **Turn the key on** — flip the switch beside the field. It reads `Secret saved` once Cursor has stored the credential. 6. **Override OpenAI Base URL** — turn that switch on too. It sits directly under the key. 7. **Enter the base URL** — `https://llm.wiro.ai/v1`. Cursor appends `/chat/completions` itself, so do not add it. 8. **Disable the built-in models** — while the override is on, Cursor sends its own model names to your endpoint too, and those do not exist in the Wiro catalog. Leave only the models you added enabled, or every request fails with an unknown model. 9. **Verify** — press `Verify`. A failure here is normally the base URL carrying `/chat/completions`, or a built-in model still switched on. Every model the gateway serves today, labelled and ready to paste one at a time; drop the `(Wiro)` part, or move it to the end, as you prefer. The same set is in [the catalog](https://wiro.ai/models?categories=llm-tool-call) and behind `GET /v1/models`. ``` (Wiro) bytedance/seed-v2-1-turbo (Wiro) bytedance/seed-v2-lite (Wiro) bytedance/seed-v2-mini (Wiro) bytedance/seed-v2-pro (Wiro) claude/fable-5 (Wiro) claude/opus-5 (Wiro) claude/sonnet-5 (Wiro) meta-llama/llama-3-2-3b-instruct (Wiro) mistralai/mistral-nemo-instruct-2407 (Wiro) openai/gpt-5-2 (Wiro) openai/gpt-5-4 (Wiro) openai/gpt-5-4-mini (Wiro) openai/gpt-5-4-nano (Wiro) openai/gpt-5-5 (Wiro) openai/gpt-5-6-luna (Wiro) openai/gpt-5-6-sol (Wiro) openai/gpt-5-6-terra (Wiro) openai/gpt-5-mini (Wiro) openai/gpt-5-nano (Wiro) qwen/qwen2-5-0-5b-instruct (Wiro) qwen/qwen2-5-1-5b-instruct (Wiro) qwen/qwen2-5-14b-instruct (Wiro) qwen/qwen2-5-32b-instruct (Wiro) qwen/qwen2-5-3b-instruct (Wiro) qwen/qwen2-5-7b-instruct (Wiro) qwen/qwen2-5-coder-32b-instruct (Wiro) qwen/qwen3-30b-a3b (Wiro) qwen/qwen3-30b-a3b-thinking-2507 (Wiro) qwen/qwen3-32b (Wiro) qwen/qwen3-5-27b (Wiro) qwen/qwen3-5-27b-heretic (Wiro) qwen/qwen3-5-4b (Wiro) qwen/qwen3-5-4b-heretic (Wiro) qwen/qwen3-5-9b (Wiro) qwen/qwen3-5-9b-heretic (Wiro) qwen/qwen3-6-27b (Wiro) qwen/qwen3-8-27b (Wiro) qwen/qwen3-8-27b-obliterated (Wiro) qwen/qwen3-8-27b-uncensored (Wiro) qwen/qwen3-coder-30b-a3b-instruct (Wiro) xai/grok-4-1-fast (Wiro) xai/grok-4-20 (Wiro) xai/grok-4-5 ``` The override covers the chat and agent panel. Tab autocomplete and inline edit keep using Cursor's own backend whatever you configure here, so they are unaffected either way. ### Claude Code Claude Code reads the gateway through the Anthropic Messages protocol. This is the **terminal** Claude Code: the desktop app manages its own provider and cannot be pointed at a gateway, so its model picker keeps listing Anthropic's models whatever you configure. 1. **Install the CLI** — in a terminal: ```bash npm i -g @anthropic-ai/claude-code ``` 2. **Write the settings file** — two places take it, and the file is the same in both. Pick by how widely you want the gateway used. **Every folder on this machine** — `~/.claude/settings.json`: ```json { "env": { "ANTHROPIC_BASE_URL": "https://llm.wiro.ai", "ANTHROPIC_API_KEY": "YOUR_API_KEY:YOUR_API_SECRET", "ANTHROPIC_MODEL": "(Wiro) openai/gpt-5-6-sol", "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1", "CLAUDE_CODE_MAX_CONTEXT_TOKENS": "1050000" } } ``` **One project only** — `/.claude/settings.json`, in the folder you will open Claude Code in. Your other projects keep whatever they were using. If that folder is a git repository, put it in `.claude/settings.local.json` instead, which is not committed — the credential should not reach the shared file: ```json { "env": { "ANTHROPIC_BASE_URL": "https://llm.wiro.ai", "ANTHROPIC_API_KEY": "YOUR_API_KEY:YOUR_API_SECRET", "ANTHROPIC_MODEL": "(Wiro) openai/gpt-5-6-sol", "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1", "CLAUDE_CODE_MAX_CONTEXT_TOKENS": "1050000" } } ``` `ANTHROPIC_BASE_URL` carries no `/v1` here — Anthropic clients append the path themselves. `ANTHROPIC_API_KEY` takes `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project, or `YOUR_API_KEY` alone for API Key Only. `ANTHROPIC_MODEL` is the model the session opens with, and is any ID from the list below, not only the `claude/` ones; the `(Wiro)` prefix is optional and the gateway strips it, but Claude Code prints whatever you write in its session header. `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY` fills the `/model` picker from the gateway rather than leaving it on Anthropic's own list. `CLAUDE_CODE_MAX_CONTEXT_TOKENS` must match the context window of the model you set — Claude Code does not recognise these names, so it otherwise assumes 200k and discards history long before it needs to. 3. **Open Claude Code in that folder**: ```bash cd /path/to/your/project && claude ``` 4. **Answer the first-run prompts** — a new install asks three things in a row: a colour theme, its security notes (`Press Enter to continue`), and whether to apply `Claude Code's terminal setup`. Answer them once. 5. **Allow the project's MCP servers, if it has any** — a folder that declares MCP servers gets `New MCP server found in this project` before the session opens. Nothing here concerns the gateway; choose whatever you would normally choose. 6. **Answer the key prompt** — `Do you want to use this API key?` → `No`. It asks about a key exported in your shell, not the one you just put in the settings file. 7. **Check the header, then start** — it names the model you configured: `(Wiro) openai/gpt-5-6-sol · API Usage Billing`. Your Claude subscription is not used while a gateway credential is set. Every model the gateway serves today, with the context window for `CLAUDE_CODE_MAX_CONTEXT_TOKENS`. The same values come from `GET /v1/models` as `context_length`. For a Wiro-hosted model that number is the window of the checkpoint we actually run, which can be smaller than the one its model card advertises. 8. **Switching models** — open on another model for one session: ```bash cd /path/to/your/project && claude --model "(Wiro) xai/grok-4-1-fast" cd /path/to/your/project && claude --model "(Wiro) bytedance/seed-v2-pro" cd /path/to/your/project && claude --model "(Wiro) openai/gpt-5-nano" ``` Or change `ANTHROPIC_MODEL` in the settings file, which every session in that scope then opens on. Move `CLAUDE_CODE_MAX_CONTEXT_TOKENS` with it — Grok 4.1 Fast holds 2M where GPT 5.6 Sol holds 1.05M: ```json { "env": { "ANTHROPIC_BASE_URL": "https://llm.wiro.ai", "ANTHROPIC_API_KEY": "YOUR_API_KEY:YOUR_API_SECRET", "ANTHROPIC_MODEL": "(Wiro) xai/grok-4-1-fast", "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1", "CLAUDE_CODE_MAX_CONTEXT_TOKENS": "2000000" } } ``` ``` Model ID Context ---------------------------------------------------- (Wiro) bytedance/seed-v2-1-turbo 256000 (Wiro) bytedance/seed-v2-lite 256000 (Wiro) bytedance/seed-v2-mini 256000 (Wiro) bytedance/seed-v2-pro 256000 (Wiro) claude/fable-5 1000000 (Wiro) claude/opus-5 1000000 (Wiro) claude/sonnet-5 1000000 (Wiro) meta-llama/llama-3-2-3b-instruct 131072 (Wiro) mistralai/mistral-nemo-instruct-2407 131072 (Wiro) openai/gpt-5-2 400000 (Wiro) openai/gpt-5-4 1050000 (Wiro) openai/gpt-5-4-mini 400000 (Wiro) openai/gpt-5-4-nano 400000 (Wiro) openai/gpt-5-5 1050000 (Wiro) openai/gpt-5-6-luna 1050000 (Wiro) openai/gpt-5-6-sol 1050000 (Wiro) openai/gpt-5-6-terra 1050000 (Wiro) openai/gpt-5-mini 400000 (Wiro) openai/gpt-5-nano 400000 (Wiro) qwen/qwen2-5-0-5b-instruct 32768 (Wiro) qwen/qwen2-5-1-5b-instruct 32768 (Wiro) qwen/qwen2-5-14b-instruct 32768 (Wiro) qwen/qwen2-5-32b-instruct 32768 (Wiro) qwen/qwen2-5-3b-instruct 32768 (Wiro) qwen/qwen2-5-7b-instruct 32768 (Wiro) qwen/qwen2-5-coder-32b-instruct 32768 (Wiro) qwen/qwen3-30b-a3b 40960 (Wiro) qwen/qwen3-30b-a3b-thinking-2507 262144 (Wiro) qwen/qwen3-32b 40960 (Wiro) qwen/qwen3-5-27b 262144 (Wiro) qwen/qwen3-5-27b-heretic 262144 (Wiro) qwen/qwen3-5-4b 262144 (Wiro) qwen/qwen3-5-4b-heretic 262144 (Wiro) qwen/qwen3-5-9b 262144 (Wiro) qwen/qwen3-5-9b-heretic 262144 (Wiro) qwen/qwen3-6-27b 262144 (Wiro) qwen/qwen3-8-27b 262144 (Wiro) qwen/qwen3-8-27b-obliterated 262144 (Wiro) qwen/qwen3-8-27b-uncensored 262144 (Wiro) qwen/qwen3-coder-30b-a3b-instruct 262144 (Wiro) xai/grok-4-1-fast 2000000 (Wiro) xai/grok-4-20 2000000 (Wiro) xai/grok-4-5 500000 ``` If Claude Code opens to the login screen instead of a session, the settings file was not read in time: a project `.claude/settings.json` applies only after the first-run prompts and the folder trust prompt above. Answer those once and reopen, or put the same `env` block in `~/.claude/settings.json`, which is read before them and applies to every folder. ### Windsurf Windsurf now ships as **Devin Desktop**; the application is `Devin.app` and its docs redirect to `docs.devin.ai`. Cascade, its own agent, has no setting for an external provider — there is no base URL to override and no custom model to register, so it cannot be pointed at this gateway. What does work is running a different agent inside the same editor. The steps below use [Cline](https://open-vsx.org/extension/saoudrizwan/claude-dev), which is free and Apache-2.0; Roo Code and Continue configure the same way. 1. **Open the extension marketplace** — Command Palette (`Cmd+Shift+P`, `Ctrl+Shift+P` on Windows) → `Extensions: Install Extensions`. The menu bar gets you to the same place: `Devin → Preferences → Extensions`. 2. **Install Cline** — search `cline` and install the one published by `saoudrizwan`. Devin resolves extensions through the Open VSX mirror, so this is the same build VS Code carries. 3. **Open the panel** — Command Palette (`Cmd+Shift+P`, `Ctrl+Shift+P` on Windows) → `Cline: Focus on View`. 4. **Bring my own API key** — Cline asks how you will use it. Pick the fourth option, `Bring my own API key`, then `Continue`. The other three are Cline's own billing; `Login to Cline` is not needed. 5. **API Provider** — choose `OpenAI Compatible`, not `OpenAI`; that one goes to the official OpenAI API. 6. **Base URL** — `https://llm.wiro.ai/v1`. Cline appends the route itself. 7. **OpenAI Compatible API Key** — `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project, `YOUR_API_KEY` alone for API Key Only. Cline keeps it locally. 8. **Model ID** — one ID from the list below; Cline holds a single model per configuration rather than a list. The `(Wiro)` prefix is optional and the gateway strips it; it earns its place here by showing up in the status bar, as `openai-compat: (Wiro) openai/gpt-5-6-sol`. 9. **Open `MODEL CONFIGURATION` and set the context window** — this one matters. Cline does not know a custom model, so it assumes `128K` and starts dropping history long before it needs to. Put the real number from the list below into `Context Window size`, and tick `Computer Use` so tool calling is enabled — without it the agent cannot run tools. `Max Output Tokens` and the price fields are in the same section. 10. **Continue, and send a message** — the status bar names the model you configured. `Plan` and `Act` can each hold a different model, and saved API configuration profiles let you keep one per model and switch between them. Every model the gateway serves today, with the context window Cline needs. The same numbers come from `GET /v1/models` as `context_length` and `max_tokens`. ``` [ { "name": "Wiro AI", "vendor": "customendpoint", "apiKey": "${input:chat.lm.secret.xxxxxxxx}", "apiType": "chat-completions", "models": [ { "id": "bytedance/seed-v2-1-turbo", "name": "(Wiro) Seed V2.1 Turbo", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 256000, "maxOutputTokens": 65536 }, { "id": "bytedance/seed-v2-lite", "name": "(Wiro) Seed V2 Lite", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 256000, "maxOutputTokens": 65536 }, { "id": "bytedance/seed-v2-mini", "name": "(Wiro) Seed V2 Mini", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 256000, "maxOutputTokens": 65536 }, { "id": "bytedance/seed-v2-pro", "name": "(Wiro) Seed V2 Pro", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 256000, "maxOutputTokens": 65536 }, { "id": "claude/fable-5", "name": "(Wiro) Fable 5", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1000000, "maxOutputTokens": 128000 }, { "id": "claude/opus-5", "name": "(Wiro) Opus 5", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1000000, "maxOutputTokens": 128000 }, { "id": "claude/sonnet-5", "name": "(Wiro) Sonnet 5", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1000000, "maxOutputTokens": 128000 }, { "id": "meta-llama/llama-3-2-3b-instruct", "name": "(Wiro) Llama 3.2 3B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 131072, "maxOutputTokens": 2048 }, { "id": "mistralai/mistral-nemo-instruct-2407", "name": "(Wiro) Mistral Nemo Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 131072, "maxOutputTokens": 2048 }, { "id": "openai/gpt-5-2", "name": "(Wiro) GPT-5.2", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 400000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-4", "name": "(Wiro) GPT-5.4", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1050000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-4-mini", "name": "(Wiro) GPT-5.4 Mini", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 400000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-4-nano", "name": "(Wiro) GPT-5.4 Nano", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 400000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-5", "name": "(Wiro) GPT-5.5", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1050000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-6-luna", "name": "(Wiro) GPT-5.6 Luna", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1050000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-6-sol", "name": "(Wiro) GPT-5.6 Sol", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1050000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-6-terra", "name": "(Wiro) GPT-5.6 Terra", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 1050000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-mini", "name": "(Wiro) GPT-5 Mini", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 400000, "maxOutputTokens": 65536 }, { "id": "openai/gpt-5-nano", "name": "(Wiro) GPT-5 Nano", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 400000, "maxOutputTokens": 65536 }, { "id": "qwen/qwen2-5-0-5b-instruct", "name": "(Wiro) Qwen2.5 0.5B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 32768, "maxOutputTokens": 2048 }, { "id": "qwen/qwen2-5-1-5b-instruct", "name": "(Wiro) Qwen2.5 1.5B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 32768, "maxOutputTokens": 2048 }, { "id": "qwen/qwen2-5-14b-instruct", "name": "(Wiro) Qwen2.5 14B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 32768, "maxOutputTokens": 2048 }, { "id": "qwen/qwen2-5-32b-instruct", "name": "(Wiro) Qwen2.5 32B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 32768, "maxOutputTokens": 2048 }, { "id": "qwen/qwen2-5-3b-instruct", "name": "(Wiro) Qwen2.5 3B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 32768, "maxOutputTokens": 2048 }, { "id": "qwen/qwen2-5-7b-instruct", "name": "(Wiro) Qwen2.5 7B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 32768, "maxOutputTokens": 2048 }, { "id": "qwen/qwen2-5-coder-32b-instruct", "name": "(Wiro) Qwen2.5 Coder 32B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 32768, "maxOutputTokens": 2048 }, { "id": "qwen/qwen3-30b-a3b", "name": "(Wiro) Qwen3 30B A3B", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 40960, "maxOutputTokens": 4096 }, { "id": "qwen/qwen3-30b-a3b-thinking-2507", "name": "(Wiro) Qwen3 30B A3B Thinking", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 4096 }, { "id": "qwen/qwen3-32b", "name": "(Wiro) Qwen3 32B", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 40960, "maxOutputTokens": 4096 }, { "id": "qwen/qwen3-5-27b", "name": "(Wiro) Qwen3.5 27B", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-5-27b-heretic", "name": "(Wiro) Qwen3.5 27B Heretic", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-5-4b", "name": "(Wiro) Qwen3.5 4B", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-5-4b-heretic", "name": "(Wiro) Qwen3.5 4B Heretic", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-5-9b", "name": "(Wiro) Qwen3.5 9B", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-5-9b-heretic", "name": "(Wiro) Qwen3.5 9B Heretic", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-6-27b", "name": "(Wiro) Qwen3.6 27B", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-8-27b", "name": "(Wiro) Qwen3.8 27B", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-8-27b-obliterated", "name": "(Wiro) Qwen3.8 27B Obliterated", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-8-27b-uncensored", "name": "(Wiro) Qwen3.8 27B Uncensored", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 16384 }, { "id": "qwen/qwen3-coder-30b-a3b-instruct", "name": "(Wiro) Qwen3 Coder 30B A3B Instruct", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": false, "maxInputTokens": 262144, "maxOutputTokens": 2048 }, { "id": "xai/grok-4-1-fast", "name": "(Wiro) Grok 4.1 Fast", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 2000000, "maxOutputTokens": 65536 }, { "id": "xai/grok-4-20", "name": "(Wiro) Grok 4.20", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 2000000, "maxOutputTokens": 65536 }, { "id": "xai/grok-4-5", "name": "(Wiro) Grok 4.5", "url": "https://llm.wiro.ai/v1/chat/completions", "toolCalling": true, "vision": true, "maxInputTokens": 500000, "maxOutputTokens": 65536 } ] } ] ``` None of this touches Cascade, which keeps using Windsurf's own models. The editor is Devin, the agent is Cline, and the models are Wiro's. ### VS Code VS Code Chat reaches the gateway through its own built-in custom endpoint — no extension required. 1. **Open the model manager** — Command Palette (`Cmd+Shift+P`, `Ctrl+Shift+P` on Windows) → `Chat: Manage Language Models`. 2. **Add a provider** — `+ Add Models` opens a provider list (Anthropic, Azure, Google, OpenAI, OpenRouter, xAI, Ollama). Pick `Custom Endpoint`, last, below the separator. 3. **Group Name** — arrives pre-filled with "Custom Endpoint"; replace it with `Wiro AI`. It is a label — it titles the next two prompts and groups the models in the picker. 4. **API Key** — `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project, `YOUR_API_KEY` alone for API Key Only. VS Code keeps it in its secret store; the file holds only a `${input:…}` reference. 5. **API Type** — choose `Chat Completions`. Responses and Messages change the request shape VS Code sends. 6. **Describe the models** — VS Code opens `chatLanguageModels.json` with one empty model. Leave `name`, `vendor`, `apiKey` and `apiType` as generated and fill the `models` array. Below is every model the gateway serves today, ready to paste; the same set is in [the catalog](https://wiro.ai/models?categories=llm-tool-call) and behind `GET /v1/models`. ```json Model ID Context Max Output ---------------------------------------------------------------- (Wiro) bytedance/seed-v2-1-turbo 256000 65536 (Wiro) bytedance/seed-v2-lite 256000 65536 (Wiro) bytedance/seed-v2-mini 256000 65536 (Wiro) bytedance/seed-v2-pro 256000 65536 (Wiro) claude/fable-5 1000000 128000 (Wiro) claude/opus-5 1000000 128000 (Wiro) claude/sonnet-5 1000000 128000 (Wiro) meta-llama/llama-3-2-3b-instruct 131072 2048 (Wiro) mistralai/mistral-nemo-instruct-2407 131072 2048 (Wiro) openai/gpt-5-2 400000 65536 (Wiro) openai/gpt-5-4 1050000 65536 (Wiro) openai/gpt-5-4-mini 400000 65536 (Wiro) openai/gpt-5-4-nano 400000 65536 (Wiro) openai/gpt-5-5 1050000 65536 (Wiro) openai/gpt-5-6-luna 1050000 65536 (Wiro) openai/gpt-5-6-sol 1050000 65536 (Wiro) openai/gpt-5-6-terra 1050000 65536 (Wiro) openai/gpt-5-mini 400000 65536 (Wiro) openai/gpt-5-nano 400000 65536 (Wiro) qwen/qwen2-5-0-5b-instruct 32768 2048 (Wiro) qwen/qwen2-5-1-5b-instruct 32768 2048 (Wiro) qwen/qwen2-5-14b-instruct 32768 2048 (Wiro) qwen/qwen2-5-32b-instruct 32768 2048 (Wiro) qwen/qwen2-5-3b-instruct 32768 2048 (Wiro) qwen/qwen2-5-7b-instruct 32768 2048 (Wiro) qwen/qwen2-5-coder-32b-instruct 32768 2048 (Wiro) qwen/qwen3-30b-a3b 40960 4096 (Wiro) qwen/qwen3-30b-a3b-thinking-2507 262144 4096 (Wiro) qwen/qwen3-32b 40960 4096 (Wiro) qwen/qwen3-5-27b 262144 16384 (Wiro) qwen/qwen3-5-27b-heretic 262144 16384 (Wiro) qwen/qwen3-5-4b 262144 16384 (Wiro) qwen/qwen3-5-4b-heretic 262144 16384 (Wiro) qwen/qwen3-5-9b 262144 16384 (Wiro) qwen/qwen3-5-9b-heretic 262144 16384 (Wiro) qwen/qwen3-6-27b 262144 16384 (Wiro) qwen/qwen3-8-27b 262144 16384 (Wiro) qwen/qwen3-8-27b-obliterated 262144 16384 (Wiro) qwen/qwen3-8-27b-uncensored 262144 16384 (Wiro) qwen/qwen3-coder-30b-a3b-instruct 262144 2048 (Wiro) xai/grok-4-1-fast 2000000 65536 (Wiro) xai/grok-4-20 2000000 65536 (Wiro) xai/grok-4-5 500000 65536 ``` - `id` — the lowercase `owner/model` from `GET /v1/models`. - `name` — a free label, shown in the picker. - `url` — the **full endpoint**, not the base URL. Every model repeats it. - `toolCalling`, `vision`, `maxInputTokens`, `maxOutputTokens` — read them from `GET /v1/models/{owner}/{model}`: `capabilities.function_tools`, `capabilities.input_modalities`, `max_input_tokens`, `max_tokens`. Do not guess. - **`maxInputTokens` is not optional.** VS Code sizes every conversation against it and answers *"Could not compact conversation"* on each turn when a model omits it, before any request reaches the gateway. Take the number from `max_input_tokens`; every catalog model reports one. For a Wiro-hosted model it is the window of the checkpoint we run rather than the model family's advertised maximum -- Qwen2.5 7B reports 32,768 here although its card says 131,072, because that is what the shipped configuration sets. Save the file and reopen the model picker; the models appear under **Wiro AI**. Any other VS Code extension that takes a custom OpenAI base URL — Cline, Roo Code, Continue — works the same way: point it at `https://llm.wiro.ai/v1` with the same credential and a model ID from the catalog. ### OpenClaw OpenClaw registers the gateway as a custom provider in its own config file. Everything below lives in one file: ``` ~/.openclaw/openclaw.json macOS, Linux %USERPROFILE%\.openclaw\openclaw.json Windows ``` Set `OPENCLAW_CONFIG_PATH` to put it elsewhere, or run `openclaw --profile ` to keep a separate one under `~/.openclaw-`. 1. **Install** — `npm i -g openclaw`. 2. **Name the provider** — the key under `providers`, `wiro` below, becomes the prefix you select a model with, as `wiro/`. Pick any name; the rest of this page assumes `wiro`. 3. **Match `api` to the model transport** — read it from `GET /v1/models/{owner}/{model}`. `openai-completions` and `openai-responses` take the `/v1` base; `anthropic-messages` takes the origin, `https://llm.wiro.ai`, with no `/v1`. One provider block per transport if you mix them. 4. **Fill `apiKey`** — `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project, `YOUR_API_KEY` alone for API Key Only. 5. **List the models** — each entry takes an `id` and a display `name`. The `id` is what reaches the gateway, so the `(Wiro)` label is optional there and stripped on arrival; the `name` is only ever shown to you. Both carry it below so the CLI and the picker read the same. 6. **Run a turn** — `openclaw agent --local --model "wiro/(Wiro) xai/grok-4-1-fast" -m "your prompt"`. `--local` runs the embedded agent against these credentials; without it the turn goes through an OpenClaw gateway instead. Every model the gateway serves today, ready to paste as the whole `models` array. The same set is in the catalog and behind `GET /v1/models`. ```json { "models": { "providers": { "wiro": { "baseUrl": "https://llm.wiro.ai/v1", "apiKey": "YOUR_API_KEY:YOUR_API_SECRET", "api": "openai-completions", "models": [ { "id": "(Wiro) bytedance/seed-v2-1-turbo", "name": "(Wiro) Seed V2.1 Turbo" }, { "id": "(Wiro) bytedance/seed-v2-lite", "name": "(Wiro) Seed V2 Lite" }, { "id": "(Wiro) bytedance/seed-v2-mini", "name": "(Wiro) Seed V2 Mini" }, { "id": "(Wiro) bytedance/seed-v2-pro", "name": "(Wiro) Seed V2 Pro" }, { "id": "(Wiro) claude/fable-5", "name": "(Wiro) Fable 5" }, { "id": "(Wiro) claude/opus-5", "name": "(Wiro) Opus 5" }, { "id": "(Wiro) claude/sonnet-5", "name": "(Wiro) Sonnet 5" }, { "id": "(Wiro) meta-llama/llama-3-2-3b-instruct", "name": "(Wiro) Llama 3.2 3B Instruct" }, { "id": "(Wiro) mistralai/mistral-nemo-instruct-2407", "name": "(Wiro) Mistral Nemo Instruct" }, { "id": "(Wiro) openai/gpt-5-2", "name": "(Wiro) GPT-5.2" }, { "id": "(Wiro) openai/gpt-5-4", "name": "(Wiro) GPT-5.4" }, { "id": "(Wiro) openai/gpt-5-4-mini", "name": "(Wiro) GPT-5.4 Mini" }, { "id": "(Wiro) openai/gpt-5-4-nano", "name": "(Wiro) GPT-5.4 Nano" }, { "id": "(Wiro) openai/gpt-5-5", "name": "(Wiro) GPT-5.5" }, { "id": "(Wiro) openai/gpt-5-6-luna", "name": "(Wiro) GPT-5.6 Luna" }, { "id": "(Wiro) openai/gpt-5-6-sol", "name": "(Wiro) GPT-5.6 Sol" }, { "id": "(Wiro) openai/gpt-5-6-terra", "name": "(Wiro) GPT-5.6 Terra" }, { "id": "(Wiro) openai/gpt-5-mini", "name": "(Wiro) GPT-5 Mini" }, { "id": "(Wiro) openai/gpt-5-nano", "name": "(Wiro) GPT-5 Nano" }, { "id": "(Wiro) qwen/qwen2-5-0-5b-instruct", "name": "(Wiro) Qwen2.5 0.5B Instruct" }, { "id": "(Wiro) qwen/qwen2-5-1-5b-instruct", "name": "(Wiro) Qwen2.5 1.5B Instruct" }, { "id": "(Wiro) qwen/qwen2-5-14b-instruct", "name": "(Wiro) Qwen2.5 14B Instruct" }, { "id": "(Wiro) qwen/qwen2-5-32b-instruct", "name": "(Wiro) Qwen2.5 32B Instruct" }, { "id": "(Wiro) qwen/qwen2-5-3b-instruct", "name": "(Wiro) Qwen2.5 3B Instruct" }, { "id": "(Wiro) qwen/qwen2-5-7b-instruct", "name": "(Wiro) Qwen2.5 7B Instruct" }, { "id": "(Wiro) qwen/qwen2-5-coder-32b-instruct", "name": "(Wiro) Qwen2.5 Coder 32B Instruct" }, { "id": "(Wiro) qwen/qwen3-30b-a3b", "name": "(Wiro) Qwen3 30B A3B" }, { "id": "(Wiro) qwen/qwen3-30b-a3b-thinking-2507", "name": "(Wiro) Qwen3 30B A3B Thinking" }, { "id": "(Wiro) qwen/qwen3-32b", "name": "(Wiro) Qwen3 32B" }, { "id": "(Wiro) qwen/qwen3-5-27b", "name": "(Wiro) Qwen3.5 27B" }, { "id": "(Wiro) qwen/qwen3-5-27b-heretic", "name": "(Wiro) Qwen3.5 27B Heretic" }, { "id": "(Wiro) qwen/qwen3-5-4b", "name": "(Wiro) Qwen3.5 4B" }, { "id": "(Wiro) qwen/qwen3-5-4b-heretic", "name": "(Wiro) Qwen3.5 4B Heretic" }, { "id": "(Wiro) qwen/qwen3-5-9b", "name": "(Wiro) Qwen3.5 9B" }, { "id": "(Wiro) qwen/qwen3-5-9b-heretic", "name": "(Wiro) Qwen3.5 9B Heretic" }, { "id": "(Wiro) qwen/qwen3-6-27b", "name": "(Wiro) Qwen3.6 27B" }, { "id": "(Wiro) qwen/qwen3-8-27b", "name": "(Wiro) Qwen3.8 27B" }, { "id": "(Wiro) qwen/qwen3-8-27b-obliterated", "name": "(Wiro) Qwen3.8 27B Obliterated" }, { "id": "(Wiro) qwen/qwen3-8-27b-uncensored", "name": "(Wiro) Qwen3.8 27B Uncensored" }, { "id": "(Wiro) qwen/qwen3-coder-30b-a3b-instruct", "name": "(Wiro) Qwen3 Coder 30B A3B Instruct" }, { "id": "(Wiro) xai/grok-4-1-fast", "name": "(Wiro) Grok 4.1 Fast" }, { "id": "(Wiro) xai/grok-4-20", "name": "(Wiro) Grok 4.20" }, { "id": "(Wiro) xai/grok-4-5", "name": "(Wiro) Grok 4.5" } ] } } } } ``` Tool calling works through the gateway, so the agent can run its own tools across as many turns as the task needs. ### Hermes Hermes Agent, from Nous Research, reaches the gateway as a custom provider. Its whole configuration is one file: ``` ~/.hermes/config.yaml ``` Set `HERMES_HOME` to put it elsewhere; the project's own Docker image already points it at `/opt/data`. The file the repository ships as `cli-config.yaml.example` is read under the name `config.yaml`. 1. **Install** — `curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash` on macOS, Linux and WSL2; `iex (irm https://hermes-agent.nousresearch.com/install.ps1)` in PowerShell. The repository also carries a `Dockerfile` and a `docker-compose.yml`. 2. **Choose the custom provider** — `provider: "custom"` is Hermes' name for any OpenAI-compatible endpoint; `ollama`, `vllm` and `llamacpp` are aliases of it. With it, `base_url` is what decides where the request goes. 3. **Fill `api_key`** — `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project, `YOUR_API_KEY` alone for API Key Only. 4. **Name the model** — `model.default` takes one ID from the list below. Hermes holds a single default rather than a list, so the `(Wiro)` label is there for your own benefit; the gateway strips it either way. 5. **Run a turn** — `hermes -z "your prompt"` for one shot, `hermes chat` for a session. Add `--yolo` when you want the agent to use its tools without stopping for approval. 6. **Switch models** — `hermes -m "(Wiro) xai/grok-4-1-fast" -z "your prompt"` overrides the default for one run; `hermes model` changes it for good. ```yaml model: default: "(Wiro) openai/gpt-5-6-sol" provider: "custom" base_url: "https://llm.wiro.ai/v1" api_key: "YOUR_API_KEY:YOUR_API_SECRET" ``` Every model the gateway serves today. The same set is in the catalog and behind `GET /v1/models`. ``` (Wiro) bytedance/seed-v2-1-turbo (Wiro) bytedance/seed-v2-lite (Wiro) bytedance/seed-v2-mini (Wiro) bytedance/seed-v2-pro (Wiro) claude/fable-5 (Wiro) claude/opus-5 (Wiro) claude/sonnet-5 (Wiro) meta-llama/llama-3-2-3b-instruct (Wiro) mistralai/mistral-nemo-instruct-2407 (Wiro) openai/gpt-5-2 (Wiro) openai/gpt-5-4 (Wiro) openai/gpt-5-4-mini (Wiro) openai/gpt-5-4-nano (Wiro) openai/gpt-5-5 (Wiro) openai/gpt-5-6-luna (Wiro) openai/gpt-5-6-sol (Wiro) openai/gpt-5-6-terra (Wiro) openai/gpt-5-mini (Wiro) openai/gpt-5-nano (Wiro) qwen/qwen2-5-0-5b-instruct (Wiro) qwen/qwen2-5-1-5b-instruct (Wiro) qwen/qwen2-5-14b-instruct (Wiro) qwen/qwen2-5-32b-instruct (Wiro) qwen/qwen2-5-3b-instruct (Wiro) qwen/qwen2-5-7b-instruct (Wiro) qwen/qwen2-5-coder-32b-instruct (Wiro) qwen/qwen3-30b-a3b (Wiro) qwen/qwen3-30b-a3b-thinking-2507 (Wiro) qwen/qwen3-32b (Wiro) qwen/qwen3-5-27b (Wiro) qwen/qwen3-5-27b-heretic (Wiro) qwen/qwen3-5-4b (Wiro) qwen/qwen3-5-4b-heretic (Wiro) qwen/qwen3-5-9b (Wiro) qwen/qwen3-5-9b-heretic (Wiro) qwen/qwen3-6-27b (Wiro) qwen/qwen3-8-27b (Wiro) qwen/qwen3-8-27b-obliterated (Wiro) qwen/qwen3-8-27b-uncensored (Wiro) qwen/qwen3-coder-30b-a3b-instruct (Wiro) xai/grok-4-1-fast (Wiro) xai/grok-4-20 (Wiro) xai/grok-4-5 ``` Tool calling works through the gateway, so the agent can run its own tools across as many turns as the task needs. ### OpenAI SDK The official `openai` package talks to the gateway unchanged — only `baseURL` moves. Everything below was run against every model the gateway serves. 1. **Install** — `npm i openai`. The Python package works the same way with `base_url`. 2. **Point the client at the gateway** — `baseURL` is `https://llm.wiro.ai/v1`. `apiKey` takes `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project, `YOUR_API_KEY` alone for API Key Only. 3. **Name a model** — any ID from the list below. The `(Wiro)` label is optional and stripped on arrival, so `openai/gpt-5-6-sol` works just as well. 4. **Give reasoning models room** — `max_completion_tokens` covers reasoning as well as the answer. Too small a budget returns `finish_reason: "length"` with empty content — not an error, just a spent budget. 512 is a safe floor, more for long answers. ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: "YOUR_API_KEY:YOUR_API_SECRET", baseURL: "https://llm.wiro.ai/v1", }); const completion = await client.chat.completions.create({ model: "(Wiro) openai/gpt-5-6-sol", max_completion_tokens: 512, messages: [{ role: "user", content: "Hello" }], }); console.log(completion.choices[0].message.content); ``` `client.models.list()` returns the same catalog, and `client.responses.create()` reaches the Responses route. Streaming works on both. Tool calling runs the whole loop — the call, the result, and further turns after it: ```javascript const tools = [{ type: "function", function: { name: "get_weather", description: "Get weather", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"], }, }, }]; const messages = [{ role: "user", content: "Weather in Istanbul?" }]; const first = await client.chat.completions.create({ model: "(Wiro) openai/gpt-5-6-sol", max_completion_tokens: 512, tools, messages, }); const call = first.choices[0].message.tool_calls[0]; messages.push(first.choices[0].message, { role: "tool", tool_call_id: call.id, content: JSON.stringify({ temperature_c: 21 }), }); const second = await client.chat.completions.create({ model: "(Wiro) openai/gpt-5-6-sol", max_completion_tokens: 512, tools, messages, }); ``` Every model the gateway serves today. The same set is in the catalog and behind `GET /v1/models`. ``` (Wiro) bytedance/seed-v2-1-turbo (Wiro) bytedance/seed-v2-lite (Wiro) bytedance/seed-v2-mini (Wiro) bytedance/seed-v2-pro (Wiro) claude/fable-5 (Wiro) claude/opus-5 (Wiro) claude/sonnet-5 (Wiro) meta-llama/llama-3-2-3b-instruct (Wiro) mistralai/mistral-nemo-instruct-2407 (Wiro) openai/gpt-5-2 (Wiro) openai/gpt-5-4 (Wiro) openai/gpt-5-4-mini (Wiro) openai/gpt-5-4-nano (Wiro) openai/gpt-5-5 (Wiro) openai/gpt-5-6-luna (Wiro) openai/gpt-5-6-sol (Wiro) openai/gpt-5-6-terra (Wiro) openai/gpt-5-mini (Wiro) openai/gpt-5-nano (Wiro) qwen/qwen2-5-0-5b-instruct (Wiro) qwen/qwen2-5-1-5b-instruct (Wiro) qwen/qwen2-5-14b-instruct (Wiro) qwen/qwen2-5-32b-instruct (Wiro) qwen/qwen2-5-3b-instruct (Wiro) qwen/qwen2-5-7b-instruct (Wiro) qwen/qwen2-5-coder-32b-instruct (Wiro) qwen/qwen3-30b-a3b (Wiro) qwen/qwen3-30b-a3b-thinking-2507 (Wiro) qwen/qwen3-32b (Wiro) qwen/qwen3-5-27b (Wiro) qwen/qwen3-5-27b-heretic (Wiro) qwen/qwen3-5-4b (Wiro) qwen/qwen3-5-4b-heretic (Wiro) qwen/qwen3-5-9b (Wiro) qwen/qwen3-5-9b-heretic (Wiro) qwen/qwen3-6-27b (Wiro) qwen/qwen3-8-27b (Wiro) qwen/qwen3-8-27b-obliterated (Wiro) qwen/qwen3-8-27b-uncensored (Wiro) qwen/qwen3-coder-30b-a3b-instruct (Wiro) xai/grok-4-1-fast (Wiro) xai/grok-4-20 (Wiro) xai/grok-4-5 ``` ## Discover models and capabilities ### **GET** /v1/models ```bash curl "https://llm.wiro.ai/v1/models?search=openai%2Fgpt-5-6-sol&limit=1" \ -H "Authorization: Bearer YOUR_API_KEY" ``` `GET /v1/chat/models` returns the same authenticated, team-filtered list. This gateway catalog is separate from the full website/model catalog at `POST /v1/Tool/List`. ```json { "object": "list", "data": [ { "id": "openai/gpt-5-6-sol", "canonical_slug": "openai/gpt-5-6-sol", "name": "GPT 5.6 Sol by OpenAI (Vision)", "description": "GPT 5.6 Sol is OpenAI's flagship reasoning and vision model for agentic coding, research, and image Q&A in one chat.", "context_length": 1050000, "architecture": { "modality": "text+image->text", "input_modalities": ["text", "image"], "output_modalities": ["text"] }, "pricing": { "prompt": "0.000005", "completion": "0.00003" }, "top_provider": { "context_length": 1050000, "max_completion_tokens": null }, "supported_parameters": [ "include_reasoning", "parallel_tool_calls", "reasoning", "response_format", "structured_outputs", "tool_choice", "tools" ], "default_parameters": {}, "object": "model", "type": "model", "created": null, "created_at": null, "owned_by": "openai", "display_name": "GPT 5.6 Sol by OpenAI (Vision)", "max_input_tokens": 1050000, "max_tokens": null, "capabilities": { "endpoints": ["anthropic", "chat", "cursor", "responses"], "input_modalities": ["text", "image"], "output": ["text", "reasoning", "function_calls", "custom_tool_calls"], "function_tools": true, "custom_tools": true, "structured_outputs": true, "structured_output_modes": ["json_object", "json_schema"], "strict_json_schema": true, "generation_controls": ["reasoning_effort"] }, "x_wiro": { "pricing_source": { "name": "wiro-pricing-catalog", "schema_version": 1, "revision": "3a2430b5188fcaf69b5d7af1cb40d0a59062cc37", "sha256": "f74c5d378dfd8b28360b2464dc2590711550df501a7c6c7f9b673db105f7798f", "source_model": "openai/gpt-5.6-sol", "basis": "local-rate" }, "pricing_tiers": null } } ], "total_count": 1, "links": { "next": null }, "has_more": false, "first_id": "openai/gpt-5-6-sol", "last_id": "openai/gpt-5-6-sol" } ``` The example is intentionally explicit about nullable values that have a Wiro source. Context, modalities, and pricing come from the audited local gateway catalog; live Run parameters and capability flags come from the selected model's current `Tool/Detail` contract. Fields without a Wiro source are omitted instead of being copied from OpenRouter with `null` placeholders. `pricing.prompt` and `pricing.completion` are USD-per-token strings. Models with tiered rates expose those exact bands in `x_wiro.pricing_tiers`. Pricing and context fields can be `null` for catalog entries without audited local metadata. The list accepts: - `search` (at most 256 bytes), `order=asc|desc`, and `limit=1..1000` - `after`/`after_id` or `before`/`before_id` cursors The default order is ascending and the default limit is `1000`. `total_count` counts the filtered catalog. `links.next` is a relative next-page URL or `null`. The discovery list contains only team-allowed models verified with the `llm-tool-call` category. Other LLMs are not advertised by this endpoint; when you already know an exact `owner/model` ID, you can still submit it to a compatible Direct LLM route or use the normal Run API. Before each request: 1. Confirm `anthropic`, `chat`, `cursor`, or `responses` in `capabilities.endpoints`. 2. Use `supported_parameters` to gate optional public request controls. Required protocol fields such as `model`, `input`, `messages`, or Anthropic `max_tokens` are not repeated there. 3. Check `capabilities.input_modalities` for canonical `text`, `image`, or `document`. OpenRouter-style `architecture.input_modalities` represents documents as `file`. 4. Check `capabilities.output`, `function_tools`, `custom_tools`, `structured_outputs`, `structured_output_modes`, `strict_json_schema`, and `generation_controls`. 5. Treat `context_length`, `max_input_tokens`, `max_tokens`, pricing, and usage as model-specific. A nullable field is not permission to infer a value from another model. Every discoverable model currently publishes the same four endpoint labels. Capability differences are expressed through modalities, supported parameters, tool flags, structured-output flags, reasoning controls, and limits. ### **GET** /v1/models/{owner}/{model} ```bash curl "https://llm.wiro.ai/v1/models/openai/gpt-5-6-sol" \ -H "Authorization: Bearer YOUR_API_KEY" ``` The detail route returns the same complete model object as the matching list entry. Query parameters are rejected on detail routes. An unavailable, non-gateway, or team-blocked model returns `model_not_found` without exposing private catalog state. ## Generic finite task completion `POST /v1/Run/{owner}/{project}/sync` lives on `https://api.wiro.ai/v1`, not this gateway. See [Run a Model](/docs/run-a-model) for JSON wait, SSE, and timeout recovery. Direct LLM clients should use Chat, Responses, or Messages below. ## Usage contract When the upstream model reports usage, Wiro returns normalized counters in the selected protocol's native shape: - Chat uses `prompt_tokens`, optional `prompt_tokens_details`, `completion_tokens`, optional `completion_tokens_details`, and `total_tokens`. - Responses uses `input_tokens`, `input_tokens_details`, `output_tokens`, `output_tokens_details`, and `total_tokens`. - Anthropic Messages uses cache-exclusive `input_tokens`, `output_tokens`, optional `cache_read_input_tokens`, optional `cache_creation_input_tokens`, and optional five-minute/one-hour `cache_creation` detail. For Chat and Responses, input/prompt totals are cache-inclusive. Their input detail objects (optional on Chat, always present on Responses) can identify `cached_tokens`, `cache_write_tokens`, `cache_write_5m_tokens`, `cache_write_1h_tokens`, and reported text/audio/image/video tokens. Output/completion totals are reasoning-inclusive; the corresponding output detail can identify `reasoning_tokens`, reported text/audio/image/video tokens, and accepted/rejected prediction tokens. Cache TTL values are subsets of `cache_write_tokens`, and all detail counters are already included in their parent total. Never add them to `total_tokens`. There is intentionally no `cached_output_tokens` field. Provider prompt caches reuse input context; they do not report previously generated output as cached output usage. Optional `server_tool_use` values count requests, not tokens. Providers omit unsupported counters, so clients must not require every detail key. Chat and Responses expose Wiro's billed amount as `usage.cost`; Anthropic exposes it as `usage.cost_usd`. The asynchronous Run/Task contract keeps the billed amount at task-level `totalcost`. ## Files compatibility The gateway's Files routes are a compatibility view over the authenticated Wiro project's existing upload folder. Uploads, lists, retrieval, and deletion use the same storage as `File/Upload`, `File/List`, and `File/Delete` on `api.wiro.ai`; the gateway does not create a second file database. The supported routes are: - `POST /v1/files` uploads one multipart field named `file`. - `GET /v1/files` lists files in the authenticated project's upload folder. - `GET /v1/files/{file_id}` returns file metadata. - `GET /v1/files/{file_id}/content` checks ownership, then redirects with `307` to the validated public CDN URL. - `DELETE /v1/files/{file_id}` deletes the project file. File IDs use `file-...` form and are valid only inside the project whose credentials created or listed them. A missing, deleted, or foreign ID returns `file_not_found`; the gateway never falls back to another project. ### OpenAI Files OpenAI clients use Bearer authentication. The only supported OpenAI purpose is `assistants`: ```javascript import fs from 'node:fs'; import OpenAI from 'openai'; const client = new OpenAI({ apiKey: process.env.WIRO_BEARER_CREDENTIAL, baseURL: 'https://llm.wiro.ai/v1', }); const file = await client.files.create({ file: fs.createReadStream('input.png'), purpose: 'assistants', }); const page = await client.files.list({ limit: 20, order: 'desc' }); const metadata = await client.files.retrieve(file.id); const content = await client.files.content(file.id); await client.files.delete(file.id); ``` OpenAI list queries accept `after`/`after_id`, `before`/`before_id`, `limit=1..1000`, `order=asc|desc`, and `purpose=assistants`. The default limit is `20` and the default order is descending. File metadata uses OpenAI's `object: "file"`, `bytes`, Unix `created_at`, `filename`, `purpose`, `status`, and `status_details` fields. ### Anthropic Files Anthropic SDKs use origin `https://llm.wiro.ai` and send the required `anthropic-version: 2023-06-01` header; that header selects the Anthropic file metadata shape: ```javascript import fs from 'node:fs'; import Anthropic from '@anthropic-ai/sdk'; const client = new Anthropic({ baseURL: 'https://llm.wiro.ai', apiKey: process.env.WIRO_API_KEY, }); const file = await client.files.upload({ file: fs.createReadStream('context.pdf'), }); const page = await client.files.list({ limit: 20 }); const metadata = await client.files.retrieveMetadata(file.id); const content = await client.files.download(file.id); await client.files.delete(file.id); ``` Anthropic list queries accept `page`, legacy `after_id`/`before_id`, and `limit=1..1000`. The response includes `next_page`, `has_more`, `first_id`, and `last_id`. Metadata uses ISO `created_at`, `filename`, `mime_type`, `size_bytes`, `type: "file"`, `downloadable`, and `expires_at`. Wiro-backed files do not expire, so `expires_at` is `null`. ### Limits and model input Gateway uploads are limited to 50 MiB per file. Empty files, multiple file parts, unknown multipart fields, and filenames over 1024 bytes are rejected. ZIP uploads are rejected because Wiro's standard File API extracts archives, which cannot represent the one-upload/one-file contract expected by these SDKs. The compatibility catalog reads at most 10,000 project files; that safety bound is not a storage quota. The original Wiro File API has its own documented limits. Use the returned ID only where the selected protocol accepts file references. For example, OpenAI Responses image input can use `{ "type": "input_image", "file_id": "file-..." }`, while Anthropic document input uses `{ "type": "file", "file_id": "file-..." }` inside its `source` object. Before Run, the gateway verifies project ownership and MIME type, then replaces the Wiro file ID with its existing public URL. Image references accept GIF, JPEG, PNG, or WebP; document references accept PDF or plain text. Invalid MIME combinations return `invalid_media_type` before model execution. Public HTTPS URLs and validated data/base64 content remain available when the model advertises the corresponding modality. A Wiro `file-...` ID is not a provider file ID and is never forwarded to OpenAI, Anthropic, xAI, or another provider. ## OpenAI Chat Completions ### **POST** /v1/chat/completions Use a model that includes `chat` in `capabilities.endpoints`. ```bash curl -X POST "https://llm.wiro.ai/v1/chat/completions" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5-6-sol", "messages": [ { "role": "system", "content": "You are a concise assistant." }, { "role": "user", "content": "What is SSE?" } ], "stream": false }' ``` The endpoint accepts `system`, `developer`, `user`, and `assistant` messages, plus `tool` results for preceding assistant tool calls. Each request is client-history: send the required conversation history in `messages`. Calls do not create a saved Wiro Chat session. Text is always available. OpenAI-style image and document content parts are accepted only when the selected model advertises those input modalities. Gateway output is text; this route is not an image, audio, or video generation API. Remote media must use a public HTTPS URL or a validated data/base64 source; supported Wiro gateway file IDs may also be used. Private-network and non-HTTP fetch targets are rejected. Provider-native file IDs are not accepted as Wiro file IDs. Generation fields such as `temperature`, `top_p`, `max_tokens`, `max_completion_tokens`, penalties, `seed`, `stop`, `reasoning`, and `response_format` are capability-gated. `n` must be `1`, `logprobs` must be `false`, and `stop` accepts at most four non-empty values without semicolons. Use `capabilities.structured_outputs` before requesting a non-text `response_format`. Standard OpenAI function tools use `tools`, `tool_choice`, and `parallel_tool_calls`. Returned function calls use `{ "id": "...", "type": "function", "function": { "name": "...", "arguments": "..." } }` entries in `message.tool_calls`. Execute calls only when `finish_reason` is `tool_calls`; a `length` or `content_filter` turn can contain partial tool data that must not be executed. Preserve the returned assistant message unchanged, then append matching `role: "tool"` results with `tool_call_id`. Wiro does not execute tools on the caller's behalf. When the model advertises `custom_tools`, Chat also accepts the nested custom tool shape: ```json { "type": "custom", "custom": { "name": "airport_code", "format": { "type": "grammar", "syntax": "regex", "definition": "[A-Z]{3}" } } } ``` Use a matching nested custom `tool_choice`. Returned custom calls use `{ "id": "...", "type": "custom", "custom": { "name": "...", "input": "..." } }` entries in `message.tool_calls`. Execute them client-side and append `role: "tool"` results with the same `tool_call_id`, just as for function calls. Reasoning is model-specific. Public model reasoning can appear in `message.reasoning` and `message.reasoning_details`; use the advertised reasoning controls and supported efforts. Tool/reasoning combinations are also model-specific, so do not infer support from another model. Final responses use `chat.completion`. Native `usage` is conditional: a successful completion is still returned when usage counters remain unavailable. For streaming, set body-level `"stream": true`. Text arrives in `choices[0].delta.content`, reasoning in `delta.reasoning` and `reasoning_details`, and function calls in `delta.tool_calls`. Concatenate tool argument fragments by call `index`. The stream terminates with: ```text data: [DONE] ``` With `stream_options.include_usage: true`, Wiro emits a final empty-choices usage chunk before `[DONE]` only when native counters are available. If a failure happens after Chat SSE has started, Wiro emits an OpenAI error object in a `data:` frame, then `data: [DONE]`, and closes the connection. Once SSE headers have been sent, the original HTTP status cannot communicate that later failure. The official `openai` package reaches this route with nothing changed but `baseURL`; the full setup, with tool calling and the model list, is under OpenAI SDK above. ## OpenAI Responses ### **POST** /v1/responses Use a model that includes `responses` in `capabilities.endpoints`. `input` can be a string or Responses items; `instructions`, tools, text formatting, multimodal input, reasoning, and generation controls are capability-gated. Remote media follows the same public-HTTPS/data/base64/Wiro-file-ID restrictions as Chat. ```bash curl -X POST "https://llm.wiro.ai/v1/responses" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5-6-sol", "instructions": "Answer in one paragraph.", "input": "Explain server-sent events.", "store": true, "stream": false }' ``` Final responses use the official `response` object with an opaque ID such as `resp_...` and an `output` item array. Items can include messages, public reasoning, function calls, and custom tool calls. Execute only tool items with `status: "completed"` from a completed response; never execute tool items from an incomplete response. Native `usage` can be absent. ### Stored response continuation `store` defaults to `true` for OpenAI compatibility. Set `store: false` for a one-off response that will not be used as `previous_response_id`. Use the ID of a stored `completed` or `incomplete` response as `previous_response_id`. Never execute partial tool calls from an incomplete parent: ```json { "model": "openai/gpt-5-6-sol", "previous_response_id": "resp_2221", "input": [{ "type": "message", "role": "user", "content": [{ "type": "input_text", "text": "Give me one concrete example." }] }], "store": true } ``` `previous_response_id` is the public continuation field for this gateway route. Do not send `previousTaskToken` to `/v1/responses`. Direct Run clients use `previousTaskToken` as described in [Run a Model](/docs/run-a-model). `session_id` is not accepted by Responses; use stored `previous_response_id` continuation instead. A stored response can be continued for up to 30 days by the same authenticated user, project, team, and model. Expired, unstored, unavailable, or mismatched responses return `response_not_found`; failed and cancelled responses are not continuable. Chains are limited to 32 responses, and the conversation plus requested output must fit the selected model's context window. `store` is not accepted on `/v1/chat/completions`; these persistence rules apply specifically to `/v1/responses`. ### Stored response retrieval and deletion Retrieve a stored response with `GET /v1/responses/{response_id}`. Delete it with `DELETE /v1/responses/{response_id}`; a successful deletion returns HTTP `204` with an empty body. `POST /v1/responses/{response_id}/cancel` is not supported and returns `response_cancel_not_supported`. ### Function and custom grammar tools Responses function tools use the standard top-level `name`, `description`, `parameters`, and optional `strict` shape. When a function call is returned, submit its result in the next request as a `function_call_output` input item with the same `call_id`. Models with `capabilities.custom_tools: true` also accept free-form or grammar-constrained tools: ```json { "type": "custom", "name": "sql_query", "description": "Produce one read-only SQL query.", "format": { "type": "grammar", "syntax": "regex", "definition": "^SELECT .+;$" } } ``` Grammar `syntax` is `regex` or `lark`. Use `format: { "type": "text" }` for a free-form custom tool. When a `custom_tool_call` is returned, submit its result in the next request as a `custom_tool_call_output` input item with the matching `call_id`. `function_call_output` and `custom_tool_call_output` are next-request input types, distinct from the returned `function_call` and `custom_tool_call` output items. Tool execution always remains in your application. ### Responses SSE Set `"stream": true` for official named Responses events, including `response.created`, `response.in_progress`, output-item/content-part events, text and reasoning deltas, `response.function_call_arguments.delta`/`.done`, and `response.custom_tool_call_input.delta`/`.done`. Every `response.*` payload carries a monotonically increasing `sequence_number`, starting at `0`. Ignore `: keep-alive` comments. The terminal event is `response.completed` or `response.incomplete`. A midstream failure emits `response.failed`. Wiro then emits `data: [DONE]` and closes the Responses stream; the official SDK consumes that sentinel. ## Anthropic Messages ### **POST** /v1/messages Use a model that includes `anthropic` in `capabilities.endpoints`. Anthropic SDKs normally send: - `x-api-key: YOUR_API_KEY` for an API Key Only project, or `x-api-key: YOUR_API_KEY:YOUR_API_SECRET` for a Signature project - `anthropic-version: 2023-06-01` - optional `anthropic-beta` labels, comma-separated or repeated `x-api-key` and `anthropic-version` are both required. An optional Bearer header is accepted only when it resolves to the same complete credential. Beta headers identify requested compatibility features; they do not override model capabilities. `?beta=true` is also accepted as a beta-route marker. ```bash curl -X POST "https://llm.wiro.ai/v1/messages" \ -H "x-api-key: YOUR_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{ "model": "claude/sonnet-5", "max_tokens": 1024, "system": "Be concise.", "messages": [{ "role": "user", "content": "What is SSE?" }], "stream": false }' ``` `max_tokens` is required and positive. Messages use Anthropic `user` and `assistant` roles; system instructions belong in the top-level `system` field. Content can include `text`, model-advertised `image` or `document` input, `tool_use`, `tool_result`, and supported thinking blocks. Remote media uses validated public HTTPS, data/base64, or Wiro gateway file references. Messages is client-history: resend the prior `user`/`assistant` content and tool blocks required for each turn. It does not use Responses storage or `previous_response_id`. Anthropic function tools use `name`, `description`, and `input_schema`. `tool_choice` supports Anthropic `auto`, `any`, `tool`, and `none` choices. Anthropic's optional `type: "custom"` still denotes an ordinary client function tool with `input_schema`; it is not OpenAI's free-form custom grammar shape. Tool calls are returned as `tool_use`; your application executes them and sends `tool_result`. Provider-hosted tools, hosted containers, and remote MCP servers are rejected by this release. Execute `tool_use` blocks only when `stop_reason` is `tool_use`; do not execute tool data from `max_tokens` or `refusal` turns. Thinking controls use: ```json { "thinking": { "type": "enabled", "budget_tokens": 2048 } } ``` `type` can be `enabled`, `adaptive`, or `disabled`. Check `supported_parameters` for public reasoning controls and `capabilities.output` for reasoning output. Do not fabricate or alter signature or redacted-thinking blocks returned by the model. Final responses use the Anthropic `message` object with opaque `msg_...` request IDs; they are not Wiro task IDs. Public output content contains `text` and `tool_use` blocks. Internal model reasoning is not projected as Anthropic `thinking` or `redacted_thinking` output. Usage is included only when native counters are available. For streaming, set `"stream": true`. The SSE sequence uses Anthropic events such as `message_start`, `content_block_start`, `content_block_delta`, `content_block_stop`, `message_delta`, and `message_stop`, with `ping` keepalives. A midstream failure emits Anthropic's native `error` event and closes the connection. A successful stream ends with `message_stop` and connection close; it does not emit `[DONE]`. Text blocks use `text_delta`, and `tool_use` blocks use `input_json_delta`. The gateway does not emit `thinking_delta`. ### Anthropic SDKs and Claude Code The official Node and Python SDKs must use origin `https://llm.wiro.ai`, not the OpenAI `/v1` base: ```javascript import Anthropic from '@anthropic-ai/sdk'; const client = new Anthropic({ baseURL: 'https://llm.wiro.ai', apiKey: process.env.WIRO_API_KEY, }); const message = await client.messages.create({ model: 'claude/sonnet-5', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello' }], }); ``` `WIRO_API_KEY` is `YOUR_API_KEY` for an API Key Only project or `YOUR_API_KEY:YOUR_API_SECRET` for a Signature project. Python uses the equivalent `base_url` and `api_key` options. For Claude Code with a Signature project: ```bash ANTHROPIC_BASE_URL="https://llm.wiro.ai" \ ANTHROPIC_API_KEY="YOUR_API_KEY:YOUR_API_SECRET" \ ANTHROPIC_MODEL="claude/sonnet-5" \ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 \ claude ``` For an API Key Only project, set `ANTHROPIC_API_KEY` to `YOUR_API_KEY`. Model discovery uses Wiro's authenticated `/v1/models` route with `limit=1000`. Claude Code adds only discovered IDs containing `claude` or `anthropic` to its picker; set `ANTHROPIC_MODEL` explicitly for any other compatible ID. Native Messages, client tools, streaming, beta headers, thinking continuation, and token-counting calls remain subject to the selected model's advertised capabilities. Provider-hosted tools remain outside this release even when a client beta can describe them. ### **POST** /v1/messages/count_tokens ```bash curl -X POST "https://llm.wiro.ai/v1/messages/count_tokens" \ -H "x-api-key: YOUR_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{ "model": "claude/sonnet-5", "system": "Be concise.", "messages": [{ "role": "user", "content": "Hello" }] }' ``` ```json { "input_tokens": 28 } ``` This is a local preflight estimate over the submitted Anthropic payload, not authoritative billing or provider-native usage. The estimate endpoint is part of the Anthropic compatibility route; the model catalog does not label it as native token counting. ## Generation metadata ### **GET** /v1/generation?id= Look up terminal metadata using the exact public ID emitted by the protocol route: the response `id` for Chat or Responses, and the `request-id` response header for Messages. The equivalent path form is `GET /v1/generation/{public-id}`. ```bash curl "https://llm.wiro.ai/v1/generation?id=resp_01HZX7Y8Q9" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```json { "data": { "id": "resp_01HZX7Y8Q9", "model": "openai/gpt-5-6-sol", "protocol": "responses", "status": "completed", "created_at": "2026-08-21T12:00:00.000Z", "completed_at": "2026-08-21T12:00:03.000Z", "usage": { "input_tokens": 20, "input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 }, "output_tokens": 12, "output_tokens_details": { "reasoning_tokens": 4 }, "total_tokens": 32 }, "upstream_metered_cost": 0.0021, "upstream_elapsed_seconds": 3 } } ``` `protocol` is `chat`, `cursor`, `messages`, or `responses`. `status` is `completed`, `failed`, or `incomplete`. `created_at` and `completed_at` are ISO 8601 strings. When token counters are available, `usage` follows the selected protocol: - `responses`: `input_tokens`, `input_tokens_details` with `cached_tokens` and `cache_write_tokens`, `output_tokens`, `output_tokens_details.reasoning_tokens`, and `total_tokens`. - `chat` and `cursor`: `prompt_tokens`, optional `prompt_tokens_details`, `completion_tokens`, optional `completion_tokens_details`, and `total_tokens`. - `messages`: `input_tokens`, `output_tokens`, and nullable `cache_creation_input_tokens` and `cache_read_input_tokens`. This shape does not add `total_tokens`. `usage` is `null` when no valid counters were recorded. `upstream_metered_cost` and `upstream_elapsed_seconds` are finite upstream task counters when available, otherwise `null`; cost is not duplicated inside metadata `usage`. Only the owning authenticated project can read a generation. Missing, expired, and foreign IDs all return `generation_not_found`. This endpoint is terminal observability metadata, not task output: it does not return a Wiro task ID, generated content, or continuation state. Read generated content from the original protocol JSON or SSE response. ## Deliberate boundaries The Direct LLM Gateway does not currently provide: - provider-hosted web search, file search, code interpreter, computer-use, or remote MCP tool execution; Wiro returns tool calls for your client to run - a native Gemini `generateContent` protocol - embeddings or reranking - automatic model routing or fallback routing - bring-your-own-provider-key (BYOK) execution - dedicated image, audio, or video generation through the Chat, Responses, or Messages routes - the legacy `/v1/completions` endpoint Use Wiro's normal [Run or /sync APIs](/docs/run-a-model) for supported finite media-generation models. Image and document **input** to an LLM is distinct from media generation and remains available when the selected model advertises it. Remote media fetches must use public HTTPS; validated data/base64 sources and Wiro gateway file IDs are accepted only where the selected protocol/model supports them. Provider-native file IDs are not interchangeable. ## Errors Error responses preserve the selected public protocol: - Chat, Responses, model, and generation errors use the OpenAI-style `{ "error": { "message", "type", "param", "code" } }` shape. - Anthropic routes use `{ "type": "error", "error": { "type", "message" } }`. Generic `/sync` waits on `api.wiro.ai` use the normal Wiro `result`/`errors` envelope. After a stream starts, inspect SSE events rather than the HTTP status: Chat emits an error `data:` frame followed by `[DONE]`, Responses emits `response.failed`, and Anthropic emits `error`. Common codes include `invalid_model`, `model_not_found`, `unsupported_model_type`, `unsupported_parameter`, `response_not_found`, `response_chain_too_deep`, `invalid_generation_id`, `generation_not_found`, `completion_timeout`, `model_execution_failed`, `task_cancelled`, and `unsupported_capability`. Files routes can also return `file_not_found`, `file_too_large`, `invalid_multipart`, `missing_file`, `invalid_filename`, `invalid_media_type`, `invalid_pagination`, `invalid_query`, `file_catalog_too_large`, `file_content_unavailable`, `file_api_unavailable`, and `invalid_file_response`. Gateway-origin and upstream execution failures use HTTP `500` with their specific error code; the public protocol does not return HTTP `502`. Responses under `/v1` include `ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset`, `request-id`, and `x-request-id`. A rate-limit response also includes `retry-after`. Protocol execution POST bodies must be JSON and are limited to 16 MiB. Files uploads are multipart and use the separate 50 MiB file limit. Unsupported fields that could change execution fail instead of being silently accepted. A model task can also fail after HTTP validation; preserve the protocol error and status rather than treating every `2xx` task creation as a successful model answer.