Lydira API
TR

API reference

Import

10 endpoints

Structured JSON ingest for tickets, hotel stays, transfers, cruises, car rentals, tour departures and bookings, customers, suppliers, leads and brought-forward documents. Requires an API key with the import (or full) scope.

Reading reaches further than writing. charters, visa_cases and events are registry entities that broadcast webhooks and page through GET /api/v1/imports/{entity}/records, but cannot be pushed: a POST to one answers 422 entity_not_writable and names the read endpoint, rather than the 404 unknown_entity an unrecognised key gets. GET /api/v1/imports/entities lists the writable set only.

Three rules hold for every writable entity:

  • external_id is required on every row — your own key in the source system. Re-sending never duplicates: people and brought-forward documents UPDATE, bookings SKIP.
  • Dry-run ≡ commit. POST …/validate runs the identical path with nothing persisted.
  • A bad row fails alone, with a per-field reason; the rest of the batch still imports.

Field lists are not enumerated here because they are registry-driven and evolve — GET /api/v1/imports/entities/{key} is the authoritative, always-current schema and ships a ready-to-POST sample body. A Postman collection generated from the same source is downloadable from Settings → API keys, and from this guide.

Where a record can be filed

GET/api/v1/workspace

Reachable with a narrow key: import

The account's companies and branches, with their ids — the values company_id and branch_id take on an import. The import endpoints have always accepted both and nothing said what they could be, so the ids had to be handed over out of band and re-sent whenever the agency opened a branch.

account.time_zone is the clock a timestamp with no offset is read in. Requires the import (or full) scope.

Every branch of the account is listed, whichever office the key's own owner sits in. headquarters marks the branch a push with no branch_id is filed under, and it is also the id to pass when you want a feed to answer with every branch at once.

Response · 200

  • accountobject
    Show fields
    • idinteger
    • namestring
    • time_zonestring
  • companiesobject[]
    Show fields
    • iduuid
    • namestring
    • defaultboolean

      Where a row that names no company_id lands.

    • tax_countrystring
    • currencystring

      What a row with no currency of its own is priced in.

  • branchesobject[]
    Show fields
    • iduuid
    • namestring
    • codestringnullable
    • company_iduuid
    • headquartersboolean

Responses

  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
  • 429Rate limit exceeded
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/workspace' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "account": {
    "id": 1000001,
    "name": "Ayşe Yılmaz",
    "time_zone": "Europe/Istanbul"
  },
  "companies": [
    {
      "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "name": "Ayşe Yılmaz",
      "default": true,
      "tax_country": "TR",
      "currency": "EUR"
    }
  ],
  "branches": [
    {
      "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "name": "Ayşe Yılmaz",
      "code": "CUST-0042",
      "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "headquarters": true
    }
  ]
}

List importable entities

GET/api/v1/imports/entities

Reachable with a narrow key: import

The catalogue — each entity's scope, idempotency rule and endpoints. data carries the entities; limits and conventions ride alongside as context. No page: the registry is a fixed set rather than a collection to walk.

Response · 200

  • dataobject[]required
    Show fields
    • keystring
    • labelstring
    • modelstring
    • scopeenum

      one ofcompanyaccount

    • dependency_orderinteger

      Import leaves first — customers/suppliers (1) before the bookings that reference them.

    • idempotencyobject
      Show fields
      • external_id_requiredboolean
      • modeenum

        upsert — re-sending a source id updates the record (people, brought-forward documents). create_once — re-sending skips it (bookings; the CreateServices have no update path).

        one ofupsertcreate_once

      • keystring
    • endpointsobject
      Show fields
      • validatestring
      • importstring
  • limitsobject
    Show fields
    • max_rows_per_requestinteger
    • sync_row_limitinteger
  • conventionsobject

Responses

  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
  • 429Rate limit exceeded
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/entities' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "data": [
    {
      "key": "hotel_stays",
      "label": "Hotel stays",
      "model": "HotelStay",
      "scope": "company",
      "dependency_order": 1,
      "idempotency": {
        "external_id_required": true,
        "mode": "upsert",
        "key": "(source_system, hotel_stays, external_id)"
      },
      "endpoints": {
        "validate": "string",
        "import": "string"
      }
    }
  ],
  "limits": {
    "max_rows_per_request": 500,
    "sync_row_limit": 100
  },
  "conventions": {}
}

Field schema + sample body for one entity

GET/api/v1/imports/entities/{key}

Reachable with a narrow key: import

