text-to-text rows in the catalog — see LLM API) are not served through the asynchronous queue described in Request lifecycle. Each one is a synchronous, OpenAI-compatible chat completions endpoint at the model’s own URL:
/chat/completions to their configured base URL themselves, so connecting is three values:
Switch models by changing the URL. Nothing else changes.
Quickstart
Model discovery and the platform base URL
Tools with a custom-provider flow — GitHub Copilot’s bring-your-own-model setup is the common case — probeGET {base_url}/models before the first call to discover model ids. Both base URLs answer it in the OpenAI list shape (with your API key; anonymous probes get a 401):
To add ModelRunner to GitHub Copilot in VS Code: open the Copilot Chat model picker → Manage Models… → choose the OpenAI-compatible provider, paste the base URL and your key, and pick models from the list Copilot fetches. Pointing Copilot at a single model’s base URL works too — its
/models answers with just that model.
The platform base is OpenAI-surface only (
POST /v1/chat/completions, model in the body). The Anthropic-compatible endpoint described below stays per-model, so ANTHROPIC_BASE_URL keeps naming one model.Streaming
Set"stream": true and the reply arrives as Server-Sent Events (data: frames, terminated by data: [DONE]). stream_options: {"include_usage": true} adds a final usage frame.
Non-streaming calls time out at roughly 290 seconds. The connection stays silent while the model generates, and the edge closes silent responses — so stream anything that can run long (thinking models, large outputs).
Tool calling, JSON mode, reasoning
Parameters the platform does not interpret are forwarded to the model unchanged — the model is the authority on what it accepts:- Tool calling — an OpenAI-format
toolsarray plustool_choice(auto,none,required, or a specific tool). The reply carriestool_calls; send the result back as atoolmessage with the matchingtool_call_id. - JSON mode —
response_format: {"type": "json_object"}. Some thinking models only honour it with thinking off; the model page says so. - Reasoning — thinking models take
reasoning_effort(low/medium/high, or a wider set on some models). Thinking tokens bill at the model’s output rate and draw frommax_tokens, so leave headroom;usage.completion_tokens_details.reasoning_tokensreports how many went to thinking. - Vision — models whose input modalities include images accept OpenAI multimodal
contentparts ({"type": "image_url", "image_url": {"url": "https://…"}},data:URLs included; the request body limit is 10 MB).
metadata object (flat string map, ≤16 keys) is stored on the request and never sent to the model — the same tagging mechanism as the queue path. Two vendor-side extras are stripped deliberately: store (conversation retention outside your data retention settings) and enable_search (billed outside token usage).
Billing
Every call is a normal request: it appears in your dashboard and inGET https://queue.modelrunner.run/requests/{id} (the response id is chatcmpl-<requestId>). Billing is per token from the model’s own reported usage at the rates on its page — separate prices per 1M input and output tokens, a reduced rate for cached input where the model supports it — exact to six decimals, with no per-request minimum. Disconnecting from a stream after output has started is not a refund: the platform finishes the upstream call and bills its exact usage.
Errors
This surface answers in the OpenAI error envelope, not the house shape:
OpenAI SDKs retry 429 and 5xx automatically, which is the behaviour you want here.
Claude Code and the Anthropic-compatible endpoint
Claude Code configures its model throughANTHROPIC_BASE_URL, which must speak the Anthropic Messages API — so every language model also answers a Messages-compatible endpoint at the same base URL:
anthropic SDKs work the same way (base_url + api_key). ANTHROPIC_API_KEY is accepted too. Text, images, tool use and tool results, system prompts, stop sequences, streaming and the thinking/effort settings translate; max_tokens is clamped to the model’s published output ceiling; the body model is ignored in favour of the URL. Unsupported content kinds (documents, server-side tools) are refused with a 400 that names them rather than silently dropped. Errors use the Messages envelope ({ "type": "error", "error": { "type", "message" } }), and an insufficient balance is reported the way the Messages API reports it — 400 invalid_request_error.
Claude Code will warn that it does not recognise the model name and assume a 200k-token context window; set
CLAUDE_CODE_MAX_CONTEXT_TOKENS to the model’s real window (shown on its page).From an MCP client
The MCP server’srun_model tool runs language models too: pass input: { "messages": [ … ] } and the reply comes back in the same call (status COMPLETED) rather than as a request to poll.

