Docs

SORAEN API reference

One HTTPS API for chat, answers from your own documents with citations, images and voice. JSON in, JSON out, from any language.

Quickstart

Subscribe on the pricing page to get an API key, then send your first request. Every example uses the base URL https://api.soraen.example.

curl https://api.soraen.example/v1/chat/completions \
  -H "Authorization: Bearer $SORAEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "soraen-lite", "messages": [{"role": "user", "content": "Hello!"}]}'

Python, with nothing but requests:

import os, requests

r = requests.post("https://api.soraen.example/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['SORAEN_API_KEY']}"},
    json={"model": "soraen-lite", "messages": [{"role": "user", "content": "Hello!"}]})
print(r.json()["choices"][0]["message"]["content"])

The request and response formats follow the widely used chat-completions convention, so most existing client libraries work by changing the base URL.

Authentication

Send your key in the Authorization header: Bearer sk-soraen-.... Keys are shown once when you subscribe; you can create a new one on the dashboard, which switches the old one off immediately.

Keep keys on your server. Never put them in a web page or mobile app, where anyone can read them.

Chat completions

POST /v1/chat/completions

FieldTypeDescription
messagesarray, requiredThe conversation: objects with role (system, user, assistant, tool) and content. Up to 100 messages.
max_tokensintegerLongest answer to generate. Default 1,024, maximum 4,096. Reserved against your quota up front.
temperature, top_pnumberSampling. Lower temperature (e.g. 0.2) for precise answers, higher for creative writing.
streambooleanSend the answer as it's written. See Streaming.
stop, seed, presence_penalty, frequency_penalty, response_formatStandard generation options.
tools, tool_choicearray, stringFunctions the model may call. See Tool calling.
ragobjectAnswer from one of your document collections: {"collection": 1, "top_k": 6}. See Answers from documents.

Other fields are ignored. The response:

{
  "model": "soraen-lite",
  "choices": [{"index": 0, "finish_reason": "stop",
               "message": {"role": "assistant", "content": "Hello! How can I help?"}}],
  "usage": {"prompt_tokens": 9, "completion_tokens": 7, "total_tokens": 16}
}

Streaming

With "stream": true the response is a stream of server-sent events: lines starting data: , each a JSON chunk whose choices[0].delta.content holds the next piece of text, ending with data: [DONE]. With rag, the first event carries sources.

with requests.post(URL, headers=H, json={**body, "stream": True}, stream=True) as r:
    for line in r.iter_lines():
        if line.startswith(b"data: ") and line != b"data: [DONE]":
            chunk = json.loads(line[6:])
            for c in chunk.get("choices", []):
                print(c["delta"].get("content", ""), end="", flush=True)

Documents

Collections hold your documents privately: only your key can list, search or answer from them. Upload plain text (Markdown, CSV and JSON work too); a document uploaded again under the same name replaces the old one.

EndpointWhat it does
POST /v1/collectionsCreate a collection: {"name": "Help center"} → {"id": 1, "name": ...}
GET /v1/collectionsYour collections, with document and passage counts
DELETE /v1/collections/{id}Delete a collection and everything in it
POST /v1/collections/{id}/documentsUpload: {"name": "refunds.md", "text": "..."} → {"document": ..., "chunks": 3}
GET /v1/collections/{id}/documentsDocuments in a collection
DELETE /v1/collections/{id}/documents/{name}Delete one document
POST /v1/collections/{id}/searchSearch without generating: {"query": "refund window", "top_k": 6} → matching passages

Answers from documents

Add rag to a chat request. SORAEN finds the most relevant passages with keyword and semantic search, answers only from them, and cites each fact as [1], [2]. The response lists what each number refers to. If the documents don't contain the answer, SORAEN says so instead of guessing.

r = requests.post(URL, headers=H, json={
    "model": "soraen-lite",
    "rag": {"collection": 1, "top_k": 6},
    "messages": [{"role": "user", "content": "How long do refunds take?"}]}).json()

r["choices"][0]["message"]["content"]  # "Refunds are issued within 14 days of purchase [1]."
r["sources"]                          # [{"index": 1, "document": "refunds.md", "position": 0, "text": "..."}]

Your own system prompt is kept and the documents are added after it. Instructions written inside documents are treated as information, never as commands.

Images

Send images inside a user message as content parts. Photos, screenshots, charts and scanned pages all work. Images must be inline (data:image/png;base64,..., or JPEG, WebP, GIF); links to images on other sites are not fetched.

import base64
img = "data:image/jpeg;base64," + base64.b64encode(open("receipt.jpg", "rb").read()).decode()
body = {"model": "soraen-lite", "messages": [{"role": "user", "content": [
    {"type": "text", "text": "What's the total on this receipt?"},
    {"type": "image_url", "image_url": {"url": img}}]}]}

Up to 4 images per request, about 5 MB each. Each image reserves 1,000 tokens of quota; you're billed what the model actually used.

Voice

POST /v1/audio/transcriptions turns speech into text (about 100 languages). Send the recording as a multipart file (WAV, MP3, M4A, OGG or WebM, up to 10 MB and 10 minutes); optionally add language, e.g. en. Then send the text to chat as usual.

r = requests.post("https://api.soraen.example/v1/audio/transcriptions", headers=H,
                  files={"file": open("question.m4a", "rb")})
r.json()  # {"text": "How long do refunds take?", "language": "en", "duration": 3.6}

Billed at 25 tokens per second of audio (about 1,500 per minute).

Tool calling

Describe functions in tools (JSON Schema parameters). When the model wants one, the reply has tool_calls instead of text; run the function, send the result back as a tool message, and ask again.

tools = [{"type": "function", "function": {
    "name": "get_order", "description": "Look up an order by its number",
    "parameters": {"type": "object", "properties": {"number": {"type": "string"}}, "required": ["number"]}}}]
# reply: {"tool_calls": [{"function": {"name": "get_order", "arguments": "{\"number\": \"A123\"}"}}]}

Usage & account

EndpointWhat it does
GET /v1/usage{"tokens_this_month": 12345, "monthly_token_limit": 3000000}
GET /v1/accountEmail, subscription status, usage and limits
POST /v1/account/rotate-keyNew key for the same account; the old key stops working at once
GET /v1/modelsAvailable models (soraen-lite)

Usage resets on the 1st of each month (UTC). Questions, answers, document passages added for rag, images and audio all count.

Errors

Errors return a JSON body with a readable message in detail (or error.message).

StatusMeaningWhat to do
400Malformed requestCheck the JSON and field types
401Missing or invalid key, or subscription endedCheck the Authorization header; see your dashboard
402Monthly tokens used up, or this request would exceed themLower max_tokens, or wait for the 1st
403A collection, document or storage limit reachedDelete something, or contact us for more
404Collection or document not foundCheck the id; collections are private to your key
413Request too large (messages, images or audio)See Limits
429More than 60 requests in a minuteWait a few seconds and retry with backoff
502, 503The model or a helper service is briefly unavailableRetry after a few seconds

Limits (Pro plan)

Tokens per month3,000,000
Requests per minute60
Answer length4,096 tokens (default 1,024)
Conversation100 messages, 200,000 characters
Images4 per request, ~5 MB each
Audio10 MB, 10 minutes per file
Collections10 per account, 500 documents each, 20 million characters in total
Document size2 million characters

Safety

Every request and answer is screened by a safety model. Harmful requests get a polite refusal with finish_reason: "content_filter" instead of an error, so your app can show it like any other reply. Use of the API is subject to the terms and acceptable use policy.