# Messages (Anthropic)

Source: https://aiand-docs-staging.pages.dev/api/messages/

```http
POST /v1/messages
```

Accepts requests in the [Anthropic Messages API](https://docs.anthropic.com/en/api/messages) format. Requests are translated to the internal OpenAI-compatible format, processed, and the response is translated back to the Anthropic format before returning.

This lets you use the Anthropic SDK with ai& without changing your code.

<Aside type="note">
  This is a compatibility adapter. For the full set of request parameters, use [Chat
  Completions](/api/chat-completions/) directly.
</Aside>

## Usage

<Tabs>
  <TabItem label="Python">
    ```python
    from anthropic import Anthropic

    client = Anthropic(
        api_key="sk-your-api-key",
        base_url="https://api.aiand.com",
    )

    message = client.messages.create(
        model="google/gemma-4-31b-it",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello!"}],
    )

    print(message.content[0].text)
    ```

  </TabItem>
  <TabItem label="JavaScript">
    ```javascript
    import Anthropic from "@anthropic-ai/sdk";

    const client = new Anthropic({
      apiKey: "sk-your-api-key",
      baseURL: "https://api.aiand.com",
    });

    const message = await client.messages.create({
      model: "google/gemma-4-31b-it",
      max_tokens: 1024,
      messages: [{ role: "user", content: "Hello!" }],
    });

    console.log(message.content[0].text);
    ```

  </TabItem>
</Tabs>

## How it works

1. Your request arrives in Anthropic Messages format
2. ai& translates it to OpenAI format and runs inference
3. The response is translated back to Anthropic format

Streaming is supported. Authentication uses the same `Authorization: Bearer` header as all other endpoints.

## Supported parameters

Standard Anthropic Messages API parameters are accepted. Parameters with no equivalent in the underlying model are silently ignored, with the exceptions below.

- **`tools`** — client tools (type `custom`, or no type) are translated to function tools. Anthropic's server-side tools (`web_search_*`, `web_fetch_*`, `code_execution_*`, `tool_search_*`, …) run inside Anthropic's API and cannot be served here. Anthropic-defined client tools (`bash_*`, `text_editor_*`, `computer_*`, …) carry no `input_schema` for a non-Anthropic model to follow; send them as custom tools instead. A request that includes any Anthropic-defined tool type is rejected with `400 invalid_request_error` naming the tool.
- **`tool_choice`** — `auto`, `any`, `none` and `tool` are mapped; `disable_parallel_tool_use: true` becomes `parallel_tool_calls: false`.
- **`output_config.effort` and `thinking`** — `output_config.effort` sets the reasoning level, and `thinking: { "type": "disabled" }` turns reasoning off on a named reasoning model that lists `none`; see [Reasoning](#reasoning) for the exceptions.
- **`tool_result` images** — on vision-capable models, images inside a tool result are handed to the model in a user message that follows the turn's tool results, each labelled with its `tool_use_id`; on text-only models the tool text carries an `[Image omitted…]` placeholder.

## Errors

Errors on this endpoint use Anthropic's envelope, `{"type":"error","error":{"type":"…","message":"…"},"request_id":"…"}`, with Anthropic's type names (`invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `rate_limit_error`, `api_error`, …). A request that exceeds the model's context window is a `400` whose message begins `prompt is too long` and carries the `capability_rejected: prompt_too_long` token, which is what Claude Code's automatic compaction looks for.

## Keep-alive

A streaming response that stays idle for 15 seconds receives a keep-alive: an SSE comment line before `message_start`, a `ping` event after it. Clients that count bytes to detect a stalled stream (Claude Code aborts after 300 seconds of silence) keep the connection through long reasoning spans.

## Reasoning

The `thinking` block does not set the reasoning level — `budget_tokens` names nothing on our effort scale, and neither does `enabled`, so depth is otherwise left to the model's own default. To control the level, send `reasoning_effort` with one of the model's published [`reasoning_efforts`](/models/catalog/), or Anthropic's own `output_config: { "effort": "…" }`, which Claude Code sends for `/effort`. An explicit `reasoning_effort` wins when both are present. Either is checked exactly as on `/v1/chat/completions`: a level the model doesn't list runs at the next listed level up, or the highest, which can cost more than the level you sent, a value outside the effort vocabulary is a `400` naming the field you sent, and the applied level comes back in [`X-Reasoning-Effort`](/reference/response-headers/).

`thinking: { "type": "disabled" }` is the one exception, because it does state something the scale can express: no reasoning at all. We apply it as `reasoning_effort: "none"` and return `X-Reasoning-Effort: none` to confirm it. It wins over `output_config.effort`, so a request that disables thinking is not billed at the effort in that field. On a reasoning model that publishes no `none` the request is a `400` naming `thinking`, with the same sentence an explicit `reasoning_effort` `none` gets. The error text is on [Errors](/errors/#turning-reasoning-off). Send `reasoning_effort` as well and that is what applies, leaving the block to control the response shape alone. A model without the `reasoning` capability ignores it, having nothing to suppress.

With the reserved [`auto`](/models/auto/) model the block sets no level either way. `auto` accepts no effort at all, so name a model directly if you need reasoning off. Claude Code's `output_config.effort` is ignored under `auto` rather than rejected.

What `thinking` does control is whether that reasoning is returned to you. Send `thinking: { "type": "enabled" }` — or `"adaptive"`, which is accepted identically — and the reasoning comes back as a `thinking` content block ahead of the text block, streaming as `thinking_delta` events. Omit it and you get the answer alone.

The `thinking` blocks we return carry no `signature` — nothing upstream signs them, so one replayed to Anthropic's own API is rejected. Echoing one back to ai& is safe: a `thinking` block on a request is dropped before it reaches the model.

Some reasoning models put their whole answer inside the reasoning and leave the message content empty. Those turns come back as an ordinary `text` block whether or not you asked for `thinking`, so a successful response always carries at least one content block.

## Image input

Image content blocks are supported on vision-capable models. All three Anthropic source types work — `base64`, `url`, and `file` (referencing a `file_id` from [`POST /v1/files`](/api/files/)). See [Anthropic's content blocks docs](https://docs.anthropic.com/en/api/messages#content-blocks) for the wire format. Requests against models without the `vision` capability are rejected with `400 invalid_request_error` before inference. A `url` source must meet the [URL requirements](/capabilities/vision/#url-requirements).

## Document input

PDF `document` blocks are supported on vision-capable models, with a `base64` source (`media_type: "application/pdf"`) or a `file` source referencing a `purpose: "document"` upload. Each page reaches the model as an image, and a PDF inside a `tool_result` is handled like a tool-result image. A `base64` PDF is stored as a `document` file and reused, with the same limits as an inline PDF on [Chat Completions](/api/chat-completions/), except under [zero data retention](/organizations/zero-data-retention/), where it is rendered in memory, not stored, and held to [its own limits](/organizations/zero-data-retention/#inline-pdfs). `text` and `content` sources are passed to the model as text. A `url` source is not fetched, but it must still meet the [URL requirements](/capabilities/vision/#url-requirements), or the request is rejected.
