NativePort
← How-to

How to use the Claude Agent SDK with NativePort

Configure the Python Claude Agent SDK's ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN to run Claude Code sessions through NativePort's native Anthropic route.

The Claude Agent SDK is the library form of Claude Code: the same tool loop, permission system and session handling that power the CLI, callable from your own Python process instead of a terminal. Point it at NativePort’s native Anthropic route and it runs against Claude on the same one key and one balance as every other provider here, billed from your NativePort balance instead of a separate Anthropic account. This guide covers the minimal working setup: the environment variables NativePort’s route requires, a copy-paste example that returns a real response, and what’s worth knowing before you enable tools or run this somewhere with restricted network egress.

What you’ll need

  • A NativePort API key. Sign up to get a key and $5 in credits.
  • Python 3.10 or later.
  • pip install claude-agent-sdk==0.2.128. This installs the bundled Claude Code CLI that the SDK spawns as a subprocess; you don’t invoke it directly.
  • Your key exported as an environment variable, never hardcoded:
export NATIVEPORT_API_KEY="<your NativePort API key>"

Claude Agent SDK is not the Anthropic Python SDK

These are two different packages that solve different problems, and mixing up their setup is the most common way to get stuck.

The Anthropic Python SDK (pip install anthropic) is a thin client for POST /v1/messages. You build the request, manage the conversation state and drive any tool-use loop yourself. Pointing it at NativePort means constructing an Anthropic(base_url=..., auth_token=...) client and calling messages.create() directly.

The Claude Agent SDK (pip install claude-agent-sdk, the subject of this guide) packages the full Claude Code agent, including its own tool loop, file and shell tools, permission system and multi-turn session handling, as a library you call from Python. Under the hood it spawns a bundled claude CLI subprocess and talks to it over a local pipe. You never call the Messages API yourself; you configure ClaudeAgentOptions and let the SDK’s spawned process do it. Because that subprocess makes the actual HTTP calls, routing it through NativePort means setting variables in its environment, not passing a base_url argument to a Python client object.

If your goal is a plain chat completion or a tool loop you write yourself, the Anthropic Python SDK against the same route is the simpler choice. If you want Claude Code’s own agent loop (multi-step tool use, file edits, session resumption) driven from your own code, the Claude Agent SDK is the one that gives you that, and it’s what this guide sets up.

The environment variables NativePort’s route needs

The Claude Agent SDK has no gateway-specific configuration option. It works by forwarding environment variables to the Claude Code subprocess it spawns, and ClaudeAgentOptions(env={...}) is the documented way to set them for that one call without touching your shell’s environment. Five variables matter here:

  • ANTHROPIC_BASE_URL: https://api.nativeport.ai/anthropic. This is NativePort’s native Anthropic-format route; requests keep the Messages API’s own request and response shape.
  • ANTHROPIC_AUTH_TOKEN: your NATIVEPORT_API_KEY. Use this variable, not ANTHROPIC_API_KEY. Claude Code sends ANTHROPIC_AUTH_TOKEN as an Authorization: Bearer <token> header, while ANTHROPIC_API_KEY sends it as x-api-key. NativePort’s route reads the bearer header, so ANTHROPIC_API_KEY here produces a 401 even with a valid key, because the credential lands in a header the route doesn’t check.
  • CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1. Stops the CLI’s own version checks, telemetry and release-note fetches, which otherwise go to Anthropic’s own hosts regardless of ANTHROPIC_BASE_URL. See the network egress note below for why this matters even when your main calls work fine without it.
  • CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: 1. Turns off the CLI’s experimental beta request headers, keeping the request shape close to Claude Code’s stable baseline.
  • CLAUDE_CODE_MAX_OUTPUT_TOKENS: 32000. This one needs its own explanation, because skipping it is the single most common way this integration fails on the very first call.

Why the output-token override is required

Claude Code requests a fixed, fairly high output-token ceiling by default when talking to a Sonnet-class model, well above what NativePort’s /anthropic route currently accepts on a single request. The route enforces its own per-request output-token cap and rejects, rather than silently reduces, any request asking for more than that. Without CLAUDE_CODE_MAX_OUTPUT_TOKENS set, the first request from an otherwise correctly configured client fails with a 400 naming the requested value and the route’s limit.

CLAUDE_CODE_MAX_OUTPUT_TOKENS overrides that default request ceiling. Setting it to 32000, at or below the route’s current per-request cap, resolves the rejection with no other change to the setup. If NativePort’s documented cap changes later, use the new value; the error response always names the limit it’s enforcing.

Minimal example

This is the smallest configuration that completes a real round trip: no tools admitted, a single turn, and a plain text response back.

import asyncio
import os

from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query

options = ClaudeAgentOptions(
    model="claude-sonnet-5",
    allowed_tools=[],
    max_turns=1,
    env={
        "ANTHROPIC_BASE_URL": "https://api.nativeport.ai/anthropic",
        "ANTHROPIC_AUTH_TOKEN": os.environ["NATIVEPORT_API_KEY"],
        "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
        "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
        "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "32000",
    },
)


