n4nAI

Migrating from Anthropic direct to a gateway API

Practical steps to migrate Anthropic direct to gateway API for Claude Opus 4.8: swap SDKs, remap requests, handle streaming, and verify.

n4n Team4 min read884 words

Audio narration

Coming soon — every post will get a voice note here.

To migrate Anthropic direct to gateway, you need to replace the Anthropic SDK with an OpenAI-compatible client and remap a few request fields. This guide walks through moving a production Claude Opus 4.8 integration to a gateway API without losing streaming, tool use, or prompt caching.

Step 1: Audit your current Anthropic direct integration

Before changing anything, capture exactly what your existing code sends to api.anthropic.com. Most teams use the official Python or TypeScript SDK, but the underlying HTTP shape matters more than the language wrapper.

Pull a representative request from logs or code. Look for these fields:

  • model (e.g., claude-opus-4.8)
  • system prompt (separate top-level parameter)
  • max_tokens, temperature, top_p, top_k
  • tools and tool_choice
  • stream flag and event handling
  • metadata and any cache_control blocks
  • anthropic-version header (typically 2023-06-01)

A typical direct call looks like this:

import anthropic

client = anthropic.Anthropic(api_key="sk-ant-...")
response = client.messages.create(
    model="claude-opus-4.8",
    max_tokens=2048,
    system="You are a terse backend assistant.",
    messages=[
        {"role": "user", "content": "Summarize the migration steps."}
    ],
    tools=[
        {
            "name": "log_event",
            "description": "Write to audit log",
            "input_schema": {
                "type": "object",
                "properties": {"msg": {"type": "string"}},
                "required": ["msg"]
            }
        }
    ]
)
print(response.content[0].text)

Note the separation of system from messages, and the input_schema style for tools. These are the two biggest differences from an OpenAI-compatible gateway. Also note that Anthropic direct requires you to pin an API version header; the gateway abstracts that away, but if you used beta features like tools-2024-04-04, confirm the gateway exposes equivalent behavior.

Step 2: Provision gateway access and API key

A gateway like n4n.ai exposes one OpenAI-compatible endpoint covering 240+ models and automatic fallback when a provider is degraded. You do not need separate credentials for Anthropic; the gateway handles upstream auth. Create an account, generate a gateway key (usually sk-gw-...), and store it in your environment.

export GATEWAY_API_KEY="sk-gw-..."
export GATEWAY_BASE_URL="https://api.n4n.ai/v1"

If you are self-hosting or using another gateway, replace the base URL accordingly. The critical property is OpenAI compatibility: the routes /v1/chat/completions, /v1/models, and /v1/embeddings must behave as OpenAI documents. Gateway metering aggregates token usage across providers, so your response usage object and any billing dashboard reflect exact counts without you stitching together Anthropic and OpenAI invoices.

Step 3: Swap the SDK and base URL

Delete the anthropic package dependency and install openai. The client constructor takes a base_url override. No other client code changes are needed at the transport layer.

# Before
# import anthropic
# client = anthropic.Anthropic(api_key="sk-ant-...")

# After
from openai import OpenAI

client = OpenAI(
    base_url="https://api.n4n.ai/v1",
    api_key="sk-gw-...",
    timeout=60.0  # raise from default 10s for long Claude completions
)

The model name typically gains a provider prefix. Where you used claude-opus-4.8, use anthropic/claude-opus-4.8 or whatever slug your gateway assigns. Check the /v1/models endpoint to confirm.

models = client.models.list()
print([m.id for m in models if "opus" in m.id])

Retries are handled by the OpenAI client by default (it uses exponential backoff on 5xx and 429). The gateway’s own fallback may route to a different provider if Anthropic is fully down, but for a pinned model like Claude Opus 4.8 it will instead return a clear error or degraded response rather than silently substituting a non-Anthropic model unless you configured that.

Step 4: Remap request parameters

OpenAI’s chat API folds the system prompt into the messages array as a system role. Tools use a function wrapper. Max tokens and sampling params keep similar names but live under chat.completions.create. Drop Anthropic-specific fields like top_k unless the gateway documents a passthrough.

System prompt and message shape

response = client.chat.completions.create(
    model="anthropic/claude-opus-4.8",
    max_tokens=2048,
    messages=[
        {"role": "system", "content": "You are a terse backend assistant."},
        {"role": "user", "content": "Summarize the migration steps."}
    ]
)
print(response.choices[0].message.content)

