Skip to content
Docs navigation: Open models / Messages

Messages

Open modelsMessages Updated

On this page
ConnectionValue
API basehttps://api.runinfra.ai
Workspace keyrp_...
Process variableRUNINFRA_GATEWAY_KEY
BillingThe existing funding rules of your open-model account.

Replace MODEL_ID with an available id from Open models; read current capabilities, limits and prices from GET /v1/models.

Text
POST https://api.runinfra.ai/v1/messages

POST /v1/messages accepts the Messages request and response grammar. It is served by the same models as POST /v1/chat/completions, with the same billing, credits, idempotency, rate limits, request-size limits and cached-input pricing. The envelope, content blocks, tool loop, thinking controls, and stream events follow the Messages-compatible contract on this page.

This API supports the Messages contract on this page. Protocol compatibility does not establish parity with the originating provider's models or every client-platform feature.

Authentication and headers

Send your open-model workspace API key in either authentication header on /v1/messages, /v1/messages/count_tokens, /v1/models and /v1/models/{model}.

HeaderBehavior
x-api-key: $RUNINFRA_GATEWAY_KEYAccepted on those four routes. A non-blank value wins when both authentication headers are present.
Authorization: Bearer $RUNINFRA_GATEWAY_KEYAccepted on those four routes and required on every other endpoint.

The response carries both request-id and x-request-id. Quote either value when you contact support.

Compatibility headerBehavior
anthropic-versionOptional. The response echoes the value you send. It does not select a different grammar.
anthropic-betaAccepted and ignored.
Claude Code headersx-claude-code-session-id and x-claude-code-agent-id route the session, as described in Coding agents. Other Claude Code headers are accepted and ignored.

Idempotency-Key protects a /v1/messages retry from duplicate inference and billing. x-session-id uses the same routing-affinity behavior as the chat-compatible routes.

Create a message

cURL
curl https://api.runinfra.ai/v1/messages \
  -H "x-api-key: $RUNINFRA_GATEWAY_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model":"MODEL_ID","max_tokens":16384,"messages":[{"role":"user","content":"Explain exponential backoff."}]}'

Give the Messages protocol SDK the bare host, https://api.runinfra.ai. The SDK appends /v1 itself. chat API clients use https://api.runinfra.ai/v1.

Request fields

FieldRequiredBehavior
modelyesA model id available to your key. List ids with GET /v1/models.
max_tokensyesAn integer of at least 1. Reasoning spends this budget before the visible answer. 0 is refused.
messagesyesAt least one user or assistant message. Consecutive user or assistant messages are merged into one turn. A system message inside messages keeps its position; on models that accept a system message only first, the gateway moves it as described in System message placement.
systemnoA string or an array of text blocks. Array blocks remain separate prompt parts. A block-level cache_control value is ignored.
toolsnoClient tool definitions with name, optional description, and input_schema.
tool_choicenoauto, none, any, or a named tool. Its optional disable_parallel_tool_use field controls whether the model may request several client tools in one turn.
thinkingnoEnables, adapts, or disables model reasoning as described below.
output_confignoSelects a supported reasoning effort or a JSON schema format.
metadata.user_idnoForwarded as user; send x-session-id for explicit routing. See Session affinity.
stop_sequencesnoPassed through as stop strings. The matched value is not reported in the response.
temperaturenoPassed through.
top_pnoPassed through.
top_knoAccepted and ignored.
streamnotrue returns event-named Server-Sent Events.

Unknown top-level fields are ignored. This keeps newer Messages protocol client request shapes usable without forwarding unsupported controls to the model.

Message content blocks

A message content value can be a string or an array of supported blocks.

BlockAllowed roleBehavior
textuser, assistantPassed as text.
imageuserAccepts a base64 source with image/png, image/jpeg, image/gif, or image/webp, or source: {"type": "url", "url": "https://..."}, on a model whose page lists image input. Public HTTP and HTTPS images are downloaded by the open-models service's gateway under the image limits.
tool_useassistantCarries the tool call id, name, and parsed input.
tool_resultuserCarries a matching tool_use_id, string content or text and image blocks, and optional is_error. Tool results must come before other blocks in that user message.
thinkingprior assistantAccepted with any signature, then dropped before the prior turn reaches the model.
redacted_thinkingprior assistantAccepted and dropped before the prior turn reaches the model.