The authoritative, always-current field list — including nested shapes (a ticket's legs / passengers, a booking's cost_lines / sell_lines) — plus entity.sample, a body you can POST as-is.

Parameters

Path

  • keystringrequired

Response · 200

  • entityobject
    Show fields
    • keystring
    • labelstring
    • modelstring
    • scopeenum

      one ofcompanyaccount

    • dependency_orderinteger

      Import leaves first — customers/suppliers (1) before the bookings that reference them.

    • idempotencyobject
      Show fields
      • external_id_requiredboolean
      • modeenum

        upsert — re-sending a source id updates the record (people, brought-forward documents). create_once — re-sending skips it (bookings; the CreateServices have no update path).

        one ofupsertcreate_once

      • keystring
    • endpointsobject
      Show fields
      • validatestring
      • importstring
    • fieldsobject[]
      Show fields
      • keystring
      • typeenum

        one ofstringemailphonetax_numbercurrencydatedatetimemoneydecimalintegerbooleanenumtaxonomyreferencelist

      • requiredboolean
      • piiboolean
      • enum_valuesstring[]

        Enforced allowlist.

      • suggested_valuesstring[]

        Standard catalogue values — documented, NOT enforced (your account may add its own).

      • taxonomy_kindstring

        The /settings/taxonomies list that governs this value.

      • notestring
      • itemsobject[]

        For list fields — the shape of each array element (may nest).

        Show fields
        • keystring
        • typeenum

          one ofstringemailphonetax_numbercurrencydatedatetimemoneydecimalintegerbooleanenumtaxonomyreferencelist

        • requiredboolean
        • piiboolean
        • enum_valuesstring[]

          Enforced allowlist.

        • suggested_valuesstring[]

          Standard catalogue values — documented, NOT enforced (your account may add its own).

        • taxonomy_kindstring

          The /settings/taxonomies list that governs this value.

        • notestring
        • itemsobject[]

          For list fields — the shape of each array element (may nest).

          Show fields
          • keystring
          • typeenum

            one ofstringemailphonetax_numbercurrencydatedatetimemoneydecimalintegerbooleanenumtaxonomyreferencelist

          • requiredboolean
          • piiboolean
          • enum_valuesstring[]

            Enforced allowlist.

          • suggested_valuesstring[]

            Standard catalogue values — documented, NOT enforced (your account may add its own).

          • taxonomy_kindstring

            The /settings/taxonomies list that governs this value.

          • notestring
          • itemsobject[]

            For list fields — the shape of each array element (may nest).

    • referencesobject[]
      Show fields
      • keystring
      • target_entitystring
      • match_bystring[]

        Tried in order — the source id first, then the target's natural keys.

      • on_missingenum

        skip imports the record unlinked; error fails the row.

        one oferrorskip

    • sampleobject

      A complete body you can POST as-is.

Responses

  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
  • 404Resource not found
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/entities/tickets' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "entity": {
    "key": "hotel_stays",
    "label": "Hotel stays",
    "model": "HotelStay",
    "scope": "company",
    "dependency_order": 1,
    "idempotency": {
      "external_id_required": true,
      "mode": "upsert",
      "key": "(source_system, hotel_stays, external_id)"
    },
    "endpoints": {
      "validate": "string",
      "import": "string"
    },
    "fields": [
      {
        "key": "ar_aging",
        "type": "string",
        "required": true,
        "pii": true,
        "enum_values": [
          "string"
        ],
        "suggested_values": [
          "string"
        ],
        "taxonomy_kind": "string",
        "note": "string",
        "items": [
          {
            "key": "ar_aging",
            "type": "string",
            "required": true,
            "pii": true,
            "enum_values": [],
            "suggested_values": [],
            "taxonomy_kind": "string",
            "note": "string",
            "items": []
          }
        ]
      }
    ],
    "references": [
      {
        "key": "customer_external_id",
        "target_entity": "customers",
        "match_by": [
          "string"
        ],
        "on_missing": "error"
      }
    ],
    "sample": {}
  }
}

Dry-run a batch

POST/api/v1/imports/{entity}/validate

Reachable with a narrow key: import

Runs the identical path the commit runs and persists nothing (service-backed entities execute the real CreateService inside a rolled-back transaction). Always synchronous.

Parameters

Path

  • entitystringrequired

    A key from GET /api/v1/imports/entities on the write endpoints. The read endpoints (/records, /records/{external_id}) additionally accept the read-only entities charters, visa_cases and events, which broadcast webhooks but cannot be pushed.

