Developer APIExperimental

Build with Leiolai.

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

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, an OpenAI SDK, or the Vercel AI SDK. When you create a key, choose Unrestricted, Private only, or Research only. An unrestricted key must send mode on every request. A restricted key may omit mode because the server infers its only permitted mode. A request cannot override a key restriction.

Get access

Open Account in the Leiolai web app. Sign in, choose Top up beside your balance, then open More, Leiolai API, and Manage keys. Choose the key's mode access. A new key is shown once. Store it on a backend you control.

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)
TypeScriptVercel AI SDK
import { generateText } from "ai";
import { leiolai } from "@leiolai/ai-sdk-provider";

const result = await generateText({
  model: leiolai("leiolai-1"),
  prompt: "Say hi in five words.",
});
console.log(result.text);
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. Audio input, embeddings, fine-tuning, and batch endpoints are not part of this compatibility surface.

Model

Leiolai 1.

Use leiolai-1 for every request. Set response depth with reasoning_effort.

Model ID

leiolai-1

Use leiolai-1 as the model ID.

"model": "leiolai-1"
Context window

Up to 11M tokens

Experimental

This one goes to 11. A floor-to-ceiling context window for the whole project.

11,000,000 tokens
Modality

Text, images, and files

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. Unknown model IDs return 404 model_not_found.

messagesarrayRequired

Role and content pairs. Role is system, user, assistant, or tool. Content is a plain string or an array of typed parts. An assistant message may include tool_calls; a tool result uses role: "tool" and the matching tool_call_id.

{"type":"text","text":"..."} {"type":"image_url","image_url":{"url":"data:..."}} {"type":"file","file":{"filename":"notes.md","file_data":"data:..."}}

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_completion_tokensintegerOptional

An optional output cap, in tokens. If omitted, Leiolai derives a ceiling from the available balance and API budget. max_tokens is a compatibility alias.

modestringConditional

Unrestricted API keys must send private or non-private. A Private-only or Research-only key may omit mode because the server infers the key's only permitted mode. A value that conflicts with the key restriction returns HTTP 403.

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.

Function tools

Call functions in your application.

Send OpenAI function definitions in tools. Leiolai returns validated calls for your application to execute. It never executes your functions.

application/jsonFunction tool request
{
  "model": "leiolai-1",
  "mode": "private",
  "messages": [{"role": "user", "content": "What is the weather in Chicago?"}],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get the weather for a city",
      "strict": true,
      "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
        "additionalProperties": false
      }
    }
  }],
  "tool_choice": "auto"
}
tool_choicestring or object

Use auto, none, required, or name one function with the standard OpenAI object shape.

parallel_tool_callsboolean

Set true to allow one assistant turn to return multiple independent calls. All calls belong to that same turn. Dependent work remains sequential.

Tool results

Execute every call in your application. Send the assistant message back unchanged, then add one role: "tool" message per result with the matching tool_call_id. Keep every returned call ID unchanged.

Validation

Each returned function name and argument object is checked against the supplied definition before delivery. A tool response uses finish_reason: "tool_calls".

Structured outputs

Schema-valid JSON.

Use response_format when the assistant response itself must be JSON. Do not combine response_format with function tools in the same request.

application/jsonJSON schema response
{
  "model": "leiolai-1",
  "mode": "private",
  "messages": [{"role": "user", "content": "Classify the sentiment."}],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "sentiment",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {"sentiment": {"type": "string", "enum": ["positive", "negative"]}},
        "required": ["sentiment"],
        "additionalProperties": false
      }
    }
  }
}
json_object

{"type":"json_object"} requires one valid JSON object.

json_schema

Accepts name, optional description, optional strict, and schema. Successful content is one schema-valid JSON value with no Markdown fence or trailing prose.

Streaming

Server-sent events.

When stream is true, the response uses OpenAI Chat Completions server-sent events and ends with data: [DONE]. The choices field is the OpenAI-compatible response array. Leiolai returns one item, so read choices[0]. In each text event, delta.content is new text; append every value to build the reply. Chunk boundaries and timing can vary, so clients must not assume token-by-token delivery or a minimum number of 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.

