API reference
Endpoints, parameters, limits and errors
The API follows OpenAI's, so the official SDKs and their documentation apply. The samples are in Python and assume the client from the quick start.
Authentication
Send your key as a bearer token on every request. Keys start with sk-tunai-.
Base URL: https://api.tunai.tn/v1
Chat completions
Supports stream, stream_options, temperature, max_tokens (up to 32000), top_p, stop, tools, tool_choice, response_format, seed, presence_penalty and frequency_penalty. They are passed on to the provider.
Streaming
stream = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "Write a haiku."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
print(delta, end="", flush=True)Responses arrive as Server-Sent Events and end with data: [DONE], as with OpenAI.
Tool calling and JSON mode
Tool calling
response = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "What's the weather in Tunis?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
)
print(response.choices[0].message.tool_calls)JSON mode
response = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "List 3 Tunisian cities as JSON."}],
response_format={"type": "json_object"},
)Embeddings
Takes a string or a list of strings, plus an optional dimensions. Use an embedding model.
response = client.embeddings.create(
model="text-embedding-3-small",
input=["Tunis", "Sfax", "Sousse"],
)
print(len(response.data), "vectors x", len(response.data[0].embedding), "dims")Jev
Asks Jev, TypeSafe's model, named questions about your content and returns typed answers with their probabilities. Same key and balance; charged by input tokens only. See the Jev guide.
Models
GET https://api.tunai.tn/v1/models/{model}
Public, no key needed. Lists only the models we can serve right now. The same list, by provider, is on Supported models.
Token usage
Every response reports the tokens it used, which is what you're charged for.
response = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.usage.prompt_tokens, response.usage.completion_tokens)
# When streaming, ask for a final usage chunk
stream = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "Hello!"}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.usage:
print("total tokens:", chunk.usage.total_tokens)Rate limits and request ids
Each key is limited to a per-minute request cap by default. On API keys you can set a lower limit, a monthly budget, an expiry, or limit a key to some endpoints.
Every response carries the current limit state:
x-ratelimit-limit-requests: 120
x-ratelimit-remaining-requests: 119
x-ratelimit-reset-requests: 60s
x-request-id: req_9e0d436bb573482f85d432f779d12304Quote the x-request-id when you contact support. It points to the exact call and what it cost.
Errors
Errors use OpenAI's { error: { message, type, code } } shape.
400invalid_request_errorInvalid parameter. param names the field.401invalid_request_errorMissing, invalid, revoked or expired API key.402insufficient_quotaOut of balance, or the key reached its monthly budget.403invalid_request_errorThis key isn't allowed to use that endpoint.404invalid_request_errorUnknown model id or route. The message names the closest real id; the full list is on Supported models.429rate_limit_errorRate limit exceeded for this key.502 / 503 / 504api_errorUpstream provider error, unavailable, or timed out.- On a 429, wait and retry with backoff.
- A 402 means the balance or the key's budget ran out. Top up to continue.
- Keep keys out of client-side code and repositories.