Senal exposes senal-1 through two OpenAI-compatible endpoints. No new SDK required. Point your existing client at https://api.senal.ai/v1 and change three lines.
Read the technical report for the detail behind how senal-1 is built and evaluated.
Log in to your dashboard, create a project, and generate an API key. Keys start with sk-senal-.
from openai import OpenAI client = OpenAI( base_url="https://api.senal.ai/v1", api_key="sk-senal-...", ) resp = client.chat.completions.create( model="senal-1", messages=[ {"role": "user", "content": "Summarise the material risks in this 10-K filing."} ], ) print(resp.choices[0].message.content) # Token usage is always returned print(resp.usage) # Usage(prompt_tokens=..., completion_tokens=..., total_tokens=...)
Pass your API key as a Bearer token in the Authorization header:
Authorization: Bearer sk-senal-...
Never expose your key client-side. Rotate keys from the API keys page if a key is compromised.
The only model available is senal-1. It is versioned and pinned. The model behind the name does not change without notice. It reasons internally before responding, which increases latency but improves answer quality on hard, correctness-critical tasks.
Typical latency is 10 to 60 seconds end-to-end, since the answer is returned once the reasoning phase completes.
OpenAI-compatible chat completions endpoint. Drop in as a replacement for gpt-4o, claude-3-5-sonnet, or any other model you already call.
POST https://api.senal.ai/v1/chat/completions
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Must be "senal-1" |
messages | array | Yes | Array of {role, content} objects. Roles: system, user, assistant |
max_tokens | integer | No | Maximum output tokens. Default 4096 |
tools / tool_choice | array | string | No | Function and tool calling |
temperature | number | No | Sampling temperature (0 to 2) |
top_p | number | No | Nucleus sampling |
stop | string | array | No | Stop sequences |
response_format | object | No | Structured outputs |
user | string | No | Caller-provided user ID, stored on request |
Standard OpenAI ChatCompletion object, including a usage field on every response.
OpenAI Responses API compatible endpoint, built for agentic / tool-calling loops. The caller owns the loop: one call is one agent turn. Send the conversation so far plus your tool definitions, get back the next turn, execute any tool calls yourself, and call again with the results appended.
POST https://api.senal.ai/v1/responses
from openai import OpenAI client = OpenAI( base_url="https://api.senal.ai/v1", api_key="sk-senal-...", ) resp = client.responses.create( model="senal-1", instructions="You are a careful analyst.", input=[{"role": "user", "content": "Is this contract clause enforceable?"}], ) print(resp.output)
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Must be "senal-1" |
input | array | string | Yes | Conversation items so far: messages, function_call and function_call_output items |
instructions | string | No | System-level instructions for this turn |
tools | array | No | Function tool definitions: {type: "function", name, description, parameters} |
max_output_tokens | integer | No | Maximum output tokens for the turn |
reasoning | object | No | Reasoning controls, e.g. {"effort": "high"} |
The response returns output, an array of reasoning, function_call and message items, plus status ("completed" or "incomplete"), and a usage object.
{
"output": [
{"type": "reasoning", "summary": []},
{"type": "function_call", "name": "read_file",
"arguments": "{\"path\": \"contract.txt\"}", "call_id": "call_..."},
{"type": "message", "role": "assistant",
"content": [{"type": "output_text", "text": "..."}]}
],
"status": "completed",
"usage": {
"input_tokens": 17000,
"output_tokens": 5200,
"output_tokens_details": {"reasoning_tokens": 3881}
}
}If status is "incomplete", the turn hit max_output_tokens before finishing. When output contains function_call items, execute them and send the results back as function_call_output items on the next call. Senal does not run your tools for you.
These are the parameters Senal supports, across both endpoints. Names from the Chat Completions and Responses APIs are both accepted.
| Parameter | Effect |
|---|---|
messages / input | The conversation |
system / instructions | System-level instructions for the turn |
tools, tool_choice | Function and tool calling |
max_tokens / max_completion_tokens / max_output_tokens | Output budget |
reasoning_effort / reasoning.effort | Depth of reasoning |
temperature, top_p, seed, stop | Sampling controls |
response_format / text.format | Structured outputs |
user | Caller-provided user ID, stored on the request |
Streaming is not yet available.
Every response includes a usage object reporting native token counts. These counts are used to calculate billing.
{
"prompt_tokens": 1234,
"completion_tokens": 456,
"total_tokens": 1690
}Senal caches automatically on both endpoints. There is nothing to enable and no extra parameter. The rate card is two lines, $4.00 per million input tokens and $24.00 per million output tokens, with no separate cached-input rate.
Caching is most effective when consecutive requests share a prefix, which is the normal shape of multi-turn and agentic work. Structuring requests the following way keeps that prefix intact.
tools identical. Changing a tool definition starts a new cache.reasoning_effort identical across a conversation.senal-1 reasons internally before generating a response. These internal reasoning steps consume tokens that are:
total_tokens and are charged at the standard output rate of $24.00 per million tokens.reasoning_tokens breakdown for transparency.This is intentional: the reasoning process is internal to the model and optimised for quality, not for inspection. If you need chain-of-thought output visible in the response, prompt the model to include its reasoning in the answer itself.
| Limit | Value | Notes |
|---|---|---|
| Requests per minute | 60 RPM | Per API key |
| Requests per day | 1,000 RPD | Per org |
| Tokens per minute | 200,000 TPM | Input + output + reasoning combined |
| Concurrent requests | 10 | In-flight requests per org |
Rate limit errors return 429 Too Many Requests with a Retry-After header. Contact us for higher limits.
| HTTP | type | Description |
|---|---|---|
| 400 | invalid_request_error | Malformed request body, missing required field, or invalid parameter value |
| 401 | authentication_error | Missing or invalid API key |
| 403 | permission_error | Key revoked, org suspended, or insufficient balance |
| 429 | rate_limit_error | Rate limit exceeded; respect Retry-After header |
| 500 | api_error | Internal server error; retry with exponential backoff |
| 529 | overloaded_error | Upstream model overloaded; retry after a short delay |
Error responses always follow this shape:
{
"error": {
"message": "Invalid API key.",
"type": "authentication_error",
"code": null
}
}