> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Native Claude protocol

> Call Messages through the gateway and validate blocks and stop reasons.

This guide calls the gateway using the native Claude Messages protocol. Confirm that your model and channel support `/v1/messages`; visibility in the model list does not establish native protocol support. `API_KEY` is a gateway token issued by your deployment, not an upstream provider credential.

## Address and request structure

| Item | Example convention |
| - | - |
| Request | `POST /v1/messages` |
| Root URL | `API_ROOT_URL` excludes `/v1` and may include a deployment path prefix |
| Authentication | `x-api-key: API_KEY` |
| Version | `anthropic-version: 2023-06-01` |
| Instructions and input | Top-level `system`; `user` / `assistant` in `messages` |
| Text output | Blocks with `type: text` in `content[]` |

The project authenticates the native header as a gateway token; the channel adapter supplies upstream credentials separately. Do not parse this response as Chat `choices` or Responses `output`. The project adapter uses the version above when the request omits it; this example sends it explicitly.

## Setup

Use Node.js 22.18+ or 24. Built-in `fetch` requires no Anthropic SDK. Set `API_KEY` in your terminal environment, then configure the root URL and an actual model ID:

```sh theme={null}
export API_ROOT_URL="https://gateway.example.com"
export API_MODEL="YOUR_MODEL_ID"
# API_KEY must already be set in the process environment.
```

Save the code below as `native-claude.mts` and run `node native-claude.mts`. From the documentation directory, run `node examples/native-claude.mts`. Success prints one model answer. The fixed `max_tokens: 256` is this example's output budget, subject to model support; it may be insufficient for a complete answer.

## TypeScript

Complete example:

```typescript theme={null}
const { API_ROOT_URL, API_KEY, API_MODEL } = process.env;
if (!API_ROOT_URL || !API_KEY || !API_MODEL) {
  throw new Error('Set API_ROOT_URL, API_KEY, and API_MODEL first.');
}

try {
  const response = await fetch(`${API_ROOT_URL.replace(/\/+$/, '')}/v1/messages`, {
    method: 'POST', redirect: 'error', signal: AbortSignal.timeout(60_000),
    headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, 'anthropic-version': '2023-06-01' },
    body: JSON.stringify({
      model: API_MODEL, max_tokens: 256,
      system: 'Answer briefly in plain text.',
      messages: [{ role: 'user', content: 'Say hello in one short sentence.' }],
    }),
  });
  if (!response.ok) {
    console.error(`Request failed (HTTP ${response.status}). No automatic retry.`);
    process.exitCode = 1;
  } else {
    const result = await response.json();
    if (result.type !== 'message' || result.role !== 'assistant' || result.stop_reason !== 'end_turn'
      || !Array.isArray(result.content) || result.content.length === 0) {
      throw new Error('No complete text response.');
    }
    const text = result.content.map((block: { type?: string; text?: string }) => {
      if (!block || block.type !== 'text' || typeof block.text !== 'string') {
        throw new Error('Unsupported content block.');
      }
      return block.text;
    }).join('');
    if (!text.trim()) throw new Error('Empty text response.');
    console.log(text);
  }
} catch {
  console.error('Claude request failed or did not produce complete plain text. No automatic retry.');
  process.exitCode = 1;
}
export {};
```

## Completion and extension boundaries

This example accepts only `stop_reason: end_turn` with text-only content. `max_tokens` may indicate truncation; `tool_use` requires a tool workflow. `pause_turn`, refusal, empty content, and other block types fail this plain-text example. HTTP 200 alone is insufficient. Redirects and automatic retries are disabled; upstream error bodies and credentials are not printed.

For multiple turns, preserve complete assistant content blocks before appending a user message. Flattening them to text loses tool or reasoning state. Native tools use `tools[].input_schema`; output uses `tool_use` with `id`, `name`, and `input`. Return a `tool_result` in a user message, linked by `tool_use_id`, rather than Chat's `role: tool`. Thinking may require signatures and block ordering to be preserved; this example neither enables nor replays thinking.

## Native streaming and token counting

Set `stream: true` on the same endpoint for Claude SSE: `message_start`, `content_block_start/delta/stop`, `message_delta`, and `message_stop`. Errors may arrive after HTTP 200. Tool arguments arrive as `input_json_delta.partial_json`; accumulate them by content block index and parse only after completion. Chat's `delta.tool_calls` parser is incompatible.

The runnable script here covers nonstreaming plain text. [Native SSE and tool loops](/en/guides/native-claude-streaming) provides Python argument assembly and thinking signature replay. Actual channels still need integration testing. [Streaming tools](/en/guides/streaming-tools) provides runnable Chat / Responses workflows, not a Claude event parser.

The project has not enabled `POST /v1/messages/count_tokens`. A provider SDK exposing token counting does not establish gateway support.

[Claude Messages reference](/en/api-reference/claude-messages) · [Error handling](/en/guides/errors)

Protocol references: [Anthropic Messages](https://platform.claude.com/docs/en/api/messages/create), [streaming events](https://platform.claude.com/docs/en/build-with-claude/streaming). Gateway routing and authentication are checked against this repository.

[阅读中文](/zh/guides/native-claude)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.