Skip to content

Hotel Booking ​

Books the offer selected from a rooms availability response.

Endpoint: POST /api/v3/bookings

Query parameters:

  • locale (optional): 2-letter language code for supplier-facing texts (e.g. en, fr).

Request body:

FieldRequiredDescription
requestIdyesThe requestId from GET /api/v3/hotels/{id}/rooms/availability. Must be used before its expiresAt (about 15 minutes).
customeryesThe person or company the booking is for. See below.
roomsyesOne entry per requested room. See below.
internalReferencenoYour own reference for this booking; echoed back as internalReference on the booking.
tourOperatorNamenoTour operator name to record on the booking.
networkNamenoDistribution network name to record on the booking.
notenoFree-text note stored on the booking.
replaceBookingIdnoExisting booking this one replaces.

customer object — firstName, lastName, email are required. Optional: phoneNumber, address, additionalAddress, city, zipCode, company, fileId (existing customer file to attach the booking to; one is created automatically when omitted).

rooms entries — one per room, in the same order as the availability request:

FieldRequiredDescription
bedCompositionIdyesThe chosen bed choice id from the rooms-availability response.
travelersyesEvery guest staying in the room. See below.
promotionIdnoTo book the promoted price shown in totalPriceWithPromo, send the bed choice's promotionInfo.promotionId (or the matching promotions[].id) here. Omitted = the room books without the promotion, at totalPriceWithoutPromo.
optionalPromotionIdsnoOptional promotions to apply, from the bed choice's promotions[].id.

The requestId + bedCompositionId pair fully identifies the offer — no supplier identifier needs to be sent back.

travelers entries — kind (adult, child or infant), gender (male, female or non_binary), firstName, lastName and birthdate (YYYY-MM-DD) are required; kind and gender are case-insensitive and any other value returns 400 with code INVALID_TRAVELER. Optional: nationality (ISO 3166-1 alpha-2), passportNumber, expirationDate (YYYY-MM-DD), ageIsExact. The traveler set must match the occupancy the availability was priced for: same adult count, and one child per childrenBirthdates entry, of the same age at check-in (see Travelers that differ from the search).

Example ​

bash
curl -X POST "https://api.koob.tech/api/v3/bookings" \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
  "requestId": "6f1f9e0c-0a52-4c5e-9c1d-3f2b7a9e8d41",
  "internalReference": "AGY-2026-118",
  "customer": {
    "firstName": "Marie",
    "lastName": "Dupont",
    "email": "marie.dupont@example.com"
  },
  "rooms": [
    {
      "bedCompositionId": "bed-812-double",
      "travelers": [
        { "kind": "adult", "gender": "female", "firstName": "Marie", "lastName": "Dupont", "birthdate": "1988-04-17" },
        { "kind": "adult", "gender": "male", "firstName": "Paul", "lastName": "Dupont", "birthdate": "1986-09-02" },
        { "kind": "child", "gender": "female", "firstName": "Emma", "lastName": "Dupont", "birthdate": "2019-05-01" }
      ]
    }
  ]
}'

Response — 201 Created, with the created booking (same shape as GET /api/v3/bookings/{id}):

json
{
  "data": {
    "id": "98765",
    "internalReference": "AGY-2026-118",
    "tourOperatorName": null,
    "networkName": null,
    "note": null,
    "currency": "USD",
    "checkIn": "2026-11-12",
    "checkOut": "2026-11-16",
    "nightsCount": 4,
    "state": "confirmed",
    "allotmentAvailable": true,
    "totalPrice": 1240.5,
    "cancellationFees": null,
    "hotel": { "id": "4242", "name": "Bali Beach Resort" },
    "customer": {
      "firstName": "Marie",
      "lastName": "Dupont",
      "company": null,
      "address": null,
      "additionalAddress": null,
      "zipCode": null,
      "city": null,
      "email": "marie.dupont@example.com",
      "phoneNumber": null,
      "fileId": "5510"
    },
    "rooms": [
      {
        "id": "31022",
        "name": "Deluxe Garden View",
        "price": 1240.5,
        "travelers": [
          {
            "id": "71201",
            "firstName": "Marie",
            "lastName": "Dupont",
            "passportNumber": null,
            "expirationDate": null,
            "kind": "adult",
            "gender": "female",
            "nationality": "FR",
            "birthdate": "1988-04-17",
            "ageIsExact": true
          }
        ]
      }
    ],
    "hotelConfirmationNumber": null,
    "createdAt": "2026-11-05T14:21:00.000Z",
    "cancelledAt": null
  }
}

allotmentAvailable tells you how the booking was taken: true means it was booked on allotment and confirmed at once; false means it went on request to the hotel and state is what to follow. nightsCount is checkOut minus checkIn. cancellationFees is set on cancellation when the rate's conditions charge a fee.

Every field the request accepts comes back on the booking, under the same name, so you can confirm what was booked. The two that do not are the request's own plumbing rather than booking state: requestId names the availability handshake, and replaceBookingId names the booking this one superseded. Room and traveler ids are stable for the lifetime of the booking — use them to match a room or a guest between calls.

The booking is created at the price shown in the availability response. Rooms with instantAvailability: false are created on-request (state: "sent") and confirmed by the supplier afterwards; re-read the booking with GET /api/v3/bookings/{id} to track the state. Which states can follow, and which are terminal, is on Booking states.

Reading a booking ​

GET /api/v3/bookings/{id} returns the same shape as the POST response. Only bookings of your own organization are visible; a booking of another of your organizations answers 403 OWNED_BY_OTHER_ORGANIZATION naming it (see Authentication), anything else is a 404.

Errors ​

StatusMeaningWhat to do
404The requestId expired (15-minute window), is unknown, or was priced for another organization than the one booking.Re-run the rooms-availability request as the booking organization and book with the fresh requestId.
400Invalid payload (missing field, malformed date, unknown bedCompositionId).Check message and details in the error body.
409The offer can no longer be booked (allotment taken since pricing).Re-run the availability search.
409 PRICE_CHANGED_FOR_TRAVELERSThe travelers' ages at check-in differ from the searched ones and raise the price.Show details.totalPrice; to accept, book again with details.requestId and details.rooms[].bedCompositionId.

Suppliers price the party you searched for. Each booked room must fit a searched room: no more adults or children than searched, and each child's age at check-in must be one of the searched ages. You may list fewer travelers than searched (for instance the lead guest only): the booking keeps the searched party and price.

When a traveler's age at check-in differs from the searched birthdate (for instance a child a year older), supported suppliers (currently Koedia) are searched again with the travelers' birthdates, looking for the same offer:

  • same or lower total price: the booking goes through at the new price;
  • higher total price: 409 PRICE_CHANGED_FOR_TRAVELERS, with details holding a fresh requestId, its expiresAt, currency, previousTotalPrice, totalPrice and, in the order you sent them, the rooms[].bedCompositionId to book;
  • offer gone: 409 OFFER_NO_LONGER_AVAILABLE.

The search runs again only when you list every searched traveler. Other suppliers, and bookings that list fewer travelers than searched while an age differs, are refused with 400 ROOM_OCCUPANCY_MISMATCH: search again with the travelers' birthdates.

Error bodies carry a requestId field — quote it when contacting support.

After booking ​

Retrieve and manage the booking with the bookings endpoints: details, documents, messages, states and cancellation.