Lumi speaks the OpenAI chat-completions protocol. Point Cursor, Raycast, Chatbox, Open WebUI or the official OpenAI SDK at the base URL below, paste a key, and you get Lumi — same persona, same tools, same voice as the app.
1. Base URL
Paste this into the field your client calls Base URL, API Host or Override OpenAI Base URL.
https://api-lumi.kynguyen.cc/v1
Four ways to get this wrong
All four return 404 Not Found, which reads like the service is down when it isn't. If you see a 404, check this list first.
https://api-lumi.kynguyen.cc/v1/v1/modelsbase URL already ends in /v1 and the client added another
https://lumi.kynguyen.cc/v1/modelsthat is the web app — the API lives on api-lumi
https://api-lumi.kynguyen.cc/api/v1/models/api/v1 is the app's own API, not this one
https://api-lumi.kynguyen.cc/modelsthe /v1 prefix is missing
A correct URL with a bad key answers 401, not 404 — so a 401 actually means your base URL is right.
2. Get an API key
Keys are issued by the admin, not self-service — every request spends the same shared model budget, so access is granted per person. Contact the admin to request one.
Once it has been issued, your key is always readable in the app: open Lumi → Settings → Account → Show. You don't need to save it anywhere else, and losing it costs nothing.
It looks like sk-lumi-… and goes in the field labelled API Key or OpenAI API Key. Treat it like a password: anyone holding it can spend your quota. Ask the admin to delete it if it leaks.
3. First request
The fastest check that everything is wired up — it needs no model call.
Streaming — -N matters, without it curl buffers and everything appears at once:
curl -N -X POST https://api-lumi.kynguyen.cc/v1/chat/completions \
-H "Authorization: Bearer $LUMI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"stream": true,
"messages": [{"role": "user", "content": "Explain SSE in five sentences."}]
}'
Or with the official Python SDK:
from openai import OpenAI
client = OpenAI(api_key="sk-lumi-…", base_url="https://api-lumi.kynguyen.cc/v1")
stream = client.chat.completions.create(
model="gemini-3.8-flash",
messages=[{"role": "user", "content": "Xin chào Lumi"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
4. Models
Put one of these slugs in the model field. A name we don't recognise — gpt-4o, or a typo — is not an error: it quietly falls back to gemini-3.8-flash. So if a reply feels like the wrong model, check the model field of the response, which always reports what actually answered.
Slug
Model
Maker
Notes
gemini-3.8-flash
Gemini 3.8 Flash
Google
default · newest; picks its own thinking depth
gemini-3.1-pro-low
Gemini 3.1 Pro (Low)
Google
gemini-3.1-pro-high
Gemini 3.1 Pro (High)
Google
claude-sonnet-4.6-thinking
Claude Sonnet 4.6 (Thinking)
Anthropic
thinks before answering
claude-opus-4.6-thinking
Claude Opus 4.6 (Thinking)
Anthropic
thinks before answering
gpt-oss-120b-medium
GPT-OSS 120B (Medium)
OpenAI
5. What works, what doesn't
Supported
Streaming and non-streaming completions
Multi-turn conversations of any length
Your own system message
Images, as data: base64 URLs
Web search, memory and Lumi's other tools — run server-side
Not supported
Function calling. A tools array is accepted and ignored — Lumi's toolset is server-owned. Anything that needs the model to call your tools (agent modes that edit files or run commands) will not work.
/v1/embeddings — no such endpoint
Remote image URLs (only data: is read)
temperature and friends are accepted, then ignored
Prefer stream: true. A non-streamed request sends no bytes until the whole reply is finished, and our edge gives up on a silent connection at around 100 seconds — so long answers have a 90-second ceiling without streaming, and none with it.
6. When something goes wrong
401
Missing, mistyped or deleted key. The base URL is fine — a wrong URL gives 404.
404
Wrong base URL. See the list in step 1.
429
Hourly reply limit reached. Wait, or ask the admin to raise your quota.
502
Every model attempt failed. Usually momentary — retry.
504
A non-streamed reply ran past the deadline. Retry with "stream": true.