SDKs & toolsOpenAI-compatible clients

OpenAI-compatible clients

The Syntic API accepts the chat completions request format that many client libraries and tools already speak. If a library lets you set a custom base URL and API key, it can talk to Syntic with no other code changes. Below: the setup, then a precise list of which parts of that format Syntic supports.

Settings

SettingValue
Base URLhttps://app.syntic.ai/v1
API keyA private sk_live_ key from Settings → API Keys in the Console
ModelA Syntic model name, for example Amara S1.2 or Amara Quick

Set up

Create a key

Create a private key in the Console and store it as SYNTIC_API_KEY. See Get an API key.

Install a client library

pip install openai        # Python
npm install openai        # Node.js / TypeScript

Point it at Syntic

Pass the Syntic base URL and key when you create the client, as in the examples below.

Use Syntic model names

Replace any model names in your code with Syntic names. Today a name Syntic does not recognise is answered by the default model, so typos do not raise errors; check them carefully. Not live yet — on develop: an unrecognised name returns 404 model_not_found, which the OpenAI libraries raise as a not-found error.

Examples

import os
from openai import OpenAI
 
client = OpenAI(
    base_url="https://app.syntic.ai/v1",
    api_key=os.environ["SYNTIC_API_KEY"],
)
 
answer = client.chat.completions.create(
    model="Amara Quick",
    stream=False,
    messages=[{"role": "user", "content": "Translate 'see you tomorrow' into Portuguese."}],
)
print(answer.choices[0].message.content)

Streaming

Ask the library for a stream and iterate over it. Text arrives in each chunk’s delta:

stream = client.chat.completions.create(
    model="Amara S1.2",
    stream=True,
    messages=[{"role": "user", "content": "List five uses for leftover rice."}],
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

The final usage chunk has an empty choices array, which is why both loops check before reading choices[0].

Tool calling

Tools use the familiar tools and tool_choice fields:

import json
 
tools = [{
    "type": "function",
    "function": {
        "name": "convert_currency",
        "description": "Convert an amount between two currencies at today's rate.",
        "parameters": {
            "type": "object",
            "properties": {
                "amount": {"type": "number"},
                "from_code": {"type": "string", "description": "ISO 4217 code, e.g. EUR"},
                "to_code": {"type": "string"},
            },
            "required": ["amount", "from_code", "to_code"],
        },
    },
}]
 
messages = [{"role": "user", "content": "How much is 250 euros in yen?"}]
first = client.chat.completions.create(
    model="Amara S1.2", stream=False, tools=tools, messages=messages,
)
reply = first.choices[0].message
 
if reply.tool_calls:
    messages.append(reply)  # keep the assistant turn exactly as returned
    for call in reply.tool_calls:
        args = json.loads(call.function.arguments)
        result = {"converted": round(args["amount"] * 162.4, 2)}  # your real lookup here
        messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
    final = client.chat.completions.create(
        model="Amara S1.2", stream=False, tools=tools, messages=messages,
    )
    print(final.choices[0].message.content)

See Tool calling and Write a tool loop for a complete loop with error handling.

Reading Syntic headers

The response headers carry the request id, billing source and model fallback signal. Most libraries expose the raw response:

raw = client.chat.completions.with_raw_response.create(
    model="Amara S1.2",
    stream=False,
    messages=[{"role": "user", "content": "Hello"}],
)
print(raw.headers.get("x-syntic-request-id"))
print(raw.headers.get("x-syntic-billing-source"))
print(raw.headers.get("x-syntic-model-downgraded"))  # None unless a fallback happened
completion = raw.parse()

Compatibility details

Request fields

FieldSupport
modelSupported. Use Syntic names. Today unknown names fall back to the default model. Not live yet — on develop: unknown names return 404 model_not_found.
messagesSupported. 1 to 500 messages.
streamSupported. Defaults to true, unlike most libraries expect.
max_tokensSupported.
toolsSupported, function tools only.
tool_choiceSupported.
temperature, top_pAccepted, not applied.
nAccepted, not applied. Always one choice.
presence_penalty, frequency_penaltyAccepted, not applied.
stream_optionsNot needed. Streams always end with a usage chunk.
response_formatIgnored. JSON mode and structured outputs are not available; request JSON through your instructions and check it in your code.
stop, seed, logprobs, top_logprobs, logit_bias, userIgnored.
parallel_tool_callsIgnored.

Message fields

FieldSupport
role: systemSupported. Put it first. Syntic’s identity instruction is placed ahead of it.
role: developerNot documented as supported; use system.
role: user, assistant, toolSupported.
content as a stringSupported.
content as an array of parts (text, images, audio)Not documented as supported.
tool_calls, tool_call_idSupported.
nameNot used.

Response fields

FieldSupport
id, object, createdReturned.
modelReturned, but may not equal the Syntic name you sent. Use the x-syntic-model-downgraded header to detect fallbacks.
choices[].message, choices[].deltaReturned, including tool_calls.
choices[].finish_reasonstop, length or tool_calls.
usageReturned on every reply, including streamed ones.
logprobsNot returned.
system_fingerprintNot guaranteed.

Endpoints

Library callSupport
chat.completions.createSupported
models.list, models.retrieveNot available. Use the list on Models overview.
embeddings, files, images, audio, batches, moderations, responses, assistantsNot available

Errors

Syntic error bodies are flat, for example {"error": "invalid_api_key"}. Libraries that expect {"error": {"message": ...}} still raise the right exception class for the HTTP status, but the message may be the raw body. Read the code from the body:

import openai
 
try:
    client.chat.completions.create(model="Amara S1.2", stream=False, messages=[])
except openai.APIStatusError as err:
    body = err.response.json()
    print(err.status_code, body["error"]["type"], body["error"]["code"])  # 400 invalid_request_error messages_required

The full list is on Error codes.

Retries and timeouts

Client libraries usually retry 429 and 5xx responses on their own. That suits Syntic: 429 responses carry Retry-After. Keep the library’s timeout generous (a couple of minutes) for long replies, or stream them.

⚠️

Two behaviours differ from what these libraries expect:

  • Streaming is on by default. Pass stream=False (Python) or stream: false (Node.js) on every non-streaming call. Libraries often leave the field out when you do not set it, and the API then sends a stream the library is not expecting to parse.
  • Errors have a flat body. error is a string code, not an object. See Errors.
Was this page helpful?