---
title: Messages
---

# Messages

`POST /v1/messages`

Generate a reply using the Anthropic Messages protocol. Send a list of conversation messages and the model returns the next `assistant` message. Ideal when you're using the Anthropic SDK or Claude Code.

<Callout type="info">
  This endpoint is compatible with the Anthropic Messages SDK. Point `base_url` at CrossModel and set `model` to a CrossModel model ID. Auth uses the `x-api-key` header.
</Callout>

## Endpoint

```http
POST https://api.crossmodel.ai/v1/messages
x-api-key: cm-YOUR_KEY
anthropic-version: 2023-06-01
Content-Type: application/json
```

Each request needs an API key in the `x-api-key` header (`Authorization: Bearer` also works). Create and manage keys on the console's **API Keys** page.

**Create message**

```bash
curl https://api.crossmodel.ai/v1/messages \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -H "x-api-key: $CROSSMODEL_API_KEY" \
  -d '{
    "model": "anthropic/claude-sonnet-4.6",
    "max_tokens": 1024,
    "system": "You are a helpful assistant.",
    "messages": [
      { "role": "user", "content": "Hello!" }
    ]
  }'
```

```typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.CROSSMODEL_API_KEY,
  baseURL: "https://api.crossmodel.ai",
});

const message = await client.messages.create({
  model: "anthropic/claude-sonnet-4.6",
  max_tokens: 1024,
  system: "You are a helpful assistant.",
  messages: [
    { role: "user", content: "Hello!" },
  ],
});

console.log(message.content);
```

```python
import os
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ["CROSSMODEL_API_KEY"],
    base_url="https://api.crossmodel.ai",
)

message = client.messages.create(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    system="You are a helpful assistant.",
    messages=[
        {"role": "user", "content": "Hello!"},
    ],
)

print(message.content)
```

**Stream message**

```bash
curl https://api.crossmodel.ai/v1/messages \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -H "x-api-key: $CROSSMODEL_API_KEY" \
  -d '{
    "model": "anthropic/claude-sonnet-4.6",
    "max_tokens": 1024,
    "messages": [{ "role": "user", "content": "Tell me a story." }],
    "stream": true
  }'
```

```typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.CROSSMODEL_API_KEY,
  baseURL: "https://api.crossmodel.ai",
});

const stream = await client.messages.create({
  model: "anthropic/claude-sonnet-4.6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Tell me a story." }],
  stream: true,
});

for await (const event of stream) {
  if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
    process.stdout.write(event.delta.text);
  }
}
```

```python
import os
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ["CROSSMODEL_API_KEY"],
    base_url="https://api.crossmodel.ai",
)

with client.messages.stream(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Tell me a story."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="")
```

**Use a tool**

```bash
curl https://api.crossmodel.ai/v1/messages \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -H "x-api-key: $CROSSMODEL_API_KEY" \
  -d '{
    "model": "anthropic/claude-sonnet-4.6",
    "max_tokens": 1024,
    "messages": [{ "role": "user", "content": "What is the weather in Shanghai?" }],
    "tools": [
      {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "input_schema": {
          "type": "object",
          "properties": {
            "city": { "type": "string" }
          },
          "required": ["city"]
        }
      }
    ]
  }'
```

```typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.CROSSMODEL_API_KEY,
  baseURL: "https://api.crossmodel.ai",
});

const message = await client.messages.create({
  model: "anthropic/claude-sonnet-4.6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "What is the weather in Shanghai?" }],
  tools: [
    {
      name: "get_weather",
      description: "Get current weather for a city.",
      input_schema: {
        type: "object",
        properties: { city: { type: "string" } },
        required: ["city"],
      },
    },
  ],
});
```

```python
import os
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ["CROSSMODEL_API_KEY"],
    base_url="https://api.crossmodel.ai",
)

message = client.messages.create(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "What is the weather in Shanghai?"}],
    tools=[
        {
            "name": "get_weather",
            "description": "Get current weather for a city.",
            "input_schema": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        }
    ],
)
```

## Request parameters

