Quickstart

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.

1. Get an API key

Log in to your dashboard, create a project, and generate an API key. Keys start with sk-senal-.

2. Make your first request

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=...)

Authentication

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.

Model

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.

POST /v1/chat/completions

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

Request body

ParameterTypeRequiredDescription
modelstringYesMust be "senal-1"
messagesarrayYesArray of {role, content} objects. Roles: system, user, assistant
max_tokensintegerNoMaximum output tokens. Default 4096
tools / tool_choicearray | stringNoFunction and tool calling
temperaturenumberNoSampling temperature (0 to 2)
top_pnumberNoNucleus sampling
stopstring | arrayNoStop sequences
response_formatobjectNoStructured outputs
userstringNoCaller-provided user ID, stored on request

Response

Standard OpenAI ChatCompletion object, including a usage field on every response.

POST /v1/responses

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)

Request body

ParameterTypeRequiredDescription
modelstringYesMust be "senal-1"
inputarray | stringYesConversation items so far: messages, function_call and function_call_output items
instructionsstringNoSystem-level instructions for this turn
toolsarrayNoFunction tool definitions: {type: "function", name, description, parameters}
max_output_tokensintegerNoMaximum output tokens for the turn
reasoningobjectNoReasoning controls, e.g. {"effort": "high"}

Response

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.

Parameters

These are the parameters Senal supports, across both endpoints. Names from the Chat Completions and Responses APIs are both accepted.

ParameterEffect
messages / inputThe conversation
system / instructionsSystem-level instructions for the turn
tools, tool_choiceFunction and tool calling
max_tokens / max_completion_tokens / max_output_tokensOutput budget
reasoning_effort / reasoning.effortDepth of reasoning
temperature, top_p, seed, stopSampling controls
response_format / text.formatStructured outputs
userCaller-provided user ID, stored on the request

Streaming is not yet available.

Usage object

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
}

Prompt caching

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.

Structuring requests for caching

Reasoning tokens

senal-1 reasons internally before generating a response. These internal reasoning steps consume tokens that are:

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.

Rate limits

LimitValueNotes
Requests per minute60 RPMPer API key
Requests per day1,000 RPDPer org
Tokens per minute200,000 TPMInput + output + reasoning combined
Concurrent requests10In-flight requests per org

Rate limit errors return 429 Too Many Requests with a Retry-After header. Contact us for higher limits.

Error codes

HTTPtypeDescription
400invalid_request_errorMalformed request body, missing required field, or invalid parameter value
401authentication_errorMissing or invalid API key
403permission_errorKey revoked, org suspended, or insufficient balance
429rate_limit_errorRate limit exceeded; respect Retry-After header
500api_errorInternal server error; retry with exponential backoff
529overloaded_errorUpstream model overloaded; retry after a short delay

Error responses always follow this shape:

{
  "error": {
    "message": "Invalid API key.",
    "type": "authentication_error",
    "code": null
  }
}