async def main():
    async for message in query(prompt="Say hello in one sentence.", options=options):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    print(block.text)


asyncio.run(main())

query() yields a stream of typed message objects as the session runs (system messages, assistant messages, a final result message); this example filters down to the assistant’s text blocks and prints them. allowed_tools=[] and max_turns=1 keep this a strictly text-in, text-out call: no file access, no shell commands, nothing that reaches outside the conversation itself. That’s the right starting point for a first integration test, and the right default for anything that shouldn’t touch your filesystem at all.

ClaudeAgentOptions.env merges with your shell’s own environment before the subprocess starts, with env taking priority. You could equally export all five variables in your shell and drop the env argument; passing them explicitly, as above, is more portable when the script runs somewhere your shell isn’t already configured, and it’s the pattern this guide sticks to.

Model selection

ANTHROPIC_BASE_URL changes where a request goes, not which models exist behind it. --model claude-sonnet-5 (set through ClaudeAgentOptions(model=...)) passes straight through to NativePort’s route, which must itself serve that model id for the call to succeed. Use a full model id rather than a bare alias like sonnet, so the exact model you tested against is the one that runs later, independent of how an alias might resolve on a future CLI version. Check the Anthropic provider page for the current list of Claude models the route serves before picking an id for anything beyond a quick test.

Traffic that still reaches Anthropic directly

ANTHROPIC_BASE_URL and the two CLAUDE_CODE_DISABLE_* flags above cover the traffic that matters for a normal call, but a few narrower paths in the Claude Code CLI reach Anthropic’s own hosts regardless of that configuration:

  • The built-in WebFetch tool runs a domain-safety preflight against api.anthropic.com before fetching any URL, independent of ANTHROPIC_BASE_URL. Turn it off with the skipWebFetchPreflight: true Claude Code setting if your network blocks that host. Only relevant if you enable WebFetch in allowed_tools; it’s inactive in the minimal example above.
  • Fast mode’s availability check also bypasses ANTHROPIC_BASE_URL and calls api.anthropic.com directly. Not relevant unless your integration enables fast mode.

None of this affects the model call itself, which always goes to ANTHROPIC_BASE_URL. It matters only if your deployment restricts outbound access to NativePort’s hosts specifically; in that case, allow the narrow exception above or avoid the features that trigger it.

Enabling tools safely

Once the minimal example works, most real uses of the Claude Agent SDK go on to allow at least some tools, at which point permissions stop being optional. A few defaults worth setting deliberately:

  • Pass allowed_tools as an explicit list rather than leaving it unset. An unset list defaults to a broad built-in set; naming exactly what you need (for example ["Read", "Grep"] for a read-only research agent) is the difference between a session that can only look at files and one that can also edit or execute them.
  • Leave permission_mode at its default, which prompts for anything not explicitly allowed, rather than switching it to a mode that skips permission checks. A mode that bypasses permission checks is appropriate only for a fully sandboxed, disposable environment, never for a process with access to real files or credentials.
  • For programmatic gating beyond a static allow-list, ClaudeAgentOptions supports a can_use_tool callback and hook functions that run before a tool executes, letting you approve, deny or rewrite a specific call based on its arguments rather than only its name.
  • Treat NATIVEPORT_API_KEY and any credentials a tool might read as you would any other secret: environment variable or secret store, never written into a prompt, a tool argument or a log line.

The official secure deployment guide covers this in more depth, including patterns for running the SDK in a sandboxed subprocess or container when tools need real filesystem or shell access.

Troubleshooting

  • 401 on every request. Almost always a header mismatch, not a bad key. NativePort’s /anthropic route reads the bearer header, so the credential must be in ANTHROPIC_AUTH_TOKEN, not ANTHROPIC_API_KEY. If the error names an invalid or unrecognized token even though the key is correct, check which of the two variables is actually set.
  • 400 naming an output-token or max-tokens field. CLAUDE_CODE_MAX_OUTPUT_TOKENS is unset or set above the route’s current cap. Set it to 32000, or to whatever value the error response itself names as the limit.
  • 400 naming a beta or experimental header. Confirm CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 is set in the same env dict (or shell environment) the failing call used.
  • 404 or a model-not-found error. Check the model id for typos first; claude-sonnet-5 is case-sensitive and must match exactly. If the id is correct, confirm it’s one of the models NativePort’s Anthropic route currently serves on the provider page.
  • Connection errors, timeouts, or DNS failures. Confirm outbound access to api.nativeport.ai from wherever the script runs. If CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC isn’t set, also allow api.anthropic.com, since background version and telemetry calls go there by default even when the model call itself is configured correctly.

What this costs

Claude models through this route are billed at Anthropic’s own metered rate, including prompt-cache pricing tiers read directly from each response rather than recomputed separately, with no per-call markup from NativePort. A zero balance fails every request with a 402 rather than degrading output quality or silently queuing. See pricing for the full billing model.

Where to go next