Hotel Room Availability
Room-level availability for a single hotel. This is the call that makes a stay bookable: besides the full room detail (bed choices, prices, cancellation conditions, board), the response carries the booking handshake — a requestId you pass to POST /api/v3/bookings.
Endpoint: GET /api/v3/hotels/{id}/rooms/availability
Path parameter:
id— the hotel ID from the availability search or the catalog (raw or encoded form).
Query parameters:
| Parameter | Required | Description |
|---|---|---|
checkIn | yes | Check-in date, YYYY-MM-DD. |
checkOut | yes | Check-out date, YYYY-MM-DD; must be strictly after checkIn. |
rooms | yes | Room occupancies, deep-object encoded: rooms[0][adults]=2&rooms[0][childrenBirthdates][0]=2019-05-01. One entry per room you want to book. Optional firstAdultNationality (ISO 3166-1 alpha-2). |
suppliers | no | Restrict to these suppliers (Koob, Koedia, Rakuten, STAAH; case-insensitive). Omitted = all suppliers. |
tripId | no | Trip ID; derives the pricing organization and currency server-side. |
replaceBookingId | no | Booking being replaced; its allotment is released for this search. |
locale | no | Content language (ISO 639-1, e.g. fr) for room, bed, facility, board and condition texts; unknown or omitted = en. Own-inventory texts fall back to en per field; bed-bank suppliers are queried in that language. |
sort | no | Comma-separated sort keys among price, promotionsCount, instantAvailability; - prefix = descending (e.g. sort=-price). Omitted = instantly-bookable rooms first. Any other key returns 400 with code INVALID_SORT. |
Use the same stay dates and room occupancies you will book — the handshake stores them.
curl "https://api.koob.tech/api/v3/hotels/4242/rooms/availability?checkIn=2026-11-12&checkOut=2026-11-16&rooms[0][adults]=2&rooms[0][childrenBirthdates][0]=2019-05-01" \
-H "X-API-Key: your-api-key-here"Response
{
"data": [
{
"name": "Deluxe Garden View",
"description": "32m² room with balcony",
"extraInformation": null,
"instantAvailability": true,
"minCapacity": 1,
"maxCapacity": 3,
"supplier": {
"name": "Koob",
"code": "Koob",
"flux": null
},
"facilities": [
{ "name": "WiFi", "iconUrl": "https://cdn.koob.tech/icons/wifi.png" }
],
"images": [
{
"id": null,
"url": "https://cdn.koob.tech/rooms/812/1.jpg",
"name": "Room view"
}
],
"bedChoices": [
{
"id": "bed-812-double",
"name": "1 double bed",
"currency": "EUR",
"totalPriceWithPromo": 576.0,
"totalPriceWithoutPromo": 640.0,
"hasExtraBed": false,
"promotions": [{ "id": "promo-77", "name": "Early Bird 10%" }],
"promotionInfo": {
"promotionId": "promo-77",
"kind": "percent",
"discount": 64.0,
"upgradedFromRoom": null,
"upgradedToRoom": null
},
"cancelConditions": [
{
"deduction": 576.0,
"refundable": false,
"startAt": "2026-11-05T00:00:00.000Z",
"endAt": "2026-11-12T00:00:00.000Z"
}
],
"contractingConditions": "Rates include taxes and service…",
"checkInInstructions": null,
"tags": [
{ "code": "refundable", "label": "Refundable", "startAt": null, "endAt": null },
{ "code": null, "label": "Breakfast included", "startAt": null, "endAt": null }
],
"mealPlans": [
{
"code": "breakfast",
"label": "Breakfast included",
"startAt": null,
"endAt": null
}
],
"hotelFees": [
{
"label": "City tax, payable on site",
"amount": 8.0,
"currency": "EUR"
}
],
"mandatorySupplements": [],
"optionalSupplements": ["Airport transfer"]
}
],
"compatibleRoomCompositions": []
}
],
"meta": {
"requestId": "6f1f9e0c-0a52-4c5e-9c1d-3f2b7a9e8d41",
"expiresAt": "2026-11-01T10:15:00.000Z"
}
}The booking handshake
meta.requestIdidentifies the priced offer set server-side. It is required byPOST /api/v3/bookingsand expires atmeta.expiresAt, about 15 minutes after the call.- Booking with an expired
requestIdreturns404. Re-run this request and book with the freshrequestId— prices may have changed, so show them again to your user. - To book, keep two values per chosen room: the
requestId(one per booking) and the chosen bed choiceid(sent asbedCompositionId). No other identifier is needed; supplier internals never round-trip through your integration.
Room fields
instantAvailability:true= instantly bookable;false= on-request (the booking waits for supplier confirmation).minCapacity/maxCapacity: guest capacity bounds;nullwhen the supplier does not expose capacity. Results are already filtered to the occupancy you requested, sonulldoes not need client-side handling.supplier: the sales channel (Koob,Koedia,Rakuten,STAAH).fluxidentifies the bed bank, chain or inventory behind a Koedia rate and isnullfor suppliers that sell direct.- The response follows the fixed-schema convention: every key is always present, empty collections are
[], absent scalars arenull.
Bed choice fields
Each bedChoices entry is one bookable rate for the room:
id— send it back asbedCompositionIdwhen booking.totalPriceWithPromo/totalPriceWithoutPromo— total for the whole stay, incurrency, promotions applied / not applied. Rounded to 2 decimals.hasExtraBed—truewhen the rate includes an extra bed (already folded into the totals).promotions/promotionInfo— the applied promotions as{ id, name }references (idisnullfor supplier-managed promotions, which are informational and already priced in), and the structured headline:promotionId, benefitkind(percent,amount,free_night,upgrade_room, ...), totaldiscount, and the upgrade room names when an upgrade applied.promotionInfoisnullwhen no promotion applied. For bed-bank suppliers (Koedia, Rakuten) the supplier has already applied its promotion in the rate it returns:totalPriceWithPromoequalstotalPriceWithoutPromo,idisnull, and the promotionnameis informational only — there is nothing to echo back when booking. To book the promoted price, echo a non-null promotionid(equal topromotionInfo.promotionIdfor the headline promotion) aspromotionIdin the booking room entry; without it the room books attotalPriceWithoutPromo.cancelConditions— the cancellation fee ladder; see Cancellation conditions.contractingConditions/checkInInstructions— supplier contract text and check-in notes.tags— refundability and extra-bed tags carry a stablecode(refundable,non_refundable,extra_bed) with alabelin the requestedlocale; board tags havecode: nulland the supplier's own wording.mealPlans— the structured board; see Meal plans. Prefer it over parsingtags.hotelFees— fees payable on site, not included in the totals.mandatorySupplements/optionalSupplements— supplement names; mandatory ones are already included in the price.
Meal plans (board)
mealPlans is the machine-readable board of a bed choice. The same information also appears as free text in tags (the label values are identical), but board tags wording varies by supplier and language — build your logic on mealPlans (and on tags[].code for refundability), and use tags[].label only for display.
Each entry:
{
"code": "half_board",
"label": "Demi-pension",
"startAt": "2026-11-12T00:00:00.000Z",
"endAt": "2026-11-14T00:00:00.000Z"
}code — the canonical board. One of exactly seven values:
code | Included meals |
|---|---|
room_only | No meals. |
breakfast | Breakfast (bed & breakfast). |
lunch | Lunch only. |
dinner | Dinner only. |
half_board | Breakfast plus one main meal (lunch or dinner). |
full_board | Breakfast, lunch and dinner. |
all_inclusive | All meals and (per the hotel's terms) drinks and extras. |
Why code can be null. Boards originate as supplier free text ("Bed & Breakfast", "Demi-pension", "Standard Rate with meals"). We classify that text into the seven codes: hotel-managed inventory carries a curated code set by the hotel's operator, and channel rates are classified from their label. When a label doesn't map to any standard board, code is null and label still carries the original text — so you can always display the board, even when you can't filter on it. Treat null as "board present but unclassified", never as room-only.
Why it is an array with startAt / endAt. Hotel-managed inventory contracts define the board per season. A stay that crosses two contract periods gets one entry per period, each with its validity window (ISO 8601 date-times) telling you which part of the stay it covers — e.g. half board until the 14th, breakfast after. Channel and bed-bank rates (Koedia, Rakuten, STAAH) always price one board for the whole stay: one entry, startAt/endAt both null. So the rules are:
- one entry,
nullwindows → the board covers the whole stay (the common case); - several entries → the board changes during the stay; the windows partition it;
startAt: null/endAt: nullon any entry → no restriction on that side.
An empty array means no board information, not room-only. Some rates carry no board at all (for example a channel rate plan whose name is just "Standard Rate"). If your product must always show a board, display nothing or "not specified" — do not assume room_only.
Integration guidance: filter and compare on code, display label, never parse labels yourself. If you need one board per rate for display, use the entry covering the check-in date, or list all windows.
The mealPlans on the availability search room previews follow exactly the same rules.
Cancellation conditions
cancelConditions is a ladder of fee windows, ordered by time. Each entry means: cancelling while startAt ≤ now < endAt costs deduction, expressed in the bed choice currency (never a percentage — supplier percentages are already converted to amounts). All timestamps are ISO 8601 in UTC.
"cancelConditions": [
{ "deduction": 128.00, "refundable": true, "startAt": "2026-10-29T00:00:00.000Z", "endAt": "2026-11-05T00:00:00.000Z" },
{ "deduction": 576.00, "refundable": false, "startAt": "2026-11-05T00:00:00.000Z", "endAt": "2026-11-12T00:00:00.000Z" }
]Read this as:
- Before the first window (here, before Oct 29): cancellation is free.
- Oct 29 – Nov 5: cancelling costs 128.00 EUR; the rest is refunded.
- Nov 5 – check-in: cancelling costs the full price —
refundable: falsemarks a window whose deduction is total.
To display "free cancellation until …", use the startAt of the first window. An empty array means the supplier returned no cancellation policy for this rate — treat the conditions as unknown and refer your user to contractingConditions, do not present the rate as freely cancellable.
Conditions are per bed choice: in a multi-room combination the last room of the branch carries the conditions for the whole combination.
Multi-room stays
When you request several rooms:
- Own-inventory rates (
Koob) return a flat list: pick one bed choice per requested room from the top-leveldata, and send one booking room entry per pick.compatibleRoomCompositionsis[]. - Bed-bank rates (
Koedia,STAAH) price the whole stay as one combination: the room for your second requested occupancy is nested inside the first room'scompatibleRoomCompositions, the third inside the second's, and so on. Walk one branch of the tree, picking one bed choice at each level, and send those bed choice IDs in request order. The last room of the branch carries the cancellation and contract conditions for the whole combination.
In both cases the booking request shape is the same: one rooms entry per requested room, each with its bedCompositionId and travelers. Continue to Hotel Booking.