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, import_issue or full scope. import_issue is import plus issuing the invoice a ticket row asks for (issue: true); an import key's row is saved and says why no invoice was cut.

Reading can reach further than writing. Every registry entity accepts a push today, but one declared read-only would still broadcast webhooks and page through GET /api/v1/imports/{entity}/records: a POST to it 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.

Looking for how to push a ticket or a stay? See the import guides. →

Where a record can be filed

GET/api/v1/workspace

Reachable with a narrow key: import, import_issue, flight_booking

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, import_issue or flight_booking (or full) scope: a booking API caller reads its company_id here too.

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, import_issue

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": "POST /api/v1/imports/hotel_stays/validate",
        "import": "POST /api/v1/imports/hotel_stays",
        "feed": "GET /api/v1/imports/hotel_stays/records",
        "read_back": "GET /api/v1/imports/hotel_stays/records/{external_id}"
      }
    }
  ],
  "limits": {
    "max_rows_per_request": 500,
    "sync_row_limit": 100
  },
  "conventions": {
    "dates": "ISO-8601 (2026-03-14). Timestamps may carry an offset; without one they are read in the agency's time zone.",
    "money": "A JSON number or a string. Use `decimal_separator` only if your source writes 1.234,56.",
    "external_id": "Required on every row. It is the source system's own key, and it is what makes a retry idempotent.",
    "idempotency": "Re-sending a row: people + brought-forward documents UPDATE, bookings SKIP (they are create-once).",
    "source_system": "Namespaces your ids (default \"api\"). Two source systems can reuse the same id without colliding.",
    "unknown_fields": "Dropped, and reported back as `ignored_fields` — the registry, not the payload, decides what exists.",
    "dry_run": "POST …/validate runs the identical path with nothing persisted."
  }
}

Field schema + sample body for one entity

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

Reachable with a narrow key: import, import_issue

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

