---
title: Conventions
url: https://lumean.app/de/developers/docs/guide/conventions
locale: de
description: "The response envelope and its single exception, the base URL, four different identifier types and the date format."
updated: 2026-09-04
source_locale: en
sources: [get-orders, post-orders, get-orders-chunks-preview-preview-id, get-voices-elevenlabs-library]
errors: ["422", "404"]
spec_version: 0.0.1
---

# Conventions

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:

```json
{ "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:

```json
{
  "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.

> [!CAUTION]
> `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.

### 422 — 422 — request not accepted

| Что произошло | Что делать |
| --- | --- |
| 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`. | 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. |

`Retry-After`: нет. Разбор: https://lumean.app/de/developers/docs/errors/422

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

| What | Value |
| --- | --- |
| REST base | `https://api.lumean.app/api/public` |
| WebSocket | `wss://ws.lumean.app:443`, app key `7c4dd881d8116ac76fe89aff71d8c2ff` |
| WS auth | `POST https://api.lumean.app/api/public/broadcasting/auth` |
| OpenAPI | `https://api.lumean.app/docs/public-openapi.json` (EN: `…/public-openapi.en.json`) |
| AsyncAPI | `https://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.

| Resource | Type | Format |
| --- | --- | --- |
| Order, Template | UUID (string) | `8f14e45f-ceea-467a-9c0e-4d2b9f1b2c3d` |
| User, ApiKey, Subscription, Service | integer | `42` |
| External voice (ElevenLabs, HeyGen) | the provider's string `voice_id` | format set by the provider |
| Chunk preview (`previewId`) | sha256, 64 hex characters, no hyphens | `3f9a1c…` |

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.

### GET /orders/chunks/preview/{previewId}

Preview Page

Справочник: https://lumean.app/de/developers/docs/api/get-orders-chunks-preview-preview-id

## 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](/developers/docs/guide/authentication) 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**.

> [!WARNING]
> 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](/developers/docs/guide/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](/developers/docs/errors),
which also has a diagnostician you can feed the entire response body to.

---

- Diese Seite im Browser: https://lumean.app/de/developers/docs/guide/conventions
- Vollständige maschinenlesbare Referenz: https://api.lumean.app/docs/llm.md