Headers

  • Idempotency-Keystring

    Your own unique string for this write (a UUID is the obvious choice), max 255 characters. Optional: omit it and nothing changes. Send it and a repeat of the same request returns this call's response verbatim, with Idempotent-Replay: true, instead of writing again.

Body

  • rowsobject[]required

    One object per record. Keys are the entity's field keys — unknown keys are dropped and reported back in ignored_fields. Every row must carry external_id.

  • rowobject

    Convenience alternative to rows for pushing a single record.

  • source_systemstringdefault api

    Namespaces your ids, so two source systems can reuse the same id.

  • company_iduuid
  • branch_iduuid

    The branch the rows are filed under, from GET /api/v1/workspace. Omit it and they are filed under the key owner's own branch — the one marked headquarters on most accounts. An id that is not a branch of this account is refused with 422 unknown_branch; a booking filed under no branch at all would be invisible to the branch that took it.

  • modeenum

    Defaults to sync at or below the sync row limit, async above it.

    one ofsyncasync

  • on_conflictenumdefault skip

    What to do when a create-once record already exists for the row's external_id. skip (default) leaves it alone, so a replay never silently rewrites a booking. update amends it through the entity's allowlisted fields — status moves through the state machine, and a cancelled status runs the vertical's cancel service (reversing the customer invoice and voiding the supplier bill) instead of writing the field. An invoiced or cancelled record is frozen except for notes, and an amendment that touches anything else is refused, not half-applied. Pricing is never amendable — cancel and re-create, or price it in-app.

    one ofskipupdate

  • decimal_separatorenum

    Only needed when your source writes amounts as 1.234,56.

    one of,.

Response · 200

  • importobject
    Show fields
    • iduuid
    • entitystring
    • source_systemstring
    • statusenum

      one ofvalidatingreadyimportingcompletedpartialfailed

    • modeenum

      one ofvalidatecommit

    • asyncboolean
    • company_iduuidnullable
    • statsobject
      Show fields
      • totalinteger
      • will_createinteger
      • will_updateinteger
      • createdinteger
      • updatedinteger
      • skippedinteger
      • invalidinteger
      • failedinteger
    • errorstringnullable
    • created_atdate-time
    • linksobject
      Show fields
      • selfstring
      • rowsstring
  • ignored_fieldsstring[]

    Keys you sent that this entity doesn't declare — a typo reads as a warning, not a silent no-op.

  • previous_batch_iduuidnullable

    An earlier batch with a byte-identical payload. Informational only.

  • resultsobject[]

    Per-row outcome. Omitted on a queued (202) commit — read it from the batch instead.

    Show fields
    • rowinteger

      1-based position in the batch you sent.

    • external_idstringnullable
    • statusenum

      one ofwill_createwill_updateimportedupdatedskippedinvalidfailed

    • recordobjectnullable
      Show fields
      • typestring
      • iduuid
    • errorsobjectnullable

      Field → messages. A nested failure names its position, e.g. {"cost_lines": ["cost_lines[0].amount: not an amount: \"abc\""]}.

Responses

  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
  • 404unknown_entity — no entity by that key at all. A key that EXISTS but is read-only answers 422 entity_not_writable instead, so a partner is not sent hunting for a spelling mistake that isn't there.
  • 422The request itself was refused before anything was staged
  • 429Rate limit exceeded
curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/hotel_stays/validate' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 8f14e45f-ea2c-4f33-9a3b-6d0c1b7e5a90' \
  -d '{
  "rows": [
    {}
  ],
  "row": {},
  "source_system": "api",
  "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "branch_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "mode": "sync"
}'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "hotel_stays",
    "source_system": "crm",
    "status": "validating",
    "mode": "validate",
    "async": true,
    "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "stats": {
      "total": 1,
      "will_create": 1,
      "will_update": 1,
      "created": 1,
      "updated": 1,
      "skipped": 1,
      "invalid": 1,
      "failed": 1
    },
    "error": "string",
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "string",
      "rows": "string"
    }
  },
  "ignored_fields": [
    "string"
  ],
  "previous_batch_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "results": [
    {
      "row": 1,
      "external_id": "CRM-88213",
      "status": "will_create",
      "record": {
        "type": "HotelStay",
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      },
      "errors": {}
    }
  ]
}

Import a batch

POST/api/v1/imports/{entity}

Reachable with a narrow key: import

Commits the batch. Batches above the sync row limit (or mode: "async") are queued and answer 202; poll GET /api/v1/imports/{id}.

Parameters

Path

  • entitystringrequired

    A key from GET /api/v1/imports/entities on the write endpoints. The read endpoints (/records, /records/{external_id}) additionally accept the read-only entities charters, visa_cases and events, which broadcast webhooks but cannot be pushed.