Query

  • company_iduuid

    The company whose own required fields company_required lists. Defaults to the account's default company, as the ingest does.

  • source_systemstring

    Names the customers a field in custom_fields is limited to by this system's external_id as well as by id.

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

        object is a free-form JSON object (a ticket's issue_options, or custom_fields, whose keys entity.custom_fields lists); strings is a list of plain strings (a ticket's tags).

        one ofstringemailphonetax_numbercurrencydatedatetimemoneydecimalintegerbooleanenumtaxonomyreferencelistobjectstrings

      • requiredboolean
      • required_unlessstringnullable

        Another field's key; this one is required on a new row while that one is blank.

      • piiboolean
      • enum_valuesstring[]

        Enforced allowlist.

      • suggested_valuesstring[]

        Standard values, documented and NOT enforced by this list. A field with a taxonomy_kind accepts the account's own list for that kind, which starts from these and may add more; any other field takes the value as sent.

      • taxonomy_kindstring

        The /settings/taxonomies list that governs this value.

      • notestring
      • itemsobject[]

        For list fields, the shape of each array element (may nest). For an object with a fixed set of keys (a ticket's issue_options), those keys; a free-form object (custom_fields) has none.

        Show fields
        • keystring
        • typeenum

          object is a free-form JSON object (a ticket's issue_options, or custom_fields, whose keys entity.custom_fields lists); strings is a list of plain strings (a ticket's tags).

          one ofstringemailphonetax_numbercurrencydatedatetimemoneydecimalintegerbooleanenumtaxonomyreferencelistobjectstrings

        • requiredboolean
        • required_unlessstringnullable

          Another field's key; this one is required on a new row while that one is blank.

        • piiboolean
        • enum_valuesstring[]

          Enforced allowlist.

        • suggested_valuesstring[]

          Standard values, documented and NOT enforced by this list. A field with a taxonomy_kind accepts the account's own list for that kind, which starts from these and may add more; any other field takes the value as sent.

        • taxonomy_kindstring

          The /settings/taxonomies list that governs this value.

        • notestring
        • itemsobject[]

          For list fields, the shape of each array element (may nest). For an object with a fixed set of keys (a ticket's issue_options), those keys; a free-form object (custom_fields) has none.

          Show fields
          • keystring
          • typeenum

            object is a free-form JSON object (a ticket's issue_options, or custom_fields, whose keys entity.custom_fields lists); strings is a list of plain strings (a ticket's tags).

            one ofstringemailphonetax_numbercurrencydatedatetimemoneydecimalintegerbooleanenumtaxonomyreferencelistobjectstrings

          • requiredboolean
          • required_unlessstringnullable

            Another field's key; this one is required on a new row while that one is blank.

          • piiboolean
          • enum_valuesstring[]

            Enforced allowlist.

          • suggested_valuesstring[]

            Standard values, documented and NOT enforced by this list. A field with a taxonomy_kind accepts the account's own list for that kind, which starts from these and may add more; any other field takes the value as sent.

          • taxonomy_kindstring

            The /settings/taxonomies list that governs this value.

          • notestring
          • itemsobject[]

            For list fields, the shape of each array element (may nest). For an object with a fixed set of keys (a ticket's issue_options), those keys; a free-form object (custom_fields) has none.

    • 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.

    • required_one_ofarray[]nullable

      Groups of field keys of which a NEW row must carry at least one.

    • payerobjectnullable

      Bookings only. How the cari the booking bills is found when the row names no customer_external_id: corporate by tax_number then exact legal_name, individual by tax_number (TCKN) then passport_number, from the bill_to_* fields or the first item of first_traveler.

      Show fields
      • first_travelerstringnullable
      • matchobject
        Show fields
        • corporatestring[]
        • individualstring[]
      • on_missingenum

        one ofcreate

    • payer_sampleobjectnullable

      Bookings only. sample under its own id, billed by its bill_to_* fields instead of a customer key.

    • response_sampleobject

      What POST /api/v1/imports/{entity}/validate answers for sample.

    • company_requiredobject[]

      The fields the company the rows would be filed under (company_id, else the default company) has made mandatory in Settings → Field requirements. They bind an import as validations. field names the import field that satisfies the rule (passengers[].national_id for a nested one); importable: false means the import cannot send it, so every new row of this entity is refused while the rule is on. conditional rules bite only on some records; payer means a booking's bill-to or first traveller also satisfies a customer rule.

      Show fields
      • keystring
      • fieldstringnullable
      • importableboolean
      • conditionalboolean
      • payerboolean
    • reference_optionsobject

      Tickets and charters. The account's own records a row names, so you can map your values to ours without an extra endpoint. Tickets: card_accounts (the company card that paid, for card_account_id), commission_partners (the introducer, for commission_partner_id), line_templates (the invoice line template, for line_template_id); agents are named by email and departments by their code among the sale's customer's own, so neither is listed. Charters: vessels (the agency's boats, each with registration_number and mmsi when known), for vessel.

    • custom_fieldsobject

      Tickets, hotel stays, transfers, cruises, car rentals and tour bookings (a tour booking on the record only). The account's own fields a row carries under custom_fields (and each passenger or guest under its own custom_fields), a JSON object by key. An unknown key, a value of the wrong type, or a missing required one fails the row and names the key. On an amendment (on_conflict: update) the object merges by key: a key you leave out is kept, a key sent as null is removed. A ticket amends the sale's and each passenger's fields; a booking amends its own, and a guest's are written when the booking is created. Charters, visa cases and events carry the account's fields too (a visa case and an event on the record only), but take no push: they come back under detail in the feed and the webhook body. A field whose visibility puts it on the customer's documents cannot change once the record is invoiced. Below the plan that carries custom fields, a row that sets or changes one fails with the error custom fields are not in this account's plan. Per account, so this envelope is all the published guide can show.

      Show fields
      • recordobject[]
        Show fields
        • keystringrequired

          What the row names the field by.

        • labelstringrequired
        • typeenumrequired

          number takes a number or a numeric string; date an ISO date; select one option value (or its label); multi_select a list of them.

          one oftextlong_textnumberdatebooleanselectmulti_select

        • requiredenumrequired

          listed_customers: required only for the customers marked required in customers.

          one ofneveralwayslisted_customers

        • applies_toenumrequired

          listed_customers: the field is accepted only on those customers' records.

          one ofalllisted_customers

        • piiboolean

          Personal data. Left out of a read-back for a key whose role cannot read personal data.

        • constraintsobject

          max_length for text; min, max, scale for a number.

        • optionsobject[]
          Show fields
          • valuestring
          • labelstring
        • customersobject[]
          Show fields
          • iduuid
          • external_idstring

            Your id for the customer, when it came in through the import (see source_system).

          • requiredboolean
      • travelerobject[]
        Show fields
        • keystringrequired

          What the row names the field by.

        • labelstringrequired
        • typeenumrequired

          number takes a number or a numeric string; date an ISO date; select one option value (or its label); multi_select a list of them.

          one oftextlong_textnumberdatebooleanselectmulti_select

        • requiredenumrequired

          listed_customers: required only for the customers marked required in customers.

          one ofneveralwayslisted_customers

        • applies_toenumrequired

          listed_customers: the field is accepted only on those customers' records.

          one ofalllisted_customers

        • piiboolean

          Personal data. Left out of a read-back for a key whose role cannot read personal data.

        • constraintsobject

          max_length for text; min, max, scale for a number.

        • optionsobject[]
          Show fields
          • valuestring
          • labelstring
        • customersobject[]
          Show fields
          • iduuid
          • external_idstring

            Your id for the customer, when it came in through the import (see source_system).

          • requiredboolean

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": "POST /api/v1/imports/hotel_stays/validate",
      "import": "POST /api/v1/imports/hotel_stays",
      "feed": "GET /api/v1/imports/hotel_stays/records",
      "read_back": "GET /api/v1/imports/hotel_stays/records/{external_id}"
    },
    "fields": [
      {
        "key": "rate_basis",
        "type": "enum",
        "required": false,
        "pii": false,
        "enum_values": [
          "net",
          "commissionable"
        ],
        "note": "net ⇒ cost typed / sell derived; commissionable ⇒ the reverse"
      }
    ],
    "references": [
      {
        "key": "customer_external_id",
        "target_entity": "customers",
        "match_by": [
          "external_id",
          "email"
        ],
        "on_missing": "error"
      }
    ],
    "sample": {
      "external_id": "HOTEL-STAY-1001",
      "check_in_on": "2026-03-14",
      "check_out_on": "2026-03-18",
      "currency": "EUR",
      "guest_count": 2,
      "customer_external_id": "CUSTOMER-1001",
      "supplier_external_id": "SUPPLIER-1001",
      "status": "draft",
      "notes": "Imported from the source system.",
      "rate_basis": "net",
      "margin_mode": "percent",
      "option_date": "2026-03-14",
      "transaction_date": "2026-03-14",
      "invoice_date": "2026-03-18",
      "cost_lines": [
        {
          "kind": "room_rate",
          "amount": "1250.00",
          "label": "Imported line",
          "currency": "EUR",
          "vat_rate": "20",
          "tax_inclusive": true,
          "quantity": 1
        }
      ],
      "sell_lines": [
        {
          "kind": "package_price",
          "amount": "1250.00",
          "label": "Imported line",
          "currency": "EUR",
          "vat_rate": "20",
          "tax_inclusive": true,
          "quantity": 1
        }
      ],
      "travelers": [
        {
          "first_name": "Ada",
          "last_name": "Lovelace",
          "nationality": "TR",
          "national_id": "10000000146",
          "passport_number": "U01234567",
          "passport_country": "TR",
          "passport_expiry": "2031-06-30",
          "date_of_birth": "1988-04-21",
          "gender": "female",
          "frequent_flyer_number": "TK123456789"
        }
      ],
      "rooms": [
        {
          "name": "Deluxe Sea View",
          "room_type": "double",
          "capacity": 2,
          "occupancy": 2,
          "nightly_rate": "180.00",
          "single_supplement_amount": "60.00",
          "single_supplement_currency": "EUR"
        }
      ]
    },
    "required_one_of": [
      [
        "customer_external_id",
        "bill_to_name"
      ]
    ],
    "payer": {
      "first_traveler": "passengers",
      "match": {
        "corporate": [
          "tax_number",
          "legal_name"
        ],
        "individual": [
          "tax_number",
          "passport_number"
        ]
      },
      "on_missing": "create"
    },
    "payer_sample": {
      "external_id": "HOTEL-STAY-1001-B",
      "check_in_on": "2026-03-14",
      "check_out_on": "2026-03-18",
      "bill_to_legal_name": "Lovelace Seyahat A.Ş.",
      "bill_to_tax_number": "1234567890",
      "bill_to_tax_office": "Beşiktaş",
      "bill_to_city": "İstanbul",
      "bill_to_country": "TR"
    },
    "response_sample": {
      "import": {
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
        "entity": "hotel_stays",
        "status": "ready",
        "mode": "validate",
        "async": false
      },
      "ignored_fields": [],
      "results": [
        {
          "row": 1,
          "external_id": "HOTEL-STAY-1001",
          "status": "will_create",
          "payer": {
            "outcome": "external_id",
            "customer_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
          }
        }
      ]
    },
    "company_required": [
      {
        "key": "ticket.national_id",
        "field": "passengers[].national_id",
        "importable": true,
        "conditional": true
      },
      {
        "key": "ticket_sale.reservation_agent",
        "field": null,
        "importable": false,
        "conditional": false
      }
    ],
    "reference_options": {
      "card_accounts": [
        {
          "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
          "label": "Corporate Visa"
        }
      ],
      "commission_partners": [],
      "line_templates": []
    },
    "custom_fields": {
      "record": [
        {
          "key": "project_code",
          "label": "Project code",
          "type": "text",
          "required": "always",
          "applies_to": "all"
        },
        {
          "key": "trip_reason",
          "label": "Trip reason",
          "type": "select",
          "required": "listed_customers",
          "applies_to": "listed_customers",
          "options": [
            {
              "value": "meeting",
              "label": "Meeting"
            },
            {
              "value": "fair",
              "label": "Trade fair"
            }
          ],
          "customers": [
            {
              "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
              "external_id": "CUST-17",
              "required": true
            }
          ]
        }
      ],
      "traveler": [
        {
          "key": "employee_no",
          "label": "Employee no",
          "type": "text",
          "required": "never",
          "applies_to": "all",
          "pii": true
        }
      ]
    }
  }
}

