Developer API

Build with Leiolai.

OpenAI-compatible chat completions and continuous generation, with adjustable reasoning effort and up to 11 million tokens of context.

Quick start

Create a key, send a request.

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.

curlYour first request
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"
  }'
PythonOpenAI SDK
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)
Base URL

https://api.leiolai.com/v1

Authorization

Send your key on every request: Authorization: Bearer sb_...

Keep the key server-side

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.

POST /v1/chat/completions

Send a conversation, get one assistant response.

POST /v1/infinite/chat/completions

Open a continuous response that can accept new context while it runs.

POST /v1/infinite/inject

Send context, buffer state, or a stop command to a continuous session.

GET /v1/models

Returns leiolai-1, the currently available model.

Compatibility boundary

Use Chat Completions, not the Responses API. Tool calls, audio input, embeddings, fine-tuning, and batch endpoints are not part of this compatibility surface.

Model

One model: Leiolai.

There is no model selection. Every request runs Leiolai. Depth is controlled by reasoning_effort, not by picking a different model.

Model ID

leiolai-1

Send this canonical ID. Provider-qualified forms ending in /leiolai-1 resolve to the same model.

"model": "leiolai-1"
Context window

Up to 11M tokens

A floor-to-ceiling context window. Bring the whole project.

11,000,000 tokens
Modality

Text, images, and files in

Messages accept text, image, and supported inline file parts. Responses are text.

text · image_url · file
Request contract

Chat completions.

Creates a completion for the supplied conversation. The body follows the OpenAI chat completions schema; the supported fields are below.

POST /v1/chat/completions
application/jsonRequest body
{
  "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"
}
modelstringRequired

Use leiolai-1. Provider-qualified forms ending in /leiolai-1 resolve to it; other non-empty IDs return 404 model_not_found.

messagesarrayRequired

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.

streambooleanOptional

When true, content is returned as server-sent event deltas. When omitted, the response is a single JSON object.

stream_options.include_usagebooleanOptional

When true with stream: true, the stream includes OpenAI's final usage-only chunk before data: [DONE].

reasoning_effortstringOptional

Response depth: low, medium, high, or xhigh. Defaults to low.

max_tokensintegerOptional

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.

modestringRequired

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.

Compatibility boundary

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.

Streaming

Server-sent events.

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.

text/event-streamWith include_usage
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]
idstring

The response ID. It stays the same across every chunk. Include it when reporting a bad answer.

choices[0].delta.rolestring

assistant, on the first chunk only.

choices[0].delta.contentstring

Append each value to build the reply. A short answer may arrive in one content chunk.

data: [DONE]sentinel

The final frame. Close the stream when you see it.

stream omittedobject

One chat.completion object with choices[0].message.content.

usageobject

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.

choices[0].finish_reasonstring

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

What's an output limit?

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.

POST /v1/infinite/chat/completions
curlOpen a continuous session
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
  }'
continuous_protocolintegerRequired

Send 2. Omitting this field or sending another value returns HTTP 400.

lookaheadbooleanOptional

When true, the stream includes a replaceable preview separate from delivered text.

consumer_leasebooleanOptional

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.

realtime_audiobooleanOptional

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.

max_tokensintegerOptional

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.

Opening messages

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.

Stream events

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.

text/event-streamEvent sequence
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]
session_idstring

The first event. Send this opaque value as the session query parameter on every inject request.

choices[].delta.contentstring

Delivered answer text. Append every value. Delivered bytes never change.

lookaheadstring

The full current preview. Replace the previous preview with this value. An empty string clears it. Never append lookahead to delivered text.

inject_ackobject

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.

data: [DONE]sentinel

Ends the session. A pause in output does not end the session. SSE comment lines are keepalives and can be ignored.

Inject context

POST /v1/infinite/inject?session=<session_id>
textstring

The user's completed message.

text_partialstring

The user's complete message so far. Each update replaces the previous value.

imagestring

The latest image as a base64 data URI. It replaces the prior live image after screening.

consumer_heartbeatboolean

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.

stopboolean

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.

application/jsonFinal injection response
{
  "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

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.

application/jsonBuffer report
{
  "buffer": {
    "remaining_seconds": 1.2,
    "drain_rate": 1.0
  }
}
remaining_secondsnumber

The non-negative number of seconds currently queued by the client.

drain_ratenumber

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.

Errors

Error contract.

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].

application/json400 response
{
  "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
401invalid_api_keyNo valid API key was provided. Check the bearer token.
402insufficient_creditsYour available balance cannot cover this request. Top up or request less output.
402api_budget_exhaustedThe key or the account reached its API budget for the period. Raise or clear the budget in your account.
402api_budget_requiredAuto reload is on for the account, so API use needs a budget. Set one, or turn auto reload off.
400invalid_valueA field is invalid, two fields conflict, or mode is missing.
400invalid_jsonThe request body is not valid JSON.
403content_policy_violationThe content policy blocked the input before streaming began.
403 before stream; 200 aftercontent_filterThe 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].
404model_not_foundThe requested model is not available.
404session_not_foundThe continuous session ended, does not exist, or belongs to another API key.
405method_not_allowedThis route does not accept that HTTP method.
413request_too_largeThe request body exceeds the accepted size.
429rate_limit_exceededYou exceeded the request rate limit. Wait for the number of seconds in Retry-After, then try again.
500streaming_unavailableStreaming is unavailable. Try again.
503 before stream; 200 afterbilling_unavailableAPI billing is temporarily unavailable. Before streaming, this is a 503. During an active continuous stream, an error data frame arrives before [DONE].
503api_budget_unverifiableSpending records are temporarily unavailable. Retry shortly.
503vision_unavailableImage understanding is temporarily unavailable. Retry shortly.
503 before stream; 200 afterservice_busyThe service is at capacity. A 503 sent before streaming includes Retry-After when available. An active continuous stream ends with an error data frame.
503service_unavailableThe request could not be completed. Retry shortly.
Pricing

Token pricing.

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.

Research

early access

(mode: "non-private")

EffortInput / 1MOutput / 1M
low$0.01$0.02
medium$0.25$0.65
high$6.40$21
xhigh$160$700

Private

(mode: "private")

EffortInput / 1MOutput / 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.

Access and billing

Pay for what you run.

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.