Headers

  • Idempotency-Keystring

    Your own unique string for this write (a UUID is the obvious choice), max 255 characters. Optional: omit it and nothing changes. Send it and a repeat of the same request returns this call's response verbatim, with Idempotent-Replay: true, instead of writing again.

Body

  • rowsobject[]required

    One object per record. Keys are the entity's field keys — unknown keys are dropped and reported back in ignored_fields. Every row must carry external_id.

  • rowobject

    Convenience alternative to rows for pushing a single record.

  • source_systemstringdefault api

    Namespaces your ids, so two source systems can reuse the same id.

  • company_iduuid
  • branch_iduuid

    The branch the rows are filed under, from GET /api/v1/workspace. Omit it and they are filed under the key owner's own branch — the one marked headquarters on most accounts. An id that is not a branch of this account is refused with 422 unknown_branch; a booking filed under no branch at all would be invisible to the branch that took it.

  • modeenum

    Defaults to sync at or below the sync row limit, async above it.

    one ofsyncasync

  • on_conflictenumdefault skip

    What to do when a create-once record already exists for the row's external_id. skip (default) leaves it alone, so a replay never silently rewrites a booking. update amends it through the entity's allowlisted fields — status moves through the state machine, and a cancelled status runs the vertical's cancel service (reversing the customer invoice and voiding the supplier bill) instead of writing the field. An invoiced or cancelled record is frozen except for notes, and an amendment that touches anything else is refused, not half-applied. Pricing is never amendable — cancel and re-create, or price it in-app.

    one ofskipupdate

  • decimal_separatorenum

    Only needed when your source writes amounts as 1.234,56.

    one of,.

Response · 200

  • importobject
    Show fields
    • iduuid
    • entitystring
    • source_systemstring
    • statusenum

      one ofvalidatingreadyimportingcompletedpartialfailed

    • modeenum

      one ofvalidatecommit

    • asyncboolean
    • company_iduuidnullable
    • statsobject
      Show fields
      • totalinteger
      • will_createinteger
      • will_updateinteger
      • createdinteger
      • updatedinteger
      • skippedinteger
      • invalidinteger
      • failedinteger
    • errorstringnullable
    • created_atdate-time
    • linksobject
      Show fields
      • selfstring
      • rowsstring
  • ignored_fieldsstring[]

    Keys you sent that this entity doesn't declare — a typo reads as a warning, not a silent no-op.

  • previous_batch_iduuidnullable

    An earlier batch with a byte-identical payload. Informational only.

  • resultsobject[]

    Per-row outcome. Omitted on a queued (202) commit — read it from the batch instead.

    Show fields
    • rowinteger

      1-based position in the batch you sent.

    • external_idstringnullable
    • statusenum

      one ofwill_createwill_updateimportedupdatedskippedinvalidfailed

    • recordobjectnullable
      Show fields
      • typestring
      • iduuid
    • errorsobjectnullable

      Field → messages. A nested failure names its position, e.g. {"cost_lines": ["cost_lines[0].amount: not an amount: \"abc\""]}.

Responses

  • 201At least one record was created
  • 202Queued; poll the batch for its result
  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
  • 404unknown_entity — no entity by that key at all. See the 422 below for a key that exists but is read-only.
  • 422entity_not_writable — the entity exists, broadcasts and is readable at GET /api/v1/imports/{entity}/records, but accepts no push (charters, visa_cases, events). Otherwise: the request was refused (no_rows, too_many_rows, no_company), or every row failed — a batch where nothing landed is never reported as success.
  • 429Rate limit exceeded
curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/hotel_stays' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 8f14e45f-ea2c-4f33-9a3b-6d0c1b7e5a90' \
  -d '{
  "rows": [
    {}
  ],
  "row": {},
  "source_system": "api",
  "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "branch_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "mode": "sync"
}'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "hotel_stays",
    "source_system": "crm",
    "status": "validating",
    "mode": "validate",
    "async": true,
    "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "stats": {
      "total": 1,
      "will_create": 1,
      "will_update": 1,
      "created": 1,
      "updated": 1,
      "skipped": 1,
      "invalid": 1,
      "failed": 1
    },
    "error": "string",
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "string",
      "rows": "string"
    }
  },
  "ignored_fields": [
    "string"
  ],
  "previous_batch_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "results": [
    {
      "row": 1,
      "external_id": "CRM-88213",
      "status": "will_create",
      "record": {
        "type": "HotelStay",
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      },
      "errors": {}
    }
  ]
}

