Skip to content

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.

Shell or the env block of managed settings
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's anthropic/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_control breakpoints, server tools, documents) and the answer comes back as the provider sent it. The anthropic-beta header is forwarded to Anthropic; anthropic-version is 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_tokens asks 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, and X-INFRO-Token-Count: estimated says so.
  • Model list. With CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1, Claude Code reads GET /v1/models and adds the Claude models your key can call to its /model picker.
  • Sessions. Claude Code's x-claude-code-session-id header becomes the request's session_id when 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.

~/.codex/config.toml
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

Python
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

EndpointSent as written (lossless)TranslatedNot served
/v1/messagesAnthropic; Claude on Vertex AI; KIE's Claude modelsEvery other provider with chat: text, images and your own tools onlyProviders without chat
/v1/responsesOpenAI; Azure OpenAI; KIE's Codex modelsEvery other provider with chat: messages, images, function tools and JSON-schema output onlyProviders 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 402 in 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 answer X-INFRO-Cost-Estimate (USD, or unpriced). The request log has the rest.
  • The response cache does not serve these endpoints yet. Where it is on, the request log says uncacheable with the reason endpoint.
  • Attribution. Send INFRO's X-INFRO-Trace-Id, X-INFRO-Workflow, X-INFRO-Step, X-INFRO-Feature and X-INFRO-User-Id headers. On /v1/messages, metadata.user_id is Anthropic's field and is passed to Anthropic; INFRO's end customer is X-INFRO-User-Id. On /v1/responses, INFRO's reserved metadata keys 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.

↑ ↓ navigate↵ open/ anywhere in docs