Dry-run a batch

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

Reachable with a narrow key: import, import_issue

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

  • entityenumrequired

    The record type to push. Each one is described, with its fields and a sample body, at GET /api/v1/imports/entities/{key}.

    one ofcustomerssuppliersleadstour_departuresbus_bookingscar_rentalscharterscruiseseventshotel_staysrail_bookingsticketstour_bookingstransfersvisa_casesoperator_invoicessupplier_bills

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, nested ones by path (passengers[].nickname). 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. service_fee_waived is the exception: it is amendable, and the account's service-fee rules follow it at once. Tickets amend through the edit page's own rules. A row names only what changes; everything it leaves out stays as stored. A passengers (or legs) list you send replaces the stored list: an item with a passenger's id (from the read-back) keeps every key it leaves out, an item without one is a new passenger, and a passenger you leave out of a list you send is removed. A draft takes everything; an issued sale takes only its references (names, ticket numbers, PNR); a PNR held at the airline keeps its itinerary. A list with no ids that is as long as the stored one pairs by position, so re-sending your original row keeps the same passengers. Sell lines the account's own rules wrote (service fee, markup) stay unless your item names that kind. issue: true on a draft issues it after the amendment.

    one ofskipupdate

  • pricingenumdefault apply_rules

    Tickets. apply_rules runs the account's markup and service-fee rules on top of the lines you send, as the capture screen does. as_sent writes your lines exactly and runs none of them: a migration, or sales invoiced in another system. The introducer commission you send applies either way.

    one ofapply_rulesas_sent

  • 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
    • pricingenum

      The batch's pricing mode.

      one ofapply_rulesas_sent

    • 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. A nested key is named by its path, without the item's position.

  • 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, in English. A nested failure names its position, e.g. {"cost_lines": ["cost_lines[0].amount: not an amount: \"abc\""]}. A booking the entity's own validation refuses names the field it failed on, like any other row: {"check_out_on": ["must be after the check-in date"]}. A ticket sale places a passenger's or a leg's failure at its position: {"passengers": ["passengers[0].name: can't be blank"]}. A failure that belongs to no one field is under base, and so is one on a record the booking builds for itself (a room, a guest, a pricing line), whose place in your payload cannot be traced.

    • warningsobject[]nullable

      The row WROTE, and something you asked for beside it did not happen. Match on code; the message is English prose. issue_refused / issue_pending_approval / issue_scope_missing: the sale is saved, the invoice is not cut (re-send the row with on_conflict: update and issue: true once fixed). issue_pending_approval keeps the draft for the approver; re-send with issue: true after it is approved and that draft is issued. issue_preview_refused: on /validate, the invoice could not even be drafted. Validate proves the draft builds; a refusal decided at issue time (missing bill-to, credit limit, closed period, approval) is only known on commit, as issue_refused. fx_rate_masked: the key's role may not move that side's money, so the rate was not written. commission_masked: the role may not set a percentage introducer commission (it needs cost and sell), so the commission was not written. A gift (complimentary) the role may not grant is not a warning: the row fails under passengers, as the screen refuses the save. option_dropped: a ticket sale sent with option_date while every passenger carries a ticket_number is saved as ticketed, not on option. The airline has issued the documents, so the ticketing deadline is over (send option_date only for a held PNR, with the numbers left blank). phone_dropped: a phone number that cannot be dialled was left blank and the row saved without it; field names the column. Numbers are stored in E.164 (+905321234567); one sent without a country code is read in the account's country. service_fee_dropped: a booking sent with service_fee or service_fee_percent has no sell to split the fee across (no priced line yet, or every guest a gift), so it was saved without the fee; field names the one sent. Price the booking and set the fee in-app. markup_dropped / discount_dropped: a booking sent with a markup or a discount has nothing to split it across (no priced line, every guest a gift, or a markup_percent with no cost to measure), so it was saved without it; field is markup or discount.

      Show fields
      • codeenumrequired

        one ofissue_refusedissue_pending_approvalissue_scope_missingissue_preview_refusedfx_rate_maskedcommission_maskedoption_droppedphone_droppedservice_fee_droppedmarkup_droppeddiscount_dropped

      • messagestringrequired
      • fieldstringnullable
    • payerobjectnullable

      The cari this booking bills, for the six booking entities. Absent for entities that bill no one. external_id: your customer_external_id resolved and won. matched: an existing cari carried the identity (by says which: tax_number, legal_name or passport); filled lists the blank fields your bill-to completed. Nothing filled is ever overwritten. created: a new cari was opened from your bill-to or the first traveller. would_create: the same, on /validate, where nothing is written. none: no cari; warning says why (payer_unidentified: no VKN, TCKN or passport to go on). A matched row may carry payer_fill_skipped: the cari could not take your bill-to's blank fields, so it was bound unchanged.

      Show fields
      • outcomeenum

        one ofexternal_idmatchedcreatedwould_createnone

      • byenum

        one oftax_numberlegal_namepassport

      • customer_iduuid
      • filledstring[]
      • warningenum

        one ofpayer_unidentifiedpayer_create_not_permittedpayer_fill_skipped

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/tickets/validate' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 8f14e45f-ea2c-4f33-9a3b-6d0c1b7e5a90' \
  -d '{
  "source_system": "crm",
  "rows": [
    {
      "external_id": "TICKET-1001",
      "pnr": "ABC123",
      "gds_pnr": "ABC123",
      "currency": "EUR",
      "settlement_channel": "bsp",
      "fare_type": "normal",
      "miles_used": 2,
      "reservation_giver_name": "Ada Lovelace",
      "issuing_system": "amadeus",
      "corporate_code": "TKCORP2026",
      "booked_at": "2026-03-14T09:30:00+03:00",
      "tags": [
        "corporate"
      ],
      "transaction_date": "2026-03-14",
      "invoice_date": "2026-03-18",
      "notes": "Imported from the source system.",
      "legs": [
        {
          "airline_code": "TK",
          "flight_number": "1985",
          "departure_airport": "IST",
          "arrival_airport": "FCO",
          "departure_at": "2026-03-14T09:30:00+03:00",
          "arrival_at": "2026-03-14T12:45:00+03:00",
          "cabin_class": "economy",
          "booking_class": "L",
          "fare_basis": "LLXOW",
          "baggage_allowance": "1PC",
          "operating_carrier": "TK",
          "fare_family": "EcoFly",
          "refundable": true,
          "changeable": true,
          "change_fee": "1250.00",
          "refund_fee": "1250.00",
          "status": "confirmed"
        }
      ],
      "passengers": [
        {
          "first_name": "Ada",
          "last_name": "Lovelace",
          "nationality": "TR",
          "national_id": "10000000146",
          "passport_number": "U01234567",
          "passport_country": "TR",
          "passport_expiry": "2031-06-30",
          "date_of_birth": "1988-04-21",
          "gender": "MRS",
          "frequent_flyer_number": "TK123456789",
          "name": "Ada Lovelace",
          "type": "adult",
          "ticket_number": "235-1234567890",
          "cost_lines": [
            {
              "kind": "base_fare",
              "amount": "1250.00",
              "label": "Imported line",
              "currency": "EUR",
              "vat_rate": "20",
              "tax_inclusive": true
            }
          ],
          "sell_lines": [
            {
              "kind": "package_price",
              "amount": "1250.00",
              "label": "Imported line",
              "currency": "EUR",
              "vat_rate": "20",
              "tax_inclusive": true,
              "customer_visible": true
            }
          ]
        }
      ],
      "customer_external_id": "CUSTOMER-1001",
      "supplier_external_id": "SUPPLIER-1001"
    }
  ]
}'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "tickets",
    "source_system": "crm",
    "status": "completed",
    "mode": "validate",
    "async": true,
    "pricing": "apply_rules",
    "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": null,
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "rows": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18/rows"
    }
  },
  "ignored_fields": [
    "ticket_no",
    "passengers[].nickname"
  ],
  "previous_batch_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "results": [
    {
      "row": 1,
      "external_id": "TICKET-1001",
      "status": "imported",
      "record": {
        "type": "TicketSale",
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      },
      "payer": {
        "outcome": "external_id",
        "customer_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      }
    }
  ]
}