A resumable change feed for one vertical

GET/api/v1/imports/{entity}/records

Reachable with a narrow key: import

Every record of the vertical, ordered by updated_at ascending — a sync wants a watermark it can resume from, not a newest-first list. Poll with ?updated_since=<the last updated_at you saw>, walk forward, remember the last one. (Descending would silently drop anything that changed mid-walk.)

Rows are ImportedRecord, the same body the single read-back and the webhooks carry, so one parser covers all three.

The feed includes bookings the AGENCY created in-app, not just the ones you pushed — that is the point of it. ?mine=true narrows it to records bearing an ExternalReference for your source_system.

Every branch, unless you name one. An API key is the account's credential rather than a seat in one of its offices, so this feed answers with the whole account. ?branch_id= is the only thing that narrows it, and it takes one id from GET /api/v1/workspace. Naming the branch whose headquarters is true answers with every branch, because that is what headquarters means here.

Parameters

Path

  • entitystringrequired

    A key from GET /api/v1/imports/entities on the write endpoints. The read endpoints (/records, /records/{external_id}) additionally accept the read-only entities charters, visa_cases and events, which broadcast webhooks but cannot be pushed.

Query

  • branch_iduuid

    One branch id from GET /api/v1/workspace. Omit for the whole account. An id that is not a branch of this account is refused with 422 unknown_branch rather than absorbed into a full feed.

  • updated_sincedate-time

    ISO-8601. Only records changed at or after this instant.

  • statusstring

    Comma-separated; unknown values are dropped rather than emptying the list. Ignored by the two entities that have no status column — a visa case is filed under state, a tour departure under inventory_state, and neither is filterable here yet. Walk the feed and read detail.

  • source_systemstringdefault api

    Which source system's ids to resolve into external_id.

  • minebooleandefault false

    Only records this source system pushed.

  • limitintegerdefault 25

    Rows per page.

  • cursorstring

    Opaque; take it from the previous response's page.next_cursor.

Response · 200

  • dataobject[]required
    Show fields
    • entitystring
    • external_idstring

      Your own id in the source system.

    • source_systemstring
    • typestring
    • iduuid
    • statusstring
    • lockedboolean

      Invoiced or cancelled — amendments other than notes will be refused.

    • lock_reasonenumnullable

      one ofinvoicedcancelled

    • currencystring
    • totalsobject

      Decimal STRINGS, never floats.

    • invoicesobject[]nullable
      Show fields
      • iduuid
      • numberstring
      • statusstring
      • currencystring
      • totalstring
      • balancestring
    • changedstring[]

      Webhook deliveries only — which watched fields moved. Usually a column name (status, supplier_confirmation_no); for a change the columns cannot show, it is the path in THIS body that moved: detail.passengers when a ticket is numbered, detail.cabins when a cabin is allocated, detail.voucher_number when a voucher is stamped.

    • detailobjectnullable

      The entity-specific half, present only for entities that publish one — GET /api/v1/imports/entities/{key} lists the keys under detail_fields. Absent entirely when the entity has none, never an empty object. Every broadcasting entity publishes one. Each carries what the agency creates or changes AFTER your push, which is what your own copy cannot have. Six of them begin with the same three: voucher_number, supplier_confirmation_no and supplier (the one actually booked, which may not be the one you named: an unresolved supplier_external_id is skipped rather than failing the row). * ticketspnr, gds_pnr, settlement_channel, passengers[] (name, type, ticket_number, status) and legs[] (flight, from, to, departure_at, arrival_at, cabin_class, booking_class, status). * hotel_stays — those three, the confirmed check_in_on / check_out_on, and rooms[] (name, room_type, board_basis, booked_pax, capacity, plus its own dates when the room leaves the stay's window). * transfers — those three, plus pickup_at, pickup_location, dropoff_location, vehicle (name, plate), driver, and flight (status, delay_minutes, suggested_pickup_at) from our flight tracker. * cruises — those three, plus ship_name, embark_on, disembark_on and cabins[] (cabin_number, category, deck, booked_pax, capacity). * car_rentals — those three, plus vehicle_class, pickup_at, return_at, pickup_location, dropoff_location. * charters — those three, plus vessel (the boat actually sailing, which a supplier may swap), starts_on, ends_on, guest_count, option_date. * visa_cases — those three, plus state and outcome. This entity has NO top-level status: its lifecycle column is state, so the key is absent from the generic half rather than null. Also destination_country, visa_class, consulate_code, priority, target_submission_date, actual_decision_date. * tour_bookingsvoucher_number, supplier_confirmation_no, party_size and the departure the seat sits on (departure_title, departure_starts_on, departure_state). No supplier: the operator belongs to the departure, not the seat. * tour_departures — inventory, so no voucher and no top-level status either: inventory_state is the lifecycle word, with title, starts_on, ends_on, max_capacity and supplier. Seats sold is not published — you already know your own. * events — no voucher and no supplier of its own (a job runs on dozens, each on its own bill). title, event_type, venue_name, venue_address, city, country_code, starts_on, ends_on, attendee_count, option_date. ticket_number, supplier_confirmation_no and cabin_number are the fields this read-back exists for: they are assigned by the agency or its supplier after your push and cannot be sent in. Traveller identity documents (passport, national id, date of birth), guest names and the bill-to block are deliberately NOT carried — this body is also POSTed to every registered webhook URL. For the same reason a visa case omits passport_country and vfs_reference_number, the handle the applicant tracks their own application with.

    • created_atdate-time
    • updated_atdate-time
  • pageobjectrequired

    The cursor for the list beside it. Re-send the same request with ?cursor=<next_cursor> while has_more is true. next_cursor is null on the last page.

    Show fields
    • next_cursorstringrequirednullable
    • has_morebooleanrequired
    • limitintegerrequired

Responses

  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
  • 404Resource not found
  • 422The request itself was refused before anything was staged
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/hotel_stays/records' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "data": [
    {
      "entity": "hotel_stays",
      "external_id": "CRM-88213",
      "source_system": "crm",
      "type": "HotelStay",
      "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "status": "confirmed",
      "locked": true,
      "lock_reason": "invoiced",
      "currency": "EUR",
      "totals": {},
      "invoices": [
        {
          "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
          "number": "string",
          "status": "confirmed",
          "currency": "EUR",
          "total": "string",
          "balance": "string"
        }
      ],
      "changed": [
        "string"
      ],
      "detail": {},
      "created_at": "2026-03-14T09:30:00Z",
      "updated_at": "2026-03-14T09:30:00Z"
    }
  ],
  "page": {
    "next_cursor": "MjAyNi0wMy0xNFQwOTozMDowMC4wMDAwMDBafDk0MQ",
    "has_more": true,
    "limit": 25
  }
}

