Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Irmu Com Booking API
version: 1.0.0
description: 'Operations tagged Booking across 2 of this provider''s published API definitions: app.irmu.com.yaml, irmu-com-openapi.yaml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://app.irmu.com/api
description: Production
tags:
- name: Booking
description: Searching Booking.com and reading its property pages, through the same crawler.
paths:
/booking/search:
get:
tags:
- Booking
summary: Search Booking.com
operationId: bookingSearch
description: 'Searches Booking.com for places to stay and returns the properties on
the first page of results, one row per property card.
You name a destination and, optionally, a stay; the search url is built
from them. Results are always read from the English site, because the
recipe that reads them reads English labels.
Every field of a row is text as Booking prints it -- `price` is
`"€ 120"`, `reviewScore` is `"8.7"` -- and a field the card does not
show is absent from its row.
**30 credits, flat.** `premium`, `country` and `city` choose where the
crawl exits, not what it costs. A search that finds nothing is still
charged for: it is Booking''s answer, and an empty `hotels` with `status`
`done` is how it reads.
The key goes in the `Authorization` header, or in `?key=`.'
security:
- apiKeyHeader: []
- apiKeyQuery: []
parameters:
- name: destination
in: query
required: true
description: 'Where to stay, as you would type it into Booking''s search box -- a
city, a region, a landmark. Up to 200 characters.
'
schema:
type: string
maxLength: 200
example: Rome
- name: checkin
in: query
required: false
description: 'The first night, as `YYYY-MM-DD`, today or later. **Requires
`checkout`.**
Optional: without dates Booking still shows the page, it just prices
nothing.
'
schema:
type: string
format: date
example: '2026-11-11'
- name: checkout
in: query
required: false
description: The day you leave, as `YYYY-MM-DD`, after `checkin`. **Requires `checkin`.**
schema:
type: string
format: date
example: '2026-11-12'
- name: adults
in: query
required: false
description: How many adults are staying.
schema:
type: integer
minimum: 1
maximum: 30
default: 2
- name: rooms
in: query
required: false
description: How many rooms they need.
schema:
type: integer
minimum: 1
maximum: 30
default: 1
- name: currency
in: query
required: false
description: 'The currency to price in, as a three-letter ISO 4217 code. Left to
Booking when absent, which usually means the currency of the country
the crawl exits from.
'
schema:
type: string
minLength: 3
maxLength: 3
pattern: ^[A-Za-z]{3}$
example: EUR
- name: premium
in: query
required: false
description: 'Go through the premium proxy pool rather than the standard one. **On
by default**, because the standard pool is the one Booking turns away;
send `false` to use the standard pool. Free either way, unlike on
`/crawl` -- the price is flat.
`true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off`, in any case.
Anything else is refused rather than read as `false`.
'
schema:
type: string
default: 'true'
example: 'true'
- name: country
in: query
required: false
description: 'The country to appear to be browsing from, as a two-letter ISO 3166-1
alpha-2 code. Free. Booking prices and ranks by where you are, so this
can change the answer.
Required to use `city`.
'
schema:
type: string
minLength: 2
maxLength: 2
pattern: ^[A-Za-z]{2}$
example: it
- name: city
in: query
required: false
description: 'The city inside that country, as the pool spells it. **Requires
`country`.** Free here, where on `/crawl` it doubles the price.
'
schema:
type: string
pattern: ^[A-Za-z][A-Za-z0-9_-]{0,63}$
example: rome
- name: key
in: query
required: false
description: 'The API key, as an alternative to the `Authorization` header. Ignored
when a header is present.
'
schema:
type: string
responses:
'200':
description: The properties the search found.
content:
application/json:
schema:
$ref: '#/components/schemas/BookingSearchResult'
example:
url: https://www.booking.com/searchresults.en-gb.html?ss=Rome&checkin=2026-11-11&checkout=2026-11-12&group_adults=2&no_rooms=1&group_children=0
destination: Rome
checkin: '2026-11-11'
checkout: '2026-11-12'
adults: 2
rooms: 1
currency: null
hotels:
- name: Hotel Artemide
url: https://www.booking.com/hotel/it/artemide.en-gb.html
image: https://cf.bstatic.com/xdata/images/hotel/square240/1.jpg
price: € 212
priceInfo: 1 night, 2 adults
taxes: +€ 12 taxes and charges
address: Termini, Rome
distance: 1.2 km from centre
reviewScore: '9.1'
reviewWord: Wonderful
reviewCount: 3,214 reviews
stars: 4 out of 5 Property rating
credits_charged: 30
credits_remaining: 970
job_id: 0198a9b6-1f4c-7c3e-9a2b-5d8e6f0a1b23_0198a9b6-2a71-7f0d-8c44-9e1b3c5d7f02
status: done
position: null
debug: null
error: null
'202':
description: 'Paid for and still going. Collect it from `/crawl/{job}`, free, as a
slow crawl is collected. That endpoint answers the crawl envelope, so
what is lifted into `hotels` here arrives under `data` there.
'
content:
application/json:
schema:
$ref: '#/components/schemas/BookingSearchResult'
'401':
description: 'No key, an unknown key, or a caller that is not an organization.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
message: This endpoint is reached with an organization API key.
'402':
description: 'Not enough credits, or the daily limit is reached. Nothing is charged
and no attempt is recorded.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: 'A malformed request -- no destination, one date without the other, dates in the past, or a flag that is not a yes or a no.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
'500':
description: 'The crawl failed outright. A search that ran and found nothing is not
this: it is a `200` with an empty `hotels`.
**The credits are refunded**, so `credits_charged` is `0`. The attempt
is still recorded against `www.booking.com`.
'
content:
application/json:
schema:
$ref: '#/components/schemas/BookingSearchResult'
example:
url: https://www.booking.com/searchresults.en-gb.html?ss=Rome&group_adults=2&no_rooms=1&group_children=0
destination: Rome
checkin: null
checkout: null
adults: 2
rooms: 1
currency: null
hotels: []
credits_charged: 0
credits_remaining: 1000
job_id: 0198a9b6-1f4c-7c3e-9a2b-5d8e6f0a1b23_0198a9b6-2a71-7f0d-8c44-9e1b3c5d7f02
status: failed
position: null
debug: null
error: Navigation timeout of 60000 ms exceeded
'503':
description: 'The crawl could not be routed through a proxy pool. Nothing is
charged.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
message: The crawler cannot route this crawl right now. Nothing was charged.
servers:
- url: https://app.irmu.com/api
description: Production
/booking/hotel:
get:
tags:
- Booking
summary: Read a Booking.com property
operationId: bookingHotel
description: 'Reads one Booking.com property page into a single object, with the parts
Booking prints as blobs resolved into values: `price` and each room''s
prices as `{ amount, currency, original, discount }`, `rating` as
`{ score, word, count }`, `facilities` grouped as the page groups them.
Beside those come the lists the page carries as text -- points of
interest, review subscores, featured reviews, questions, sections.
Room prices only exist for a stay, so send `checkin` and `checkout` to
get them. The url''s own query string is ignored: the stay goes in this
endpoint''s parameters, and the page is always read in English.
**30 credits, flat.** A page that could not be read -- usually Booking''s
challenge page -- is not charged for.
The key goes in the `Authorization` header, or in `?key=`.'
security:
- apiKeyHeader: []
- apiKeyQuery: []
parameters:
- name: url
in: query
required: true
description: 'A Booking.com property page, `https://www.booking.com/hotel/<country>/<name>.html`,
in any language. Anything else is refused.
'
schema:
type: string
format: uri
maxLength: 2048
example: https://www.booking.com/hotel/it/relais-la-maison-de-luxe-b-b.html
- name: checkin
in: query
required: false
description: 'The first night, as `YYYY-MM-DD`, today or later. **Requires
`checkout`.**
Optional: without dates Booking still shows the page, it just prices
nothing.
'
schema:
type: string
format: date
example: '2026-11-11'
- name: checkout
in: query
required: false
description: The day you leave, as `YYYY-MM-DD`, after `checkin`. **Requires `checkin`.**
schema:
type: string
format: date
example: '2026-11-12'
- name: adults
in: query
required: false
description: How many adults are staying.
schema:
type: integer
minimum: 1
maximum: 30
default: 2
- name: rooms
in: query
required: false
description: How many rooms they need.
schema:
type: integer
minimum: 1
maximum: 30
default: 1
- name: currency
in: query
required: false
description: 'The currency to price in, as a three-letter ISO 4217 code. Left to
Booking when absent, which usually means the currency of the country
the crawl exits from.
'
schema:
type: string
minLength: 3
maxLength: 3
pattern: ^[A-Za-z]{3}$
example: EUR
- name: premium
in: query
required: false
description: 'Go through the premium proxy pool rather than the standard one. **On
by default**, because the standard pool is the one Booking turns away;
send `false` to use the standard pool. Free either way, unlike on
`/crawl` -- the price is flat.
`true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off`, in any case.
Anything else is refused rather than read as `false`.
'
schema:
type: string
default: 'true'
example: 'true'
- name: country
in: query
required: false
description: 'The country to appear to be browsing from, as a two-letter ISO 3166-1
alpha-2 code. Free. Booking prices and ranks by where you are, so this
can change the answer.
Required to use `city`.
'
schema:
type: string
minLength: 2
maxLength: 2
pattern: ^[A-Za-z]{2}$
example: it
- name: city
in: query
required: false
description: 'The city inside that country, as the pool spells it. **Requires
`country`.** Free here, where on `/crawl` it doubles the price.
'
schema:
type: string
pattern: ^[A-Za-z][A-Za-z0-9_-]{0,63}$
example: rome
- name: key
in: query
required: false
description: 'The API key, as an alternative to the `Authorization` header. Ignored
when a header is present.
'
schema:
type: string
responses:
'200':
description: The property.
content:
application/json:
schema:
$ref: '#/components/schemas/BookingHotelResult'
example:
url: https://www.booking.com/hotel/it/relais-la-maison-de-luxe-b-b.en-gb.html?checkin=2026-11-11&checkout=2026-11-12&group_adults=2&no_rooms=1&group_children=0
checkin: '2026-11-11'
checkout: '2026-11-12'
adults: 2
rooms: 1
currency: null
hotel:
name: Relais La Maison de Luxe B&B
address:
'@type': PostalAddress
streetAddress: Via Nazionale 10
addressLocality: Rome
addressCountry: IT
description: Set in Rome, 600 metres from Termini...
rating:
score: 8.7
word: Fabulous
count: 426
price:
amount: 112
currency: EUR
original: 140
discount: 28
rooms:
- name: Double Room
prices:
- amount: 112
currency: EUR
original: 140
discount: 28
facilities:
- group: Internet
items:
- Free WiFi
images:
- https://cf.bstatic.com/xdata/images/hotel/max1024x768/1.jpg
pointsOfInterest:
- Termini 600 m
reviewSubscores:
- Staff 9.2
reviews:
- Great location and very clean.
questions:
- Is breakfast included?
sections:
- Property surroundings...
url: https://www.booking.com/hotel/it/relais-la-maison-de-luxe-b-b.en-gb.html?checkin=2026-11-11&checkout=2026-11-12&group_adults=2&no_rooms=1&group_children=0
credits_charged: 30
credits_remaining: 970
job_id: 0198a9b6-1f4c-7c3e-9a2b-5d8e6f0a1b23_0198a9b6-2a71-7f0d-8c44-9e1b3c5d7f02
status: done
position: null
debug: null
error: null
'202':
description: 'Paid for and still going. Collect it from `/crawl/{job}`, free, as a
slow crawl is collected. That endpoint answers the crawl envelope, so
what is lifted into `hotel` here arrives under `data` there.
'
content:
application/json:
schema:
$ref: '#/components/schemas/BookingHotelResult'
'401':
description: 'No key, an unknown key, or a caller that is not an organization.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
message: This endpoint is reached with an organization API key.
'402':
description: 'Not enough credits, or the daily limit is reached. Nothing is charged
and no attempt is recorded.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: 'A malformed request -- no url, a url that is not a Booking.com property page, one date without the other, dates in the past, or a flag that is not a yes or a no.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
'500':
description: 'Nothing came back: the crawl failed outright, or every block ran and
found nothing -- Booking''s challenge page, or a layout the recipe no
longer matches.
**The credits are refunded either way**, so `credits_charged` is `0`.
The attempt is still recorded against `www.booking.com`.
'
content:
application/json:
schema:
$ref: '#/components/schemas/BookingHotelResult'
example:
url: https://www.booking.com/hotel/it/relais-la-maison-de-luxe-b-b.en-gb.html?group_adults=2&no_rooms=1&group_children=0
checkin: null
checkout: null
adults: 2
rooms: 1
currency: null
hotel: null
credits_charged: 0
credits_remaining: 1000
job_id: 0198a9b6-1f4c-7c3e-9a2b-5d8e6f0a1b23_0198a9b6-2a71-7f0d-8c44-9e1b3c5d7f02
status: done
position: null
debug: null
error: The crawler could not read that property page. Nothing was charged.
'503':
description: 'The crawl could not be routed through a proxy pool. Nothing is
charged.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
message: The crawler cannot route this crawl right now. Nothing was charged.
servers:
- url: https://app.irmu.com/api
description: Production
components:
schemas:
BookingSearchResult:
type: object
description: 'What `/booking/search` answers. `hotels` is empty while the search is
still running, when it failed and when it found nothing; `status` tells
those apart.
'
required:
- destination
- hotels
- url
- checkin
- checkout
- adults
- rooms
- currency
- credits_charged
- credits_remaining
- job_id
- status
- position
- error
- debug
properties:
url:
type: string
description: The Booking.com page that was read, as built from the request.
checkin:
type:
- string
- 'null'
format: date
checkout:
type:
- string
- 'null'
format: date
adults:
type: integer
rooms:
type: integer
currency:
type:
- string
- 'null'
destination:
type: string
hotels:
type: array
description: 'One row per property card, each field as text the way Booking prints
it. A field the card does not show is absent.
'
items:
type: object
properties:
name:
type: string
url:
type: string
image:
type: string
price:
type: string
priceInfo:
type: string
taxes:
type: string
address:
type: string
distance:
type: string
reviewScore:
type: string
reviewWord:
type: string
reviewCount:
type: string
stars:
type: string
credits_charged:
type: integer
description: 30, or 0 when nothing came back.
credits_remaining:
type: integer
job_id:
type: string
description: What this ran under. Collect a `202` from `/crawl/{job}` with it.
status:
type: string
enum:
- done
- queued
- running
- failed
position:
type:
- integer
- 'null'
description: Where it sits in the crawler's queue while `queued`.
error:
type:
- string
- 'null'
description: Why nothing came back. Null otherwise.
debug:
description: 'Where the crawl had got to, when the crawler stopped waiting and
looked. Only sent when the request carries `debuga=1` -- a screenshot
and the page source run to a megabyte -- and null otherwise.
'
oneOf:
- $ref: '#/components/schemas/CrawlSnapshot'
- type: 'null'
CrawlSnapshot:
type: object
description: 'Where a crawl had got to, for one that is still running or failed. Only
sent when the request carries `debuga=1`.
The crawler stops waiting long before it stops working, and when it stops
waiting it looks at the tab: the url the browser is on, a picture of what
is on screen, and the page source as it stands. That is what this is --
a diagnosis for a request that timed out, not a result. Nothing is
charged for it.
Null on anything that is not still running. A finished or failed crawl has
no tab left to look at, and a crawl still in the queue has not opened one
yet.
Two things to know when reading one. The image is the **viewport**, not
the whole page, so it is not the picture `/screenshot` sells -- it is a
thumbnail at quality 60, meant to be looked at rather than kept. And
either artefact may be missing: they are captured independently, because a
navigation can destroy one without touching the other, so half a snapshot
is ordinary. The missing half reads as null rather than going absent.
A blank image beside a near-empty `html` is not a broken capture -- it
means the crawl stalled *inside* a navigation, with the old document
already replaced. That is itself the answer.
'
required:
- url
- screenshot
- html
properties:
url:
type:
- string
- 'null'
format: uri
description: 'The url the tab was on, which a redirect may have moved away from the
one you asked for.
'
example: https://shop.example/p/1
screenshot:
type:
- string
- 'null'
format: byte
description: 'A base64 JPEG of the viewport, with no `data:` prefix -- decode it as
it stands. Null when the capture raced a navigation and lost.
'
example: /9j/4AAQSkZJRgABAQ...
html:
type:
- string
- 'null'
description: 'The page source at that moment, half-loaded and all. Null when the
capture raced a navigation and lost.
'
example: <!doctype html>...
ValidationError:
type: object
required:
- message
- errors
properties:
message:
type: string
errors:
type: object
additionalProperties:
type: array
items:
type: string
BookingHotelResult:
type: object
description: 'What `/booking/hotel` answers. `hotel` is null while the page is still
being read and when it could not be; `status` and `error` tell those
apart.
'
required:
- hotel
- url
- checkin
- checkout
- adults
- rooms
- currency
- credits_charged
- credits_remaining
- job_id
- status
- position
- error
- debug
properties:
url:
type: string
description: The Booking.com page that was read, as built from the request.
checkin:
type:
- string
- 'null'
format: date
checkout:
type:
- string
- 'null'
format: date
adults:
type: integer
rooms:
type: integer
currency:
type:
- string
- 'null'
hotel:
oneOf:
- type: object
additionalProperties: true
description: 'The property. `name`, `address`, `description`, `rating`, `price`,
`rooms`, `facilities` and `images` are resolved by the crawler;
`pointsOfInterest`, `reviewSubscores`, `reviews`, `questions` and
`sections` are lists of text. `price` is the cheapest of the room
prices, and absent without a stay.
'
- type: 'null'
credits_charged:
type: integer
description: 30, or 0 when nothing came back.
credits_remaining:
type: integer
job_id:
type: string
description: What this ran under. Collect a `202` from `/crawl/{job}` with it.
status:
type: string
enum:
- done
- queued
- running
- failed
position:
type:
- integer
- 'null'
description: Where it sits in the crawler's queue while `queued`.
error:
type:
- string
- 'null'
description: Why nothing came back. Null otherwise.
debug:
description: 'Where the crawl had got to, when the crawler stopped waiting and
looked. Only sent when the request carries `debuga=1` -- a screenshot
and the page source run to a megabyte -- and null otherwise.
'
oneOf:
- $ref: '#/components/schemas/CrawlSnapshot'
- type: 'null'
Error:
type: object
required:
- message
properties:
message:
type: string
securitySchemes:
apiKeyHeader:
type: http
scheme: bearer
description: '`Authorization: Bearer <key>`. The usual way in.
'
apiKeyQuery:
type: apiKey
in: query
name: key
description: '`?key=<key>`. Accepted on the crawl route only, and only when no
`Authorization` header was sent.
'
x-refined-from:
- app.irmu.com.yaml
- irmu-com-openapi.yaml