Import a batch

POST/api/v1/imports/{entity}

Reachable with a narrow key: import, import_issue

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

Who the booking bills (tickets, hotel_stays, transfers, cruises, car_rentals, tour_bookings). Send customer_external_id if you know the cari. Otherwise send the bill_to_* fields: a corporate payer is matched on bill_to_tax_number (VKN), then on an exact bill_to_legal_name, and opened from them when neither matches. An individual payer is matched on a TCKN, then a passport number, and opened when neither matches: from bill_to_tax_number / bill_to_passport_number (with bill_to_first_name and bill_to_last_name) when you send them, else from the first traveller (passengers[0] / travelers[0]: national_id, passport_number, name). bill_to_type is inferred when omitted: a legal name or a 10-digit tax number makes the payer corporate. Email, phone and names are never matched on, and a matched cari only has its blank fields filled. Each row's payer says what happened.

The service fee (hotel_stays, transfers, cruises, car_rentals, tour_bookings). A row without service_fee gets the fee the account's service-fee rules write. A row with one names the agency's fee for the whole booking, in its currency: the rules write nothing, and on a booking billed per guest the figure is split across the guests by their sell, as on the booking form. service_fee_percent sends the fee as a percentage instead, from 0 to 100, of the booking's sale before fee and discount (10 on a 1,200.00 sale is 120.00), split the same way; a row with both fields, or a percentage outside that range, is refused. A 0 in either is no fee of yours, so the rules write theirs. service_fee_vat_rate states its VAT (0 for exempt); left out, the law decides, else the seller's standard rate. A booking with no sell to split the fee across is saved without it and the row carries a service_fee_dropped warning. service_fee_waived: true stops the rules (a fee you send still stands), and an amendment may switch it on or off until the booking is invoiced. On the four per-guest bookings a service_fee item inside sell_lines is refused: every sell line lands on the first guest, so send the fee as service_fee. A tour booking takes either. Tickets carry the fee per passenger: a service_fee sell line, service_fee_waived on the passenger and options.pricing.

