Skip to content

Hotel Search ​

Hotel integration takes four calls, all on the v3 API:

  1. Resolve a destination or hotel — GET /api/v3/search or the location catalogs
  2. Search availability — GET /api/v3/hotels/availability
  3. Get bookable rooms — GET /api/v3/hotels/{id}/rooms/availability
  4. Book — POST /api/v3/bookings

A static hotel catalog is also available to sync hotel metadata (names, images, locations) without running an availability search.

If you own your hotels rather than only selling them, Manage Hotels covers creating and editing them.

Legacy v1/v2 endpoints

The v1/v2 hotel endpoints stay live for existing integrations and are documented in the API Reference. New integrations must use v3 — it is the only version receiving new features.

v3 conventions ​

These rules hold on every v3 endpoint, so your client code needs no special cases:

  • Authentication: send your API key in the X-API-Key header on every request. See Authentication.

  • IDs: everywhere an ID is accepted (path parameters, filters, booking payloads), both the raw numeric form (4242) and the encoded form returned by the API are valid, interchangeably.

  • Fixed response schema: every documented response field is always present. An empty collection is [] (never omitted, never null); an absent scalar is null (never omitted). You can parse one stable shape without existence checks.

  • Array query parameters: use bracket notation, either indexed (hotels[0]=1&hotels[1]=2) or plain (hotels[]=1&hotels[]=2). Nested objects use deep-object encoding: rooms[0][adults]=2.

  • Pagination: list endpoints take page (1-based) and perPage (default 20, max 1000) and return a meta object:

    json
    { "currentPage": 1, "lastPage": 8, "perPage": 20, "total": 154 }
  • Errors: error responses are JSON with name, message, a machine-readable code, optional details, and a requestId. Quote the requestId when contacting support — it lets us find your exact request in our logs.

    json
    {
      "name": "BadRequestError",
      "message": "Invalid countries id 'Morocco': expected a numeric id or an encoded id",
      "code": "INVALID_LOCATION_ID",
      "details": { "param": "countries", "value": "Morocco" },
      "requestId": "1f6b9c2e-..."
    }
  • Rate limits: every response carries X-RateLimit-Limit and X-RateLimit-Remaining headers. When the limit is exceeded you get 429 (code RATE_LIMIT_EXCEEDED) with a Retry-After header in seconds. Search endpoints have their own, stricter bucket. Back off and retry after the indicated delay.

Resolve a destination ​

Fuzzy search across hotels, locations, experiences and trip templates. Use it to turn free text ("bali beach") into the IDs the availability and catalog filters accept.

Endpoint: GET /api/v3/search

Query parameters:

ParameterRequiredDescription
queryyesFree-text search terms, minimum 2 characters.
typesyesResult types to search: hotel, experience, activity, transfer, program, city, region, country, trip_template. Repeat the parameter (types=hotel&types=city) or pass a comma-separated value. Unknown values return 400 with code INVALID_FILTER_VALUE.
localenoLocale to boost matches in (e.g. fr); falls back to en matches.
countriesnoCountry IDs to filter by; repeat the parameter (countries=1&countries=2).
organizationIdnoOnly return hotels, experiences and trip templates owned by this organization; locations are unaffected.
standaloneExperiencesOnlynoOnly return experiences bookable on their own. Default false.
searchDescriptionsnoAlso match experience descriptions, highlights and day programs. Default true.
page, perPagenoPagination.
bash
curl "https://api.koob.tech/api/v3/search?query=bali%20beach&types=hotel,city,region,country" \
  -H "X-API-Key: your-api-key-here"

Response:

json
{
  "data": [
    {
      "id": "4242",
      "type": "hotel",
      "title": "Bali Beach Resort",
      "description": "Beachfront resort in Sanur",
      "score": 12.4,
      "alpha2": "ID"
    },
    {
      "id": "318",
      "type": "region",
      "title": "Bali",
      "score": 9.1,
      "alpha2": "ID"
    }
  ],
  "meta": { "currentPage": 1, "lastPage": 1, "perPage": 20, "total": 2 }
}