Tool definitions

Convert each Anthropic tool to the OpenAI function format:

{
  "type": "function",
  "function": {
    "name": "log_event",
    "description": "Write to audit log",
    "parameters": {
      "type": "object",
      "properties": {"msg": {"type": "string"}},
      "required": ["msg"]
    }
  }
}

In Python:

tools = [{
    "type": "function",
    "function": {
        "name": "log_event",
        "description": "Write to audit log",
        "parameters": {
            "type": "object",
            "properties": {"msg": {"type": "string"}},
            "required": ["msg"]
        }
    }
}]

response = client.chat.completions.create(
    model="anthropic/claude-opus-4.8",
    messages=[{"role": "user", "content": "Log that we migrated."}],
    tools=tools,
    tool_choice="auto"
)

If your downstream code parsed Anthropic’s response.content[0].input for tool calls, switch to response.choices[0].message.tool_calls. The shape is different but the semantics are equivalent.

Step 5: Handle streaming and response parsing

Anthropic’s direct API streams named events (content_block_delta, etc.). OpenAI streams ChatCompletionChunk objects with choices[0].delta. If you previously used client.messages.stream(), replace it with stream=True.

stream = client.chat.completions.create(
    model="anthropic/claude-opus-4.8",
    messages=[{"role": "user", "content": "Stream a haiku."}],
    stream=True
)

for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:
        print(delta.content, end="", flush=True)

For TypeScript, the pattern is identical with for await (const chunk of stream). Do not assume usage is present in the first chunk; gateways often emit a final chunk with usage details. Capture it:

usage = None
for chunk in stream:
    if chunk.usage:
        usage = chunk.usage
    # ... print content
print("Tokens:", usage.total_tokens if usage else "n/a")

If you consume the raw SSE stream via fetch, the gateway emits data: {json}\n\n lines exactly like OpenAI. Parse each data: payload and ignore [DONE].

Step 6: Preserve prompt caching and routing hints

If your direct integration used Anthropic prompt caching via cache_control blocks, you can keep the optimization. n4n.ai honors client routing directives and forwards provider cache-control hints, so the upstream cache still triggers. Attach cache control inside the message content where the model expects it, or send a gateway-specific header if documented.

messages = [
    {"role": "system", "content": "Long static context here..."},
    {"role": "user", "content": "Question about the context."}
]

client.chat.completions.create(
    model="anthropic/claude-opus-4.8",
    messages=messages,
    extra_headers={
        "x-n4n-route": "anthropic",
        "anthropic-cache-control": "ephemeral"
    }
)

The x-n4n-route directive forces the gateway to use Anthropic even if other providers serve the same model slug. The cache header is passed through to Anthropic’s API. Per-token usage metering still applies, so you will see cached input tokens separately in the usage object if the gateway exposes it. If you previously set anthropic-beta headers, move them to extra_headers as well; the gateway forwards unknown headers to the upstream provider.

Step 7: Verify the migration

Run a side-by-side test against a fixed input. Use a small script that calls both the old Anthropic client (if still available) and the new gateway client, then diff the textual output and tool calls.

curl -s https://api.n4n.ai/v1/chat/completions \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-opus-4.8",
    "max_tokens": 256,
    "messages": [{"role":"user","content":"Ping"}]
  }' | jq '.choices[0].message.content'

Success criteria:

  1. Response content matches the direct API within tolerance (Claude is deterministic only at temperature=0; expect minor variance).
  2. Streaming completes without truncated JSON.
  3. Tool calls return valid arguments parseable by your existing executor.
  4. The usage block reports prompt_tokens and completion_tokens; if you sent cache hints, prompt_tokens_details.cached_tokens is non-zero.
  5. No 429 from the gateway when you stay under your provisioned rate; if a provider degrades, the gateway returns a different model’s response or a clear error, not a silent timeout.

Add a CI check that hits the gateway with a golden prompt and asserts the response contains expected substrings. After verification, flip the API key in production, keep the Anthropic SDK as a fallback branch for a week, then delete it. The gateway’s automatic fallback means a single endpoint now covers Claude Opus 4.8 and every other model you might add later.

Tagsmigrationanthropicclaude-opusgateway

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All accessing claude opus 4.8 via gateway vs anthropic direct posts →