DocsGet started
Claude Code & Codex
Route Claude Code, Codex and the agent SDKs through INFRO by changing the base URL and the key: Anthropic's Messages API and OpenAI's Responses API.
INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See Early access.
On the roadmap — not available yet. /v1/messages and /v1/responses are built and in testing, and not deployed: https://api.infro.io does not answer them today. This page describes the contract they will ship with.
Coding agents speak their provider's own API, not Chat Completions. INFRO answers both: POST /v1/messages in Anthropic's Messages shape and POST /v1/responses in OpenAI's Responses shape. Point the agent at INFRO with an INFRO gateway key, and every request it makes goes through the same path as any other: your routes and failover, model allowlists, budgets and spend limits checked before the provider is called, guardrails, the request log with an estimated provider cost, and attribution. Your provider still bills you for the tokens; INFRO adds nothing to them.
Give each developer, or each agent, its own gateway key with a spend limit. A request that limit cannot cover is refused with 402 before the provider sees it, and the request log shows spend per key.
Claude Code
Set the base URL and an INFRO key. ANTHROPIC_AUTH_TOKEN is sent as Authorization: Bearer; ANTHROPIC_API_KEY as x-api-key. INFRO accepts either.
export ANTHROPIC_BASE_URL="https://api.infro.io"
export ANTHROPIC_AUTH_TOKEN="sk_infro_..."
# Optional: attribute every request (newline-separated "Name: Value" pairs)
export ANTHROPIC_CUSTOM_HEADERS="X-INFRO-Workflow: claude-code"- Model names. Claude Code sends Anthropic's own ids (
claude-sonnet-5-5,claude-haiku-4-5-20251001); INFRO reads them as the catalog'santhropic/claude-sonnet-5.5. A route slug works too (ANTHROPIC_MODEL=your-route), but Claude Code does not recognize the name and assumes a 200K context window. - Lossless on Anthropic. On an Anthropic connection, or Claude on Vertex AI, the body Claude Code sends reaches the provider as written (thinking blocks and their signatures,
cache_controlbreakpoints, server tools, documents) and the answer comes back as the provider sent it. Theanthropic-betaheader is forwarded to Anthropic;anthropic-versionis accepted and pinned. - Streaming is relayed event by event. A provider that opens the stream and reports an error before any content (an
overloaded_error) is failed over to the next target, like one that refused outright. - Token counting.
POST /v1/messages/count_tokensasks the first Anthropic connection that serves the model; it is free and leaves no request row. With no such connection the answer is an estimate, andX-INFRO-Token-Count: estimatedsays so. - Model list. With
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1, Claude Code readsGET /v1/modelsand adds the Claude models your key can call to its/modelpicker. - Sessions. Claude Code's
x-claude-code-session-idheader becomes the request'ssession_idwhen you set none, so one session groups in the request log.
The Claude Agent SDK runs Claude Code, so it reads the same variables.
Codex
Add INFRO as a model provider in ~/.codex/config.toml. Codex sends the key from env_key as a bearer token, to <base_url>/responses.
model = "gpt-5.6-sol"
model_provider = "infro"
[model_providers.infro]
name = "INFRO"
base_url = "https://api.infro.io/v1"
env_key = "INFRO_API_KEY"
wire_api = "responses"
http_headers = { "X-INFRO-Workflow" = "codex" }Codex sends the whole conversation each turn (store: false, its reasoning carried back as encrypted content), which is what INFRO needs: see state below. On an OpenAI or Azure OpenAI connection the body goes to the provider as written, Codex's custom apply_patch tool and reasoning items included.
OpenAI Agents SDK
from openai import AsyncOpenAI
from agents import set_default_openai_client
set_default_openai_client(
AsyncOpenAI(base_url="https://api.infro.io/v1", api_key="sk_infro_..."),
use_for_tracing=False, # the INFRO key is not an OpenAI key; trace with your own, or set_tracing_disabled(True)
)Which providers serve it, and how
| Endpoint | Sent as written (lossless) | Translated | Not served |
|---|---|---|---|
/v1/messages | Anthropic; Claude on Vertex AI; KIE's Claude models | Every other provider with chat: text, images and your own tools only | Providers without chat |
/v1/responses | OpenAI; Azure OpenAI; KIE's Codex models | Every other provider with chat: messages, images, function tools and JSON-schema output only | Providers without chat |
A translation drops what has no meaning for the other model (thinking settings and past thinking blocks, top_k, reasoning items, cache_control), names it in X-INFRO-Params-Dropped, and answers in the dialect you called. What it cannot carry without changing the request (a server tool such as web_search, a document block, a custom or hosted tool, MCP servers) makes that target ineligible for the request: it is left out of the chain, never sent a reduced request. When no target is left, the request is refused with 400 unsupported_on_targets naming what stood in the way. X-INFRO-Ingress says which way a request was served.
Anthropic does not support Claude Code on non-Claude models. INFRO can translate a Claude Code request to another model, but for failover give Claude Code's routes Claude targets on more than one provider (Anthropic and Claude on Vertex AI): those are served as written.
What is the same, and what differs
- Budgets and caps reserve the request's worst case before any provider is called, exactly as on chat completions. A refusal is
402in the dialect's envelope, and Claude Code shows its message. - Guardrails read every text block, tool input and tool result. Thinking blocks are not inspected: their signature covers the text, so a redaction would make Anthropic refuse the next turn. An output rule needs the whole answer: a streamed request it applies to is read whole from the provider, checked, then streamed to you, with SSE comments every few seconds meanwhile so the client does not time out.
- The answer keeps the provider's shape. INFRO's facts ride on response headers:
X-INFRO-Request-Id,X-INFRO-Model,X-INFRO-Provider,X-INFRO-Route,X-INFRO-Ingress, and on an unstreamed answerX-INFRO-Cost-Estimate(USD, orunpriced). The request log has the rest. - The response cache does not serve these endpoints yet. Where it is on, the request log says
uncacheablewith the reasonendpoint. - Attribution. Send INFRO's
X-INFRO-Trace-Id,X-INFRO-Workflow,X-INFRO-Step,X-INFRO-FeatureandX-INFRO-User-Idheaders. On/v1/messages,metadata.user_idis Anthropic's field and is passed to Anthropic; INFRO's end customer isX-INFRO-User-Id. On/v1/responses, INFRO's reservedmetadatakeys are read and not forwarded.
State stays with the caller
INFRO keeps no responses. previous_response_id and conversation are refused with 400 unsupported_parameter on every target, because a failover to another account or another model could not resolve them; background: true is refused for the same reason. Send the whole conversation in input each turn. store is passed to a provider that takes the request as written: it is your own account.
Errors
Errors keep the documented taxonomy — type, code, message — inside each API's envelope: on /v1/messages, Anthropic's {"type": "error", "error": {…}}; on /v1/responses, the same {"error": {…}} as everywhere on /v1. A provider's refusal of the request carries the provider's own words in message, which is what Claude Code's automatic recovery reads. After a stream has started, a failure is the API's own stream event: error on /v1/messages, response.failed on /v1/responses.