choices[0].delta.tool_callsarray

Collect calls by index. Keep the first id, append each function.arguments fragment in order, and parse the completed JSON string after the terminal finish_reason: "tool_calls" chunk.

data: [DONE]sentinel

Marks the end of the stream.

stream omittedobject

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

usageobject

Token counts for the request: prompt_tokens, completion_tokens, and total_tokens. A non-streaming response includes them when counts are available. 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. When present, these report the token counts used for billing.

choices[0].finish_reasonstring

stop when Leiolai completed the answer, tool_calls when your application should execute returned functions, length when it reached your max_completion_tokens cap, and content_filter when the content policy stopped the response. Null before the terminal chunk.

Continuous generation

No one likes output limits.

Experimental

Continuous generation keeps a response open and lets you add context while it runs. There is no output-token ceiling. The session will end when you stop it or if your available balance or API budget is exhausted. 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",
    "lookahead": true,
    "consumer_lease": true,
    "realtime_audio": true
  }'
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.

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. You pay the input rate for the opening messages once. Inject updates do not add another input charge.

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.

The choices field is the OpenAI-compatible response array. Leiolai returns one item, so read choices[0]. In a text event, delta.content is new text; append each value.

text/event-streamEvent sequence
data: {"session_id":"inf-0123456789abcdef"}

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[0].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 tells you how much assistant text belongs before it and is measured in UTF-8 bytes from the start of the delivered answer.

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."
  }
}

inject_ack.anchor tells you how much assistant text belongs before the user’s message. It is a UTF-8 byte count from the start of the delivered answer. Once you have received that many bytes, show the user’s message. Show any later answer text beneath 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 reports are optional. They tell Leiolai how much consumable output remains queued and how quickly it is draining. Use them for latency-sensitive playback or another consumer that can measure queued work in seconds. Text-only clients can ignore them.

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.

Report whenever queued output or its drain_rate changes. For latency-sensitive playback, periodic updates around every 500 ms can help correct timing drift. The server estimates the remaining buffer between reports. Less frequent updates 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 an SSE stream begins, its HTTP status remains 200 because the response headers were already sent. A terminal API error then arrives in the stream before data: [DONE]. If the content policy stops a finite stream, its final choice instead has finish_reason: "content_filter".

application/json400 response
{
  "error": {
    "message": "mode is required for unrestricted API keys. 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 an unrestricted API key omits mode.
400invalid_tool_schemaA function definition or its JSON Schema is invalid.
400invalid_tool_resultAssistant tool-call history or a caller-supplied tool result is invalid.
400context_length_exceededThe request exceeds the available model context. Shorten the messages or tool history.
400invalid_jsonThe request body is not valid JSON.
403api_key_mode_restrictedThe request conflicts with the API key's Private-only or Research-only restriction.
403content_policy_violationThe content policy blocked the input before streaming began.
403content_filterThe content policy stopped the response. An active finite stream reports finish_reason: "content_filter".
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.
503billing_unavailableAPI billing is temporarily unavailable. Retry shortly.
503api_budget_unverifiableSpending records are temporarily unavailable. Retry shortly.
503context_length_unavailableThe service could not verify the request's context length. Retry shortly.
503vision_unavailableImage understanding is temporarily unavailable. Retry shortly.
503service_busyThe service is at capacity. Retry after the number of seconds in Retry-After when that header is present.
503service_unavailableThe request could not be completed. Retry shortly.
502invalid_tool_callA generated tool call failed deterministic validation after repair. Retry the request.
Pricing

Token pricing.

Low, medium, and high Research rates use launch pricing. xhigh and Private use standard pricing.

Research

(mode: "non-private")

EffortInput, USD / 1M tokensOutput, USD / 1M tokens
low$20$0.01$90$0.02
medium$40$0.25$175$0.65
high$80$6.40$350$21
xhigh$160$700

Launch pricing

Private

(mode: "private")

EffortInput, USD / 1M tokensOutput, USD / 1M tokens
low$20$90
medium$40$175
high$80$350
xhigh$160$700