| Parameter | Type | Required | Notes |
|------|------|------|------|
| `model` | string | Yes | The model ID to use, e.g. `anthropic/claude-sonnet-4.6`. Call `/v1/models` for the full list. |
| `messages` | array | Yes | The conversation so far, in chronological order, with at least one entry. See [Message object](#message-object) below. |
| `max_tokens` | integer | Yes | Max tokens to generate for this reply. |
| `system` | string or array | No | A system prompt that sets the model's behavior and persona. |
| `temperature` | number | No | Sampling temperature, typically `0`–`1`. Higher is more random and varied. |
| `top_p` | number | No | Nucleus-sampling threshold. Usually adjust either `temperature` or `top_p`, not both. |
| `top_k` | integer | No | Sample only from the K highest-probability candidates. |
| `stop_sequences` | string[] | No | Custom stop sequences. Generation halts at the first match. |
| `tools` | array | No | The tools the model may call. See [Function tools](#function-tools) below. |
| `tool_choice` | object | No | Controls whether and how the model calls tools. |
| `metadata` | object | No | Request metadata; may include `user_id` for safety and abuse detection. |
| `thinking` | object | No | Extended-thinking config. `type` is `enabled`, `disabled`, or `adaptive`; when `enabled`, provide `budget_tokens`. The effect depends on the model. |
| `stream` | boolean | No | Whether to stream the result. Default `false`. |

## Message object

`messages` is a chronological array with at least one message.

| Field | Type | Required | Notes |
|------|------|------|------|
| `role` | string | Yes | The sender's role; only `user` and `assistant`. Put the system prompt in the top-level `system` field, not in `messages`. |
| `content` | string or array | Yes | The message content. Use a string for plain text; use a content-block array for images or tool content (see [Content blocks](#content-blocks)). |

## Content blocks

When `content` is an array, each element is a content block with a `type` field:

| type | Fields | Notes |
|------|------|------|
| `text` | `text` | A piece of text. |
| `image` | `source` | Image input. `source` is `{ "type": "base64", "media_type": ..., "data": ... }` or `{ "type": "url", "url": ... }`. |
| `tool_use` | `id`, `name`, `input` | A tool call issued by the `assistant`. |
| `tool_result` | `tool_use_id`, `content`, `is_error` | The result of running a tool; `tool_use_id` points at the corresponding `tool_use`. |

<Callout type="warning">
  Document and PDF input (the `document` content block) isn't supported yet.
</Callout>

## Function tools

Declare functions the model can call via the `tools` field. The model returns a `tool_use` content block when needed; your code runs it and passes the result back as a `tool_result` content block.

```json
{
  "tools": [
    {
      "name": "get_weather",
      "description": "Get current weather for a city.",
      "input_schema": {
        "type": "object",
        "properties": {
          "city": { "type": "string" }
        },
        "required": ["city"]
      }
    }
  ]
}
```

| Field | Type | Required | Notes |
|------|------|------|------|
| `name` | string | Yes | The function name the model uses to reference the tool. |
| `description` | string | No | What the function does. A clear description helps the model decide when to call it. |
| `input_schema` | object | No | JSON Schema for the function's parameters. |

## Example request

```bash
curl https://api.crossmodel.ai/v1/messages \
  -H "x-api-key: cm-YOUR_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.6",
    "max_tokens": 1024,
    "system": "You are a helpful assistant.",
    "messages": [
      { "role": "user", "content": "Hello!" }
    ]
  }'
```

## Response

A non-streaming request returns a `message` object.

```json
{
  "id": "msg_abc123",
  "type": "message",
  "role": "assistant",
  "model": "anthropic/claude-sonnet-4.6",
  "content": [
    { "type": "text", "text": "Hello! How can I help?" }
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 18,
    "output_tokens": 14,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0
  }
}
```

| Field | Type | Notes |
|------|------|------|
| `id` | string | A unique ID for this reply. |
| `type` | string | Always `message`. |
| `role` | string | Always `assistant`. |
| `model` | string | The model ID used for this request. |
| `content` | array | The generated content blocks. |
| `stop_reason` | string | Why it stopped — see below. |
| `stop_sequence` | string or null | The stop sequence that was matched, or `null` if none. |
| `usage` | object | Token usage for this request, used for billing. |

### Response content blocks

In the response `content` array, each element is a content block with a `type` field:

| type | Fields | Notes |
|------|------|------|
| `text` | `text` | The generated text. |
| `tool_use` | `id`, `name`, `input` | A function call issued by the model. `input` is the arguments object. |

### stop_reason

| Value | Notes |
|------|------|
| `end_turn` | The model finished its reply naturally. |
| `max_tokens` | Hit the `max_tokens` cap. |
| `stop_sequence` | Matched a stop sequence. |
| `tool_use` | The model switched to issuing a tool call. |

### The usage object

| Field | Type | Notes |
|------|------|------|
| `input_tokens` | integer | Input tokens. |
| `output_tokens` | integer | Output tokens. |
| `cache_creation_input_tokens` | integer | Input tokens written to the cache. |
| `cache_read_input_tokens` | integer | Input tokens that hit the cache. |

## Streaming response

Set `stream` to `true` and the endpoint returns `text/event-stream`. A reply is made up of these events, in order:

| Event | Notes |
|------|------|
| `message_start` | The message begins, with initial message info. |
| `content_block_start` | A content block begins. |
| `content_block_delta` | An increment of a content block (`text_delta`, `input_json_delta`, or `thinking_delta`). |
| `content_block_stop` | A content block ends. |
| `message_delta` | Carries `stop_reason` and `usage`. |
| `message_stop` | The message ends. |

```bash
curl https://api.crossmodel.ai/v1/messages \
  -H "x-api-key: cm-YOUR_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.6",
    "max_tokens": 1024,
    "messages": [{ "role": "user", "content": "Hi" }],
    "stream": true
  }'
```

## Errors

Errors use the Anthropic format:

```json
{
  "type": "error",
  "error": {
    "type": "authentication_error",
    "message": "Missing API key."
  }
}
```

| HTTP status | `error.type` | Notes |
|------------|--------------|------|
| `400` | `invalid_request_error` | Malformed body JSON, message structure, or parameters. |
| `401` | `authentication_error` | API key missing or invalid. |
| `402` | `permission_error` | Out of balance or not permitted. |
| `404` | `invalid_request_error` | The requested model doesn't exist or is currently unavailable. |
| `429` | `rate_limit_error` | Too many requests — rate limited. |
| `502` | `api_error` | The model service returned an error or an abnormal response. |
| `503` | `overloaded_error` | Model temporarily unavailable — retry shortly. |
| `500` | `api_error` | A CrossModel internal error. |
