EZAT RouterDocs

curl

Copy-paste curl requests for both API formats - a plain request, streaming, a tool call round trip, an image, counting tokens, listing models and handling errors.

When to use this page

You want to try the API from a terminal, or see the exact HTTP request behind an SDK call. The same requests in code are on Node.js and Python.

Before you start

Create a key in the dashboard (https://router.ezat.io/keys) and put it in an environment variable:

export EZAT_API_KEY="ezat_sk_…"

Every request here uses one of two formats. Pick the one your code or tool speaks:

FormatEndpointKey header
Anthropichttps://api.router.ezat.io/v1/messagesx-api-key: $EZAT_API_KEY (or Authorization: Bearer)
OpenAIhttps://api.router.ezat.io/openai/v1/chat/completionsAuthorization: Bearer $EZAT_API_KEY

--fail-with-body makes curl exit with an error on any HTTP error and still print the error body. anthropic/claude-sonnet-5-5 is the model these samples use; any id from Models works.

A plain request

curl -sS --fail-with-body https://api.router.ezat.io/v1/messages \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_tokens": 64,
    "messages": [{"role": "user", "content": "Say hello in five words."}]
  }'

The response has the text and a usage object with the token counts you are charged for. See Pricing for what each count costs.

Streaming

Add "stream": true. -N makes curl print each event as it arrives.

curl -sS -N --fail-with-body https://api.router.ezat.io/v1/messages \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_tokens": 64,
    "stream": true,
    "messages": [{"role": "user", "content": "Count from one to five."}]
  }'

The Anthropic format sends message_start, content_block_delta, message_delta and message_stop events, with ping events during long pauses. The OpenAI format sends chat.completion.chunk events and ends with data: [DONE]; with include_usage, a chunk with the usage and empty choices comes just before it.

A tool call round trip

Step 1: offer the tool

curl -sS --fail-with-body https://api.router.ezat.io/v1/messages \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_tokens": 64,
    "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."}]
  }'

The model answers with a tool call: a tool_use block and "stop_reason": "tool_use" in the Anthropic format, or tool_calls and "finish_reason": "tool_calls" in the OpenAI format.

Step 2: send the result back

Run the tool yourself, then send the whole conversation again: your message, the model's tool call, and the result. Use the tool call's own id where these samples have toolu_example or call_example.

curl -sS --fail-with-body https://api.router.ezat.io/v1/messages \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_tokens": 64,
    "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."},
      {"role": "assistant", "content": [
        {"type": "tool_use", "id": "toolu_example", "name": "get_weather", "input": {"city": "Paris"}}
      ]},
      {"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": "toolu_example", "content": "Sunny and 18 degrees Celsius"}
      ]}
    ]
  }'

The model now answers in text, using the result.

An image

Images go inline as base64. This one is an 8×8 red PNG. For a real file, write the body to a file and send it with -d @body.json, since a large image doesn't fit on a command line.

curl -sS --fail-with-body https://api.router.ezat.io/v1/messages \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_tokens": 64,
    "messages": [{"role": "user", "content": [
      {"type": "image", "source": {"type": "base64", "media_type": "image/png",
        "data": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR42mP4z8CAFTEMLQkAKP8/wc53yE8AAAAASUVORK5CYII="}},
      {"type": "text", "text": "What colour is this image? Answer in one word."}
    ]}]
  }'

JPEG, PNG, GIF and WebP work. The OpenAI format also takes an https:// URL in image_url, which the gateway downloads (up to 10 MB per image).

Count tokens

Anthropic format only. Counting is free and isn't charged, but it counts toward the key's requests per minute.

curl -sS --fail-with-body https://api.router.ezat.io/v1/messages/count_tokens \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "messages": [{"role": "user", "content": "Say hello in five words."}]
  }'

The response is {"input_tokens": …}.

List models

curl -sS --fail-with-body https://api.router.ezat.io/v1/models \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01"

These list the models your key can call. For every model with its prices, without a key, use curl -sS https://api.router.ezat.io/api/models. See Models.

Handle errors

This request asks for a model that doesn't exist, so it fails with 404. -D headers.txt saves the response headers, which carry the ids support asks for.

if curl -sS --fail-with-body -D headers.txt https://api.router.ezat.io/v1/messages \
  -H "x-api-key: $EZAT_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "anthropic/no-such-model", "max_tokens": 16, "messages": [{"role": "user", "content": "Hi"}]}'
then
  echo "Expected an error" >&2
  exit 1
fi
echo
grep -iE '^(HTTP/|request-id|x-request-id|retry-after)' headers.txt

curl prints the error body, for example {"type":"error","error":{"type":"not_found_error","message":"model: anthropic/no-such-model"},…}, and exits with code 22. In a script, retry only on 429, 500, 503 and 529, after the retry-after seconds when there is one. Every status and type is on Errors.

For AI agents

  1. Read the key from $EZAT_API_KEY; never paste a key into a command.
  2. Anthropic format: POST https://api.router.ezat.io/v1/messages with x-api-key, and max_tokens is required. OpenAI format: POST https://api.router.ezat.io/openai/v1/chat/completions with Authorization: Bearer.
  3. Use --fail-with-body in scripts so HTTP errors stop the script and still show the error body.
  4. Get model ids from GET https://api.router.ezat.io/v1/models instead of guessing them.

On this page