Lydira API
TR

Import guides

Bus and rail tickets

Tickets sold in the carrier's name: legs, passengers, seats and the carrier's fare.

Bus and rail tickets are sold in the carrier's name. Send the journey's legs, then each passenger with the carrier's fare and their seats; a seat names its leg by position, from 0. The fare is written as the carrier's money, so the agency's own income is its service fee. With no customer the sale is a walk-in billed to the first passenger. A seat or e-ticket number already sold fails the row.

On this page

A key with one of these scopes reaches it:importimport_issue

Bus ticketsbus_bookings

POST/api/v1/imports/bus_bookings

Create-once: re-sending the same external_id skips rather than duplicating.

Endpoints

  • validatePOST /api/v1/imports/bus_bookings/validate
  • importPOST /api/v1/imports/bus_bookings
  • feedGET /api/v1/imports/bus_bookings/records
  • read_backGET /api/v1/imports/bus_bookings/records/{external_id}

Sample request billed by its bill-to (no customer key)

curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/bus_bookings/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": "BUS-BOOKING-1001-B",
      "currency": "EUR",
      "supplier_external_id": "SUPPLIER-1001",
      "billing_basis": "agent",
      "fare_collected_by": "agency",
      "status": "draft",
      "reference": "Reference",
      "supplier_confirmation_no": "Supplier confirmation no",
      "notes": "Imported from the source system.",
      "option_date": "2026-03-20T18:00",
      "transaction_date": "2026-03-14",
      "invoice_date": "2026-03-18",
      "legs": [
        {
          "origin_name": "İstanbul",
          "origin_code": "IST",
          "destination_name": "Ankara",
          "destination_code": "ANK",
          "departs_at": "2026-03-14T09:30:00+03:00",
          "arrives_at": "2026-03-14T12:45:00+03:00",
          "service_number": "S-101",
          "operator_name": "Express Lines",
          "fare_name": "Standard"
        }
      ],
      "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": "MRS",
          "frequent_flyer_number": "TK123456789",
          "passenger_type": "adult",
          "phone": "+905551112233",
          "fare": "850.00",
          "seats": [
            {
              "leg": 0,
              "seat_number": "12",
              "coach_number": "3",
              "ticket_number": "BUS-2026-000101",
              "fare_amount": "850.00"
            }
          ]
        }
      ],
      "cost_lines": [
        {
          "kind": "ticket_fare",
          "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
        }
      ],
      "bill_to_legal_name": "Lovelace Seyahat A.Ş.",
      "bill_to_tax_number": "1234567890",
      "bill_to_tax_office": "Beşiktaş",
      "bill_to_address_line": "Nispetiye Cad. No:12",
      "bill_to_city": "İstanbul",
      "bill_to_country": "TR"
    }
  ]
}'
Fields(36)
  • external_idstring
  • currencycurrency

    omit for the company's own

  • customer_external_idreference

    the cari; omit it for a walk-in sale billed to the first passenger

  • supplier_external_idreference

    the carrier

  • billing_basisenum

    agent ⇒ the fare is the carrier's money and the agency bills its own fee; gross ⇒ the agency resells the fare. Omit for the company's default (agent in Türkiye)

    One of:agentgross

  • fare_collected_byenum

    who took the fare from the passenger; a gross sale is always the agency's

    One of:agencycarrier

  • statusenum

    default issued; cancelled on an amendment cancels the booking

    One of:draftoptionissuedcancelled

  • referencestring

    the carrier's booking reference (PNR)

  • supplier_confirmation_nostring
  • notesstring
  • option_datedate
  • transaction_datedate

    işlem tarihi — the FX anchor for every line

  • invoice_datedate
  • custom_fieldsobject

    the account's own fields on the booking, by key

  • legsobject[]

    the journey in order; a seat names its leg by position

    Each item

    • origin_namestring
    • origin_codestring
    • destination_namestring
    • destination_codestring
    • departs_atdatetime
    • arrives_atdatetime
    • service_numberstring

      the trip or train number

    • operator_namestring
    • fare_namestring
  • travelersobject[]PII

    passengers; auto-saved to the cari's traveller directory. The first is the walk-in payer

    Each item

    • first_namestringPII
    • last_namestringPII
    • nationalitystring
    • national_idstringPII
    • passport_numberstringPII
    • passport_countrystring
    • passport_expirydate
    • date_of_birthdatePII
    • genderstring

      Values the app uses:MRMRSMISSMSTR

    • frequent_flyer_numberstring
    • passenger_typeenum

      One of:adultchildinfant

    • phonephonePII
    • faremoney

      the carrier's fare for this passenger, written as its ticket_fare sell and cost line

    • custom_fieldsobject

      the account's own fields on the passenger, by key

    • seatsobject[]

      one per leg the passenger rides

      Each item

      • leginteger

        the leg's position in legs, from 0

      • seat_numberstring
      • coach_numberstring
      • ticket_numberstring

        the e-ticket number

      • fare_amountmoney

        this leg's share of the fare

  • cost_linesobject[]

    cost-side pricing lines for the "bus" pricing context

    Each item

    • kindstringrequired

      Values the app uses:ticket_farecarrier_commissionapplied_fareservice_feesupplier_refundinsurance_premium

    • amountmoneyrequired
    • labelstring
    • currencycurrency
    • vat_ratedecimal

      percent (20 ⇒ 20% KDV); omit to take the kind's default

    • tax_inclusiveboolean

      true ⇒ the amount includes VAT. A hotel stay or a charter takes one VAT mode: the lines' flags decide it, lines stating both are refused, and every line follows the booking's mode afterwards

    • quantityinteger
  • sell_linesobject[]

    sell-side pricing lines for the "bus" pricing context

    Each item

    • kindstringrequired

      Values the app uses:package_pricemarkupservice_feesupplier_commissioncancellation_feeinsurance_premiumdiscountrefund_allowanceticket_farecarrier_commissionapplied_faresupplier_refund

    • amountmoneyrequired
    • labelstring
    • currencycurrency
    • vat_ratedecimal

      percent (20 ⇒ 20% KDV); omit to take the kind's default

    • tax_inclusiveboolean

      true ⇒ the amount includes VAT. A hotel stay or a charter takes one VAT mode: the lines' flags decide it, lines stating both are refused, and every line follows the booking's mode afterwards

    • quantityinteger
  • service_feemoney

    the agency's fee for the whole booking, in its currency; split across the guests like the booking form's. Sent, the account's service-fee rules write nothing

  • service_fee_percentdecimal

    the fee as a percentage (0-100) of the booking's sale before fee and discount, instead of service_fee; split like it

  • service_fee_vat_ratedecimal

    the fee's VAT, percent (0 ⇒ exempt); omit it and the law decides, else the seller's standard rate

  • service_fee_waivedboolean

    true: the account's service-fee rules write no fee on this booking

  • bill_to_typeenum

    omit to infer: a legal name or a 10-digit tax number ⇒ corporate

    One of:corporateindividual

  • bill_to_legal_namestring
  • bill_to_tax_numbertax_numberPII

    VKN for a corporate payer, TCKN for an individual; matched digits-only

  • bill_to_tax_officestring
  • bill_to_passport_numberstringPII

    individual only; used when there is no TCKN

  • bill_to_first_namestringPII
  • bill_to_last_namestringPII
  • bill_to_emailemailPII

    stored on a NEW cari; never used to match

  • bill_to_phonephonePII

    stored on a NEW cari; never used to match

  • bill_to_address_linestringPII
  • bill_to_districtstringPII
  • bill_to_citystringPII
  • bill_to_zipstringPII
  • bill_to_countrystring

    ISO-2 code, e.g. TR

