Messages
POST https://app.syntic.ai/v1/messagesThe Messages endpoint is a second way to talk to the same Amara models. Where Chat completions carries each message as a string (or a short list of parts), Messages carries every turn as an ordered list of typed content blocks: text, images, documents, tool calls, tool results and reasoning. Each block keeps its own metadata, so features such as citations, reasoning and document input fit naturally instead of being squeezed into extra fields.
Both endpoints run on one engine. They share models, billing, rate limits, Files, caching and the error format. An adapter turns each request shape into one internal form, so a feature that ships on one endpoint is available on the other unless a page says otherwise.
When to use it
| Use Messages when | Stay on Chat completions when |
|---|---|
| You rely on citations, reasoning blocks or document blocks and want them as first-class objects. | You already have code written for the chat completions shape. |
| You want the system prompt as a top-level field rather than a message. | You use an OpenAI-compatible SDK and do not want a second client. |
| You build an agent loop and prefer tool calls and tool results as blocks inside ordinary turns. | Your requests are plain text and tools. |
Authentication
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer <key> with an inference key (sk_live_...). See Authentication. |
Content-Type | Yes | application/json |
syntic-version | No | Pins the API behaviour date. See Versioning. |
syntic-beta | No | Comma-separated preview features to turn on. |
Request body
| Field | Type | Required | Details |
|---|---|---|---|
model | string | Yes | A model id from GET /v1/models. An unknown id is rejected with 404 model_not_found rather than falling back. |
messages | array of turns | Yes | Alternating user and assistant turns, oldest first. 1 to 500 items. |
max_tokens | integer | Yes | The most output tokens the reply may use, reasoning included. Must not exceed the model’s max_output_tokens. |
system | string or array of text blocks | No | Instructions for the whole conversation. |
stream | boolean | No | Defaults to false on this endpoint. Set true for server-sent events. |
stop_sequences | array of strings | No | Up to 4 strings. Generation stops when one appears. See Request parameters. |
temperature, top_p, top_k | number | No | Honoured where the model supports them. See Sampling. |
tools | array | No | Function tools and built-in tools. Same definitions as on chat completions; see Tool calling. |
tool_choice | object | No | {"type":"auto"}, {"type":"none"}, {"type":"any"} (must call some tool) or {"type":"tool","name":"..."}. |
parallel_tool_calls | boolean | No | Defaults to true. See Parallel tool calls. |
reasoning | object | No | See Reasoning. |
response_format | object | No | See JSON output. |
context_management | object | No | See Context management. |
mcp_servers | array | No | See MCP connector. |
metadata | object | No | end_user_id and up to 16 string key/value pairs stored with the usage record. |
prompt_cache_key | string | No | See Prompt caching. |
Turn object
| Field | Type | Details |
|---|---|---|
role | string | user or assistant. System instructions go in the top-level system field, not in a turn. |
content | string or array of blocks | A string is shorthand for one text block. |
Content blocks
type | Allowed in | Fields |
|---|---|---|
text | user, assistant | text; on assistant turns optionally citations[] |
image | user | source: {type:"url", url}, {type:"base64", media_type, data} or {type:"file", file_id}. See Vision. |
document | user | source as for images (PDF, plain text, Office files), optional title, citations:{enabled}. See PDF and documents. |
tool_call | assistant | id, name, input (an object, not a string) |
tool_result | user | tool_call_id, content (string or text/image blocks), optional is_error |
reasoning | assistant | text, signature. Return unchanged in later turns. |
compaction | assistant | summary. Inserted by the server when context management runs. |
Assistant turns you send back must contain the blocks exactly as received. Editing or dropping a reasoning block returns 400 invalid_reasoning_block.
Response
200 OK, Content-Type: application/json
{
"id": "msg_01JBX4K7Q2V9",
"type": "message",
"role": "assistant",
"model": "Amara S1.2",
"content": [
{ "type": "text", "text": "The lease ends on 31 March 2027." }
],
"stop_reason": "stop",
"stop_sequence": null,
"usage": {
"input_tokens": 1840,
"output_tokens": 14,
"cached_input_tokens": 1536,
"reasoning_tokens": 0
},
"request_id": "req_01JBX4K7Q2V9ZQ"
}| Field | Type | Description |
|---|---|---|
id | string | Identifier of this reply, prefixed msg_. |
type | string | Always message. |
role | string | Always assistant. |
model | string | The Syntic model id that answered. |
content | array | Output blocks in order: reasoning, text, tool_call, compaction. |
stop_reason | string | One of the fixed values on Stop reasons. |
stop_sequence | string or null | The stop string that ended the reply, when stop_reason is stop_sequence. |
usage.input_tokens | integer | All input tokens, cached ones included. |
usage.cached_input_tokens | integer | The part of the input read from cache. |
usage.output_tokens | integer | Output tokens, reasoning included. |
usage.reasoning_tokens | integer | The part of the output spent on reasoning. |
usage.server_tool_use | object | Present when built-in tools ran, for example {"web_search_requests": 2}. |
request_id | string | Same value as the x-syntic-request-id header. |
Streaming events
With "stream": true the reply is a series of named server-sent events. Every event has an event: line and a JSON data: line whose type repeats the event name.
| Event | When | Payload |
|---|---|---|
message_start | Once, first | A message object with empty content and input usage. |
block_start | A new content block begins | index and the block with empty fields. |
block_delta | Content for the open block | index and one of {type:"text_delta", text}, {type:"input_json_delta", partial_json}, {type:"reasoning_delta", text}, {type:"citation_delta", citation}. |
block_stop | The block is complete | index |
message_delta | Once, near the end | stop_reason, stop_sequence and final usage. |
message_stop | Once, last | Empty. |
ping | About every 15 seconds while nothing else is sent | Empty. Ignore it. |
error | A failure after the stream started | The standard error envelope. The connection closes afterwards. |
event: message_start
data: {"type":"message_start","message":{"id":"msg_01JBX4K7Q2V9","type":"message","role":"assistant","model":"Amara S1.2","content":[],"usage":{"input_tokens":1840,"output_tokens":0}}}
event: block_start
data: {"type":"block_start","index":0,"block":{"type":"text","text":""}}
event: block_delta
data: {"type":"block_delta","index":0,"delta":{"type":"text_delta","text":"The lease ends"}}
event: block_stop
data: {"type":"block_stop","index":0}
event: message_delta
data: {"type":"message_delta","stop_reason":"stop","stop_sequence":null,"usage":{"output_tokens":14}}
event: message_stop
data: {"type":"message_stop"}Tool-call input arrives as input_json_delta fragments; join them and parse once the block stops.
Examples
curl https://app.syntic.ai/v1/messages \
-H "Authorization: Bearer $SYNTIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "Amara S1.2",
"max_tokens": 400,
"system": "You answer questions about the attached lease. Quote clause numbers.",
"messages": [{
"role": "user",
"content": [
{ "type": "document", "source": { "type": "file", "file_id": "file_01JBW9" }, "title": "Lease" },
{ "type": "text", "text": "When does the lease end?" }
]
}]
}'A tool round trip
Tool calls and results are blocks inside ordinary turns:
{
"model": "Amara S1.2",
"max_tokens": 300,
"tools": [{ "type": "function", "function": { "name": "check_stock", "parameters": { "type": "object", "properties": { "sku": { "type": "string" } }, "required": ["sku"] } } }],
"messages": [
{ "role": "user", "content": "Is LAV-2L in stock?" },
{ "role": "assistant", "content": [
{ "type": "tool_call", "id": "call_01", "name": "check_stock", "input": { "sku": "LAV-2L" } }
]},
{ "role": "user", "content": [
{ "type": "tool_result", "tool_call_id": "call_01", "content": "{\"units\":38}" }
]}
]
}Errors
Errors use the unified envelope:
{
"error": {
"type": "invalid_request",
"code": "invalid_block_type",
"message": "Block type 'tool_result' is not allowed in an assistant turn.",
"param": "messages[2].content[0].type"
},
"request_id": "req_01JBX4K7Q2V9ZQ"
}| Status | type | code | Cause |
|---|---|---|---|
| 400 | invalid_request_error | max_tokens_required | max_tokens missing or above the model limit. |
| 400 | invalid_request_error | invalid_block_type | A block type used in the wrong role. |
| 400 | invalid_request_error | unmatched_tool_call | A tool_call without a matching tool_result in the next user turn. |
| 400 | invalid_request_error | invalid_reasoning_block | A reasoning block was edited or reordered. |
| 400 | invalid_request_error | unsupported_content | The model cannot read this block type (for example images on a text-only model). |
| 401 | authentication_error | invalid_api_key | Missing or invalid key. |
| 402 | quota_exhausted | quota_exhausted | Plan allowance and credits cannot cover the request. |
| 404 | not_found_error | model_not_found | Unknown model id. |
| 413 | invalid_request_error | payload_too_large | Body larger than 32 MB. |
| 429 | rate_limit_error | rate_limited | Too many requests or tokens this minute. |
| 500 | api_error | internal_error | Unexpected failure. Retry with back-off. |
| 503 | service_unavailable_error | overloaded | Temporary capacity shortage. Retry after Retry-After. |
Limits
| Limit | Value |
|---|---|
| Turns per request | 500 |
| Request body | 32 MB (use Files for anything larger) |
| Images per request | 100 |
| Documents per request | 20 |
| Stop sequences | 4 |
metadata keys | 16, values up to 512 characters |
Build notes for the platform team
- One internal request form; two adapters (chat completions, messages). New fields land in the internal form first.
- Unknown fields return
400 unknown_parameter; never drop them silently. modelids are Syntic ids only. Never return an upstream provider’s model name, in a body, header or error.- Usage rows record
endpoint: "messages"so reports can split traffic.