Accommodation API
Hotel Availability and Prices From Booking.com, Expedia and Ostrovok
Real-time hotel availability and prices from Booking.com, Expedia and Ostrovok: price up to 10,000 hotels by ID in one call, or search a destination.
Accommodation API
Live hotel prices and availability, for any stay and any traveler#
Price the hotels you track by their IDs, up to 10,000 in one call, search every hotel of a destination, or build a price calendar for one hotel across up to 500 date ranges. Prices are read live from Booking.com, Expedia and Ostrovok for the exact stay you describe: dates, rooms, adults, children's ages, the traveler's country, the currency and the device.
At a glance
- Up to 10,000 hotel IDs priced in one call
- Price calendars: up to 500 date ranges for one hotel
- Any room setup: rooms, adults, children aged 0–18
- Quote from a traveler's country, in a chosen currency, on desktop or mobile
- Prices with and without taxes and fees, plus a ready booking link
Why teams choose Zembra for this#
Performance
Synchronous: thousands of hotels are priced in parallel and returned in the same response.
Scale
10,000 hotel IDs per call, every hotel of a destination in one search, 500 date ranges per hotel.
Precision
Prices for the exact stay: check-in and check-out dates, each room's adults and children's ages, quoted as a traveler from a given country.
Transparent prices
Booking.com prices come back both as the displayed price and as the total including excluded taxes and fees. When a hotel shows several offers, the cheapest is kept.
Ready to act
Every result includes a booking link pre-filled with the dates, rooms and guests, the hotel's photo, location and review score.
Fair pricing
Priced per block of 1,000 hotels or 25 date ranges. Requests that fail, or that the platform can't serve, cost nothing.
How it works
From request to data#
-
1
Pick how you price
Send the hotel IDs you track, a destination to search, or one hotel with a list of date ranges.
-
2
Describe the stay
Dates,
roomswith adults and children's ages, and optionally the traveler's country, the currency andforMobile. -
3
Zembra queries the platform
Requests go out in parallel, from the traveler's country when you set one. For a destination search, Zembra pages through every result the platform announces.
-
4
Get standardized prices
Each hotel comes back with its price, availability, review score, photo, address and booking link, in the same format for every platform.
Pricing
Pay per use, no commitment#
Pay per hotels priced, per date ranges or per search; requests the platform can't serve cost nothing. Credits get cheaper as your volume grows: how credit pricing works.
What a call costs#
| Call | Credits | Starts at |
|---|---|---|
| Price by ID, per started block of 1,000 hotels 1 to 1,000 IDs cost 240; 1,001 to 2,000 cost 480. |
240 | $0.048 |
| Price calendar, per started block of 25 date ranges 1 to 25 ranges cost 100; up to 500 ranges cost 2,000. |
100 | $0.02 |
| Destination search, first platform Per search. |
125 | $0.025 |
| Destination search, each extra platform Per extra platform available at the time of the search. |
100 | $0.02 |
No result or unknown hotel (404)Destination search shown: a 404 is charged like a successful call and not refunded, for every Accommodation endpoint. |
125 | $0.025 |
| Errors and outages Invalid parameters, platforms not enabled, rate limits and platform errors ( 503) are not charged. |
0 | Free |
Examples#
| Project | Credits | Price |
|---|---|---|
| Price 5,000 hotels once | 1,200 | Free on sign-up |
| Price 1,000 hotels daily for 30 days | 7,200 | Free on sign-up |
| One hotel, 365 one-night stays | 1,500 | Free on sign-up |
| One destination search | 125 | Free on sign-up |
Prices use the credit tiers of the cost estimator, where you can estimate your own project. The first 10,000 credits are free.
API reference
Requests and parameters#
Base URL https://api.zembra.io, authenticated with Authorization: Bearer <token>. A sandbox at https://sandbox.zembra.io returns realistic dummy data without using credits.
curl --location 'https://api.zembra.io/accommodations/lookup?searchString=Rome%2C%20IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009' \
--header 'Authorization: Bearer {token}'
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.zembra.io/accommodations/lookup?searchString=Rome, IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009");
request.Headers.Add("Authorization", "Bearer {token}");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://api.zembra.io/accommodations/lookup?searchString=Rome%2C%20IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009"
method := "GET"
client := &http.Client {
}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Authorization", "Bearer {token}")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
.url("https://api.zembra.io/accommodations/lookup?searchString=Rome, IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009")
.method("GET", body)
.addHeader("Authorization", "Bearer {token}")
.build();
Response response = client.newCall(request).execute();
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer {token}");
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
fetch("https://api.zembra.io/accommodations/lookup?searchString=Rome, IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
<?php
$client = new Client();
$headers = [
'Authorization' => 'Bearer {token}'
];
$request = new Request('GET', 'https://api.zembra.io/accommodations/lookup?searchString=Rome, IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009', $headers);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
import requests
url = "https://api.zembra.io/accommodations/lookup?searchString=Rome, IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009"
payload = {}
headers = {
'Authorization': 'Bearer {token}'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
require "uri"
require "net/http"
url = URI("https://api.zembra.io/accommodations/lookup?searchString=Rome, IT&targetCountry=US¤cy=USD&checkin=2023-01-01&checkout=2023-01-04&rooms=&latitude=25.77178&longitude=-80.19009")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = "Bearer {token}"
response = https.request(request)
puts response.read_body
Response
{
"message": "List of properties for Miami",
"data": {
"properties": {
"booking": {
"network": "booking",
"id": 123456789,
"name": "The Grand Hotel",
"district": "Larger San Diego",
"distanceFromDestination": "1.3 miles from downtown",
"address": {
"street": "123 Main St.",
"city": "San Diego",
"zipCode": "92078-4605",
"country": "United States",
"countryCode": "us",
"latitude": 25.77178,
"longitude": -80.19009
},
"price": {
"total": 1562.78,
"withoutFees": 1344.99,
"currency": "USD"
},
"reviews": {
"score": 9.2,
"count": 465
},
"link": "",
"photo": ""
}
},
"counts": {
"returned": {
"booking": 501
},
"displayable": {
"booking": 2786
},
"total": 501
}
}
}
{
"message": "Invalid parameter limit"
}
{
"message": "Invalid bearer token"
}
{
"message": "Cannot process request: balance too low!"
}
{
"message": "Requested network [Expedia] is not enabled for your account"
}
{
"message": "Hotels not found!"
}
POST /accommodations/fetch/{network}: Price the hotels you track, by ID#
network | path, required: booking or expediaThe platform the IDs belong to. One platform per call. |
|---|---|
propertyIds | array of numbers, required, 1–10,000 The hotels' IDs on the platform. Duplicates are ignored. |
checkin | date YYYY-MM-DD, requiredUp to 500 days from today. |
checkout | date YYYY-MM-DD, requiredAfter check-in, for a stay shorter than 90 nights. |
rooms | array, required One object per room: adults (at least 1) and optional children, a list of ages from 0 to 18. |
targetCountry | ISO 3166-1 alpha-2, optional Quote the price as a traveler from this country. |
currency | ISO 4217, optional The currency of the prices. Default USD. |
forMobile | boolean, optional Quote the price shown on mobile. |
POST /accommodations/fetch/{network}/{propertyId}: Price calendar for one hotel#
network | path, required: bookingThe platform. |
|---|---|
propertyId | path, required, number The hotel's ID on the platform. |
dateRanges | array, required, 1–500 Each entry has a checkin and a checkout, with the same rules as above. Duplicates are ignored. |
rooms, targetCountry, currency, forMobile | as aboverooms is required. |
GET /accommodations/lookup: Search every hotel of a destination#
networks[] | array, required The platforms to search: booking. |
|---|---|
searchString | string, required The destination: a city, region, district, landmark or hotel name. |
latitude, longitude | numbers, optional, sent together When the destination's name is ambiguous, the suggestion closest to these coordinates is used. |
checkin, checkout | dates YYYY-MM-DD, requiredThe stay. |
rooms, targetCountry, currency, forMobile | as aboverooms is required. |
Status codes#
200 | Prices returned |
|---|---|
400 | Invalid parameter: dates, rooms, country, currency or platform |
401 | Missing or invalid token |
402 | Balance too low |
403 | Platform not enabled on your account |
404 | No hotel for this search, or unknown hotel ID (charged) |
429 | Rate limit reached |
503 | Platform temporarily unavailable, not charged |
Data
What you get back#
A hotel#
id, name | number or string, string The hotel's ID on the platform and its name. |
|---|---|
available | boolean Whether the hotel can be booked for the stay (search, Expedia, and each date range). |
price.total | number The price of the whole stay. On Booking.com it includes the taxes and fees shown separately. |
price.withoutFees | number Booking.com: the displayed price, without those taxes and fees. |
price.currency | string The currency of the prices. |
address | objectstreet, city, zipCode, country, countryCode, latitude, longitude (Booking.com). |
district, distanceFromDestination | string The area and the distance to the searched destination, as the platform shows them. |
reviews | objectscore and count: the hotel's review score and number of reviews (search and Expedia). |
link | URL The hotel's page, pre-filled with the dates, rooms and guests. |
photo | URL The hotel's main photo. |
dateRanges | array Price calendar only: for each range, dates, available, price and link. |
Example response
{
"status": "SUCCESS",
"message": "List of properties for Miami",
"data": {
"properties": {
"booking": [
{
"id": 123456,
"name": "The Grand Hotel Miami",
"available": true,
"district": "Downtown Miami, Miami",
"distanceFromDestination": "0.6 miles from downtown",
"address": {
"street": "123 Biscayne Blvd",
"city": "Miami",
"zipCode": "33132",
"country": "United States",
"countryCode": "us",
"latitude": 25.77178,
"longitude": -80.19009
},
"reviews": {
"score": 8.7,
"count": 1465
},
"link": "https://www.booking.com/hotel/us/the-grand-miami.html?dest_id=123456&selected_currency=USD&aid=304142&checkin=2026-12-10&checkout=2026-12-13&dest_type=hotel&group_adults=2&req_adults=2&no_rooms=1&group_children=0&req_children=0&sr_order=popularity&sb_price_type=total&type=total#hprt-table",
"photo": "https://cf.bstatic.com/xdata/images/hotel/max1024x768/123.jpg",
"price": {
"total": 1562.78,
"withoutFees": 1344.99,
"currency": "USD"
}
}
]
},
"counts": {
"returned": {
"booking": 512
},
"displayable": {
"booking": 512
},
"total": 512
}
},
"cost": 125,
"balance": 98875
}
Three ways to price#
By ID (POST /accommodations/fetch/{network}) for the hotels you track, on Booking.com or Expedia. Price calendar (POST /accommodations/fetch/{network}/{propertyId}) for one Booking.com or Ostrovok hotel across many date ranges. Destination search (GET /accommodations/lookup) for every hotel of a city, region, district or landmark on Booking.com, or of a destination on Ostrovok.
How destination search works#
The destination is resolved with the platform's own suggestions; add latitude and longitude to pick the right one when the name is ambiguous. Zembra then pages through every result the platform announces. counts.displayable is what the platform announced, counts.returned what came back.
Sold out and unavailable hotels#
When pricing by ID on Booking.com, hotels with no price for the stay are left out of the response. On Expedia they come back with available: false and no price. Sold out, closed, minimum-stay rules or too few rooms for the guests are the usual reasons.
Traveler country and currency#
targetCountry sends the request from that country, so you see the prices its travelers see. On Booking.com an unsupported currency falls back to the hotel's local currency. On Expedia, the country selects the matching Expedia site, and a currency that site doesn't offer falls back to its default currency.
Desktop or mobile prices#
Platforms sometimes show different prices on mobile. Set forMobile to quote what a mobile traveler sees.
Taxes and fees#
On Booking.com, price.total adds the taxes and fees the platform shows separately to the displayed price, which is in price.withoutFees.
Performance
Speed and limits#
Hotels per call #
1 to 10,000 IDs, all from the same platform.
Date ranges per call #
1 to 500, for one hotel.
Dates #
Check-in up to 500 days from today; check-out after check-in and less than 90 nights later.
Guests #
At least one room, each with at least 1 adult; children aged 0 to 18.
Rate limit #
100 requests per minute by default, per account and per service; 429 beyond that.
Sandbox #
The Accommodation API is not available in the sandbox.
Supported platforms
3 platforms, one format#
Use cases
What teams build with it#
Rate shopping
Price your hotel and your competitors every day, for the same stay and the same guests.
Rate parity monitoring
Check that a hotel shows the same price on Booking.com and Expedia, in every country and on mobile.
Revenue management
Build price calendars of up to 500 date ranges to see how rates move across dates.
Market studies
Pull every hotel of a destination with its price, score and location to map supply and pricing.
FAQ
Common questions#
How many hotels can I price in one call?
Up to 10,000 hotel IDs per call, all from the same platform. #
Which platforms are supported?
Booking.com and Expedia for pricing by ID; Booking.com and Ostrovok for price calendars and destination search. #
How far ahead can I search?
Check-in up to 500 days ahead, for stays shorter than 90 nights. #
Can I see prices from another country?
Yes. Set targetCountry to quote as a traveler from that country, and currency for the currency. On Expedia, only the currencies of the matching Expedia site are available. #
Can I price one hotel across many dates?
Yes. Send up to 500 date ranges for one Booking.com or Ostrovok hotel in one call, at 100 credits per 25 ranges. #
Do prices include taxes and fees?
On Booking.com you get both: price.total with the taxes and fees shown separately, and price.withoutFees without them. #
What happens to sold-out hotels?
On Booking.com they're left out of the response; on Expedia they come back with available: false. #
Am I charged if the platform fails?
No. Platform errors return 503 and cost nothing; retry after a few minutes. #
Start today for free
Create an account and get 10,000 free credits to try the Accommodation API and every other Zembra service. No commitment, no minimum.