This section as Markdown: View Download

Direct LLM Gateway

Use Wiro LLMs through OpenAI Chat Completions, OpenAI Responses, and Anthropic Messages.

Base URLs: OpenAI-compatible clients use 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, and the original Wiro File API remain on https://api.wiro.ai/v1; WebSocket remains on wss://socket.wiro.ai/v1. The Direct LLM Gateway also provides compatible /v1/files routes backed by the same authenticated project storage.

The gateway uses the same Wiro task, project-access, team-policy, and billing system as the Run API. Discover a model before calling it: supported endpoints, parameters, modalities, tools, reasoning, and limits are model-specific.

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. It is not a Direct LLM protocol.
  • GET /v1/models lists verified llm-tool-call models. The list is not filtered by a team's Model Access settings; those apply when a model runs. GET /v1/chat/models is equivalent.
  • GET /v1/models/{owner}/{model} returns one model's 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/SSE.
  • POST /v1/responses uses OpenAI Responses JSON and named SSE.
  • POST /v1/messages uses Anthropic Messages JSON/SSE.
  • POST /v1/messages/count_tokens estimates Anthropic input tokens before a run.
  • GET /v1/generation?id=... returns authorized generation metadata.
Separate methods: /sync is a newly added generic wait method on api.wiro.ai; it does not replace asynchronous Run. Direct LLM routes remain separate on llm.wiro.ai.

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. Compatible clients should use project Bearer authentication:

  • API Key Only: Authorization: Bearer YOUR_API_KEY
  • Signature: Authorization: Bearer YOUR_API_KEY:YOUR_API_SECRET, or the base64 encoding of that pair

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 Bearer and x-api-key are sent together, they must contain the same complete project credential. Mismatched keys or secrets and malformed credentials return 401 without cross-project fallback.

Never expose a project key or secret in browser or mobile application code.

Anthropic Messages takes the credential in x-api-key, where Anthropic SDKs send it, or in an Authorization: Bearer header. 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. Use a project Bearer credential: API Key Only projects send YOUR_API_KEY; Signature projects send YOUR_API_KEY:YOUR_API_SECRET. Generic finite-model waits stay on Run a Model /sync.

Every client below needs the exact lowercase owner/model ID. Two places give it to you:

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 carrying a Wiro prefix, with the Add Custom Model button below them.
  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 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/fable-5-1
