Lydira API
TR

Import guides

Hotel stays

Hotel bookings with their rooms, guests and pricing lines.

A stay carries its dates, rooms, guests and pricing. A room with a nightly rate prices itself across the nights.

On this page

A key with one of these scopes reaches it:importimport_issue

Hotel stayshotel_stays

POST/api/v1/imports/hotel_stays

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

Endpoints

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

A new row must carry

check_in_oncheck_out_on

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

curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/hotel_stays/validate' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 8f14e45f-ea2c-4f33-9a3b-6d0c1b7e5a90' \
  -d '{
  "source_system": "crm",
  "rows": [
    {
      "external_id": "HOTEL-STAY-1001-B",
      "check_in_on": "2026-03-14",
      "check_out_on": "2026-03-18",
      "currency": "EUR",
      "guest_count": 2,
      "supplier_external_id": "SUPPLIER-1001",
      "status": "draft",
      "notes": "Imported from the source system.",
      "rate_basis": "net",
      "margin_mode": "percent",
      "option_date": "2026-03-20T18:00",
      "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": "MRS",
          "frequent_flyer_number": "TK123456789"
        }
      ],
      "rooms": [
        {
          "name": "Deluxe Sea View",
          "room_type": "double",
          "capacity": 2,
          "occupancy": 2,
          "nightly_rate": "180.00",
          "cost_rate": "1250.00",
          "single_supplement_amount": "60.00",
          "single_supplement_currency": "EUR"
        }
      ],
      "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(41)
  • external_idstring
  • check_in_ondaterequired
  • check_out_ondaterequired
  • currencycurrency
  • guest_countinteger
  • customer_external_idreference
  • supplier_external_idreference
  • custom_fieldsobject

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

  • statusenum

    One of:draftoptionconfirmedin_housechecked_outcancelledno_show

  • notesstring
  • rate_basisenum

    net ⇒ cost typed / sell derived; commissionable ⇒ the reverse

    One of:netcommissionable

  • margin_modeenum

    percent ⇒ the margin was typed as a %; amount ⇒ as a flat figure

    One of:percentamount

  • option_datedate
  • transaction_datedate

    işlem tarihi — the FX anchor for every cost line

  • invoice_datedate
  • cost_linesobject[]

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

    Each item

    • kindstringrequired

      Values the app uses:room_ratecity_taxtourism_feeresort_feebreakfasthalf_boardfull_boardall_inclusive_supplementparkingextra_bedkonaklama_vergisiservice_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 "hotel" pricing context

    Each item

    • kindstringrequired

      Values the app uses:package_pricemarkupservice_feesupplier_commissioncancellation_feeinsurance_premiumdiscountrefund_allowanceroom_ratecity_taxtourism_feeresort_feebreakfasthalf_boardfull_boardall_inclusive_supplementparkingextra_bedkonaklama_vergisisupplier_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
  • markupmoney

    the agency's hidden markup on the whole booking, in its currency: 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

  • markup_percentdecimal

    the markup as a percentage of the booking's cost; wins over markup

  • discountmoney

    the whole booking's discount (iskonto), a positive figure: split across the guests by what each owes, after the markup and the service fee

  • discount_percentdecimal

    the discount as a percentage of everything the booking sells, the markup and the service fee included; wins over discount

  • 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

  • travelersobject[]PII

    guests; auto-saved to the customer's traveller directory

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

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

  • roomsobject[]

    a room with a nightly_rate contributes a room_rate SELL line × nights

    Each item

    • namestring
    • room_typestring

      Your account's hotel_room_type list; it starts with:singletwindoubletriplequadsuitecabin

    • capacityinteger
    • occupancyinteger
    • nightly_ratemoney
    • cost_ratemoney

      the room's nightly cost: a room_rate COST line × nights, spread across its guests like the sell

    • single_supplement_amountmoney
    • single_supplement_currencycurrency
  • 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/hotel_stays' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY'
Response · custom_fields (excerpt)
200
{
  "key": "hotel_stays",
  "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/hotel_stays/records/HOTEL-STAY-1001
200
{
  "entity": "hotel_stays",
  "external_id": "HOTEL-STAY-1001",
  "source_system": "crm",
  "type": "HotelStay",
  "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
  "created_at": "2026-03-14T09:30:00Z",
  "updated_at": "2026-03-15T11:05:00Z",
  "status": "confirmed",
  "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"
    },
    "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
      }
    ]
  }
}

Request · dry run, every field

curl -X POST 'https://YOUR-CELL.lydira.com/api/v1/imports/hotel_stays/validate' \
  -H 'Authorization: Bearer sek_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 8f14e45f-ea2c-4f33-9a3b-6d0c1b7e5a90' \
  -d '{
  "source_system": "crm",
  "rows": [
    {
      "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",
      "custom_fields": {
        "project_code": "PRJ-2026-001",
        "trip_reason": "meeting"
      },
      "status": "draft",
      "notes": "Imported from the source system.",
      "rate_basis": "net",
      "margin_mode": "percent",
      "option_date": "2026-03-20T18:00",
      "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
        }
      ],
      "markup": "1250.00",
      "markup_percent": "10",
      "discount": "1250.00",
      "discount_percent": "5",
      "service_fee": "1250.00",
      "service_fee_vat_rate": "20",
      "service_fee_waived": true,
      "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",
          "custom_fields": {
            "employee_no": "E-10442"
          }
        }
      ],
      "rooms": [
        {
          "name": "Deluxe Sea View",
          "room_type": "double",
          "capacity": 2,
          "occupancy": 2,
          "nightly_rate": "180.00",
          "cost_rate": "1250.00",
          "single_supplement_amount": "60.00",
          "single_supplement_currency": "EUR"
        }
      ]
    }
  ]
}'
Response
200
{
  "import": {
    "id": "9d2f7c3a-1b4e-4a7d-8e51-2c0f6b9a4d18",
    "entity": "hotel_stays",
    "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": "HOTEL-STAY-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 →