Python SDK
syntic is the official Python library for the Syntic API. It gives you typed request and response objects, automatic retries with back-off, streaming helpers, automatic pagination, a tool-loop runner, and access to every Syntic endpoint: chat completions, files, batches, agents, sessions and the Admin API.
Today there is no official package. Use an OpenAI-compatible client with Syntic’s base URL, as shown in OpenAI-compatible clients. That route keeps working after the SDK ships; the SDK adds the Syntic-only features (agents, admin, batches, typed errors) that a generic client cannot reach.
Install
pip install syntic| Requirement | Value |
|---|---|
| Python | 3.9 or newer |
| Package | syntic on PyPI |
| Dependencies | httpx, pydantic (v2), typing-extensions |
| Optional extras | syntic[aiohttp] for an aiohttp transport |
Create a client
from syntic import Syntic
client = Syntic() # reads SYNTIC_API_KEY from the environment| Argument | Default | Notes |
|---|---|---|
api_key | os.environ["SYNTIC_API_KEY"] | A private sk_live_ key, or a workload identity sat_ token. |
base_url | os.environ.get("SYNTIC_BASE_URL", "https://app.syntic.ai/v1") | Point at https://app.syntic.ai/v1 if you prefer the alias. |
version | The version the SDK release was built for | Sent as syntic-version. |
betas | [] | Sent as syntic-beta, for example ["agents-2026-11-01"]. Methods that need a beta add it automatically. |
max_retries | 2 | Retries rate_limit, overloaded, api_error and connection errors with exponential back-off and jitter, honouring retry-after. |
timeout | 600 seconds | A float, or an httpx.Timeout. |
default_headers | {} | Extra headers on every request. |
AsyncSyntic has the same arguments and async methods.
A first request
from syntic import Syntic
client = Syntic()
reply = client.chat.completions.create(
model="Amara S1.2",
messages=[{"role": "user", "content": "Give me three names for a bakery."}],
max_tokens=200,
)
print(reply.choices[0].message.content)
print(reply.usage.total_tokens, reply.request_id)The SDK always sends stream explicitly, so create() returns one object and stream() returns a stream, whatever the API’s default is.
Streaming
with client.chat.completions.stream(
model="Amara S1.2",
messages=[{"role": "user", "content": "Write a haiku about rain."}],
) as stream:
for text in stream.text_deltas():
print(text, end="", flush=True)
final = stream.final_completion() # the assembled message, tool calls and usage
print("\n", final.usage)The stream helper joins tool-call fragments for you, asks for usage in the last chunk, ignores keep-alive comments, and raises a typed error if the stream ends with an error event. stream.events() gives you the raw events if you want them.
Tool loop
client.tools.run() handles the loop of calling the model, running your Python functions, and sending their results back until the model answers:
from syntic import Syntic, tool
client = Syntic()
@tool
def get_order(order_id: str) -> dict:
"""Look up an order by its id."""
return {"order_id": order_id, "status": "shipped", "carrier": "DHL"}
result = client.tools.run(
model="Amara S1.2",
messages=[{"role": "user", "content": "Where is order 88123?"}],
tools=[get_order], # the schema is built from the type hints and docstring
max_rounds=8,
)
print(result.final_message.content)
for step in result.steps:
print(step.tool_name, step.arguments, step.output)Tool exceptions are sent back to the model as error results rather than crashing the loop; pass raise_on_tool_error=True to stop instead.
Errors
Every API error becomes an exception subclass named after its envelope type:
| Exception | error.type |
|---|---|
syntic.InvalidRequestError | invalid_request |
syntic.AuthenticationError | authentication |
syntic.PermissionDeniedError | permission |
syntic.NotFoundError | not_found |
syntic.QuotaExhaustedError | quota_exhausted |
syntic.RateLimitError | rate_limit |
syntic.OverloadedError | overloaded |
syntic.InternalServerError | api_error |
syntic.APIConnectionError, syntic.APITimeoutError | No response received |
All API errors inherit from syntic.APIStatusError and carry .status_code, .type, .code, .message, .param, .details and .request_id.
import syntic
try:
client.chat.completions.create(model="Amara S1.2", messages=[])
except syntic.QuotaExhaustedError as e:
print("Out of allowance until", e.details.get("resets_at"))
except syntic.InvalidRequestError as e:
print(e.code, e.param, e.request_id)Pagination
List methods return a page that iterates over every item, fetching more pages as needed:
for agent in client.agents.list(limit=50):
print(agent.id, agent.name)
page = client.agents.list(limit=50)
print(page.has_more, page.last_id) # or work page by pageFiles, batches, agents and admin
# Files
f = client.files.upload(path="contract.pdf", purpose="document")
# Batches: submit JSONL, then wait
batch = client.batches.create(requests=[
{"custom_id": "a1", "endpoint": "/v1/chat/completions", "body": {"model": "Amara Quick", "messages": [{"role": "user", "content": "Hi"}]}},
])
batch = client.batches.wait(batch.id, poll_interval=30)
for line in client.batches.results(batch.id):
print(line.custom_id, line.result.type)
# Agents and sessions (adds the agents beta flag automatically)
session = client.sessions.create(agent_id="agt_01JC4R8W6Q2N9X3T5V7Y",
input=[{"type": "user.message", "content": "Refund order 88123"}])
for event in client.sessions.stream(session.id):
if event.type == "message.completed":
print(event.content)
# Admin API with an admin key
from syntic import SynticAdmin
admin = SynticAdmin() # reads SYNTIC_ADMIN_KEY
for m in admin.users.list():
print(m.email, m.role)Raw responses and headers
raw = client.chat.completions.with_raw_response.create(model="Amara Quick", messages=[...])
print(raw.headers["x-syntic-request-id"], raw.headers.get("x-syntic-billing-source"))
reply = raw.parse()Every returned object also has .request_id.
Versioning of the SDK
- The SDK follows semantic versioning. Minor releases add endpoints and fields; major releases follow a new default
syntic-version. - Each release pins the API version it was tested against. Upgrading the package is how you move to a newer API version, so read the changelog first.
- Supported for at least 12 months after the next major release.
Build notes for the platform team
- Generate the core (models, resources, pagination) from openapi.yaml; hand-write only the helpers: stream accumulator, tool runner, file upload, batch wait and results, session stream.
- Publish under the
synticname on PyPI from CI with trusted publishing; release together with the TypeScript SDK so docs examples always match. - The SDK never sends telemetry. It identifies itself only with
User-Agent: syntic-python/<version>.