BuildChat basics

Chat basics

A chat request is a conversation written out as a list of messages. The model reads the whole list and writes the next assistant message. The API keeps no memory between requests: to continue a conversation, send the earlier turns again along with the new one.

This page covers the parts every integration uses: messages and roles, the request fields, what a reply looks like, how to keep a conversation going, and the limits to plan around.

Messages and roles

Each message is an object with a role and a content string.

RoleWho wrote itTypical use
systemYou, the developerStanding instructions: tone, format, rules. Put it first.
userYour end userThe question or task.
assistantThe modelEarlier replies, sent back so the model keeps context.
toolYour codeThe result of a function the model asked for. See Tool calling.

A two-turn conversation looks like this:

{
  "model": "Amara S1.2",
  "stream": false,
  "messages": [
    { "role": "system", "content": "You are a support assistant for a bike repair shop. Keep answers under 80 words." },
    { "role": "user", "content": "My chain keeps slipping when I climb." },
    { "role": "assistant", "content": "That usually points to a worn chain or cassette. How many miles has the chain done?" },
    { "role": "user", "content": "About 3,000." }
  ]
}

content is plain text. The API does not accept images, audio or file attachments in a message today; paste the text you want the model to read straight into content.

How the system message is handled

Syntic adds a short platform instruction to every request. It describes the assistant as Syntic AI and keeps answers about the model’s identity consistent.

  • If your first message has the system role, the platform instruction is placed at the top of that same message, followed by your text.
  • If you send no system message, the platform instruction becomes the first message on its own.

Your instructions still apply in full. Write your system message so it stands on its own and does not depend on being the very first line the model reads.

Request fields

FieldTypeRequiredNotes
modelstringNoA model name such as "Amara S1.2". If you leave it out, or name a model the API does not know, the default model answers. See Choosing a model.
messagesarrayYesAt least 1 message, at most 500.
streambooleanNoStreaming is on unless you send false. See Streaming.
max_tokensintegerNoUpper bound on the length of the reply, in tokens.
toolsarrayNoFunctions the model may ask you to run.
tool_choicestring or objectNoWhether, and which, tool the model should call.

Sampling settings such as temperature, top_p, n, presence_penalty and frequency_penalty are accepted without an error but are removed before the request reaches the model: the Amara models run with fixed sampling. Shape the output through your instructions instead. Because n is ignored, every reply has exactly one choice.

The reply

With "stream": false the API answers with one JSON object:

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 1767225600,
  "model": "...",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "At 3,000 miles the chain has likely stretched..." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 112, "completion_tokens": 64, "total_tokens": 176 }
}
  • choices[0].message is the new assistant turn. Append it to your list before the next request.
  • finish_reason says why the model stopped (see the table below).
  • usage counts the tokens read (prompt_tokens) and written (completion_tokens). It is what billing is based on.

Finish reasons

finish_reasonMeaningWhat to do
stopThe model finished its answer.Show it.
lengthThe reply hit max_tokens or the context window. The text is cut off.Raise max_tokens, ask for a shorter answer, or ask the model to continue in a new turn.
tool_callsThe model wants your code to run one or more functions.Run them and send the results back. See Tool calling.

Keep a conversation going

Because the API is stateless, your application owns the history. A simple chat loop appends each user message and each assistant reply to the same list:

import os
from openai import OpenAI
 
client = OpenAI(base_url="https://app.syntic.ai/v1", api_key=os.environ["SYNTIC_API_KEY"])
 
history = [
    {"role": "system", "content": "You are a patient Spanish tutor. Correct mistakes gently."},
]
 
def ask(text: str) -> str:
    history.append({"role": "user", "content": text})
    reply = client.chat.completions.create(
        model="Amara S1.2",
        stream=False,
        max_tokens=400,
        messages=history,
    )
    answer = reply.choices[0].message.content
    history.append({"role": "assistant", "content": answer})
    return answer
 
print(ask("Yo soy cansado después del trabajo."))
print(ask("Why is it 'estoy' and not 'soy' here?"))

You can also write assistant turns yourself, for example to show the model a worked example of the answer style you want before the real question.

Managing a long history

Every request resends the full conversation, so input tokens grow with each turn. Common strategies:

  • Trim: keep the system message and the most recent turns, and drop the oldest ones.
  • Summarise: when the history gets long, ask the model to summarise the earlier part, then replace those turns with one message holding the summary.
  • Keep tool results short: store large data on your side and send only what the model needs.

Repeated prefixes cost less

When consecutive requests start with the same messages, as they do in a chat or a tool loop, the model service can reuse the work it already did on that shared beginning. Syntic charges those input tokens at the reduced cached input rate shown on Pricing. Nothing needs to be turned on; to get the most from it:

  • Keep the system message and any reference material at the start, and keep them byte-for-byte identical between requests.
  • Add new turns at the end rather than editing earlier ones.

Choosing a model

Pass the model’s Syntic name exactly, for example "Amara S1.2" or "Amara Quick". The Models overview lists every model, and Pricing lists the rate for each.

  • Missing or unknown name: the default model, Amara S1.2, answers. No error is returned, so check the spelling if replies look unexpected.
  • Model not in your plan: some models need a higher plan. If you ask for one your organization’s plan does not include, the default model answers instead and the response carries an x-syntic-model-downgraded header naming the model that was used.
  • Zero data retention: organizations set up for zero data retention cannot use models that need standard retention. Asking for one returns 400 model_unavailable.

Response headers

HeaderMeaning
x-syntic-request-idA unique id for the request. Log it, and quote it in any support ticket.
x-syntic-billing-sourcesubscription when your plan allowance paid for the request, api_credit when usage credits did.
x-syntic-model-downgradedPresent only when the model you asked for is not included in your plan; its value names the model that answered instead.

Browsers only let page scripts read the request id and billing source headers. x-syntic-model-downgraded is visible to server-side code only.

Limits to plan around

LimitValueError when exceeded
Request bodyabout 2 MB413 payload_too_large
Messages per request500413 too_many_messages
Context window262,144 tokens (input plus reply)upstream_error, with the status the model service returned
Requests per key600 per minute429

More detail is on Limits. For anything that goes wrong, see Errors.

What the API does not do yet

  • No structured-output or JSON mode. Describe the shape in your prompt and validate it; see Prompting tips.
  • No images or files in messages.
  • No server-side conversation storage: you always send the history.
Was this page helpful?