API documentation
Mirai v1 is an affordable chat completions API for the agents you build. It does not serve Responses. If your harness already speaks chat completions, pointing it at this base URL and an API key is usually the only change needed.
Base URL
This is the only client base URL. A chat completions call goes to https://satuapps.com/v1/chat/completions. Do not set the base to the website origin.
https://satuapps.com/v1Authentication
Every request needs an API key in the Authorization header, using the Bearer scheme. A live Mirai key starts with mirai-. A key that starts with sk- is the wrong kind. Keep it on the server. Never embed it in a browser or mobile client.
Authorization: Bearer YOUR_API_KEYDon't have a key yet? Create one in a moment, and copy it right away because the key is shown only once. Get an API key.
Chat completions
POST /v1/chat/completions
One call, one answer, on chat completions. There is no Responses endpoint and no session or conversation ID. Send the full message history with every request.
curl https://satuapps.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mirai-1",
"messages": [
{"role": "user", "content": "Apa itu basis data relasional?"}
]
}'The response:
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1780000000,
"model": "mirai-1",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Basis data relasional adalah ..."},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 24, "completion_tokens": 118, "total_tokens": 142}
}Parameters
| Name | Type | Description |
|---|---|---|
| model | string | One of mirai-1, mirai-2, or a flash model id from the model list. Defaults to mirai-1 if omitted. |
| messages | array | Conversation history. Each item has a role (user or assistant) and content. Client system messages are ignored. A request that only contains system messages is rejected. Mirai v1 always applies its own system prompt. |
| max_tokens | integer | Upper limit on answer length. Lowers the model's own output budget; never raises it. finish_reason stays "stop" even when this limit cuts the answer short. Length is the only signal you'll get. |
| temperature | number | Answer variability, from 0 (most deterministic) to 2 (most varied). Left unset, the model's own default applies. |
| stream | boolean | If true, the answer is sent as incremental chunks instead of one JSON object. See Streaming below. Default false. |
Images
The last message's content can carry an image alongside text, using the chat completions contract's own format, built from parts: {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}. Only data URIs are accepted. Plain https:// links are rejected, so Mirai v1 never fetches a URL on your behalf. Limits: up to 8 images per request, 8 MB each, PNG, JPEG, WebP, or GIF only.
Streaming
Set stream: true to receive the answer as a sequence of server sent events instead of a single JSON object. The stream always ends with a data: [DONE] line.
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1780000000,"model":"mirai-1","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1780000000,"model":"mirai-1","choices":[{"index":0,"delta":{"content":"Basis"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1780000000,"model":"mirai-1","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]Tokens are sent as the model produces them, so the first words arrive long before the answer is finished. Measured in production on 19 August 2026: a 300 word answer arrived as 254 chunks spread over 7.2 seconds, with the first content at 2.2 seconds.
Add "stream_options": {"include_usage": true} to receive one extra event carrying the token counts, sent right before [DONE].
Agent harnesses
Mirai is a model backend. Keep the harness you already use and point it at https://satuapps.com/v1 with a mirai- key. Use chat completions, not Responses.
Hermes
Hermes keeps its settings in ~/.hermes/config.yaml. Set provider to custom, base_url to https://satuapps.com/v1, and model.default to mirai-1 or mirai-2. The field is model.default, not model.model. Use a Mirai key that starts with mirai-. A key that starts with sk- is the wrong kind. Transport is chat completions, not Responses. You can also run hermes config set from the terminal.
# ~/.hermes/config.yaml
model:
provider: custom
default: "mirai-1"
base_url: "https://satuapps.com/v1"
api_key: "YOUR_API_KEY"OpenClaw
OpenClaw keeps its settings in ~/.openclaw/openclaw.json. Add Mirai under models.providers, then list the model again under agents.defaults.models.
// ~/.openclaw/openclaw.json
{
models: {
providers: {
mirai: {
baseUrl: "https://satuapps.com/v1",
apiKey: "${MIRAI_API_KEY}",
api: "openai-completions",
models: [
{ id: "mirai-1", name: "Mirai 1",
contextWindow: 200000, maxTokens: 32768 }
]
}
}
},
agents: {
defaults: {
model: { primary: "mirai/mirai-1" },
models: { "mirai/mirai-1": { alias: "Mirai 1" } }
}
}
}That second block is not optional. OpenClaw rejects any model that is not in the agents.defaults.models allowlist, even when the provider is configured correctly, and the error does not say why.
Models
GET /v1/models Returns the public models and current pricing, using the same key as chat completions. The list is Mirai 1, Mirai 2, and six flash models.
Public models
| id | Use case | Input per 1M | Output per 1M | Context | Max output |
|---|---|---|---|---|---|
| mirai-1 | free | $0.10 | $0.80 | 200,000 | 32,768 |
| mirai-2 | Deep work and code | $0.50 | $1.50 | 500,000 | 131,072 |
Public models
| id | Use case | Input per 1M | Output per 1M | Context | Max output |
|---|---|---|---|---|---|
| gemini-3.8-flash | free limited | $0.75 | $3.75 | 1,048,576 | 4,096 |
Mirai 1 · Mirai 2 · Google Gemini 3.8 Flash
Errors
Authentication, model, quota, and server errors share one JSON shape:
{"error": {"message": "Missing or invalid API key.", "type": "authentication_error"}}Message text is always English, whatever language you're reading this page in. Branch your code on type, not on the message string.
One exception: requests that fail basic validation (malformed JSON, an unsupported image) get FastAPI's default {"detail": ...} body instead of the shape above.
| Code | Meaning |
|---|---|
| 400 | The request body is invalid, usually an image that violates the supported format, size, or count limits. |
| 422 | A required field is missing or the wrong type (for example, an empty messages array). This comes from automatic schema validation, not Mirai v1 logic. |
| 401 | The API key is missing or wrong. The header must be Authorization: Bearer <key>. |
| 403 | This key has been disabled on our end. Contact support. This isn't something you can fix on your end. |
| 404 | The requested model isn't in the public catalogue. The error message itself lists the valid choices. |
| 429 | Requests are being rate limited right now. Wait briefly before retrying. |
| 503 | The service isn't accepting requests at the moment: maintenance, the daily spending cap was reached, or a specific model is temporarily disabled. Retry later. |
| 504 | The request to the model took too long and was cut off. Retry; if it keeps happening, try a different model. |
| 500 | An unexpected error on our side. Rare, and not specific to your request. Retry. |
Health check
GET /health Needs no key and only ever returns {"status":"ok"}. Use it for uptime checks, not for anything that depends on the model backend.
Ready to make your first call?
Get an API key, point your harness at satuapps.com/v1, and keep the rest of your integration as it is.
Get an API key