References

  • customer_external_id → customers · matched by external_id, email · when missing skip
  • supplier_external_id → suppliers · matched by external_id, code · when missing skip

Your account's own fields (custom_fields)

Each agency adds its own fields in Settings → Custom fields: a project code, a trip reason, an employee number. They differ from account to account, so this page cannot list them. Your integration asks the API for them.

GET /api/v1/imports/entities/{key} answers, under custom_fields, the fields your key's account has on this entity: record for the record itself, traveler for each traveller. Each one names its key (what the row sends), its type, whether it is required, which customers it applies_to and, for a choice, its options. Read it when you build the mapping and again when the agency says it changed a field: it is the account's live list.

Request · the account's fields

curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/entities/bus_bookings' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response · custom_fields (excerpt)
200
{
  "key": "bus_bookings",
  "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
      }
    ]
  }
}

What each type takes

typevalue in the row
text"PRJ-2026-001"
long_text"Fair delegation, second group"
number12.5
date"2026-03-14"
booleantrue
select"meeting"
multi_select["meeting", "fair"]

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.

required and applies_to

  • requiredneveralwayslisted_customers

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

  • applies_toalllisted_customers

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

Send the values as one JSON object by key. An unknown key, a value of the wrong type or a missing required field 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 field the agency prints on the customer's documents cannot change once the record is invoiced.