The markup and the discount (hotel_stays, transfers, cruises, car_rentals). They are the whole booking's, as on the booking form: send markup (a figure) or markup_percent (of the booking's cost), and discount (a positive figure) or discount_percent (of everything the booking sells); a percentage wins over the figure beside it. The markup is split across the guests by their cost (by their sell when the booking carries no cost) and folded into each guest's fare on the invoice; the discount is split by what each guest owes. They are written in the form's order: markup, service fee, discount, so a fee rule and the discount's percentage both see the markup. A booking with nothing to split one across is saved without it and the row carries a markup_dropped or discount_dropped warning. A negative value fails the row. On these four bookings a markup or discount item inside sell_lines is refused: every sell line lands on the first guest. A stay's room can carry its nightly cost_rate, spread across the room's guests like nightly_rate, so the markup follows each guest's own cost. A tour booking (one holder) keeps both in sell_lines.

Parameters

Path

  • entityenumrequired

    The record type to push. Each one is described, with its fields and a sample body, at GET /api/v1/imports/entities/{key}.

    one ofcustomerssuppliersleadstour_departuresbus_bookingscar_rentalscharterscruiseseventshotel_staysrail_bookingsticketstour_bookingstransfersvisa_casesoperator_invoicessupplier_bills

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, nested ones by path (passengers[].nickname). 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. service_fee_waived is the exception: it is amendable, and the account's service-fee rules follow it at once. Tickets amend through the edit page's own rules. A row names only what changes; everything it leaves out stays as stored. A passengers (or legs) list you send replaces the stored list: an item with a passenger's id (from the read-back) keeps every key it leaves out, an item without one is a new passenger, and a passenger you leave out of a list you send is removed. A draft takes everything; an issued sale takes only its references (names, ticket numbers, PNR); a PNR held at the airline keeps its itinerary. A list with no ids that is as long as the stored one pairs by position, so re-sending your original row keeps the same passengers. Sell lines the account's own rules wrote (service fee, markup) stay unless your item names that kind. issue: true on a draft issues it after the amendment.

    one ofskipupdate

  • pricingenumdefault apply_rules

    Tickets. apply_rules runs the account's markup and service-fee rules on top of the lines you send, as the capture screen does. as_sent writes your lines exactly and runs none of them: a migration, or sales invoiced in another system. The introducer commission you send applies either way.

    one ofapply_rulesas_sent

  • 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
    • pricingenum

      The batch's pricing mode.

      one ofapply_rulesas_sent

    • 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. A nested key is named by its path, without the item's position.

  • 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, in English. A nested failure names its position, e.g. {"cost_lines": ["cost_lines[0].amount: not an amount: \"abc\""]}. A booking the entity's own validation refuses names the field it failed on, like any other row: {"check_out_on": ["must be after the check-in date"]}. A ticket sale places a passenger's or a leg's failure at its position: {"passengers": ["passengers[0].name: can't be blank"]}. A failure that belongs to no one field is under base, and so is one on a record the booking builds for itself (a room, a guest, a pricing line), whose place in your payload cannot be traced.

    • warningsobject[]nullable

      The row WROTE, and something you asked for beside it did not happen. Match on code; the message is English prose. issue_refused / issue_pending_approval / issue_scope_missing: the sale is saved, the invoice is not cut (re-send the row with on_conflict: update and issue: true once fixed). issue_pending_approval keeps the draft for the approver; re-send with issue: true after it is approved and that draft is issued. issue_preview_refused: on /validate, the invoice could not even be drafted. Validate proves the draft builds; a refusal decided at issue time (missing bill-to, credit limit, closed period, approval) is only known on commit, as issue_refused. fx_rate_masked: the key's role may not move that side's money, so the rate was not written. commission_masked: the role may not set a percentage introducer commission (it needs cost and sell), so the commission was not written. A gift (complimentary) the role may not grant is not a warning: the row fails under passengers, as the screen refuses the save. option_dropped: a ticket sale sent with option_date while every passenger carries a ticket_number is saved as ticketed, not on option. The airline has issued the documents, so the ticketing deadline is over (send option_date only for a held PNR, with the numbers left blank). phone_dropped: a phone number that cannot be dialled was left blank and the row saved without it; field names the column. Numbers are stored in E.164 (+905321234567); one sent without a country code is read in the account's country. service_fee_dropped: a booking sent with service_fee or service_fee_percent has no sell to split the fee across (no priced line yet, or every guest a gift), so it was saved without the fee; field names the one sent. Price the booking and set the fee in-app. markup_dropped / discount_dropped: a booking sent with a markup or a discount has nothing to split it across (no priced line, every guest a gift, or a markup_percent with no cost to measure), so it was saved without it; field is markup or discount.

      Show fields
      • codeenumrequired

        one ofissue_refusedissue_pending_approvalissue_scope_missingissue_preview_refusedfx_rate_maskedcommission_maskedoption_droppedphone_droppedservice_fee_droppedmarkup_droppeddiscount_dropped

      • messagestringrequired
      • fieldstringnullable
    • payerobjectnullable

      The cari this booking bills, for the six booking entities. Absent for entities that bill no one. external_id: your customer_external_id resolved and won. matched: an existing cari carried the identity (by says which: tax_number, legal_name or passport); filled lists the blank fields your bill-to completed. Nothing filled is ever overwritten. created: a new cari was opened from your bill-to or the first traveller. would_create: the same, on /validate, where nothing is written. none: no cari; warning says why (payer_unidentified: no VKN, TCKN or passport to go on). A matched row may carry payer_fill_skipped: the cari could not take your bill-to's blank fields, so it was bound unchanged.

      Show fields
      • outcomeenum

        one ofexternal_idmatchedcreatedwould_createnone

      • byenum

        one oftax_numberlegal_namepassport

      • customer_iduuid
      • filledstring[]
      • warningenum

        one ofpayer_unidentifiedpayer_create_not_permittedpayer_fill_skipped

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/tickets' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 8f14e45f-ea2c-4f33-9a3b-6d0c1b7e5a90' \
  -d '{
  "source_system": "crm",
  "rows": [
    {
      "external_id": "TICKET-1001",
      "pnr": "ABC123",
      "gds_pnr": "ABC123",
      "currency": "EUR",
      "settlement_channel": "bsp",
      "fare_type": "normal",
      "miles_used": 2,
      "reservation_giver_name": "Ada Lovelace",
      "issuing_system": "amadeus",
      "corporate_code": "TKCORP2026",
      "booked_at": "2026-03-14T09:30:00+03:00",
      "tags": [
        "corporate"
      ],
      "transaction_date": "2026-03-14",
      "invoice_date": "2026-03-18",
      "notes": "Imported from the source system.",
      "legs": [
        {
          "airline_code": "TK",
          "flight_number": "1985",
          "departure_airport": "IST",
          "arrival_airport": "FCO",
          "departure_at": "2026-03-14T09:30:00+03:00",
          "arrival_at": "2026-03-14T12:45:00+03:00",
          "cabin_class": "economy",
          "booking_class": "L",
          "fare_basis": "LLXOW",
          "baggage_allowance": "1PC",
          "operating_carrier": "TK",
          "fare_family": "EcoFly",
          "refundable": true,
          "changeable": true,
          "change_fee": "1250.00",
          "refund_fee": "1250.00",
          "status": "confirmed"
        }
      ],
      "passengers": [
        {
          "first_name": "Ada",
          "last_name": "Lovelace",
          "nationality": "TR",
          "national_id": "10000000146",
          "passport_number": "U01234567",
          "passport_country": "TR",
          "passport_expiry": "2031-06-30",
          "date_of_birth": "1988-04-21",
          "gender": "MRS",
          "frequent_flyer_number": "TK123456789",
          "name": "Ada Lovelace",
          "type": "adult",
          "ticket_number": "235-1234567890",
          "cost_lines": [
            {
              "kind": "base_fare",
              "amount": "1250.00",
              "label": "Imported line",
              "currency": "EUR",
              "vat_rate": "20",
              "tax_inclusive": true
            }
          ],
          "sell_lines": [
            {
              "kind": "package_price",
              "amount": "1250.00",
              "label": "Imported line",
              "currency": "EUR",
              "vat_rate": "20",
              "tax_inclusive": true,
              "customer_visible": true
            }
          ]
        }
      ],
      "customer_external_id": "CUSTOMER-1001",
      "supplier_external_id": "SUPPLIER-1001"
    }
  ]
}'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "tickets",
    "source_system": "crm",
    "status": "completed",
    "mode": "validate",
    "async": true,
    "pricing": "apply_rules",
    "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": null,
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "rows": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18/rows"
    }
  },
  "ignored_fields": [
    "ticket_no",
    "passengers[].nickname"
  ],
  "previous_batch_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "results": [
    {
      "row": 1,
      "external_id": "TICKET-1001",
      "status": "imported",
      "record": {
        "type": "TicketSale",
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      },
      "payer": {
        "outcome": "external_id",
        "customer_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      }
    }
  ]
}