An image source with type: "file" returns 400 invalid_request_error. A document block or another unsupported Messages protocol-only block also returns 400 and names the block type.

URL images must be PNG, JPEG, WebP or a non-animated GIF. The gateway image limits apply: 20 MiB transferred and decoded per remote image, 50 MiB of expanded image data URLs per request containing remote images, and a 54 MiB expanded body. Inline images count toward that aggregate when remote images are present. Existing inline GIF input is unchanged. Image download refusals use this API's Messages error envelope.

Tool use

Declare client tools with JSON Schema input. Fields such as strict, defer_loading, cache_control, and input_examples are accepted inside a tool definition and ignored.

JSON
{
  "model": "MODEL_ID",
  "max_tokens": 16384,
  "messages": [
    { "role": "user", "content": "What is the weather in Amman?" }
  ],
  "tools": [
    {
      "name": "get_weather",
      "description": "Read the current weather for a city.",
      "input_schema": {
        "type": "object",
        "properties": {
          "city": { "type": "string" }
        },
        "required": ["city"],
        "additionalProperties": false
      }
    }
  ],
  "tool_choice": { "type": "auto" }
}

A tool call returns a tool_use content block and stop_reason: "tool_use". Send the result in the next user message before any text or image blocks.

JSON
{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01H7JQ",
      "content": [{ "type": "text", "text": "18 C and clear" }]
    },
    { "type": "text", "text": "Summarize that in one sentence." }
  ]
}

tool_choice accepts these forms:

ValueBehavior
{ "type": "auto" }The model decides whether to call a tool.
{ "type": "none" }The model does not call a tool.
{ "type": "tool", "name": "get_weather" }The model must call the named tool.
{ "type": "any" }The model must call a tool. On models whose page says forced tool calling is limited, this value with several tools is refused. Use auto or name one tool.

Add "disable_parallel_tool_use": true inside tool_choice when the model must request at most one tool in that turn.

Messages protocol server tools, including bash, text editor, web search, and computer use, are refused. This route supports client tools that your application executes.

Thinking and structured output

Set thinking.type to enabled or adaptive to keep model reasoning on. Set it to disabled to turn reasoning off on a model that supports disabling it.

On some models, either enabled or adaptive turns reasoning on even though the model's default is not to reason.

Some models cannot disable reasoning. The request still succeeds, the setting is ignored, and X-RunInfra-Hint-Warnings says thinking cannot be disabled on this model.

The model-specific source reference identifies the models with these reasoning rules. Check current availability before selecting one.

thinking.budget_tokens is accepted and ignored. Reasoning spends the same max_tokens budget as the final answer.

Use output_config.effort with none, minimal, low, medium, high, xhigh, or max to select the reasoning effort. A value the model does not support can be moved to the nearest one it does, and X-RunInfra-Hint-Warnings then carries reasoning_effort_clamped. Use output_config.format with type: "json_schema" to request structured output.

Accepted compatibility fields

These Messages protocol-only fields are accepted and ignored: cache_control, service_tier, container, mcp_servers, context_management, inference_geo, and speed. They do not change routing, billing, or model behavior.

Response

The content array preserves this order when the parts exist: a thinking block, a text block, then tool_use blocks.

The thinking signature is opaque. It is not a protocol attestation. You may send it back in a prior assistant turn, where it is accepted and dropped.

FieldBehavior
stop_reasonend_turn, max_tokens, tool_use, or null.
stop_sequenceAlways null. The model honors a matching sequence, but the response does not report which one matched.
usage.input_tokensPrompt tokens not served from cache.
usage.cache_read_input_tokensPrompt-prefix tokens served from cache.
usage.cache_creation_input_tokensAlways 0.
usage.output_tokensAll generated output tokens, including reasoning.