Traveller fields go on each traveller, under that item's own custom_fields.

They come back the same way: under detail.custom_fields in the read-back and the webhook body (and on each passenger of a ticket). A field marked personal comes back only to a key whose role can read personal data, and never in a webhook.

Reading it back

Once the agency works the record (confirms it, numbers the tickets, invoices it) this is what GET /api/v1/imports/{entity}/records/{external_id} answers, and what every webhook delivers. detail carries what only the agency can add after your push.

GET /api/v1/imports/bus_bookings/records/BUS-BOOKING-1001
200
{
  "entity": "bus_bookings",
  "external_id": "BUS-BOOKING-1001",
  "source_system": "crm",
  "type": "BusBooking",
  "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "created_at": "2026-03-14T09:30:00Z",
  "updated_at": "2026-03-15T11:05:00Z",
  "status": "issued",
  "locked": true,
  "lock_reason": "invoiced",
  "currency": "EUR",
  "totals": {
    "cost": "1100.0",
    "sell": "1250.0"
  },
  "invoices": [
    {
      "id": "4b8e1f60-7c2d-4e9a-b3f5-0a6d2c9e8b71",
      "number": "LYD2026000000117",
      "status": "issued",
      "currency": "EUR",
      "total": "1250.0",
      "balance": "1250.0"
    }
  ],
  "detail": {
    "voucher_number": "VCH-2026-000412",
    "supplier_confirmation_no": "CNF-77821",
    "supplier": "Lovelace Travel Partners",
    "custom_fields": {
      "project_code": "PRJ-2026-001",
      "trip_reason": "meeting"
    },
    "route": "İstanbul → Ankara",
    "departs_at": "2026-03-14T06:30:00Z",
    "billing_basis": "agent",
    "option_date": "2026-03-10"
  }
}

Request · dry run, every field

curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/bus_bookings/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": "BUS-BOOKING-1001",
      "currency": "EUR",
      "customer_external_id": "CUSTOMER-1001",
      "supplier_external_id": "SUPPLIER-1001",
      "billing_basis": "agent",
      "fare_collected_by": "agency",
      "status": "draft",
      "reference": "Reference",
      "supplier_confirmation_no": "Supplier confirmation no",
      "notes": "Imported from the source system.",
      "option_date": "2026-03-20T18:00",
      "transaction_date": "2026-03-14",
      "invoice_date": "2026-03-18",
      "custom_fields": {
        "project_code": "PRJ-2026-001",
        "trip_reason": "meeting"
      },
      "legs": [
        {
          "origin_name": "İstanbul",
          "origin_code": "IST",
          "destination_name": "Ankara",
          "destination_code": "ANK",
          "departs_at": "2026-03-14T09:30:00+03:00",
          "arrives_at": "2026-03-14T12:45:00+03:00",
          "service_number": "S-101",
          "operator_name": "Express Lines",
          "fare_name": "Standard"
        }
      ],
      "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": "MRS",
          "frequent_flyer_number": "TK123456789",
          "passenger_type": "adult",
          "phone": "+905551112233",
          "fare": "850.00",
          "custom_fields": {
            "employee_no": "E-10442"
          },
          "seats": [
            {
              "leg": 0,
              "seat_number": "12",
              "coach_number": "3",
              "ticket_number": "BUS-2026-000101",
              "fare_amount": "850.00"
            }
          ]
        }
      ],
      "cost_lines": [
        {
          "kind": "ticket_fare",
          "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
        }
      ],
      "service_fee": "1250.00",
      "service_fee_vat_rate": "20",
      "service_fee_waived": true
    }
  ]
}'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "bus_bookings",
    "source_system": "crm",
    "status": "ready",
    "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "stats": {
      "total": 1,
      "will_create": 1,
      "will_update": 0,
      "created": 0,
      "updated": 0,
      "skipped": 0,
      "invalid": 0,
      "failed": 0,
      "not_selected": 0
    },
    "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"
    },
    "mode": "validate",
    "async": false,
    "on_conflict": "skip",
    "pricing": "apply_rules"
  },
  "ignored_fields": [],
  "results": [
    {
      "row": 1,
      "external_id": "BUS-BOOKING-1001",
      "status": "will_create",
      "payer": {
        "outcome": "external_id",
        "customer_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      }
    }
  ]
}

Rail ticketsrail_bookings

POST/api/v1/imports/rail_bookings

Create-once: re-sending the same external_id skips rather than duplicating.

Endpoints

  • validatePOST /api/v1/imports/rail_bookings/validate
  • importPOST /api/v1/imports/rail_bookings
  • feedGET /api/v1/imports/rail_bookings/records
  • read_backGET /api/v1/imports/rail_bookings/records/{external_id}

Sample request billed by its bill-to (no customer key)

curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/rail_bookings/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": "RAIL-BOOKING-1001-B",
      "currency": "EUR",
      "supplier_external_id": "SUPPLIER-1001",
      "billing_basis": "agent",
      "fare_collected_by": "agency",
      "status": "draft",
      "reference": "Reference",
      "supplier_confirmation_no": "Supplier confirmation no",
      "notes": "Imported from the source system.",
      "option_date": "2026-03-20T18:00",
      "transaction_date": "2026-03-14",
      "invoice_date": "2026-03-18",
      "legs": [
        {
          "origin_name": "Ankara",
          "origin_code": "ANK",
          "destination_name": "Konya",
          "destination_code": "KNY",
          "departs_at": "2026-03-14T09:30:00+03:00",
          "arrives_at": "2026-03-14T12:45:00+03:00",
          "service_number": "YHT-81002",
          "operator_name": "Express Lines",
          "travel_class": "economy",
          "fare_name": "Standard"
        }
      ],
      "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": "MRS",
          "frequent_flyer_number": "TK123456789",
          "passenger_type": "adult",
          "phone": "+905551112233",
          "fare": "850.00",
          "fare_type": "full",
          "discount_document_no": "Discount document no",
          "seats": [
            {
              "leg": 0,
              "seat_number": "12",
              "coach_number": "3",
              "ticket_number": "RAIL-2026-000101",
              "fare_amount": "850.00"
            }
          ]
        }
      ],
      "cost_lines": [
        {
          "kind": "ticket_fare",
          "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
        }
      ],
      "bill_to_legal_name": "Lovelace Seyahat A.Ş.",
      "bill_to_tax_number": "1234567890",
      "bill_to_tax_office": "Beşiktaş",
      "bill_to_address_line": "Nispetiye Cad. No:12",
      "bill_to_city": "İstanbul",
      "bill_to_country": "TR"
    }
  ]
}'
Fields(36)
  • external_idstring
  • currencycurrency

    omit for the company's own

  • customer_external_idreference

    the cari; omit it for a walk-in sale billed to the first passenger

  • supplier_external_idreference

    the carrier

  • billing_basisenum

    agent ⇒ the fare is the carrier's money and the agency bills its own fee; gross ⇒ the agency resells the fare. Omit for the company's default (agent in Türkiye)

    One of:agentgross

  • fare_collected_byenum

    who took the fare from the passenger; a gross sale is always the agency's

    One of:agencycarrier

  • statusenum

    default issued; cancelled on an amendment cancels the booking

    One of:draftoptionissuedcancelled

  • referencestring

    the carrier's booking reference (PNR)

  • supplier_confirmation_nostring
  • notesstring
  • option_datedate
  • transaction_datedate

    işlem tarihi — the FX anchor for every line

  • invoice_datedate
  • custom_fieldsobject

    the account's own fields on the booking, by key

  • legsobject[]

    the journey in order; a seat names its leg by position

    Each item

    • origin_namestring
    • origin_codestring
    • destination_namestring
    • destination_codestring
    • departs_atdatetime
    • arrives_atdatetime
    • service_numberstring

      the trip or train number

    • operator_namestring
    • travel_classstring

      Values the app uses:economybusinesssleepercouchette

    • fare_namestring
  • travelersobject[]PII

    passengers; auto-saved to the cari's traveller directory. The first is the walk-in payer

    Each item

    • first_namestringPII
    • last_namestringPII
    • nationalitystring
    • national_idstringPII
    • passport_numberstringPII
    • passport_countrystring
    • passport_expirydate
    • date_of_birthdatePII
    • genderstring

      Values the app uses:MRMRSMISSMSTR

    • frequent_flyer_numberstring
    • passenger_typeenum

      One of:adultchildinfant

    • phonephonePII
    • faremoney

      the carrier's fare for this passenger, written as its ticket_fare sell and cost line

    • fare_typetaxonomy

      Your account's rail_fare_type list; it starts with:fullchildyouthsenior_60senior_65teachermilitarypressdisableddisabled_companion

    • discount_document_nostring

      the card or document behind a discounted fare

    • custom_fieldsobject

      the account's own fields on the passenger, by key

    • seatsobject[]

      one per leg the passenger rides

      Each item

      • leginteger

        the leg's position in legs, from 0

      • seat_numberstring
      • coach_numberstring

        required on a train

      • ticket_numberstring

        the e-ticket number

      • fare_amountmoney

        this leg's share of the fare

  • cost_linesobject[]

    cost-side pricing lines for the "rail" pricing context

    Each item

    • kindstringrequired

      Values the app uses:ticket_farereservation_feeseat_upgradecarrier_commissionapplied_fareservice_feesupplier_refundinsurance_premium

    • amountmoneyrequired
    • labelstring
    • currencycurrency
    • vat_ratedecimal

      percent (20 ⇒ 20% KDV); omit to take the kind's default

    • tax_inclusiveboolean

      true ⇒ the amount includes VAT. A hotel stay or a charter takes one VAT mode: the lines' flags decide it, lines stating both are refused, and every line follows the booking's mode afterwards

    • quantityinteger
  • sell_linesobject[]

    sell-side pricing lines for the "rail" pricing context

    Each item

    • kindstringrequired

      Values the app uses:package_pricemarkupservice_feesupplier_commissioncancellation_feeinsurance_premiumdiscountrefund_allowanceticket_farereservation_feeseat_upgradecarrier_commissionapplied_faresupplier_refund

    • amountmoneyrequired
    • labelstring
    • currencycurrency
    • vat_ratedecimal

      percent (20 ⇒ 20% KDV); omit to take the kind's default

    • tax_inclusiveboolean

      true ⇒ the amount includes VAT. A hotel stay or a charter takes one VAT mode: the lines' flags decide it, lines stating both are refused, and every line follows the booking's mode afterwards

    • quantityinteger
  • service_feemoney

    the agency's fee for the whole booking, in its currency; split across the guests like the booking form's. Sent, the account's service-fee rules write nothing

  • service_fee_percentdecimal

    the fee as a percentage (0-100) of the booking's sale before fee and discount, instead of service_fee; split like it

  • service_fee_vat_ratedecimal

    the fee's VAT, percent (0 ⇒ exempt); omit it and the law decides, else the seller's standard rate

  • service_fee_waivedboolean

    true: the account's service-fee rules write no fee on this booking

  • bill_to_typeenum

    omit to infer: a legal name or a 10-digit tax number ⇒ corporate

    One of:corporateindividual

  • bill_to_legal_namestring
  • bill_to_tax_numbertax_numberPII

    VKN for a corporate payer, TCKN for an individual; matched digits-only

  • bill_to_tax_officestring
  • bill_to_passport_numberstringPII

    individual only; used when there is no TCKN

  • bill_to_first_namestringPII
  • bill_to_last_namestringPII
  • bill_to_emailemailPII

    stored on a NEW cari; never used to match

  • bill_to_phonephonePII

    stored on a NEW cari; never used to match

  • bill_to_address_linestringPII
  • bill_to_districtstringPII
  • bill_to_citystringPII
  • bill_to_zipstringPII
  • bill_to_countrystring

    ISO-2 code, e.g. TR