Read a pushed record back by your own id

GET/api/v1/imports/{entity}/records/{external_id}

Reachable with a narrow key: import

Resolves the source system's id to the live Lydira record and reports its current state — status, whether it is frozen (invoiced or cancelled, i.e. your amendments will be refused), totals, and any invoices raised against it.

This is how a partner learns what the AGENCY did after the push. The other half is outbound webhooks: subscribe to record.updated and the same payload is delivered when the record changes in-app.

Parameters

Path

  • entitystringrequired

    A key from GET /api/v1/imports/entities on the write endpoints. The read endpoints (/records, /records/{external_id}) additionally accept the read-only entities charters, visa_cases and events, which broadcast webhooks but cannot be pushed.

  • external_idstringrequired

Query

  • source_systemstringdefault api

Response · 200

  • recordobject
    Show fields
    • entitystring
    • external_idstring

      Your own id in the source system.

    • source_systemstring
    • typestring
    • iduuid
    • statusstring
    • lockedboolean

      Invoiced or cancelled — amendments other than notes will be refused.

    • lock_reasonenumnullable

      one ofinvoicedcancelled

    • currencystring
    • totalsobject

      Decimal STRINGS, never floats.

    • invoicesobject[]nullable
      Show fields
      • iduuid
      • numberstring
      • statusstring
      • currencystring
      • totalstring
      • balancestring
    • changedstring[]

      Webhook deliveries only — which watched fields moved. Usually a column name (status, supplier_confirmation_no); for a change the columns cannot show, it is the path in THIS body that moved: detail.passengers when a ticket is numbered, detail.cabins when a cabin is allocated, detail.voucher_number when a voucher is stamped.

    • detailobjectnullable

      The entity-specific half, present only for entities that publish one — GET /api/v1/imports/entities/{key} lists the keys under detail_fields. Absent entirely when the entity has none, never an empty object. Every broadcasting entity publishes one. Each carries what the agency creates or changes AFTER your push, which is what your own copy cannot have. Six of them begin with the same three: voucher_number, supplier_confirmation_no and supplier (the one actually booked, which may not be the one you named: an unresolved supplier_external_id is skipped rather than failing the row). * ticketspnr, gds_pnr, settlement_channel, passengers[] (name, type, ticket_number, status) and legs[] (flight, from, to, departure_at, arrival_at, cabin_class, booking_class, status). * hotel_stays — those three, the confirmed check_in_on / check_out_on, and rooms[] (name, room_type, board_basis, booked_pax, capacity, plus its own dates when the room leaves the stay's window). * transfers — those three, plus pickup_at, pickup_location, dropoff_location, vehicle (name, plate), driver, and flight (status, delay_minutes, suggested_pickup_at) from our flight tracker. * cruises — those three, plus ship_name, embark_on, disembark_on and cabins[] (cabin_number, category, deck, booked_pax, capacity). * car_rentals — those three, plus vehicle_class, pickup_at, return_at, pickup_location, dropoff_location. * charters — those three, plus vessel (the boat actually sailing, which a supplier may swap), starts_on, ends_on, guest_count, option_date. * visa_cases — those three, plus state and outcome. This entity has NO top-level status: its lifecycle column is state, so the key is absent from the generic half rather than null. Also destination_country, visa_class, consulate_code, priority, target_submission_date, actual_decision_date. * tour_bookingsvoucher_number, supplier_confirmation_no, party_size and the departure the seat sits on (departure_title, departure_starts_on, departure_state). No supplier: the operator belongs to the departure, not the seat. * tour_departures — inventory, so no voucher and no top-level status either: inventory_state is the lifecycle word, with title, starts_on, ends_on, max_capacity and supplier. Seats sold is not published — you already know your own. * events — no voucher and no supplier of its own (a job runs on dozens, each on its own bill). title, event_type, venue_name, venue_address, city, country_code, starts_on, ends_on, attendee_count, option_date. ticket_number, supplier_confirmation_no and cabin_number are the fields this read-back exists for: they are assigned by the agency or its supplier after your push and cannot be sent in. Traveller identity documents (passport, national id, date of birth), guest names and the bill-to block are deliberately NOT carried — this body is also POSTed to every registered webhook URL. For the same reason a visa case omits passport_country and vfs_reference_number, the handle the applicant tracks their own application with.

    • created_atdate-time
    • updated_atdate-time

Responses

  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
  • 404No record was imported under that id from that source system
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/hotel_stays/records/external_id' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "record": {
    "entity": "hotel_stays",
    "external_id": "CRM-88213",
    "source_system": "crm",
    "type": "HotelStay",
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "status": "confirmed",
    "locked": true,
    "lock_reason": "invoiced",
    "currency": "EUR",
    "totals": {},
    "invoices": [
      {
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
        "number": "string",
        "status": "confirmed",
        "currency": "EUR",
        "total": "string",
        "balance": "string"
      }
    ],
    "changed": [
      "string"
    ],
    "detail": {},
    "created_at": "2026-03-14T09:30:00Z",
    "updated_at": "2026-03-14T09:30:00Z"
  }
}

