SDKs & toolsPython SDK

Python SDK

⚠️
Preview — this feature is being built; the design below is the spec.

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
RequirementValue
Python3.9 or newer
Packagesyntic on PyPI
Dependencieshttpx, pydantic (v2), typing-extensions
Optional extrassyntic[aiohttp] for an aiohttp transport

Create a client

from syntic import Syntic
 
client = Syntic()  # reads SYNTIC_API_KEY from the environment
ArgumentDefaultNotes
api_keyos.environ["SYNTIC_API_KEY"]A private sk_live_ key, or a workload identity sat_ token.
base_urlos.environ.get("SYNTIC_BASE_URL", "https://app.syntic.ai/v1")Point at https://app.syntic.ai/v1 if you prefer the alias.
versionThe version the SDK release was built forSent as syntic-version.
betas[]Sent as syntic-beta, for example ["agents-2026-11-01"]. Methods that need a beta add it automatically.
max_retries2Retries rate_limit, overloaded, api_error and connection errors with exponential back-off and jitter, honouring retry-after.
timeout600 secondsA 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:

Exceptionerror.type
syntic.InvalidRequestErrorinvalid_request
syntic.AuthenticationErrorauthentication
syntic.PermissionDeniedErrorpermission
syntic.NotFoundErrornot_found
syntic.QuotaExhaustedErrorquota_exhausted
syntic.RateLimitErrorrate_limit
syntic.OverloadedErroroverloaded
syntic.InternalServerErrorapi_error
syntic.APIConnectionError, syntic.APITimeoutErrorNo 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 page

Files, 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 syntic name 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>.
Was this page helpful?