Your billed prompt count is input_tokens + cache_read_input_tokens.

Count input tokens

Text
POST https://api.runinfra.ai/v1/messages/count_tokens

Use the same body as POST /v1/messages. max_tokens is optional for this operation. The endpoint estimates input tokens without running the model, so the call is not billed. The per-key request rate limit still applies.

cURL
curl https://api.runinfra.ai/v1/messages/count_tokens \
  -H "x-api-key: $RUNINFRA_GATEWAY_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model":"MODEL_ID","messages":[{"role":"user","content":"Summarize this design."}]}'
JSON
{
  "input_tokens": 12
}
FieldBehavior
input_tokensEstimated input tokens for the translated request.

Streaming

Set stream: true to receive event-named Server-Sent Events. There is no data: [DONE] sentinel.

Blocks open only when their first output arrives:

  • A thinking block emits thinking_delta fragments, then one signature_delta.
  • A text block emits text_delta fragments.
  • A tool-use block opens with input: {}, then emits input_json_delta fragments. Concatenate each partial_json value to recover the full input object.

The stream order is message_start, lazily opened content blocks, message_delta, then message_stop. The server emits ping every 15 seconds while the model is silent.

message_start reports zero usage. The cumulative four-field usage on message_delta is authoritative.

The official SDK stream helpers work unchanged, including Python messages.stream() and get_final_message(), and TypeScript messages.stream() and finalMessage().

If a failure happens after the stream opens, the final frame is an error event and there is no message_stop:

Text
event: error
data: {"type":"error","error":{"type":"api_error","message":"The model connection closed before completion."},"request_id":"req_..."}

Errors

Every 4xx and 5xx on the two Messages-compatible routes uses this envelope:

JSON
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "max_tokens must be at least 1."
  },
  "request_id": "req_..."
}

Messages match the corresponding chat-completions errors. Branch on error.type, not the message text. A prompt longer than the model's context window is the exception: its invalid_request_error message starts prompt is too long, the wording Claude Code compacts on.

Statuserror.typeTypical cause
400invalid_request_errorInvalid JSON, a missing field, an unsupported content block, or an unsupported server tool.
401authentication_errorThe key is missing or invalid.
402billing_errorA billing refusal, including insufficient credits, an account hold, or a coding plan limit. Read the message for the reason and next step.
403permission_errorThe key cannot use the selected model.
404not_found_errorThe model is not available to this key.
409conflict_errorThe same idempotency key is still running.
413request_too_largeThe encoded body exceeds the request-size limit.
429rate_limit_errorA request, token, or concurrency limit refused the call. Read Retry-After.
500, 502api_errorA transient gateway or model failure.
503overloaded_errorThe service or selected model is temporarily unavailable. The same type is returned when an Idempotency-Key could not be checked: the message says so, the request did not run, and nothing was charged. Retry with the same key after Retry-After.
504timeout_errorThe inference deadline expired.

Coding plan funding

A covered Messages request follows the same plan, credits, and Standby order as chat completions. Funding headers identify the request's admission decision. When the plan or Standby pays, Messages success usage also carries usage.cost (always 0) and usage.runinfra with cost_microcents, plan_value_microcents and paid_from, in the JSON body and in the final message_delta of a stream. Credit-funded and pay-as-you-go Messages usage carries the four token counters only, so read x-runinfra-funding to see that credits paid after a limit.

A plan limit returns HTTP 402, x-should-retry: false, and no Retry-After. The error type is billing_error. Only the message text survives from the detailed plan refusal, so it carries the product, limit, reset time, Standby condition, and next step with its Billing URL. The envelope does not carry error.code, standby_blocker, credit counters, or fixes.

In Messages clients such as Claude Code, read the message for the reason and next step. The CLI's connection check preserves that self-contained refusal text even when the envelope has no error code. Display the message and read the funding headers. Use GET /v1/usage for structured current plan state. Do not try to read the chat-compatible plan limit fields from a Messages error.