A resumable change feed for one vertical

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

Reachable with a narrow key: import, import_issue

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

  • entityenumrequired

    A key from GET /api/v1/imports/entities.

    one ofcustomerssuppliersleadstour_departuresbus_bookingscar_rentalscharterscruiseseventshotel_staysrail_bookingsticketstour_bookingstransfersvisa_casesoperator_invoicessupplier_bills

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). * tickets — pnr, gds_pnr, settlement_channel, corporate_code, custom_fields, passengers[] (id, name, type, ticket_number, status, custom_fields) and legs[] (flight, from, to, departure_at, arrival_at, cabin_class, booking_class, fare_basis, baggage_allowance, fare_family, refundable, changeable, change_fee, refund_fee, 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. * bus_bookings — those three, plus route (first stop → last stop), departs_at (the first departure), billing_basis (agent: the fare is the carrier's money, the agency bills its own fee; gross: resold) and option_date. * rail_bookings — the same fields as bus_bookings. * 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_bookings — voucher_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. Every booking entity, tour_departures excepted, also carries custom_fields: the account's own fields by key, as GET /api/v1/imports/entities/{key} lists them under custom_fields. A field marked personal is left out for a key whose role cannot read personal data, and a webhook delivery never carries one. 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/tickets/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": {
        "cost": "1100.0",
        "sell": "1250.0"
      },
      "invoices": [
        {
          "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
          "number": "INV-2026-000117",
          "status": "paid",
          "currency": "EUR",
          "total": "1250.0",
          "balance": "0.0"
        }
      ],
      "changed": [
        "supplier_confirmation_no"
      ],
      "detail": {
        "voucher_number": "HS-2026-00042",
        "supplier_confirmation_no": "HTL-884213",
        "supplier": "Museum Hotel",
        "check_in_on": "2026-03-14",
        "check_out_on": "2026-03-18",
        "rooms": [
          {
            "name": "Deluxe Sea View",
            "room_type": "double",
            "board_basis": "bed_breakfast",
            "booked_pax": 2,
            "capacity": 2
          }
        ]
      },
      "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, import_issue

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

  • entityenumrequired

    A key from GET /api/v1/imports/entities.

    one ofcustomerssuppliersleadstour_departuresbus_bookingscar_rentalscharterscruiseseventshotel_staysrail_bookingsticketstour_bookingstransfersvisa_casesoperator_invoicessupplier_bills

  • 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). * tickets — pnr, gds_pnr, settlement_channel, corporate_code, custom_fields, passengers[] (id, name, type, ticket_number, status, custom_fields) and legs[] (flight, from, to, departure_at, arrival_at, cabin_class, booking_class, fare_basis, baggage_allowance, fare_family, refundable, changeable, change_fee, refund_fee, 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. * bus_bookings — those three, plus route (first stop → last stop), departs_at (the first departure), billing_basis (agent: the fare is the carrier's money, the agency bills its own fee; gross: resold) and option_date. * rail_bookings — the same fields as bus_bookings. * 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_bookings — voucher_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. Every booking entity, tour_departures excepted, also carries custom_fields: the account's own fields by key, as GET /api/v1/imports/entities/{key} lists them under custom_fields. A field marked personal is left out for a key whose role cannot read personal data, and a webhook delivery never carries one. 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/tickets/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": {
      "cost": "1100.0",
      "sell": "1250.0"
    },
    "invoices": [
      {
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
        "number": "INV-2026-000117",
        "status": "paid",
        "currency": "EUR",
        "total": "1250.0",
        "balance": "0.0"
      }
    ],
    "changed": [
      "supplier_confirmation_no"
    ],
    "detail": {
      "voucher_number": "HS-2026-00042",
      "supplier_confirmation_no": "HTL-884213",
      "supplier": "Museum Hotel",
      "check_in_on": "2026-03-14",
      "check_out_on": "2026-03-18",
      "rooms": [
        {
          "name": "Deluxe Sea View",
          "room_type": "double",
          "board_basis": "bed_breakfast",
          "booked_pax": 2,
          "capacity": 2
        }
      ]
    },
    "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, import_issue

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
    • pricingenum

      The batch's pricing mode.

      one ofapply_rulesas_sent

    • 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": "tickets",
      "source_system": "crm",
      "status": "completed",
      "mode": "validate",
      "async": true,
      "pricing": "apply_rules",
      "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": null,
      "created_at": "2026-03-14T09:30:00Z",
      "links": {
        "self": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
        "rows": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18/rows"
      }
    }
  ],
  "page": {
    "next_cursor": "MjAyNi0wMy0xNFQwOTozMDowMC4wMDAwMDBafDk0MQ",
    "has_more": true,
    "limit": 25
  }
}

