Home / Docs / API reference
/v1/messages
POST /v1/messages
Authentication relay key (bearer)
Request body
| Field | Type | Description |
|---|---|---|
model | string | The model id. In a request it is optional: without it the key's routing chooses. |
messages required | array of object | |
messages[].role | string | Who wrote the message: system, user, assistant or tool. |
messages[].content | string or array | The text of the message. |
system | string or array | A string, or a list of {type:text,text} blocks (joined). An empty list contributes nothing. |
max_tokens | number | Upper bound on the tokens the model may generate for this answer. Send this or max_completion_tokens, not both. |
temperature | number | Sampling temperature: lower is more deterministic, higher more varied. Usually between 0 and 2. |
stream | boolean | |
thinking | string or object | This gateway reads a plain effort string, or an object with an effort field; it does not translate Anthropic's budget_tokens block. |
Example request
curl
curl -X POST https://api.massrelay.ai/v1/messages \
-H "Authorization: Bearer $RELAY_KEY" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Say hello in one sentence."}],"max_tokens":256}'Python
import os, requests
r = requests.post(
"https://api.massrelay.ai/v1/messages",
headers={"Authorization": f"Bearer {os.environ['RELAY_KEY']}"},
json={
"messages": [
{
"role": "user",
"content": "Say hello in one sentence."
}
],
"max_tokens": 256
},
)
r.raise_for_status()
print(r.json())TypeScript
const r = await fetch("https://api.massrelay.ai/v1/messages", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.RELAY_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({"messages":[{"role":"user","content":"Say hello in one sentence."}],"max_tokens":256}),
});
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
console.log(await r.json());Response
| Field | Type | Description |
|---|---|---|
id required | string | msg_<decisionId> |
type required | string | Always "message". |
role required | string | Who wrote the message: system, user, assistant or tool. Always "assistant". |
model required | string | The model id. In a request it is optional: without it the key's routing chooses. |
content required | array of object | The text of the message. |
content[].type | string | Always "text". |
content[].text | string | |
stop_reason required | string | |
stop_sequence | null | |
usage required | object | Tokens counted for this call. |
usage.input_tokens | number | Tokens in the input. |
usage.output_tokens | number | Tokens generated. |
Example response (shape)
{
"id": "msg_1f2e3d4c",
"type": "message",
"role": "assistant",
"model": "provider/model-id",
"content": [
{
"type": "text",
"text": "Hello there."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 12,
"output_tokens": 6
}
}Common errors
401invalid_api_key400the body is not usable402out of credit or over a budget413the body is over the limit403insufficient_scope429over the request rate; read Retry-After
Every error has the same JSON shape; see Errors and Limits.
Full schema (JSON)
Request schema
{
"description": "Every other field is accepted and not read (for example top_p, top_k, stop_sequences, tools are not read on this surface). Fields the gateway does not read are not documented as honoured.",
"type": "object",
"required": [
"messages"
],
"additionalProperties": true,
"properties": {
"model": {
"type": "string"
},
"messages": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": true,
"properties": {
"role": {
"type": "string"
},
"content": {
"type": [
"string",
"array"
]
}
}
}
},
"system": {
"description": "A string, or a list of {type:text,text} blocks (joined). An empty list contributes nothing.",
"type": [
"string",
"array"
]
},
"max_tokens": {
"type": "number"
},
"temperature": {
"type": "number"
},
"stream": {
"type": "boolean"
},
"thinking": {
"description": "This gateway reads a plain effort string, or an object with an effort field; it does not translate Anthropic's budget_tokens block.",
"type": [
"string",
"object"
]
}
}
}Response schema
{
"description": "Every other field is accepted and not read (streaming is not described here). Fields the gateway does not read are not documented as honoured.",
"type": "object",
"additionalProperties": true,
"required": [
"id",
"type",
"role",
"model",
"content",
"stop_reason",
"usage"
],
"properties": {
"id": {
"type": "string",
"description": "msg_<decisionId>"
},
"type": {
"const": "message"
},
"role": {
"const": "assistant"
},
"model": {
"type": "string"
},
"content": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"const": "text"
},
"text": {
"type": "string"
}
}
}
},
"stop_reason": {
"type": "string"
},
"stop_sequence": {
"type": "null"
},
"usage": {
"type": "object",
"properties": {
"input_tokens": {
"type": "number"
},
"output_tokens": {
"type": "number"
}
}
}
}
}Every answer carries a decision id; the explain endpoint returns why that model was chosen.