leiolai-1
Send this canonical ID. Provider-qualified forms ending in /leiolai-1 resolve to the same model.
OpenAI-compatible chat completions and continuous generation, with adjustable reasoning effort and up to 11 million tokens of context.
The API authenticates with bearer tokens. The finite endpoint implements the documented subset of OpenAI Chat Completions below, so you can use raw HTTP or an OpenAI SDK. Changing only the SDK base URL is not enough because every request must also choose mode.
curl https://api.leiolai.com/v1/chat/completions \
-H "Authorization: Bearer sb_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "leiolai-1",
"messages": [{"role": "user", "content": "Say hi in five words."}],
"stream": true,
"mode": "private"
}' from openai import OpenAI
client = OpenAI(
api_key="sb_your_key",
base_url="https://api.leiolai.com/v1",
)
response = client.chat.completions.create(
model="leiolai-1",
messages=[{"role": "user", "content": "Say hi in five words."}],
reasoning_effort="low",
extra_body={"mode": "private"},
)
print(response.choices[0].message.content) https://api.leiolai.com/v1
Send your key on every request: Authorization: Bearer sb_...
Never put an API key in frontend JavaScript. Anyone can extract it and spend your balance. Keep the key on a backend you control, then have your frontend call that backend.
Send a conversation, get one assistant response.
Open a continuous response that can accept new context while it runs.
Send context, buffer state, or a stop command to a continuous session.
Returns leiolai-1, the currently available model.
Use Chat Completions, not the Responses API. Tool calls, audio input, embeddings, fine-tuning, and batch endpoints are not part of this compatibility surface.
There is no model selection. Every request runs Leiolai. Depth is controlled by reasoning_effort, not by picking a different model.
Send this canonical ID. Provider-qualified forms ending in /leiolai-1 resolve to the same model.
A floor-to-ceiling context window. Bring the whole project.
Messages accept text, image, and supported inline file parts. Responses are text.
Creates a completion for the supplied conversation. The body follows the OpenAI chat completions schema; the supported fields are below.
{ "model": "leiolai-1", "messages": [ { "role": "user", "content": "Explain why the sky changes color at sunset." } ], "stream": true, "stream_options": {"include_usage": true}, "reasoning_effort": "medium", "mode": "private" }
Use leiolai-1. Provider-qualified forms ending in /leiolai-1 resolve to it; other non-empty IDs return 404 model_not_found.
Role and content pairs. Role is system, user, or assistant. Content is a plain string or an array of typed parts: {"type":"text","text":"..."}, {"type":"image_url","image_url":{"url":"..."}}, and {"type":"file","file":{"filename":"notes.md","file_data":"data:text/markdown;base64,..."}}. Images work in both modes and use base64 data: URLs; remote image URLs are not accepted. Inline files may contain up to 2 MiB of UTF-8 text or code. file_id references and binary files such as PDFs are not accepted.
When true, content is returned as server-sent event deltas. When omitted, the response is a single JSON object.
When true with stream: true, the stream includes OpenAI's final usage-only chunk before data: [DONE].
Response depth: low, medium, high, or xhigh. Defaults to low.
An optional output cap, in tokens. If omitted, Leiolai derives a ceiling from the available balance and API budget. max_completion_tokens is an alias.
Send private or non-private. There is no default because this choice changes both privacy and price. Images and supported inline files work in both modes. Omitting mode returns HTTP 400.
This is a Chat Completions-compatible subset, not a promise that every OpenAI field or SDK feature is implemented. Only the fields listed here are part of the contract. You always receive one choice.
When stream is true, the response uses OpenAI Chat Completions server-sent events and ends with data: [DONE]. Append every choices[0].delta.content value to build the reply. Leiolai may hold content until it is final, so do not assume token-by-token timing or a minimum number of content chunks. When stream is omitted, the response is a single chat.completion object.
data: {"id":"chatcmpl-8f1c2d3a4b5e6f7a8b9c0d1e","object":"chat.completion.chunk","created":1787000000,"model":"leiolai-1","choices":[{"delta":{"role":"assistant","content":"Hi there"},"index":0,"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-8f1c2d3a4b5e6f7a8b9c0d1e","object":"chat.completion.chunk","created":1787000000,"model":"leiolai-1","choices":[{"delta":{},"index":0,"finish_reason":"stop"}],"usage":null}
data: {"id":"chatcmpl-8f1c2d3a4b5e6f7a8b9c0d1e","object":"chat.completion.chunk","created":1787000000,"model":"leiolai-1","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":2,"total_tokens":16}}
data: [DONE] The response ID. It stays the same across every chunk. Include it when reporting a bad answer.
assistant, on the first chunk only.
Append each value to build the reply. A short answer may arrive in one content chunk.
The final frame. Close the stream when you see it.
One chat.completion object with choices[0].message.content.
Token counts for the request: prompt_tokens, completion_tokens, and total_tokens. The non-streaming object always includes them. A stream includes them only when you send stream_options.include_usage: true; OpenAI's usage-only chunk has an empty choices array and arrives immediately before [DONE]. Earlier chunks carry usage: null. These are the counts you are billed for.
stop when Leiolai completed the answer, length when it reached your max_tokens cap, and content_filter when the content policy stopped the response. Null before the terminal chunk.
Continuous generation keeps a response open and lets you add context while it runs. There is no fixed product output limit. If you omit max_tokens, Leiolai derives the session ceiling from your available balance and API budget. The opening endpoint always returns server-sent events. It is a Leiolai extension, not a standard Chat Completions stream, so use an SSE client that preserves every event.
curl -N https://api.leiolai.com/v1/infinite/chat/completions \
-H "Authorization: Bearer sb_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "leiolai-1",
"messages": [{"role": "user", "content": "Teach me astronomy until I stop you."}],
"reasoning_effort": "medium",
"mode": "private",
"continuous_protocol": 2,
"lookahead": true,
"consumer_lease": true,
"realtime_audio": true
}' Send 2. Omitting this field or sending another value returns HTTP 400.
When true, the stream includes a replaceable preview separate from delivered text.
When true, send consumer_heartbeat while the stream is live. This lets Leiolai close the session if the downstream consumer disconnects but a proxy keeps the HTTP connection open.
Set this when the client speaks delivered text as it arrives. Leiolai will not rewrite words the user may already have heard. Send buffer reports to pace delivery.
An output-token ceiling for the session. max_completion_tokens is an alias. If both are sent, they must be the same positive integer. The stream ends at the ceiling.
model, messages, reasoning_effort, and mode have the same meaning as on finite chat. Text, image, and supported inline file parts work in both modes.
The first event carries an opaque session ID. Keep it and send the same API key on every later inject. A session belongs to the API key that opened it.
data: {"session_id":"inf-0123456789abcdef","continuous_protocol":2}
data: {"choices":[{"delta":{"content":"The first sentence. "}}]}
data: {"lookahead":"This preview may still change."}
data: {"lookahead":""}
data: {"inject_ack":{"id":"opaque-receipt-id","seq":1,"anchor":20,"text":"Wait, explain that more simply."}}
data: {"choices":[{"delta":{"content":"Here is the simpler version. "}}]}
: keepalive
data: [DONE] The first event. Send this opaque value as the session query parameter on every inject request.
Delivered answer text. Append every value. Delivered bytes never change.
The full current preview. Replace the previous preview with this value. An empty string clears it. Never append lookahead to delivered text.
Confirms a completed user message. anchor is the UTF-8 byte position where that message belongs in the delivered answer. It may be ahead of the answer bytes already received. Leiolai sends inject_ack before sending answer bytes beyond that position.
Ends the session. A pause in output does not end the session. SSE comment lines are keepalives and can be ignored.
The user's completed message.
The user's complete message so far. Each update replaces the previous value.
The latest image as a base64 data URI. It replaces the prior live image after screening.
Send only this field with true to confirm the downstream consumer is still connected. Use it only when the opening request set consumer_lease: true.
Send true by itself to end the session.
Send inject requests one at a time, in order, within each session. Do not send text and text_partial together. Send stop by itself. Unknown fields return HTTP 400.
As the user interrupts, send the complete transcript so far through text_partial. Leiolai clears the old lookahead and pauses delivery. When the user finishes, send the completed message through text. Leiolai adds it to the conversation, sends inject_ack with the exact insertion point, and resumes from the updated context.
{
"ok": true,
"inject_ack": {
"id": "opaque-receipt-id",
"seq": 1,
"anchor": 20,
"text": "Wait, explain that more simply."
}
} Use the SSE receipt to place the completed user message. If anchor is ahead of the delivered answer, wait until you have received that many UTF-8 bytes. Split the answer at anchor, insert the user message, then display later answer text after it.
Opening messages accept the same image and inline file parts as finite chat. During a live session, image updates the current visual context. A live image must be a base64 data URI with up to 8 MiB of decoded image data; the complete inject body may be up to 12 MiB. Files cannot be added through the inject endpoint after the session opens.
Buffer control lets a client maintain a target amount of consumable output. It is not voice-specific. Use it for speech, captions, translation, rendering, or any consumer that measures queued work in seconds.
{
"buffer": {
"remaining_seconds": 1.2,
"drain_rate": 1.0
}
} The non-negative number of seconds currently queued by the client.
The queue's consumption speed relative to real time, from 0 through 4. Send 0 while paused or unable to consume, 1 at normal speed, and 2 at twice normal speed.
The server updates its queue estimate between reports by subtracting elapsed time multiplied by the last drain_rate. Report immediately when queued output changes, consumption pauses, resumes, or fails, or the drain rate changes. While output is draining, report at least every 500 ms. Faster reports are accepted. Send one request at a time so reports arrive in order.
At the current defaults, Leiolai pauses delivery when the client reports more than 2.5 seconds of queued output. Generation continues while delivery is paused, so lookahead may keep changing. Leiolai resumes delivery when the queue reaches 2.5 seconds or less. If the queue drops below 0.8 seconds and more output is ready, Leiolai may add enough to target about 1.5 seconds. These thresholds are controlled by the server. Clients should report their actual state instead of reproducing this logic.
Before streaming begins, API-generated errors use the OpenAI-style JSON envelope below. After a stream begins, its HTTP status remains 200. If the content policy stops a finite stream, its final choice has finish_reason: "content_filter". If a continuous stream fails after it starts, it sends an error data frame before data: [DONE].
{
"error": {
"message": "mode is required. Send \"private\" or \"non-private\".",
"type": "invalid_request_error",
"param": "mode",
"code": "invalid_value"
}
} error.type is the broad error class. error.code is the stable programmatic code, and error.param names the request field when one caused the failure. Every API-generated response includes X-Request-Id. Log it when you contact support. You may send X-Client-Request-Id; valid values are echoed in the response.
| Status | Code | What it means |
|---|---|---|
| 401 | invalid_api_key | No valid API key was provided. Check the bearer token. |
| 402 | insufficient_credits | Your available balance cannot cover this request. Top up or request less output. |
| 402 | api_budget_exhausted | The key or the account reached its API budget for the period. Raise or clear the budget in your account. |
| 402 | api_budget_required | Auto reload is on for the account, so API use needs a budget. Set one, or turn auto reload off. |
| 400 | invalid_value | A field is invalid, two fields conflict, or mode is missing. |
| 400 | invalid_json | The request body is not valid JSON. |
| 403 | content_policy_violation | The content policy blocked the input before streaming began. |
| 403 before stream; 200 after | content_filter | The content policy stopped the response. Before streaming, this is a 403. In an active finite stream, finish_reason is "content_filter". In an active continuous stream, an error data frame arrives before [DONE]. |
| 404 | model_not_found | The requested model is not available. |
| 404 | session_not_found | The continuous session ended, does not exist, or belongs to another API key. |
| 405 | method_not_allowed | This route does not accept that HTTP method. |
| 413 | request_too_large | The request body exceeds the accepted size. |
| 429 | rate_limit_exceeded | You exceeded the request rate limit. Wait for the number of seconds in Retry-After, then try again. |
| 500 | streaming_unavailable | Streaming is unavailable. Try again. |
| 503 before stream; 200 after | billing_unavailable | API billing is temporarily unavailable. Before streaming, this is a 503. During an active continuous stream, an error data frame arrives before [DONE]. |
| 503 | api_budget_unverifiable | Spending records are temporarily unavailable. Retry shortly. |
| 503 | vision_unavailable | Image understanding is temporarily unavailable. Retry shortly. |
| 503 before stream; 200 after | service_busy | The service is at capacity. A 503 sent before streaming includes Retry-After when available. An active continuous stream ends with an error data frame. |
| 503 | service_unavailable | The request could not be completed. Retry shortly. |
Billing is per token, with separate input and output rates. reasoning_effort and mode determine those rates. Early access pricing currently discounts research rates; it does not apply to xhigh or private requests.
(mode: "non-private")
| Effort | Input / 1M | Output / 1M |
|---|---|---|
| low | $0.01 | $0.02 |
| medium | $0.25 | $0.65 |
| high | $6.40 | $21 |
| xhigh | $160 | $700 |
(mode: "private")
| Effort | Input / 1M | Output / 1M |
|---|---|---|
| low | $20 | $90 |
| medium | $40 | $175 |
| high | $80 | $350 |
| xhigh | $160 | $700 |
Prices in USD per 1M tokens. The request charge is (input tokens × input rate + output tokens × output rate) / 1,000,000. The original submitted input is charged once. For continuous generation, that means the opening messages; later inject updates are not charged as a second input request. The rates are fixed when the request is accepted, so an in-flight request cannot be repriced.
Keys are created and revoked in your Leiolai account. Revoking a key takes effect immediately.
API usage draws on your account balance and is billed separately from app subscriptions.