One import batch

GET/api/v1/imports/{id}

Reachable with a narrow key: import, import_issue

Parameters

Path

  • iduuidrequired

Response · 200

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

      one ofvalidatingreadyimportingcompletedpartialfailed

    • modeenum

      one ofvalidatecommit

    • asyncboolean
    • pricingenum

      The batch's pricing mode.

      one ofapply_rulesas_sent

    • 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": "tickets",
    "source_system": "crm",
    "status": "completed",
    "mode": "validate",
    "async": true,
    "pricing": "apply_rules",
    "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": null,
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "rows": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18/rows"
    }
  }
}

Per-row outcomes

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

Reachable with a narrow key: import, import_issue

?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
    • pricingenum

      The batch's pricing mode.

      one ofapply_rulesas_sent

    • 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, in English. A nested failure names its position, e.g. {"cost_lines": ["cost_lines[0].amount: not an amount: \"abc\""]}. A booking the entity's own validation refuses names the field it failed on, like any other row: {"check_out_on": ["must be after the check-in date"]}. A ticket sale places a passenger's or a leg's failure at its position: {"passengers": ["passengers[0].name: can't be blank"]}. A failure that belongs to no one field is under base, and so is one on a record the booking builds for itself (a room, a guest, a pricing line), whose place in your payload cannot be traced.

    • warningsobject[]nullable

      The row WROTE, and something you asked for beside it did not happen. Match on code; the message is English prose. issue_refused / issue_pending_approval / issue_scope_missing: the sale is saved, the invoice is not cut (re-send the row with on_conflict: update and issue: true once fixed). issue_pending_approval keeps the draft for the approver; re-send with issue: true after it is approved and that draft is issued. issue_preview_refused: on /validate, the invoice could not even be drafted. Validate proves the draft builds; a refusal decided at issue time (missing bill-to, credit limit, closed period, approval) is only known on commit, as issue_refused. fx_rate_masked: the key's role may not move that side's money, so the rate was not written. commission_masked: the role may not set a percentage introducer commission (it needs cost and sell), so the commission was not written. A gift (complimentary) the role may not grant is not a warning: the row fails under passengers, as the screen refuses the save. option_dropped: a ticket sale sent with option_date while every passenger carries a ticket_number is saved as ticketed, not on option. The airline has issued the documents, so the ticketing deadline is over (send option_date only for a held PNR, with the numbers left blank). phone_dropped: a phone number that cannot be dialled was left blank and the row saved without it; field names the column. Numbers are stored in E.164 (+905321234567); one sent without a country code is read in the account's country. service_fee_dropped: a booking sent with service_fee or service_fee_percent has no sell to split the fee across (no priced line yet, or every guest a gift), so it was saved without the fee; field names the one sent. Price the booking and set the fee in-app. markup_dropped / discount_dropped: a booking sent with a markup or a discount has nothing to split it across (no priced line, every guest a gift, or a markup_percent with no cost to measure), so it was saved without it; field is markup or discount.

      Show fields
      • codeenumrequired

        one ofissue_refusedissue_pending_approvalissue_scope_missingissue_preview_refusedfx_rate_maskedcommission_maskedoption_droppedphone_droppedservice_fee_droppedmarkup_droppeddiscount_dropped

      • messagestringrequired
      • fieldstringnullable
    • payerobjectnullable

      The cari this booking bills, for the six booking entities. Absent for entities that bill no one. external_id: your customer_external_id resolved and won. matched: an existing cari carried the identity (by says which: tax_number, legal_name or passport); filled lists the blank fields your bill-to completed. Nothing filled is ever overwritten. created: a new cari was opened from your bill-to or the first traveller. would_create: the same, on /validate, where nothing is written. none: no cari; warning says why (payer_unidentified: no VKN, TCKN or passport to go on). A matched row may carry payer_fill_skipped: the cari could not take your bill-to's blank fields, so it was bound unchanged.

      Show fields
      • outcomeenum

        one ofexternal_idmatchedcreatedwould_createnone

      • byenum

        one oftax_numberlegal_namepassport

      • customer_iduuid
      • filledstring[]
      • warningenum

        one ofpayer_unidentifiedpayer_create_not_permittedpayer_fill_skipped

  • 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": "tickets",
    "source_system": "crm",
    "status": "completed",
    "mode": "validate",
    "async": true,
    "pricing": "apply_rules",
    "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": null,
    "created_at": "2026-03-14T09:30:00Z",
    "links": {
      "self": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
      "rows": "/api/v1/imports/9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18/rows"
    }
  },
  "data": [
    {
      "row": 1,
      "external_id": "TICKET-1001",
      "status": "imported",
      "record": {
        "type": "TicketSale",
        "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      },
      "payer": {
        "outcome": "external_id",
        "customer_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      }
    }
  ],
  "page": {
    "next_cursor": "MjAyNi0wMy0xNFQwOTozMDowMC4wMDAwMDBafDk0MQ",
    "has_more": true,
    "limit": 25
  }
}