Messages

⚠️
Preview — this feature is being built; the design below is the spec.
POST https://app.syntic.ai/v1/messages

The 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 whenStay 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

HeaderRequiredValue
AuthorizationYesBearer <key> with an inference key (sk_live_...). See Authentication.
Content-TypeYesapplication/json
syntic-versionNoPins the API behaviour date. See Versioning.
syntic-betaNoComma-separated preview features to turn on.

Request body

FieldTypeRequiredDetails
modelstringYesA model id from GET /v1/models. An unknown id is rejected with 404 model_not_found rather than falling back.
messagesarray of turnsYesAlternating user and assistant turns, oldest first. 1 to 500 items.
max_tokensintegerYesThe most output tokens the reply may use, reasoning included. Must not exceed the model’s max_output_tokens.
systemstring or array of text blocksNoInstructions for the whole conversation.
streambooleanNoDefaults to false on this endpoint. Set true for server-sent events.
stop_sequencesarray of stringsNoUp to 4 strings. Generation stops when one appears. See Request parameters.
temperature, top_p, top_knumberNoHonoured where the model supports them. See Sampling.
toolsarrayNoFunction tools and built-in tools. Same definitions as on chat completions; see Tool calling.
tool_choiceobjectNo{"type":"auto"}, {"type":"none"}, {"type":"any"} (must call some tool) or {"type":"tool","name":"..."}.
parallel_tool_callsbooleanNoDefaults to true. See Parallel tool calls.
reasoningobjectNoSee Reasoning.
response_formatobjectNoSee JSON output.
context_managementobjectNoSee Context management.
mcp_serversarrayNoSee MCP connector.
metadataobjectNoend_user_id and up to 16 string key/value pairs stored with the usage record.
prompt_cache_keystringNoSee Prompt caching.

Turn object

FieldTypeDetails
rolestringuser or assistant. System instructions go in the top-level system field, not in a turn.
contentstring or array of blocksA string is shorthand for one text block.

Content blocks

typeAllowed inFields
textuser, assistanttext; on assistant turns optionally citations[]
imageusersource: {type:"url", url}, {type:"base64", media_type, data} or {type:"file", file_id}. See Vision.
documentusersource as for images (PDF, plain text, Office files), optional title, citations:{enabled}. See PDF and documents.
tool_callassistantid, name, input (an object, not a string)
tool_resultusertool_call_id, content (string or text/image blocks), optional is_error
reasoningassistanttext, signature. Return unchanged in later turns.
compactionassistantsummary. 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"
}
FieldTypeDescription
idstringIdentifier of this reply, prefixed msg_.
typestringAlways message.
rolestringAlways assistant.
modelstringThe Syntic model id that answered.
contentarrayOutput blocks in order: reasoning, text, tool_call, compaction.
stop_reasonstringOne of the fixed values on Stop reasons.
stop_sequencestring or nullThe stop string that ended the reply, when stop_reason is stop_sequence.
usage.input_tokensintegerAll input tokens, cached ones included.
usage.cached_input_tokensintegerThe part of the input read from cache.
usage.output_tokensintegerOutput tokens, reasoning included.
usage.reasoning_tokensintegerThe part of the output spent on reasoning.
usage.server_tool_useobjectPresent when built-in tools ran, for example {"web_search_requests": 2}.
request_idstringSame 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.

EventWhenPayload
message_startOnce, firstA message object with empty content and input usage.
block_startA new content block beginsindex and the block with empty fields.
block_deltaContent for the open blockindex and one of {type:"text_delta", text}, {type:"input_json_delta", partial_json}, {type:"reasoning_delta", text}, {type:"citation_delta", citation}.
block_stopThe block is completeindex
message_deltaOnce, near the endstop_reason, stop_sequence and final usage.
message_stopOnce, lastEmpty.
pingAbout every 15 seconds while nothing else is sentEmpty. Ignore it.
errorA failure after the stream startedThe 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"
}
StatustypecodeCause
400invalid_request_errormax_tokens_requiredmax_tokens missing or above the model limit.
400invalid_request_errorinvalid_block_typeA block type used in the wrong role.
400invalid_request_errorunmatched_tool_callA tool_call without a matching tool_result in the next user turn.
400invalid_request_errorinvalid_reasoning_blockA reasoning block was edited or reordered.
400invalid_request_errorunsupported_contentThe model cannot read this block type (for example images on a text-only model).
401authentication_errorinvalid_api_keyMissing or invalid key.
402quota_exhaustedquota_exhaustedPlan allowance and credits cannot cover the request.
404not_found_errormodel_not_foundUnknown model id.
413invalid_request_errorpayload_too_largeBody larger than 32 MB.
429rate_limit_errorrate_limitedToo many requests or tokens this minute.
500api_errorinternal_errorUnexpected failure. Retry with back-off.
503service_unavailable_erroroverloadedTemporary capacity shortage. Retry after Retry-After.

Limits

LimitValue
Turns per request500
Request body32 MB (use Files for anything larger)
Images per request100
Documents per request20
Stop sequences4
metadata keys16, 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.
  • model ids 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.
Was this page helpful?