References

  • customer_external_id → customers · matched by external_id, email · when missing skip
  • supplier_external_id → suppliers · matched by external_id, code · when missing skip

Your account's own fields (custom_fields)

Each agency adds its own fields in Settings → Custom fields: a project code, a trip reason, an employee number. They differ from account to account, so this page cannot list them. Your integration asks the API for them.

GET /api/v1/imports/entities/{key} answers, under custom_fields, the fields your key's account has on this entity: record for the record itself, traveler for each traveller. Each one names its key (what the row sends), its type, whether it is required, which customers it applies_to and, for a choice, its options. Read it when you build the mapping and again when the agency says it changed a field: it is the account's live list.

Request · the account's fields

curl -X GET 'https://YOUR-CELL.lydira.com/api/v1/imports/entities/rail_bookings' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response · custom_fields (excerpt)
200
{
  "key": "rail_bookings",
  "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
      }
    ]
  }
}

What each type takes

typevalue in the row
text"PRJ-2026-001"
long_text"Fair delegation, second group"
number12.5
date"2026-03-14"
booleantrue
select"meeting"
multi_select["meeting", "fair"]

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.

required and applies_to

  • requiredneveralwayslisted_customers

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

  • applies_toalllisted_customers

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

Send the values as one JSON object by key. An unknown key, a value of the wrong type or a missing required field 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 field the agency prints on the customer's documents cannot change once the record is invoiced.

Traveller fields go on each traveller, under that item's own custom_fields.

They come back the same way: under detail.custom_fields in the read-back and the webhook body (and on each passenger of a ticket). A field marked personal comes back only to a key whose role can read personal data, and never in a webhook.

Reading it back

Once the agency works the record (confirms it, numbers the tickets, invoices it) this is what GET /api/v1/imports/{entity}/records/{external_id} answers, and what every webhook delivers. detail carries what only the agency can add after your push.