List import batches

GET/api/v1/imports

Reachable with a narrow key: import

This account's API import history, newest first. Comma-separated values are accepted on both filters; unknown ones are dropped rather than emptying the list.

Parameters

Query

  • entitystring
  • statusstring
  • limitintegerdefault 25

    Rows per page.

  • cursorstring

    Opaque; take it from the previous response's page.next_cursor.

Response · 200

  • dataobject[]required
    Show fields
    • iduuid
    • entitystring
    • source_systemstring
    • statusenum

      one ofvalidatingreadyimportingcompletedpartialfailed

    • modeenum

      one ofvalidatecommit

    • asyncboolean
    • company_iduuidnullable
    • statsobject
      Show fields
      • totalinteger
      • will_createinteger
      • will_updateinteger
      • createdinteger
      • updatedinteger
      • skippedinteger
      • invalidinteger
      • failedinteger
    • errorstringnullable
    • created_atdate-time
    • linksobject
      Show fields
      • selfstring
      • rowsstring
  • pageobjectrequired

    The cursor for the list beside it. Re-send the same request with ?cursor=<next_cursor> while has_more is true. next_cursor is null on the last page.

    Show fields
    • next_cursorstringrequirednullable
    • has_morebooleanrequired
    • limitintegerrequired

Responses

  • 401Missing or invalid Bearer token
  • 403The key lacks the required scope, or the plan doesn't include API access
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "data": [
    {
      "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "entity": "hotel_stays",
      "source_system": "crm",
      "status": "validating",
      "mode": "validate",
      "async": true,
      "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "stats": {
        "total": 1,
        "will_create": 1,
        "will_update": 1,
        "created": 1,
        "updated": 1,
        "skipped": 1,
        "invalid": 1,
        "failed": 1
      },
      "error": "string",
      "created_at": "2026-03-14T09:30:00Z",
      "links": {
        "self": "string",
        "rows": "string"
      }
    }
  ],
  "page": {
    "next_cursor": "MjAyNi0wMy0xNFQwOTozMDowMC4wMDAwMDBafDk0MQ",
    "has_more": true,
    "limit": 25
  }
}

