EZAT RouterDocs

Python

Python examples with the Anthropic and OpenAI SDKs - a plain request, streaming, a tool call round trip, an image, counting tokens, listing models and handling errors.

When to use this page

You are calling EZAT Router from Python with the official Anthropic SDK (anthropic) or OpenAI SDK (openai). The raw HTTP requests are on curl.

Set up

Install the SDK you want (or both), and put your key in an environment variable:

pip install anthropic openai
export EZAT_API_KEY="ezat_sk_…"

Each SDK needs only its base URL changed:

SDKbase_urlEndpoint it calls
anthropichttps://api.router.ezat.io/v1/messages
openaihttps://api.router.ezat.io/openai/v1/openai/v1/chat/completions

Both SDKs also read the base URL and key from the environment, which is handy for tools you don't write yourself: ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY, or OPENAI_BASE_URL and OPENAI_API_KEY.

A plain request

import os

import anthropic

client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])

message = client.messages.create(
    model="anthropic/claude-sonnet-5-5",
    max_tokens=64,
    messages=[{"role": "user", "content": "Say hello in five words."}],
)
for block in message.content:
    if block.type == "text":
        print(block.text)
print(message.usage)
print("request-id:", message._request_id)

usage has the token counts you are charged for; see Pricing.

Streaming

import os

import anthropic

client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])

with client.messages.stream(
    model="anthropic/claude-sonnet-5-5",
    max_tokens=64,
    messages=[{"role": "user", "content": "Count from one to five."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    message = stream.get_final_message()
print()
print(message.usage)

A tool call round trip

The model asks for a tool, your code runs it, and you send the result back with the conversation so far.

import os

import anthropic

client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])

tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather in a city.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    }
]
messages = [{"role": "user", "content": "What is the weather in Paris? Use the get_weather tool."}]

first = client.messages.create(model="anthropic/claude-sonnet-5-5", max_tokens=64, tools=tools, messages=messages)
call = next((block for block in first.content if block.type == "tool_use"), None)
if call is None:
    raise SystemExit(f"No tool call (stop_reason: {first.stop_reason})")
print("Tool call:", call.name, call.input)

# Run the tool, then send its result back.
messages.append({"role": "assistant", "content": first.content})
messages.append(
    {
        "role": "user",
        "content": [
            {"type": "tool_result", "tool_use_id": call.id, "content": "Sunny and 18 degrees Celsius"}
        ],
    }
)

second = client.messages.create(model="anthropic/claude-sonnet-5-5", max_tokens=64, tools=tools, messages=messages)
for block in second.content:
    if block.type == "text":
        print(block.text)

An image

Images go inline as base64. This one is an 8×8 red PNG; for a file of your own, use base64.b64encode(open("photo.png", "rb").read()).decode().

import os

import anthropic

client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])

png = "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR42mP4z8CAFTEMLQkAKP8/wc53yE8AAAAASUVORK5CYII="

message = client.messages.create(
    model="anthropic/claude-sonnet-5-5",
    max_tokens=64,
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": png}},
                {"type": "text", "text": "What colour is this image? Answer in one word."},
            ],
        }
    ],
)
for block in message.content:
    if block.type == "text":
        print(block.text)

Count tokens

Anthropic SDK only. Counting is free, but it counts toward the key's requests per minute.

import os

import anthropic

client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])

count = client.messages.count_tokens(
    model="anthropic/claude-sonnet-5-5",
    messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(count.input_tokens)

List models

These list the models your key can call. See Models for the public catalog with prices.

import os

import anthropic

client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])

for model in client.models.list():
    print(model.id, model.display_name)

Handle errors

Each SDK raises its own exception classes. The SDKs already retry connection errors, 429 and 5xx twice, so an exception you catch has run out of retries (raise max_retries on the client to try longer). This request asks for a model that doesn't exist, so it fails with 404.

import os

import anthropic

client = anthropic.Anthropic(base_url="https://api.router.ezat.io", api_key=os.environ["EZAT_API_KEY"])

try:
    client.messages.create(
        model="anthropic/no-such-model",
        max_tokens=16,
        messages=[{"role": "user", "content": "Hi"}],
    )
except anthropic.APIConnectionError as error:
    raise SystemExit(f"Could not reach the API: {error}")
except anthropic.RateLimitError as error:
    print("Rate limited. Retry after", error.response.headers.get("retry-after"), "seconds")
except anthropic.APIStatusError as error:
    print("status:", error.status_code, "type:", error.type)
    # request-id is set once a request reaches a model; x-request-id is on every response.
    print("request-id:", error.request_id)
    print("x-request-id:", error.response.headers.get("x-request-id"))
Anthropic SDKOpenAI SDKStatus
BadRequestErrorBadRequestError400
AuthenticationErrorAuthenticationError401
APIStatusError with status_code 402APIStatusError with status_code 402402: add credit
PermissionDeniedErrorPermissionDeniedError403
NotFoundErrorNotFoundError404
RequestTooLargeErrorAPIStatusError with status_code 413413
RateLimitErrorRateLimitError429
InternalServerErrorInternalServerError500 and above
OverloadedErrorInternalServerErrorOverloaded: 529 (Anthropic format), 503 (OpenAI format)
APIConnectionErrorAPIConnectionErrorNo response: network problem or timeout

What each status means and whether to retry it is on Errors.

For AI agents

  1. Set base_url to https://api.router.ezat.io for anthropic, or https://api.router.ezat.io/openai/v1 for openai, and read the key from os.environ["EZAT_API_KEY"].
  2. Use model ids from GET https://api.router.ezat.io/v1/models; don't invent them.
  3. max_tokens is required with the Anthropic SDK.
  4. Don't add your own retry loop on top of the SDK's for 429 and 5xx unless you need more than its two retries; raise max_retries instead.
  5. Log request-id (or x-request-id) from errors so the user can give it to support@ezat.io.

On this page