(Wiro) claude/opus-5
(Wiro) claude/opus-5-5
(Wiro) claude/sonnet-5
(Wiro) deepseek/v4-flash
(Wiro) deepseek/v4-pro
(Wiro) glm/5-2
(Wiro) google/gemini-2-5-flash
(Wiro) google/gemini-3-5-flash
(Wiro) google/gemini-3-5-flash-lite
(Wiro) google/gemini-3-6-flash
(Wiro) google/gemini-3-7-flash
(Wiro) google/gemini-3-8-flash
(Wiro) google/gemini-3-flash
(Wiro) google/gemini-3-pro
(Wiro) google/gemini-4-argon
(Wiro) meta-llama/llama-3-2-3b-instruct
(Wiro) mistralai/mistral-7b-instruct-v0-3
(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) openai/gpt-6-1-sol
(Wiro) openai/gpt-6-astra
(Wiro) openai/gpt-6-luna
(Wiro) openai/gpt-6-sol
(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/qwen2-5-coder-7b-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) 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: 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:

    {
      "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 — <project>/.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:

    {
      "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 adds gateway models to the /model picker, but Claude Code keeps only IDs containing claude or anthropic, so the picker gains the claude/ models alone; open any other model with ANTHROPIC_MODEL or --model. 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 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.
  8. Switching models Open on another model for one session:
    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:
    {
      "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"
      }
    }

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 prompt the GPU it runs on actually has room for, measured on the fleet — usually well below the window its checkpoint was built for, and below what its model card advertises. Set the variable to the number the catalog reports, not the card’s.

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/fable-5-1                      1000000
(Wiro) claude/opus-5                         1000000
(Wiro) claude/opus-5-5                       1000000
(Wiro) claude/sonnet-5                       1000000
(Wiro) deepseek/v4-flash                     1048576
(Wiro) deepseek/v4-pro                       1048576
(Wiro) glm/5-2                               1048576
(Wiro) google/gemini-2-5-flash               1048576
(Wiro) google/gemini-3-5-flash               1048576
(Wiro) google/gemini-3-5-flash-lite          1048576
(Wiro) google/gemini-3-6-flash               1048576
(Wiro) google/gemini-3-7-flash               1048576
(Wiro) google/gemini-3-8-flash               1048576
(Wiro) google/gemini-3-flash                 1048576
(Wiro) google/gemini-3-pro                   1048576
(Wiro) google/gemini-4-argon                 1048576
(Wiro) meta-llama/llama-3-2-3b-instruct        51200
(Wiro) mistralai/mistral-7b-instruct-v0-3      17408
(Wiro) mistralai/mistral-nemo-instruct-2407    35840
(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) openai/gpt-6-1-sol                    1050000
(Wiro) openai/gpt-6-astra                    1050000
(Wiro) openai/gpt-6-luna                     1050000
(Wiro) openai/gpt-6-sol                      1050000
(Wiro) qwen/qwen2-5-0-5b-instruct              32768
(Wiro) qwen/qwen2-5-1-5b-instruct              32768
(Wiro) qwen/qwen2-5-14b-instruct               29696
(Wiro) qwen/qwen2-5-32b-instruct               17408
(Wiro) qwen/qwen2-5-3b-instruct                32768
(Wiro) qwen/qwen2-5-7b-instruct                32768
(Wiro) qwen/qwen2-5-coder-32b-instruct         17408
(Wiro) qwen/qwen2-5-coder-7b-instruct          32768
(Wiro) qwen/qwen3-30b-a3b                       7168
(Wiro) qwen/qwen3-30b-a3b-thinking-2507         7168
(Wiro) qwen/qwen3-32b                          17408
(Wiro) qwen/qwen3-5-27b                        25600
(Wiro) qwen/qwen3-5-27b-heretic                25600
(Wiro) qwen/qwen3-5-4b                         72704
(Wiro) qwen/qwen3-5-4b-heretic                 72704
(Wiro) qwen/qwen3-5-9b                         25600
(Wiro) qwen/qwen3-5-9b-heretic                 25600
(Wiro) qwen/qwen3-6-27b                        25600
(Wiro) qwen/qwen3-8-27b                        25600
(Wiro) qwen/qwen3-8-27b-obliterated            25600
(Wiro) qwen/qwen3-8-27b-uncensored             25600
(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.

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 and behind GET /v1/models.
[
  {
    "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/fable-5-1",
        "name": "(Wiro) Fable 5.1",
        "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/opus-5-5",
        "name": "(Wiro) Opus 5.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": "deepseek/v4-flash",
        "name": "(Wiro) DeepSeek V4 Flash",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": false,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 384000
      },
      {
        "id": "deepseek/v4-pro",
        "name": "(Wiro) DeepSeek V4 Pro",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": false,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 384000
      },
      {
        "id": "glm/5-2",
        "name": "(Wiro) GLM 5.2",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": false,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 128000
      },
      {
        "id": "google/gemini-2-5-flash",
        "name": "(Wiro) Gemini 2.5 Flash",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-3-5-flash",
        "name": "(Wiro) Gemini 3.5 Flash",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-3-5-flash-lite",
        "name": "(Wiro) Gemini 3.5 Flash-Lite",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-3-6-flash",
        "name": "(Wiro) Gemini 3.6 Flash",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-3-7-flash",
        "name": "(Wiro) Gemini 3.7 Flash",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-3-8-flash",
        "name": "(Wiro) Gemini 3.8 Flash",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-3-flash",
        "name": "(Wiro) Gemini 3 Flash",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-3-pro",
        "name": "(Wiro) Gemini 3 Pro",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "id": "google/gemini-4-argon",
        "name": "(Wiro) Gemini 4 Argon",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1048576,
        "maxOutputTokens": 65536
      },
      {
        "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": 51200,
        "maxOutputTokens": 2048
      },
      {
        "id": "mistralai/mistral-7b-instruct-v0-3",
        "name": "(Wiro) Mistral 7B Instruct v0.3",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": false,
        "maxInputTokens": 17408,
        "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": 35840,
        "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": "openai/gpt-6-1-sol",
        "name": "(Wiro) GPT-6.1 Sol",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1050000,
        "maxOutputTokens": 128000
      },
      {
        "id": "openai/gpt-6-astra",
        "name": "(Wiro) GPT-6 Astra",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1050000,
        "maxOutputTokens": 128000
      },
      {
        "id": "openai/gpt-6-luna",
        "name": "(Wiro) GPT-6 Luna",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1050000,
        "maxOutputTokens": 128000
      },
      {
        "id": "openai/gpt-6-sol",
        "name": "(Wiro) GPT-6 Sol",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": true,
        "maxInputTokens": 1050000,
        "maxOutputTokens": 128000
      },
      {
        "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": 29696,
        "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": 17408,
        "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": 17408,
        "maxOutputTokens": 2048
      },
      {
        "id": "qwen/qwen2-5-coder-7b-instruct",
        "name": "(Wiro) Qwen2.5 Coder 7B 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": 7168,
        "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": 7168,
        "maxOutputTokens": 4096
      },
      {
        "id": "qwen/qwen3-32b",
        "name": "(Wiro) Qwen3 32B",
        "url": "https://llm.wiro.ai/v1/chat/completions",
        "toolCalling": true,
        "vision": false,
        "maxInputTokens": 17408,
        "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": 25600,
        "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": 25600,
        "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": 72704,
        "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": 72704,
        "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": 25600,
        "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": 25600,
        "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": 25600,
        "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": 25600,
        "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": 25600,
        "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": 25600,
        "maxOutputTokens": 16384
      },
      {
        "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
      }
    ]
  }
]
  • 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 the number is the input that deployment actually admits, measured on the running fleet — not the model family’s advertised maximum, and not the window of the checkpoint either. Qwen2.5 7B reports 32,768, which is what its shipped configuration sets although its card says 131,072; Qwen3.8 27B reports 25,600 even though the checkpoint it runs was built for 262,144, because that is roughly how much prompt fits beside the model on the GPUs that serve it. It is the lowest limit measured across them: most requests get somewhat more in and an occasional one slightly less, so leave some headroom. Send a Wiro-hosted model more than fits and the input is trimmed before the model sees it: the oldest history messages are dropped first, and if it still does not fit, the end of the latest user message is cut off. On a turn that returns tool results, nothing from the latest user message onward is cut; if dropping older history is not enough, the request is rejected with context_length_exceeded. Tool definitions are never trimmed, and neither is the system message, except on the two Mistral models: their chat format has no system role, so the system text travels with the oldest message and is dropped with it. Both count toward the limit. If even the question would be cut away entirely, the request is rejected with context_length_exceeded and nothing is charged. In a long prompt, put the question at the start, so a cut takes the material after it rather than the question itself.

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.

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, 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.

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/fable-5-1                      1000000      128000
(Wiro) claude/opus-5                         1000000      128000
(Wiro) claude/opus-5-5                       1000000      128000
(Wiro) claude/sonnet-5                       1000000      128000
(Wiro) deepseek/v4-flash                     1048576      384000
(Wiro) deepseek/v4-pro                       1048576      384000
(Wiro) glm/5-2                               1048576      128000
(Wiro) google/gemini-2-5-flash               1048576       65536
(Wiro) google/gemini-3-5-flash               1048576       65536
(Wiro) google/gemini-3-5-flash-lite          1048576       65536
(Wiro) google/gemini-3-6-flash               1048576       65536
(Wiro) google/gemini-3-7-flash               1048576       65536
(Wiro) google/gemini-3-8-flash               1048576       65536
(Wiro) google/gemini-3-flash                 1048576       65536
(Wiro) google/gemini-3-pro                   1048576       65536
(Wiro) google/gemini-4-argon                 1048576       65536
(Wiro) meta-llama/llama-3-2-3b-instruct        51200        2048
(Wiro) mistralai/mistral-7b-instruct-v0-3      17408        2048
(Wiro) mistralai/mistral-nemo-instruct-2407    35840        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) openai/gpt-6-1-sol                    1050000      128000
(Wiro) openai/gpt-6-astra                    1050000      128000
(Wiro) openai/gpt-6-luna                     1050000      128000
(Wiro) openai/gpt-6-sol                      1050000      128000
(Wiro) qwen/qwen2-5-0-5b-instruct              32768        2048
(Wiro) qwen/qwen2-5-1-5b-instruct              32768        2048
(Wiro) qwen/qwen2-5-14b-instruct               29696        2048
(Wiro) qwen/qwen2-5-32b-instruct               17408        2048
(Wiro) qwen/qwen2-5-3b-instruct                32768        2048
(Wiro) qwen/qwen2-5-7b-instruct                32768        2048
(Wiro) qwen/qwen2-5-coder-32b-instruct         17408        2048
(Wiro) qwen/qwen2-5-coder-7b-instruct          32768        2048
(Wiro) qwen/qwen3-30b-a3b                       7168        4096
(Wiro) qwen/qwen3-30b-a3b-thinking-2507         7168        4096
(Wiro) qwen/qwen3-32b                          17408        4096
(Wiro) qwen/qwen3-5-27b                        25600       16384
(Wiro) qwen/qwen3-5-27b-heretic                25600       16384
(Wiro) qwen/qwen3-5-4b                         72704       16384
(Wiro) qwen/qwen3-5-4b-heretic                 72704       16384
(Wiro) qwen/qwen3-5-9b                         25600       16384
(Wiro) qwen/qwen3-5-9b-heretic                 25600       16384
(Wiro) qwen/qwen3-6-27b                        25600       16384
(Wiro) qwen/qwen3-8-27b                        25600       16384
(Wiro) qwen/qwen3-8-27b-obliterated            25600       16384
(Wiro) qwen/qwen3-8-27b-uncensored             25600       16384
(Wiro) xai/grok-4-1-fast                     2000000       65536
(Wiro) xai/grok-4-20                         2000000       65536
(Wiro) xai/grok-4-5                           500000       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.

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 <name> to keep a separate one under ~/.openclaw-<name>.

  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/<id>. 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.

