# Messages

> The Anthropic Messages format, used by Claude Code.

Creates a message in the Anthropic Messages format, the format Claude Code uses. Works with the official Anthropic SDKs.

## Method and path

`POST /v1/messages`

Anthropic SDKs take the base URL `https://api.relayvia.dev`. Send the key as `x-api-key: sk-...` or `Authorization: Bearer sk-...`, together with the `anthropic-version` header your SDK already sends.

## Common parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | `string` | Yes | Official model name. |
| `max_tokens` | `integer` | Yes | Output limit (required); it decides how much is reserved before the call. |
| `messages` | `array` | Yes | The conversation: user and assistant turns with text, image and document content. |
| `system` | `string \| array` | No | System prompt. |
| `stream` | `boolean` | No | `true` to receive Server-Sent Events. |
| `tools` | `array` | No | Tools your code runs: no `type`, or `type: custom`. |
| `tool_choice` | `object` | No | Whether the model must, may or must not call a tool. |
| `thinking` | `object` | No | Extended thinking settings. |
| `metadata` | `object` | No | `user_id` for abuse tracking. |

Requests are forwarded in the original format, so the original API's parameters work unless the next section says otherwise. This table lists the common ones; see the [official reference](https://docs.claude.com/en/api/messages) for the rest.

## Differences from the original API

- `tools[]` only accepts tools your code runs: no `type`, or `type: custom`. Server tools (web fetch, code execution and similar) and `mcp_servers` return 400 `unsupported_parameter`.
- Web search is not available. `web_search_*` tools are removed and the request runs without search. If web search is the only tool, as in Claude Code's WebSearch requests, or `tool_choice` names it, the request returns 400.
- Files from the Files API (`source.type: file`) and documents by URL (`source.type: url`) return 400 `unsupported_parameter`; send them as base64.
- `anthropic-beta` values Relayvia does not support are dropped without an error; the rest are forwarded.
- Message Batches (`/v1/messages/batches`) are not provided and return 400.
- `metadata.user_id` is scoped to your account before forwarding, so it is never shared with other accounts.
- Events are forwarded as they arrive, and `message_delta` carries the final output usage. A `: keepalive` comment is sent after every 15 seconds without data. `message.model` shows the name you sent.
- A non-streaming request with no response within 95 seconds returns 500 `upstream_timeout`; use streaming for long outputs. Or send the header `X-Robi-Keepalive: 1`: if there is no response within 30 seconds you get a 200 right away and a space every 15 seconds until the JSON arrives, and a later failure arrives as an error object in the body.

## Request examples

**cURL**

```bash
curl https://api.relayvia.dev/v1/messages \
  -H "x-api-key: sk-..." \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-opus-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello!"}]}'
```

**Python**

```python
import anthropic

client = anthropic.Anthropic(base_url="https://api.relayvia.dev", api_key="sk-...")
resp = client.messages.create(model="claude-opus-5", max_tokens=1024, messages=[{"role": "user", "content": "Hello!"}])
print(resp.content[0].text)
```

**JavaScript**

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

const client = new Anthropic({ baseURL: "https://api.relayvia.dev", apiKey: "sk-..." });
const resp = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello!" }],
});
console.log(resp.content[0].text);
```

## Response and streaming examples

**Response**

```json
{
  "id": "msg_abc123",
  "type": "message",
  "role": "assistant",
  "model": "claude-opus-5",
  "content": [
    {
      "type": "text",
      "text": "Hello! How can I help?"
    }
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 9,
    "output_tokens": 8
  }
}
```

**Streaming**

```text
event: message_start
data: {"type":"message_start","message":{"id":"msg_abc123","model":"claude-opus-5","usage":{"input_tokens":9,"output_tokens":1}}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello!"}}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":8}}

event: message_stop
data: {"type":"message_stop"}
```

## Errors this endpoint returns

| HTTP | code | When |
| --- | --- | --- |
| `400` | `invalid_request` | A parameter is invalid. If the model rejected it, the message is this sentence, or `Invalid value for parameter <param>.` with `param` set. |
| `400` | `unsupported_parameter` | The request uses something Relayvia does not offer: stored server-side state, a hosted tool or a remote file. `param` names it. |
| `400` | `invalid_media` | The model reports that an input image or file format is not supported. |
| `400` | `media_unreachable` | The model could not read the media URL you sent. |
| `400` | `content_policy` | The input did not pass the model's safety review. If the model does not say whether the input or the output was refused, the message is `Content did not pass safety review.` |
| `400` | `content_policy_output` | The model generated a result, but the result did not pass safety review. Your input was not the problem. |
| `401` | `invalid_api_key` | The API key does not exist, is disabled or has expired. |
| `402` | `insufficient_quota` | Not enough credits: the account's credits, the key's limit, the team member's limit or an account spend limit cannot cover the request, or the account is frozen. |
| `403` | `forbidden` | The key may not use this model, the source IP is not on the allowlist, or the account is disabled. |
| `404` | `model_not_found` | The model is not listed. |
| `413` | `request_too_large` | The request body is over 20 MB, or the model reports that the input exceeds its context or size limit (then with this sentence). |
| `429` | `rate_limited` | Too many requests: per IP, per key, per account, or too many requests in progress at once. |
| `500` | `model_price_error` | The model has no price yet, so it cannot be called. |
| `500` | `upstream_error` | Relayvia tried other model services and every attempt failed. |
| `503` | `no_available_channel` | No model service is available for this model right now. |
| `503` | `service_busy` | Every model service for this model reported being busy. |
| `500` | `upstream_timeout` | Every attempt timed out. |

Every code, its fixed message and what to do are on the [error codes](https://relayvia.dev/docs/errors) page.