Results are ordered by relevance (score). Keep the id: a hotel ID goes into the hotels filter, and a city / region / country ID into the matching cities / regions / countries filter.

Location catalogs ​

When you need full location lists rather than fuzzy search (for example to build a destination picker), three flat catalogs are available:

  • GET /api/v3/countries
  • GET /api/v3/regions — optional countries filter (repeat the parameter)
  • GET /api/v3/cities — optional countries and regions filters

All three take search (accent-insensitive name filter), locale (localized country names, default en), scope=organization (only locations where your organization operates; for a group, where any of its organizations operates), sort (name or id, - prefix for descending; default name) and pagination. The IDs they return are the same location IDs the hotel and experience filters accept.

bash
curl "https://api.koob.tech/api/v3/cities?countries=62&search=ubud" \
  -H "X-API-Key: your-api-key-here"

Hotel catalog ​

Static hotel metadata — no prices, no availability. Use it to sync or display hotel content; use the availability search for prices.

Endpoints:

  • GET /api/v3/hotels — paginated list
  • GET /api/v3/hotels/{id} — one hotel

List query parameters (all optional, combined with AND):

ParameterDescription
searchFree-text hotel name filter (fuzzy, accent-insensitive).
countries, regions, citiesLocation IDs (from GET /api/v3/search or the location catalogs).
hotelsHotel IDs.
starsExact star-rating filter (1-5).
kinds, stylesHotel kind / style IDs.
sustainabilitySustainability level (exact match): zero, low, medium, strong.
localeLocale for localized labels (country, styles, kinds), e.g. fr; defaults to en. Also accepted on GET /api/v3/hotels/{id}.
sortname or stars, - prefix = descending (e.g. sort=-stars). Omitted = relevance order, search matches first.
page, perPagePagination.
bash
curl "https://api.koob.tech/api/v3/hotels?cities[0]=318&stars=5" \
  -H "X-API-Key: your-api-key-here"

Response (one element of data):

json
{
  "id": "4242",
  "name": "Bali Beach Resort",
  "address": "Jalan Pantai, Sanur",
  "city": { "id": "3411", "name": "Sanur" },
  "country": { "id": "62", "name": "Indonesia" },
  "lat": "-8.6785",
  "lon": "115.2621",
  "stars": 5,
  "styles": [{ "id": "8", "name": "Beachfront" }],
  "kinds": [{ "id": "2", "name": "Resort" }],
  "currency": "USD",
  "images": [
    {
      "id": "911",
      "url": "https://cdn.koob.tech/hotels/4242/main.jpg",
      "name": "Facade"
    }
  ],
  "giataId": "123456",
  "koediaId": "KD-99812",
  "rakutenId": null,
  "organizationId": "456",
  "organization": { "id": "456", "name": "Bali Horizons DMC" }
}

Every key is always present: a value the hotel doesn't have is null (empty collections are []). city, country, styles and kinds are nested references — their id values work directly as cities/countries/styles/kinds filter values. Each image's id is the attachment ID the image upload endpoints accept, so you can update a hotel's images without losing the existing ones. giataId, koediaId and rakutenId are external identifiers you can use to map the hotel to your own or third-party content databases. organization names the owner: a group's list holds every child's hotels, and this field tells them apart.

Search hotels with live prices for a stay. Returns one page of hotels, each with a headline price and a lightweight room list. Full bookable rooms (bed choices, cancellation conditions) live on the rooms availability endpoint.

Endpoint: GET /api/v3/hotels/availability

Query parameters:

ParameterRequiredDescription
searchone destination criterion requiredFree-text destination (hotel, city, region or country name), resolved server-side.
cities, regions, countries↑Location IDs (from GET /api/v3/search with types=city,region,country, or the location catalogs).
hotels↑Hotel IDs.
checkInyesCheck-in date, YYYY-MM-DD.
checkOutyesCheck-out date, YYYY-MM-DD; must be strictly after checkIn.
roomsyesRoom occupancies, deep-object encoded: rooms[0][adults]=2&rooms[0][childrenBirthdates][0]=2019-05-01. One entry per room; for N identical rooms repeat the entry N times. Children are declared by birthdate (YYYY-MM-DD) because suppliers price by age, computed server-side. Optional firstAdultNationality (ISO 3166-1 alpha-2) — some suppliers price by nationality.
starsnoExact star-rating filter (1-5).
kinds, stylesnoHotel kind / style IDs.
sustainabilitynoSustainability level (exact match): zero, low, medium, strong.
priceMin, priceMaxnoBounds on the headline price (inclusive); use either or both.
suppliersnoRestrict to these suppliers (Koob, Koedia, Rakuten, STAAH; case-insensitive). Omitted = all suppliers.
tripIdnoTrip ID; derives the pricing organization and currency server-side.
replaceBookingIdnoBooking being replaced; its allotment is released for this search.
localenoContent language (ISO 639-1, e.g. fr) for room and board texts; unknown or omitted = en. Own-inventory texts fall back to en; bed-bank suppliers are queried in that language.
sortnoComma-separated sort keys among price, promotionsCount, instantAvailability; - prefix = descending (e.g. sort=-price,promotionsCount). Omitted = instantly-bookable hotels first. Any other key returns 400 with code INVALID_SORT.
page, perPagenoPagination.

The response currency is derived from your organization — it is not a request parameter.

Passing anything other than an ID (for example a country name) in cities / regions / countries returns 400 with code INVALID_LOCATION_ID; the same in hotels / kinds / styles returns 400 with code INVALID_FILTER_VALUE.

bash
curl "https://api.koob.tech/api/v3/hotels/availability?checkIn=2026-11-12&checkOut=2026-11-16&cities[0]=318&rooms[0][adults]=2&rooms[0][childrenBirthdates][0]=2019-05-01" \
  -H "X-API-Key: your-api-key-here"

Response (one element of data):

json
{
  "id": "4242",
  "name": "Bali Beach Resort",
  "price": 640.0,
  "currency": "EUR",
  "promotions": [
    {
      "id": "promo-77",
      "name": "Early Bird",
      "numberOfNights": 4,
      "minNumberOfRooms": 1
    }
  ],
  "instantAvailability": true,
  "minimumStay": 1,
  "rooms": [
    {
      "id": "812",
      "name": "Deluxe Garden View",
      "description": "32m² room with balcony",
      "price": 640.0,
      "minimumStay": 1,
      "tags": ["Breakfast included", "Refundable"],
      "mealPlans": [
        {
          "code": "breakfast",
          "label": "Breakfast included",
          "startAt": null,
          "endAt": null
        }
      ],
      "instantAvailability": true,
      "supplier": {
        "name": "Koob",
        "code": "Koob",
        "flux": null
      }
    }
  ]
}

Field notes:

  • price is the headline (lowest) total price for the whole stay and party; null when unpriced. All prices are rounded to 2 decimals.
  • instantAvailability: true means instantly bookable; false means on-request (the booking needs supplier confirmation).
  • rooms is a lightweight preview. To book, call the rooms availability endpoint — it returns the bookable bed choices and the booking handshake.
  • mealPlans is the structured board (room_only, breakfast, lunch, dinner, half_board, full_board, all_inclusive). Prefer it over parsing tags; the full semantics — nullable code, validity windows, empty-array meaning — are on Meal plans.
  • supplier identifies the sales channel. name/code is the channel (Koob, Koedia, Rakuten, STAAH); flux identifies the bed bank, chain or inventory behind a Koedia rate (with its own name, code, logo) and is null for suppliers that sell direct.