Docs

Claude Agent SDK

The Agent SDK has no base-URL option — it spawns the Claude Code CLI and hands it your whole environment. That is the routing seam, and two variables are all it takes.

Esta documentação está em inglês. Para um guia de início rápido em português, veja:Guia em português — Claude API no Brasil

Searching the SDK for a base_url option finds nothing, and that is not an omission in the docs — the option does not exist. The SDK runs the Claude Code CLI as a subprocess, and the CLI is the thing that reads ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN. Set those two and every call the agent makes is routed, with no change to your agent code.

# The SDK has no base_url option. The CLI it spawns reads these, and the
# SDK passes the parent environment straight through — so exporting them
# before your program starts is enough.
export ANTHROPIC_BASE_URL=https://api.kunavo.com
export ANTHROPIC_AUTH_TOKEN=sk-kn-...

python my_agent.py
The endpoint is the service root https://api.kunavo.com, no /v1. Anthropic clients append /v1/messages themselves. The same rule that trips people up in every other Anthropic-shaped client applies here, and it is explained on the ANTHROPIC_BASE_URL page.

Why the environment reaches the CLI at all

This is worth one paragraph, because it is the difference between a trick that might stop working and a documented property you can build on. The Python SDK's subprocess transport constructs the child's environment as the parent's os.environ with a single key removed CLAUDECODE, so the child does not think it is running inside a Claude Code session — then merges CLAUDE_CODE_ENTRYPOINT, then ClaudeAgentOptions.env, then the SDK version.

Two things follow, and the second is the one people get wrong. Everything in your shell reaches the CLI, so exporting the two variables works. And options.env is merged on top of the inherited environment, so the explicit form wins over a stale export rather than losing to it. The code is in subprocess_cli.py.

The explicit form, and when to insist on it

Exported variables are fine on your own machine and fragile everywhere else: the agent's endpoint becomes a property of how the process was launched, which breaks the first time it runs under a scheduler, in a container, or in a CI job that does not source your profile. Passing env on the options object makes routing part of the program.

my_agent.py
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

# The explicit form. options.env is merged ON TOP of the inherited
# environment, so this wins over whatever the shell happens to hold —
# which is what you want in anything that is not your own laptop.
options = ClaudeAgentOptions(
    env={
        "ANTHROPIC_BASE_URL": "https://api.kunavo.com",
        "ANTHROPIC_AUTH_TOKEN": "sk-kn-...",
    },
)

async with ClaudeSDKClient(options=options) as client:
    await client.query("Summarise the open TODOs in this repo")
    async for message in client.receive_response():
        print(message)

Step by step

  1. Create a key at /app/keys and copy it — it is shown once.
  2. Decide where routing lives: exported variables for local work, ClaudeAgentOptions(env=…) for anything that runs unattended.
  3. Set ANTHROPIC_BASE_URL to https://api.kunavo.com and ANTHROPIC_AUTH_TOKEN to your sk-kn-… key.
  4. Optionally set ANTHROPIC_DEFAULT_HAIKU_MODEL so the background sub-tasks the CLI fires land on the cheapest tier.
  5. Run your program. Nothing in the agent code changes.

Which tier for which sub-task

An agent fans out — one thing you ask for becomes many billed round trips — so the tier mapping matters more here than in a chat app. Rates are USD per 1M tokens, input / output, read live from the catalog.

Model idKunavo in / outWhere it fits
claude-haiku-4-5$0.40 / $2.00Background sub-tasks the CLI generates on its own — frequent, automatic, easy to overpay for
claude-sonnet-4-6$1.20 / $6.00The working default for the agent's actual reasoning
claude-opus-5$2.00 / $10.00Only where a cheaper tier needs several attempts to get there
The arithmetic for that last row — how much worse a cheaper tier has to be before it stops being cheaper — is on Opus vs Sonnet vs Haiku. If the agent runs unattended, the spend side of that is on running without permission prompts.

Verify before you debug the SDK

One request settles whether a failure is the key, the endpoint, or the SDK. If this returns 200, the same credential works for the CLI the SDK spawns, and anything still broken is where the variables are set rather than what they contain.

# Settles whether a failure is the key, the endpoint, or the SDK.
# 200 here means the same credential works for the CLI the SDK spawns.
curl -sS https://api.kunavo.com/v1/messages \
  -H "Authorization: Bearer sk-kn-..." \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"claude-haiku-4-5","max_tokens":16,
       "messages":[{"role":"user","content":"ping"}]}'

Reference

The SDK is open source at anthropics/claude-agent-sdk-python. The environment behaviour described here is from its own subprocess transport, read on 2026-09-04. The TypeScript SDK has the same architecture — it drives the CLI rather than calling the API — so the CLI again reads the routing variables; its README documents neither the option nor the behaviour, so confirm the option name in its types before relying on the explicit form there. Kunavo's side is the Messages API, and the other clients that route the same way are on the integrations hub.

FAQ

Can the Claude Agent SDK use a custom base URL?

Yes, but not through an SDK option — there is no base_url parameter, which is why searching the README for one turns up nothing. The SDK runs the Claude Code CLI as a subprocess, and the CLI is what reads ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN. Setting those two variables in the environment your program runs in routes every call the agent makes, with no change to your agent code.

How does the SDK pass environment variables to the CLI?

It inherits the whole parent environment and filters exactly one key. In the Python SDK's subprocess transport, the child environment is built as the parent os.environ minus CLAUDECODE, then merged with CLAUDE_CODE_ENTRYPOINT, then with ClaudeAgentOptions.env, then with the SDK version. Two consequences follow: anything in your shell reaches the CLI, and options.env wins over the shell because it is merged on top.

Should I use the environment or ClaudeAgentOptions(env=...)?

Use options.env anywhere that is not your own laptop. Relying on the ambient shell means the agent's endpoint depends on how the process was launched, which breaks the first time it runs under a scheduler, a container or a CI job that does not carry your profile. Passing env explicitly on the options object makes routing a property of the program rather than of its surroundings, and it is merged on top of the inherited environment so it also wins over a stale export.

Does the Agent SDK need a separate Anthropic account?

It needs a credential that the Claude Code CLI accepts, which does not have to be a first-party one. Because routing happens through ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN, an endpoint serving the Anthropic Messages API works — on Kunavo that is one sk-kn- key against https://api.kunavo.com, billed per token from a prepaid balance rather than a plan.

Which models should an Agent SDK program use?

Match the tier to the sub-task, because an agent fans out. Claude Haiku 4.5 at $0.40 / $2.00 per 1M tokens is the right home for the background work the CLI generates on its own; Claude Sonnet 4.6 at $1.20 / $6.00 is the working default; Claude Opus 5 at $2.00 / $10.00 is worth it only where a cheaper tier needs several attempts. Setting ANTHROPIC_DEFAULT_HAIKU_MODEL alongside the two routing variables is one line that reduces every run.

Does the TypeScript Agent SDK work the same way?

It has the same architecture — the SDK drives the Claude Code CLI rather than calling the API directly — so the CLI is again what reads the routing variables. This page states the mechanism for the Python SDK because that is the source that was read; if you are on the TypeScript SDK, confirm the option name in its own types before relying on the explicit form, and use the exported environment variables meanwhile.

Why does the SDK filter CLAUDECODE out of the environment?

So an SDK-spawned CLI does not believe it is running inside a Claude Code parent session. It is the only key removed from the inherited environment, and it matters here only as evidence of how completely everything else is passed through — including the two routing variables this page depends on.