# curl

URL: https://docs.router.ezat.io/examples/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](https://docs.router.ezat.io/examples/node.md) and [Python](https://docs.router.ezat.io/examples/python.md).

## Before you start

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

```bash
export EZAT_API_KEY="ezat_sk_…"
```

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

| Format | Endpoint | Key header |
| --- | --- | --- |
| Anthropic | `https://api.router.ezat.io/v1/messages` | `x-api-key: $EZAT_API_KEY` (or `Authorization: Bearer`) |
| OpenAI | `https://api.router.ezat.io/openai/v1/chat/completions` | `Authorization: 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](https://docs.router.ezat.io/models.md) works.

## A plain request

```bash tab="Anthropic format" test
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."}]
  }'
```

```bash tab="OpenAI format" test
curl -sS --fail-with-body https://api.router.ezat.io/openai/v1/chat/completions \
  -H "Authorization: Bearer $EZAT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_completion_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](https://docs.router.ezat.io/pricing.md) for what each count costs.

## Streaming

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

```bash tab="Anthropic format" test
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."}]
  }'
```

```bash tab="OpenAI format" test
curl -sS -N --fail-with-body https://api.router.ezat.io/openai/v1/chat/completions \
  -H "Authorization: Bearer $EZAT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_completion_tokens": 64,
    "stream": true,
    "stream_options": {"include_usage": 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

```bash tab="Anthropic format" test
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."}]
  }'
```

```bash tab="OpenAI format" test
curl -sS --fail-with-body https://api.router.ezat.io/openai/v1/chat/completions \
  -H "Authorization: Bearer $EZAT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_completion_tokens": 64,
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather in a city.",
        "parameters": {"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`.

```bash tab="Anthropic format" test
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"}
      ]}
    ]
  }'
```

```bash tab="OpenAI format" test
curl -sS --fail-with-body https://api.router.ezat.io/openai/v1/chat/completions \
  -H "Authorization: Bearer $EZAT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_completion_tokens": 64,
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather in a city.",
        "parameters": {"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": null, "tool_calls": [
        {"id": "call_example", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\": \"Paris\"}"}}
      ]},
      {"role": "tool", "tool_call_id": "call_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.

```bash tab="Anthropic format" test
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."}
    ]}]
  }'
```

```bash tab="OpenAI format" test
curl -sS --fail-with-body https://api.router.ezat.io/openai/v1/chat/completions \
  -H "Authorization: Bearer $EZAT_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5-5",
    "max_completion_tokens": 64,
    "messages": [{"role": "user", "content": [
      {"type": "image_url", "image_url": {"url": "data:image/png;base64,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.

```bash test
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

```bash tab="Anthropic format" test
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"
```

```bash tab="OpenAI format" test
curl -sS --fail-with-body https://api.router.ezat.io/openai/v1/models \
  -H "Authorization: Bearer $EZAT_API_KEY"
```

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](https://docs.router.ezat.io/models.md).

## 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.

```bash tab="Anthropic format" test
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
```

```bash tab="OpenAI format" test
if curl -sS --fail-with-body -D headers.txt https://api.router.ezat.io/openai/v1/chat/completions \
  -H "Authorization: Bearer $EZAT_API_KEY" \
  -H "content-type: application/json" \
  -d '{"model": "anthropic/no-such-model", "max_completion_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](https://docs.router.ezat.io/reference/errors.md).

## 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.