One import batch

GET/api/v1/imports/{id}

Reachable with a narrow key: import

Parameters

Path

  • iduuidrequired

Response · 200

  • importobject
    Show fields
    • iduuid
    • entitystring
    • source_systemstring
    • statusenum

      one ofvalidatingreadyimportingcompletedpartialfailed

    • modeenum

      one ofvalidatecommit

    • asyncboolean
    • company_iduuidnullable
    • statsobject
      Show fields
      • totalinteger
      • will_createinteger
      • will_updateinteger
      • createdinteger
      • updatedinteger
      • skippedinteger
      • invalidinteger
      • failedinteger
    • errorstringnullable
    • created_atdate-time
    • linksobject
      Show fields
      • selfstring
      • rowsstring

Responses

  • 401Missing or invalid Bearer token
  • 404Resource not found
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "hotel_stays",
    "source_system": "crm",
    "status": "validating",
    "mode": "validate",
    "async": true,
    "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "stats": {
      "total": 1,
      "will_create": 1,
      "will_update": 1,
      "created": 1,
      "updated": 1,
      "skipped": 1,
      "invalid": 1,
      "failed": 1
    },
    "error": "string",
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "string",
      "rows": "string"
    }
  }
}

Per-row outcomes

GET/api/v1/imports/{id}/rows

Reachable with a narrow key: import

?status=invalid,failed is the fix-list after a partial import. Ordered by row ascending, so it reads in the order you sent the batch.

data is the rows; import is the batch they belong to, carried alongside for context. Its stats hold the per-status counts, so the page needs no total of its own.

Parameters

Path

  • iduuidrequired

Query

  • statusstring
  • limitintegerdefault 25

    Rows per page.

  • cursorstring

    Opaque; take it from the previous response's page.next_cursor.

Response · 200

  • importobjectrequired
    Show fields
    • iduuid
    • entitystring
    • source_systemstring
    • statusenum

      one ofvalidatingreadyimportingcompletedpartialfailed

    • modeenum

      one ofvalidatecommit

    • asyncboolean
    • company_iduuidnullable
    • statsobject
      Show fields
      • totalinteger
      • will_createinteger
      • will_updateinteger
      • createdinteger
      • updatedinteger
      • skippedinteger
      • invalidinteger
      • failedinteger
    • errorstringnullable
    • created_atdate-time
    • linksobject
      Show fields
      • selfstring
      • rowsstring
  • dataobject[]required
    Show fields
    • rowinteger

      1-based position in the batch you sent.

    • external_idstringnullable
    • statusenum

      one ofwill_createwill_updateimportedupdatedskippedinvalidfailed

    • recordobjectnullable
      Show fields
      • typestring
      • iduuid
    • errorsobjectnullable

      Field → messages. A nested failure names its position, e.g. {"cost_lines": ["cost_lines[0].amount: not an amount: \"abc\""]}.

  • pageobjectrequired

    The cursor for the list beside it. Re-send the same request with ?cursor=<next_cursor> while has_more is true. next_cursor is null on the last page.

    Show fields
    • next_cursorstringrequirednullable
    • has_morebooleanrequired
    • limitintegerrequired

Responses

  • 401Missing or invalid Bearer token
  • 404Resource not found
curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18/rows' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "hotel_stays",
    "source_system": "crm",
    "status": "validating",
    "mode": "validate",
    "async": true,
    "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "stats": {
      "total": 1,
      "will_create": 1,
      "will_update": 1,
      "created": 1,
      "updated": 1,
      "skipped": 1,
      "invalid": 1,
      "failed": 1
    },
    "error": "string",
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "string",
      "rows": "string"
    }
  },
  "data": [
    {
      "row": 1,
      "external_id": "CRM-88213",
      "status": "will_create",
      "record": {
        "type": "HotelStay",
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      },
      "errors": {}
    }
  ],
  "page": {
    "next_cursor": "MjAyNi0wMy0xNFQwOTozMDowMC4wMDAwMDBafDk0MQ",
    "has_more": true,
    "limit": 25
  }
}