Skip to content
Lumean API

Conventions

The response envelope and its single exception, the base URL, four different identifier types and the date format.

Everything that's the same across all operations in the spec: the response shape, addresses, identifiers and time. Read it once, and it saves you effort on every endpoint afterward.

Response envelope

Success (200/201) and most rejections arrive in the same envelope:

{ "success": true,  "message": "<human-readable>", "data": <payload> }
{ "success": false, "message": "<reason>" }

In errors, data is either absent or null (401 and 403 send it explicitly). The errors key is not used in ordinary rejections — it appears only with 422.

Some domain 4xx responses carry machine-readable fields on top of the envelope: reason almost always, plus shortfall_tokens/quote_token/expires_at with 402, actual_lmc_minor and quoted_lmc_minor with 409, and window/limit/used/reset_at/retry_after with the token quota.

The exception: 422 arrives outside the envelope

Validation uses the standard Laravel format, and there's no success field in it at all:

{
  "message": "<summary message>",
  "errors": { "config.tts_settings.voice_id": ["error text", "..."] }
}

The response schema declares both fields required — message and errors. The errors keys are dotted request field paths, not top-level names.

Careful

if (!res.success) on a 422 compares against undefined and behaves unpredictably: the client sees “not an error” where the order wasn't created. Check the HTTP code, and read success only after you've made sure the code isn't 422. The spec's introductory text (“all responses are in the format { success, message, data, errors? }”) doesn't account for this case.

422422 — request not accepted
What happened
The only API response that comes OUTSIDE the common envelope: `{"message": "…", "errors": {"field": ["…"]}}` — it has no `success` field. Code like `if (!res.success)` works on a 422 only by coincidence, because it is comparing against `undefined`.
What to do right now
First check whether `errors` is present. If it is, fix the request according to the listed fields. If it is not, but there is a `reason`, the request shape is not the problem, and repeating the same body gets the same refusal; read the page for that specific code.
Open the code page

Two different sources live under 422. If there's errors, the request shape is broken — fix the fields. If there's no errors but there is a reason (content_blocked, text_empty, service_not_found…), the shape isn't the problem, and repeating the same body will get the same rejection.

Base URL and environments

WhatValue
REST basehttps://api.lumean.app/api/public
WebSocketwss://ws.lumean.app:443, app key 7c4dd881d8116ac76fe89aff71d8c2ff
WS authPOST https://api.lumean.app/api/public/broadcasting/auth
OpenAPIhttps://api.lumean.app/docs/public-openapi.json (EN: …/public-openapi.en.json)
AsyncAPIhttps://api.lumean.app/docs/public-asyncapi.json

There's only one environment: the public API has no sandbox or staging host, and the spec declares a single server. Test runs go to the same address and are billed the same way — to check the request shape without being charged, use the free chunking preview (POST /orders/chunks/preview).

All requests are HTTPS. The body is JSON (Content-Type: application/json), except for file uploads (multipart/form-data). Responses are JSON in UTF-8.

Identifier types

There are four, each of a different nature. Mixing them up is costly: a client that validates “any id” as a UUID breaks on three of the four.

ResourceTypeFormat
Order, TemplateUUID (string)8f14e45f-ceea-467a-9c0e-4d2b9f1b2c3d
User, ApiKey, Subscription, Serviceinteger42
External voice (ElevenLabs, HeyGen)the provider's string voice_idformat set by the provider
Chunk preview (previewId)sha256, 64 hex characters, no hyphens3f9a1c…

Three traps in a row:

  1. preview_id is not a resource identifier but a content hash. It is deterministic for the triple “text + template + mode”: the same text gives the same preview_id and is served from cache instantly — including when that text has already been processed by a web session of the same account (the cache is shared per owner, not per sign-in method). Waiting for a “new id” on a repeated request is pointless.
  2. An external provider's voice_id doesn't go into the order. There is no orders.voice_id field: the voice lives in the template's config.tts_settings.voice_id.
  3. The numeric user id is needed for WebSocket. The private channel name is private-orders.{userId}, and {userId} here is exactly the integer from GET /user, not the order UUID.

Dates and time

All points in time are ISO-8601 with an explicit offset: 2026-07-08T12:34:56+00:00. By default this is UTC; the X-Timezone header with an IANA zone name rewrites the same moments in your zone (more in the Keys, scopes and headers guide). Calendar dates in Y-m-d format — the boundaries of validity periods — are not converted by zone: they have no zone.

Pagination in brief

Regular lists put an object in data: { "items": [...], "current_page": 1, "last_page": 5, "per_page": 20, "total": 93 }, with the parameters page (starting at 1) and per_page. External voice catalogs use their own wrapper { voices, total, page, page_size, has_more }, where page counts from zero.

Gotcha

The data.meta field doesn't exist, even though four descriptions in the spec refer to it: the counters sit next to items, directly in data. This and the other pagination pitfalls are covered in the Pagination guide.

How to read errors

The order is exactly this:

  1. HTTP code — it alone distinguishes 422 (outside the envelope) from everything else.
  2. reason in the body — the machine-readable rejection code. Branch on it and nothing else.
  3. errors — only with 422, and only when the request shape is broken: a dictionary of “field path → array of messages”.
  4. message — a human-readable string in the key's language. The wording changes without notice, is localized by the X-Locale header and is not an error code.

There's no catalog of reason codes in the OpenAPI: only four values are described by machine schemas, and the remaining couple dozen live in prose. Each one is broken down in the error catalog, which also has a diagnostician you can feed the entire response body to.

Feed this page to an AI

Open .mdllm.md

Updated 09/04/2026 · spec 0.0.1