{
  "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/fable-5-1", "name": "(Wiro) Fable 5.1" },
          { "id": "(Wiro) claude/opus-5", "name": "(Wiro) Opus 5" },
          { "id": "(Wiro) claude/opus-5-5", "name": "(Wiro) Opus 5.5" },
          { "id": "(Wiro) claude/sonnet-5", "name": "(Wiro) Sonnet 5" },
          { "id": "(Wiro) deepseek/v4-flash", "name": "(Wiro) DeepSeek V4 Flash" },
          { "id": "(Wiro) deepseek/v4-pro", "name": "(Wiro) DeepSeek V4 Pro" },
          { "id": "(Wiro) glm/5-2", "name": "(Wiro) GLM 5.2" },
          { "id": "(Wiro) google/gemini-2-5-flash", "name": "(Wiro) Gemini 2.5 Flash" },
          { "id": "(Wiro) google/gemini-3-5-flash", "name": "(Wiro) Gemini 3.5 Flash" },
          { "id": "(Wiro) google/gemini-3-5-flash-lite", "name": "(Wiro) Gemini 3.5 Flash-Lite" },
          { "id": "(Wiro) google/gemini-3-6-flash", "name": "(Wiro) Gemini 3.6 Flash" },
          { "id": "(Wiro) google/gemini-3-7-flash", "name": "(Wiro) Gemini 3.7 Flash" },
          { "id": "(Wiro) google/gemini-3-8-flash", "name": "(Wiro) Gemini 3.8 Flash" },
          { "id": "(Wiro) google/gemini-3-flash", "name": "(Wiro) Gemini 3 Flash" },
          { "id": "(Wiro) google/gemini-3-pro", "name": "(Wiro) Gemini 3 Pro" },
          { "id": "(Wiro) google/gemini-4-argon", "name": "(Wiro) Gemini 4 Argon" },
          { "id": "(Wiro) meta-llama/llama-3-2-3b-instruct", "name": "(Wiro) Llama 3.2 3B Instruct" },
          { "id": "(Wiro) mistralai/mistral-7b-instruct-v0-3", "name": "(Wiro) Mistral 7B Instruct v0.3" },
          { "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) openai/gpt-6-1-sol", "name": "(Wiro) GPT-6.1 Sol" },
          { "id": "(Wiro) openai/gpt-6-astra", "name": "(Wiro) GPT-6 Astra" },
          { "id": "(Wiro) openai/gpt-6-luna", "name": "(Wiro) GPT-6 Luna" },
          { "id": "(Wiro) openai/gpt-6-sol", "name": "(Wiro) GPT-6 Sol" },
          { "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/qwen2-5-coder-7b-instruct", "name": "(Wiro) Qwen2.5 Coder 7B 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) 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.
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/fable-5-1
(Wiro) claude/opus-5
(Wiro) claude/opus-5-5
(Wiro) claude/sonnet-5
(Wiro) deepseek/v4-flash
(Wiro) deepseek/v4-pro
(Wiro) glm/5-2
(Wiro) google/gemini-2-5-flash
(Wiro) google/gemini-3-5-flash
(Wiro) google/gemini-3-5-flash-lite
(Wiro) google/gemini-3-6-flash
(Wiro) google/gemini-3-7-flash
(Wiro) google/gemini-3-8-flash
(Wiro) google/gemini-3-flash
(Wiro) google/gemini-3-pro
(Wiro) google/gemini-4-argon
(Wiro) meta-llama/llama-3-2-3b-instruct
(Wiro) mistralai/mistral-7b-instruct-v0-3
(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) openai/gpt-6-1-sol
(Wiro) openai/gpt-6-astra
(Wiro) openai/gpt-6-luna
(Wiro) openai/gpt-6-sol
(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/qwen2-5-coder-7b-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) 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.
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:

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/fable-5-1
(Wiro) claude/opus-5
(Wiro) claude/opus-5-5
(Wiro) claude/sonnet-5
(Wiro) deepseek/v4-flash
(Wiro) deepseek/v4-pro
(Wiro) glm/5-2
(Wiro) google/gemini-2-5-flash
(Wiro) google/gemini-3-5-flash
(Wiro) google/gemini-3-5-flash-lite
(Wiro) google/gemini-3-6-flash
(Wiro) google/gemini-3-7-flash
(Wiro) google/gemini-3-8-flash
(Wiro) google/gemini-3-flash
(Wiro) google/gemini-3-pro
(Wiro) google/gemini-4-argon
(Wiro) meta-llama/llama-3-2-3b-instruct
(Wiro) mistralai/mistral-7b-instruct-v0-3
(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) openai/gpt-6-1-sol
(Wiro) openai/gpt-6-astra
(Wiro) openai/gpt-6-luna
(Wiro) openai/gpt-6-sol
(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/qwen2-5-coder-7b-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) xai/grok-4-1-fast
(Wiro) xai/grok-4-20
(Wiro) xai/grok-4-5

Discover models and capabilities

GET /v1/models

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 list. The gateway catalog is separate from the full catalog at POST /v1/Tool/List.

{
  "object": "list",
  "data": [
    {
      "id": "openai/gpt-5-6-sol",
      "canonical_slug": "openai/gpt-5-6-sol",
      "name": "(Wiro) GPT 5.6 Sol by OpenAI (Vision)",
      "description": "OpenAI’s GPT 5.6 Sol is a flagship reasoning model that can analyze images and produce detailed text answers for coding, research, and visual Q&A.",
      "context_length": 1050000,
      "architecture": {
        "modality": "text+image+file->text",
        "input_modalities": ["text", "image", "file"],
        "output_modalities": ["text"]
      },
      "pricing": {
        "prompt": "0.000005",
        "completion": "0.00003"
      },
      "top_provider": {
        "context_length": 1050000,
        "max_completion_tokens": 65536
      },
      "supported_parameters": [
        "max_completion_tokens",
        "max_tokens",
        "parallel_tool_calls",
        "reasoning_effort",
        "response_format",
        "structured_outputs",
        "tool_choice",
        "tools"
      ],
      "default_parameters": {},
      "object": "model",
      "type": "model",
      "created": 1783670833,
      "created_at": "2026-07-10T08:07:13.000Z",
      "owned_by": "openai",
      "display_name": "(Wiro) GPT 5.6 Sol by OpenAI (Vision)",
      "max_input_tokens": 1050000,
      "max_tokens": 65536,
      "capabilities": {
        "endpoints": ["anthropic", "chat", "responses"],
        "input_modalities": ["text", "image", "document"],
        "output": ["text", "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": ["max_output_tokens", "reasoning_effort", "verbosity"]
      },
      "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 the models verified with the llm-tool-call category. It is not filtered by team Model Access: a model your team blocks is still listed, but calling it fails. 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, 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 endpoint labels. Capability differences are expressed through modalities, supported parameters, tool flags, structured-output flags, reasoning controls, and limits.

Structured-output support is per model, not per family: read structured_output_modes and strict_json_schema on the model you are about to call. Some models declare both json_object and json_schema, some declare only one of them, and some declare no structured output at all. A model whose runtime cannot constrain decoding can promise a valid JSON document but nothing about the schema, so it declares json_object alone. strict_json_schema is separate again: a model can accept json_schema and still refuse "strict": true.

GET /v1/models/{owner}/{model}

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 or non-gateway model returns model_not_found without exposing private catalog state. Team Model Access is checked when the model runs, not on this route.

Generic finite task completion

POST /v1/Run/{owner}/{project}/sync lives on https://api.wiro.ai/v1, not this gateway. See 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:

Method Route Behavior
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:

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:

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.

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, then match the exact mode: json_object and json_schema are declared independently in capabilities.structured_output_modes, and "strict": true inside a json_schema request additionally requires capabilities.strict_json_schema. A mode the model does not declare is refused with HTTP 400, code unsupported_capability and param: "response_format", before any task is created or billed.

Structured output uses OpenAI's two response_format shapes. json_object asks only for a valid JSON document:

{ "response_format": { "type": "json_object" } }

json_schema is nested: the definition sits under a json_schema key rather than beside type.

{
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "person",
      "schema": {
        "type": "object",
        "properties": { "full_name": { "type": "string" } },
        "required": ["full_name"],
        "additionalProperties": false
      },
      "strict": true
    }
  }
}

The definition accepts name, schema, description, and strict, and rejects any other key. name is required and must match ^[A-Za-z0-9_-]{1,64}$; a missing or invalid name returns invalid_schema. schema must have an object root, may reference only local # fragments, and may nest at most 24 levels. json_object guarantees parseable JSON but guarantees nothing about which keys appear, so use json_schema when the key names matter.

Standard 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; length and content_filter turns 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:

{
  "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 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.

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.format structured output, multimodal input, reasoning, and generation controls are capability-gated. Remote media follows the same public-HTTPS/data/base64/Wiro-file-ID restrictions as Chat.

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
  }'

Structured output on this route is text.format, and it uses the flat shape rather than Chat's nested one: name, schema, and strict sit beside type.

{
  "text": {
    "format": {
      "type": "json_schema",
      "name": "person",
      "schema": {
        "type": "object",
        "properties": { "full_name": { "type": "string" } },
        "required": ["full_name"],
        "additionalProperties": false
      },
      "strict": true
    }
  }
}

{ "text": { "format": { "type": "json_object" } } } is also accepted. The same capabilities.structured_output_modes and capabilities.strict_json_schema gates apply; the responses endpoint label does not by itself mean structured output is available. A top-level response_format is accepted as an alternative to text.format, but sending both returns HTTP 400 with param: "text".

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:

{
  "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. 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:

{
  "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

anthropic-version is required. The credential can arrive in x-api-key or in an Authorization: Bearer header; when both are sent, they must resolve 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.

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.

Structured output uses output_config, not response_format. This route accepts json_schema only, and always requests it strictly:

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,
    "messages": [{
      "role": "user",
      "content": "Who wrote Dune, and in what year?"
    }],
    "output_config": {
      "format": {
        "type": "json_schema",
        "schema": {
          "type": "object",
          "properties": {
            "author": { "type": "string" },
            "year": { "type": "integer" }
          },
          "required": ["author", "year"],
          "additionalProperties": false
        }
      }
    }
  }'

output_config.format takes exactly type and schema. Both are required, and any other key is rejected, including OpenAI's name and strict. The gateway applies strict JSON Schema output on this route unconditionally, so the model must declare capabilities.strict_json_schema as well as json_schema in capabilities.structured_output_modes. {"type": "json_object"} is refused here for every model. output_config.effort is the separate reasoning control and can be sent alongside format. A top-level response_format is not a Messages field and is rejected as an unsupported parameter. Anthropic error bodies carry only type and message, so a capability refusal arrives here without the code and param fields the OpenAI routes return.

Thinking controls use:

{ "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:

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:

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

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" }]
  }'
{ "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}.

curl "https://llm.wiro.ai/v1/generation?id=resp_01HZX7Y8Q9" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "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, 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: 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 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.

A model blocked by your team's Model Access settings is refused before it runs with HTTP 403 and the message This model is not available for your team. Chat and Responses return type permission_error, code permission_denied and param model; Anthropic routes return type permission_error. A streamed Responses request is different: its stream is already open, so it answers 200 and ends with response.failed carrying code permission_denied and the same message, like other errors that happen when the run is submitted.

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.

unsupported_capability means the model does not declare something the request asked for. A structured-output mode missing from capabilities.structured_output_modes carries param: "response_format" and the message Model '<id>' does not support json_object structured output; "strict": true on a model without capabilities.strict_json_schema uses the same code with does not support strict JSON Schema output; tool controls on a model that declares neither function_tools nor custom_tools carry param: "tool_choice". The request is refused during validation, so no task is created and nothing is billed. Anthropic routes report the same refusal in the Anthropic error shape, which carries only type and message.

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.