GET /api/v1/imports/rail_bookings/records/RAIL-BOOKING-1001
200
{
  "entity": "rail_bookings",
  "external_id": "RAIL-BOOKING-1001",
  "source_system": "crm",
  "type": "RailBooking",
  "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "created_at": "2026-03-14T09:30:00Z",
  "updated_at": "2026-03-15T11:05:00Z",
  "status": "issued",
  "locked": true,
  "lock_reason": "invoiced",
  "currency": "EUR",
  "totals": {
    "cost": "1100.0",
    "sell": "1250.0"
  },
  "invoices": [
    {
      "id": "4b8e1f60-7c2d-4e9a-b3f5-0a6d2c9e8b71",
      "number": "LYD2026000000117",
      "status": "issued",
      "currency": "EUR",
      "total": "1250.0",
      "balance": "1250.0"
    }
  ],
  "detail": {
    "voucher_number": "VCH-2026-000412",
    "supplier_confirmation_no": "CNF-77821",
    "supplier": "Lovelace Travel Partners",
    "custom_fields": {
      "project_code": "PRJ-2026-001",
      "trip_reason": "meeting"
    },
    "route": "Ankara → Konya",
    "departs_at": "2026-03-14T06:30:00Z",
    "billing_basis": "agent",
    "option_date": "2026-03-10"
  }
}

Request · dry run, every field

curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/rail_bookings/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": "RAIL-BOOKING-1001",
      "currency": "EUR",
      "customer_external_id": "CUSTOMER-1001",
      "supplier_external_id": "SUPPLIER-1001",
      "billing_basis": "agent",
      "fare_collected_by": "agency",
      "status": "draft",
      "reference": "Reference",
      "supplier_confirmation_no": "Supplier confirmation no",
      "notes": "Imported from the source system.",
      "option_date": "2026-03-20T18:00",
      "transaction_date": "2026-03-14",
      "invoice_date": "2026-03-18",
      "custom_fields": {
        "project_code": "PRJ-2026-001",
        "trip_reason": "meeting"
      },
      "legs": [
        {
          "origin_name": "Ankara",
          "origin_code": "ANK",
          "destination_name": "Konya",
          "destination_code": "KNY",
          "departs_at": "2026-03-14T09:30:00+03:00",
          "arrives_at": "2026-03-14T12:45:00+03:00",
          "service_number": "YHT-81002",
          "operator_name": "Express Lines",
          "travel_class": "economy",
          "fare_name": "Standard"
        }
      ],
      "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": "MRS",
          "frequent_flyer_number": "TK123456789",
          "passenger_type": "adult",
          "phone": "+905551112233",
          "fare": "850.00",
          "fare_type": "full",
          "discount_document_no": "Discount document no",
          "custom_fields": {
            "employee_no": "E-10442"
          },
          "seats": [
            {
              "leg": 0,
              "seat_number": "12",
              "coach_number": "3",
              "ticket_number": "RAIL-2026-000101",
              "fare_amount": "850.00"
            }
          ]
        }
      ],
      "cost_lines": [
        {
          "kind": "ticket_fare",
          "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
        }
      ],
      "service_fee": "1250.00",
      "service_fee_vat_rate": "20",
      "service_fee_waived": true
    }
  ]
}'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "rail_bookings",
    "source_system": "crm",
    "status": "ready",
    "company_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "stats": {
      "total": 1,
      "will_create": 1,
      "will_update": 0,
      "created": 0,
      "updated": 0,
      "skipped": 0,
      "invalid": 0,
      "failed": 0,
      "not_selected": 0
    },
    "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"
    },
    "mode": "validate",
    "async": false,
    "on_conflict": "skip",
    "pricing": "apply_rules"
  },
  "ignored_fields": [],
  "results": [
    {
      "row": 1,
      "external_id": "RAIL-BOOKING-1001",
      "status": "will_create",
      "payer": {
        "outcome": "external_id",
        "customer_id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18"
      }
    }
  ]
}

The flow

  1. Dry-run. POST …/validate runs the same path and writes nothing.
  2. Send. The same body without /validate. A bad row fails alone and names its field.
  3. Read back. GET /api/v1/imports/{entity}/records/{external_id} answers with your own key.
  4. Listen. Webhooks tell you when the agency changes the record.

Conventions →Webhooks →