Keys, scopes and headers
X-API-KEY as a separate surface, eleven domain scopes plus a wildcard, three presets — and why “automation” doesn't cover the basic scenario end to end.
Übersetzung in Arbeit
The public API authenticates with a single header and nothing else. It has no anonymous endpoints:
every operation in the spec requires a key, and almost every one names a specific scope in its own
description. The exception is the AI model catalog: any active key works for it. There is one fewer
reference page than there are operations: PUT and PATCH /templates/{template} are collapsed
into one (it's a single operation under two methods), and POST /broadcasting/auth is explained
in the Realtime section.
X-API-KEY: <your key>A separate surface, not a second way to sign in
The public surface (/api/public/*) and the dashboard API are different surfaces with different
authentication. The dashboard Bearer token doesn't work here; conversely, a key doesn't open
dashboard endpoints. The header is called exactly X-API-KEY — neither Authorization nor Api-Key
is recognized, and both get the same response as a missing key.
- Was passiert ist
- The request was rejected before any business logic ran: no action was performed and nothing was charged. The body is the same on every endpoint: `{"success": false, "message": "API key is missing. Provide it in the X-API-KEY header.", "data": null}`. The public API does not accept anonymous requests anywhere.
- Was jetzt zu tun ist
- Make sure the header is named exactly `X-API-KEY` (not `Authorization`, not `Api-Key`) and the value is sent in full. If the key is correct, open the developer section and check that the key is active and has not expired. Retrying is pointless: a 401 is deterministic, and a retry with the same key gets the same response.
The key is hashed (sha256) and resolved with a cache of about an hour. The practical consequence: editing a key's scopes or deactivating it in the dashboard doesn't take effect instantly — requests may keep going through on the old state for a while.
Eleven scopes and a wildcard
The scope is checked before any business logic: a key without the required scope gets 403 and nothing
is executed. The “Operations” column shows how many public API endpoints the scope opens
(reference pages plus POST /broadcasting/auth).
| Scope | What it opens | Operations |
|---|---|---|
orders.read | Reading orders and chunks, the text chunking preview, WebSocket subscription to orders | 9 |
orders.write | Creating, canceling and retrying orders; retrying and regenerating chunks | 6 |
orders.download | POST /storage/url — exchanging a file path for a temporary link | 1 |
templates.read | Reading templates, browse, config-options | 4 |
templates.write | Creating, updating and deleting templates | 3 |
voices.read | All voice catalogs: LumVoice, ElevenLabs, HeyGen, Cartesia, Fish Audio; your own clones and their quota | 14 |
voices.write | Creating, retrying and archiving Cartesia and Fish Audio voice clones | 6 |
billing.read | usage, subscriptions | 3 |
profile.read | GET /user | 1 |
reseller.read | Reseller dashboard: profile and balance, settings, statistics, codes with their full value | 5 |
reseller.write | Issuing gift codes from the reseller balance and canceling them | 2 |
* | Passes any scope check, including scopes that don't exist yet — except reseller.read and reseller.write | all except reseller |
The two AI model catalog operations (GET /ai/models and GET /ai/models/{key}) require
no scope at all — they're not in the table.
Stolperfalle
* does not cover the reseller scopes, and they aren't part of any preset: a gift code is
bearer money, so a key gets reseller.read and reseller.write only when they are
listed explicitly. A wildcard key will get 403 on GET /reseller.
Stolperfalle
orders.download is not covered by orders.read. A key that reads orders perfectly well
and sees the paths in result.files[] will get 403 when exchanging a path for a link. This is the most common
cause of an unexpected rejection at the end of a scenario — when the file has already been generated and paid for.
Get File URL
Three presets
When you create a key, its scopes are usually picked with a preset.
| Preset | Scopes | Doesn't open |
|---|---|---|
read_only | orders.read, orders.download, templates.read, voices.read, billing.read, profile.read | anything that writes orders, templates and voice clones, and the reseller dashboard |
automation | the same plus orders.write | POST, PUT/PATCH and DELETE /templates/{template}, creating, retrying and archiving clones, the reseller dashboard |
full | all scopes except the reseller ones (including templates.write and voices.write) | the reseller dashboard |
The wildcard * is a separate value, not a synonym for the full preset: it also passes checks
that didn't exist when the key was issued (but not the reseller scopes). Which of the two
ways to “grant everything” the dashboard applies when you pick full isn't specified by the sources — if it
matters, check the scope list of the already created key in the dashboard.
The automation trap
The preset named “for automation” doesn't include templates.write — only
templates.read. This isn't a documentation typo or a bug: that's how it's designed. By the same
logic it also lacks voices.write: for a key that clones voices, this scope is added
manually, or you take full.
The consequence is concrete. For TTS, the voice is set not by an order field but by the template's config.tts_settings.voice_id
— so the canonical scenario starts with POST /templates. With an automation key
it stops at the very first step: 403, and the order is never created.
Richtig
For the “create a template → order TTS” scenario, take the full preset — or automation
with templates.write added manually when you create the key.
Falsch
Take automation because the task is called automation, and then look for the cause of the
403 in the order itself — even though it was the template creation step that was rejected.
If the template was created in advance (on the web or with another key), automation is enough: the template's
owner is the account, not a specific key. Someone else's template, however, won't open with any key.
- Was passiert ist
- A domain refusal of order creation: HTTP 403, body `{"success": false, "message": "…", "reason": "template_access_denied"}`. The order was not created.
- Was jetzt zu tun ist
- Check that the template is visible to the key. A template is owned by the account, not by a specific key: a template created on the web works for the API and vice versa, but a template that belongs to another account will not open with any key. If the template is yours, the problem is scopes: `automation` does not include `templates.write`, but it does include `templates.read`.
Language and timezone headers
Both headers are optional, and neither is declared as a parameter in the OpenAPI — a client generated from the spec won't know about them, so you'll have to send them manually.
X-Locale— the language of human-readable strings:messagein responses and names in reference lists such asGET /voices/tags. It has no effect whatsoever on machine fields (reason, statuses, codes), and that's exactly why you must not branch onmessage. The sources disagree: the OpenAPI names this header in 79 descriptions and never mentionsAccept-Language, while the LLM reference does the opposite; if the response language is critical, check the actual behavior with your own key and send both headers.X-Timezone— an IANA zone name (Europe/Moscow). Points in time in responses arrive in ISO-8601 with an explicit offset, UTC by default. A bare offset (+03:00) is rejected: it doesn't know about daylight saving time. The server returns the zone it actually applied in the same header — send garbage and you'll seeX-Timezone: UTCin the response.
What a key can't do
The public surface doesn't process payments and doesn't manage itself. With a key you cannot: top up
the balance, make transfers, buy subscriptions, change the profile, or manage the API keys themselves.
All of that stays in the dashboard. Separately: orders have no update/delete — only cancel
and retry, and PUT/PATCH/DELETE /orders/{order} will answer 405/404.
Key smoke test
The cheapest check is the profile: one operation, one scope, no side effects.
curl -s -o /dev/null -w '%{http_code}\n' \
'https://api.lumean.app/api/public/user' \
-H 'X-API-KEY: <your key>'| Code | What it means |
|---|---|
200 | The key is active and has the profile.read scope |
403 | The key works — authentication passed, only profile.read is missing |
401 | The key wasn't accepted: not sent, truncated, unknown, deactivated or expired |
The key nuance is the second row: 403 confirms that the key is alive. If your key lacks profile.read
by design, check GET /orders?per_page=1 instead — it needs only
orders.read.
Get profile of API key owner
Keys are created in the Developers section, which also shows the active scopes and limits of each key. Don't have a key yet? Sign up first — keys are issued only to the account owner.
Diese Seite einer KI geben
Aktualisiert 17.10.2026 · Spezifikation 0.0.1