# Anthropic-compatible API

https://modelportal.ai/docs/api-anthropic

Base URL `https://modelportal.ai`, authenticated with `x-api-key: YOUR_API_KEY` or `Authorization: Bearer YOUR_API_KEY`, plus an `anthropic-version` header (such as `2023-06-01`). Requests and responses follow the Anthropic Messages API. The old `/anthropic/*` addresses return 410 `request.endpoint_moved`.

## POST /v1/messages

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `model` | string | Yes | Model ID or alias; any text model in the catalog |
| `max_tokens` | integer | Yes | Maximum output |
| `messages` | array | Yes | Messages with text, images, `tool_use`, `tool_result` and thinking blocks |
| `system` | string or array | No | System prompt; content blocks may carry `cache_control` |
| `stream` | boolean | No | `true` returns SSE with the official events, ending in `message_stop` |
| `thinking` | object | No | See [Reasoning](/docs/reasoning) |
| `tools`, `tool_choice` | — | No | Tool use |
| `temperature`, `top_p`, `top_k` | number | No | Sampling parameters |
| `metadata.user_id` | string | No | Used as the session identifier for affinity, removed before forwarding |

Pass routing preferences in the `X-ModelPortal-Provider-Preferences` header. The `anthropic-beta` header is forwarded upstream.

```json
{
  "id": "msg_...",
  "type": "message",
  "role": "assistant",
  "model": "MODEL_ID",
  "content": [{"type": "text", "text": "Hello!"}],
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 9, "output_tokens": 4, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cost": 0.000013},
  "provider": "PROVIDER_ID",
  "attempts": [{"provider": "PROVIDER_ID", "endpoint": "ENDPOINT_ID", "status": 200, "cost": 0.000013}]
}
```

`usage` is the billed usage: `input_tokens` excludes the cache, cache writes and reads are listed separately, and `output_tokens` includes reasoning. `usage.cost` is the charge for this call (USD); `provider` and `attempts` name the provider that served it and each attempt. When streaming, these three are in the `message_delta` event. Response headers are in the [API overview](/docs/api-overview).

## POST /v1/messages/count_tokens

Counts input tokens without charging or holding balance. Fields as in `/v1/messages`; only `model` and `messages` are needed.

## GET /v1/models

With the `anthropic-version` header, returns the models this key may call in the Anthropic format.

## Errors

Error bodies are `{"type": "error", "error": {"type", "message"}}`, with no machine-readable code:

| HTTP | `error.type` | Platform codes |
| --- | --- | --- |
| 400 | `invalid_request_error` | `invalid_request`, `unsupported_parameter` |
| 401 | `authentication_error` | `invalid_api_key`, `key_inactive` |
| 402 | `billing_error` | `insufficient_balance`, `budget_exceeded` |
| 403 | `permission_error` | `key_restricted`, `request_blocked`, `region_restricted`, `verification_required` |
| 404 / 410 | `not_found_error` | `model_not_found`, `model_retired`, `request.endpoint_moved` |
| 422 | `invalid_request_error` | `no_eligible_endpoint`, with exclusion reasons in `error.excluded` |
| 429 | `rate_limit_error` | `rate_limited`, `capacity_exhausted` |
| 502 | `api_error` | `upstream_failed` |

When the `x-should-retry` response header is `true` (429 and 5xx), retry with backoff. All codes are in [Common errors](/docs/errors).
