# Conventions

> How every endpoint writes times, credits, durations, ids and lists, how it pages, and what null and an absent field mean.

Every endpoint — and the [agent's WebSocket](https://docs.lumavox.ai/agents/websocket.md) — writes its values the same way. Learn these once and every response reads alike.

## Field names

Every field, query parameter, enum value and error code is `snake_case`, in every response and in the SDKs' types: what the reference shows is what you send and what you get back.

A quantity carries its unit in its name: `duration_seconds`, `size_bytes`, `credits_micros`, `expires_in_seconds`. A field naming a moment ends in `_at`. A reference to another resource is `<thing>_id`.

## Times

A moment is an ISO 8601 string in UTC, always with three fractional digits and a `Z`:

```json title="An instant"
{ "created_at": "2026-10-24T09:30:00.000Z" }
```

It is exactly what JavaScript's `Date.prototype.toISOString()` writes, so `new Date(value)` reads it and `toISOString()` gives the same text back. A calendar day is `YYYY-MM-DD`, in UTC.

A time you send — `since` and `until` on a listing, an expiry — needs its zone, `Z` or an offset such as `+02:00`. A time without one is ambiguous, and is refused with a `422`.

## Credits

An amount of credits is an **integer of micro-credits**: one credit is `1000000`. Every such field ends in `_micros`:

```json title="A balance"
{ "credit_balance_micros": 2500000 }
```

That is 2.5 credits. Integers add and compare exactly, so sum them as they come and divide by a million only to show a person. You send amounts the same way — an ephemeral key's `credit_limit_micros` is an integer.

## Lists and pages

Every list answers one shape:

```json title="A page"
{
  "data": [{ "id": "…" }, { "id": "…" }],
  "next_cursor": "eyJrIjoiZ2VuZXJhdGlvbnMi…"
}
```

Ask for a page size with `limit`, and for the next page by passing `next_cursor` back as `cursor`. On the last page `next_cursor` is `null`:

```bash title="curl"
curl "https://api.lumavox.ai/v1/generations?limit=50&cursor=eyJrIjoiZ2VuZXJhdGlvbnMi…" \
  -H "Authorization: Bearer $API_KEY"
```

- **A cursor is opaque**: pass it back unchanged, to the list that gave it. One it cannot read is a `422`, never the first page again.
- **Lists do not count their rows**: there is no total. Page until `next_cursor` is `null`.
- **A list that is small by nature** — your agents, the voices, the templates — answers the same shape with `next_cursor` always `null`.
- **A list inside a resource** — a session's messages — is the same shape, as a field.

## Null and absent

A response always has every field its schema names. `null` means "none": a key that was never used has `"last_used_at": null`, never a missing field.

In a request, a field you leave out takes its default when you create something, and stays as it is when you update it. `null` clears a field that may be empty.

A request body may hold only the fields its endpoint knows: an unknown one — a typo such as `voiceId` for `voice_id` — is a `422` naming it, rather than being dropped for its default.

## Ids

Every resource's `id` is a string the API made: a prefixed id such as `gen_a1B2c3D4e5F6g7H8` for generations, assets, folders, voices, end users, templates and requests, and a UUID for agents, sessions, knowledge entries and keys. Treat it as opaque: send it back as you got it, and do not parse it.

## Enums

A field with a fixed set of values — a `status`, a `kind`, an error `code` — names its values in the reference. A set may grow: handle a value you do not know, rather than failing on it.

## Related

- [Errors and limits](https://docs.lumavox.ai/errors.md) — the error envelope and every `code`.
- [Credits and usage](https://docs.lumavox.ai/usage.md) — the balance and what spends it.
