Lydira API
TR

Guides

Conventions

The handful of rules that hold everywhere, so you only have to learn them once.

Base URL

There is no single API address. Lydira runs one server per residency region, each with its own database, and your account lives on exactly one of them. A key issued on one cell does not resolve on another: you get 401 unauthorized, which reads like a bad key rather than the wrong address, and that is the failure worth recognising before you spend an afternoon on it.

Take the host from the address bar of your Lydira tab. Settings → API keys states it next to the key, which is the one place it is certain to be right. Then pick it at the top of this page and every sample here switches to it. A self-hosted install or a custom domain uses its own; locally that is http://localhost:3000.

Under that host, everything lives beneath /api/v1. Unlike the browser URLs there is no locale and no account number in the path, because the key already says which account you are.
https://YOUR-CELL.lydira.com/api/v1/customers
RegionHostServers inAccount numbers
Türkiyetr.lydira.comTR10000000–19999999
Europe / Rest of worldeu.lydira.comDE20000000–29999999
Americasus.lydira.comUS30000000–39999999

The account number in your browser URL tells you which cell you are on without asking anyone: the bands never overlap.

Envelopes

A list always puts its rows under data. A list over records carries page beside them; a bounded list does not, because there is nothing to walk: a trip's days, the account's seats, the report catalogue. A few responses put context alongside the collection, like unread_count on the inbox. Two endpoints have no envelope at all, because the object is the payload: GET /api/v1/me and a report result.
{
  "data": [  ],
  "page": { "next_cursor": "…", "has_more": true, "limit": 25 }
}

Paging

Cursors, not page numbers. A page number drifts the moment a row is written above it; a cursor does not. Re-send the same request with ?cursor= while has_more is true. ?limit= takes 1 to 100 and defaults to 25. Cursors are opaque: do not parse them, do not build them.
cursor = None
while True:
    params = {"limit": 100, **({"cursor": cursor} if cursor else {})}
    page = requests.get(url, headers=headers, params=params).json()

    for row in page["data"]:
        handle(row)

    if not page["page"]["has_more"]:
        break
    cursor = page["page"]["next_cursor"]

Idempotency

Any write may carry an Idempotency-Key header: your own unique string, up to 255 characters. It is optional, and omitting it changes nothing. Send one and a repeat of the same request returns the first call's response verbatim with Idempotent-Replay: true instead of writing again. Only a success is remembered, so a failed write leaves the key free to retry. Reusing a key for a different body or path answers 422 idempotency_key_reuse; repeating while the first call is still running answers 409 idempotency_conflict with a Retry-After. Keys are scoped to your account and kept for 7 days.
Idempotency-Key: 8f14e45f-ea2c-4f33-9a3b-6d0c1b7e5a90

Rate limits

Every response to a request carrying a resolvable key reports its budget, refusals included. X-RateLimit-Reset is the UTC second at which the window rolls and Remaining returns to Limit. The figure is the tightest ceiling standing between you and a 429, which on the ingest writes is their own lower limit of 30 a minute rather than your account's rate. A request with no usable key never reached a budget and carries none of these.
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 587
X-RateLimit-Reset: 1773483660

# on a 429, the same three plus:
Retry-After: 18

Dates, money, nulls

Timestamps are ISO-8601 and dates are YYYY-MM-DD. Money is a decimal string, never a float, so nothing rounds on the way through JSON. A null on a money or personal-data field can mean two different things and they are worth telling apart: the value is genuinely absent, or your key's role cannot see it. Where it is the second, the record usually says so, as content_masked does on a message.
{
  "price_amount": "1250.00",
  "currency": "EUR",
  "start_date": "2026-03-14",
  "updated_at": "2026-03-14T09:30:00Z"
}