Airbnb API v1
Airbnb listings, prices, guest reviews and availability as JSON or Markdown, from https://api.fetchlayer.dev/airbnb. Reviews say how complete they are in the response itself, prices arrive as structured objects rather than display strings, availability comes back night by night with the stay lengths each night allows, and dates are optional on every route that takes them.
Reviews tell you how complete they are
This is the reason to come here for Airbnb reviews. Ask for ten and you get ten — and you get told there are fifty-two, in the same response, with no second call to find out:
"totalReviewCount": 52,
"reviewCount": 10,
"hasMore": true,
"notes": [
"These are 10 of the 52 reviews the source reports for this listing.",
"More reviews are available. Continue with nextCursor."
] Pull the whole set and the same fields say so instead — one note, "These are 52 of the 52 reviews the source reports for this listing.", with hasMore: false and nextCursor: null. Both responses were measured against production on 17 September 2026 for listing 1695539435866678513.
| What you see | What it means | What to do |
|---|---|---|
| reviewCount === totalReviewCount | You have every review | Aggregate freely |
| reviewCount < totalReviewCount | This is a slice of a larger set | Continue with nextCursor, or say your figure is a sample |
| hasMore === false | Nothing further to fetch | Stop paging |
Why it matters: an average rating over ten of fifty-two reviews and an average over all fifty-two are different numbers, and everywhere else in this category they look identical on the wire. Here the response draws the distinction for you.
Each review keeps the text in the language the guest wrote it, with a language code and the source's own translatedText where one is published — the original is never replaced by a machine translation. Across 100 reviews measured on one Lisbon listing, 86 were English and the rest spanned German, Portuguese, Korean, French, Japanese, Icelandic, Dutch, Norwegian and Spanish; 14 carried a published translation and 2 carried a host reply. The response also returns topics, the source's own tally of what reviewers wrote about, so you get the shape of 465 reviews from a page of ten.
Per-category ratings (accuracy, cleanliness, check-in, location, communication, value) are published for the listing, on /listing-detail as listing.rating.categories. An individual review carries a single overall rating, not a breakdown.
Dates are optional — and the two routes behave differently on purpose
This asymmetry is deliberate, and it reads as a bug unless someone tells you. So:
| Route | With no dates | What it tells you |
|---|---|---|
| /search-listings | Always prices. Every listing carries a price for the source's own default stay window. | The window it used, on checkIn/checkOut and in a note |
| /listing-detail | price: null. No stay is invented. | A note saying to pass checkIn/checkOut to price a stay |
A search has to price to be a search — a results page with no prices answers nothing. A detail read will not invent a stay you did not ask about, because on a single listing the honest answer to "what does it cost" without dates is "you have not said when".
The exact notes, measured against production on 17 September 2026:
- Search, no dates — “No check-in/check-out was given, so each listing's price reflects the source's own default stay (2026-10-16 to 2026-10-21), not necessarily the dates you want. Pass checkIn and checkOut to price a specific stay.”
- Detail, no dates — “No check-in/check-out was given, so no price is included. Pass checkIn and checkOut to price a specific stay.”
- Detail, dates that cannot be priced — still a 200,
price: null, and “This stay (2026-12-08 to 2026-12-12) could not be priced. It may be unavailable for those dates.”
checkIn and checkOut are enforced as a pair. One without the other is a 400: {"error":"checkIn and checkOut must be given together"}.
price is an object, not a number
A real priced response, from /listing-detail on 17 September 2026:
"price": {
"total": { "amount": 940.47, "currency": "USD", "formatted": "$940.47" },
"originalTotal":{ "amount": 1083.09, "currency": "USD", "formatted": "$1,083.09" },
"perNight": { "amount": 225.94, "currency": "USD", "formatted": "$225.94" },
"nights": 4,
"taxesAndFees": { "amount": 36.70, "currency": "USD", "formatted": "$36.70" },
"qualifier": "total",
"note": "Pay $0 today with no extra fees."
} Every money value is {amount, currency, formatted}, so you get the number and the source's own rendering of it without parsing a string. originalTotal above total is a discount — here $142.62 off — and it is null when there is none to report. taxesAndFees is broken out rather than folded in, nights is the stay length the figures cover, and qualifier is what the source called the figures — "total" or "for 4 nights". Any of the money fields can be null where the source published nothing.
Search rows carry the same object, so a per-night comparison across a city is arithmetic rather than a regex over "$1,245".
Availability is a calendar, not a price probe
Passing dates to /listing-detail answers “what would this stay cost”, and that is all it answers. Which nights are actually open is /listing-availability: one entry per night, each with available, bookable, availableForCheckin, availableForCheckout and the minNights/maxNights the host enforces for a stay starting on it. Up to twelve months in a single call — the source's own ceiling per request — and one credit whether you ask for one month or twelve.
Read the booking-window note before reading a run of closed nights as a closure. Every response carries it verbatim: “Dates beyond the host's own booking window come back marked unavailable rather than omitted — a long unbroken run of closed nights near the end of this window usually means the window, not a closure.” The source answers every night you ask for, so a host who has opened four months still returns months five through twelve when you ask for twelve — marked available: false, identical to a booked night. A solid block of closed nights running all the way to endDate is the edge of what the host has opened, not a fully booked property; scattered closed nights in the middle of an open range are real closures.
A closed night that still allows checkout is the far end of a booked stay. available: false with availableForCheckout: true and availableForCheckin: false is the last night of somebody else's booking — you cannot start a stay on it, but a stay could end on it. That pairing is how you find where a booking ends.
There is no per-night pricing on this route. It says which nights are open and how long a stay has to be; it does not price them. For a priced stay, call /listing-detail with checkIn and checkOut.
Available endpoints
Paging and billing
One credit per page fetched, stated on every response as pagesFetched. A search page is up to 18 listings and costs the same whether it comes back full or half empty; up to 15 pages in one request. Reviews walk up to 20 pages and 1000 reviews in one request, and a walk stops early when limit is reached — so {"pages":-1,"limit":25} billed pagesFetched: 1, not twenty. Pass -1 on pages or limit to mean “the maximum”.
/resolve-url fetches nothing at all and is free, reporting pagesFetched: 0.
/listing-availability is one credit per call up to its twelve-month ceiling: a year of calendar came back on a single page, reporting pagesFetched: 1, the same as the three-month default.
Measured against production on 17 September 2026, three runs each: a one-page search of Lisbon answered in 1.6–1.9s with 18 listings, a listing read in 0.96–1.19s, ten reviews in 0.53–0.58s, three months of calendar in 0.36s with 91 nights, and a link resolved in about 0.10s.
Errors
- 400 — the request is wrong, and the message names the problem: a missing required field (
"location: Invalid input: expected string, received undefined"), an unknown one ("Unrecognized key: \"sortBy\"", because the body is a strict object), half a date pair ("checkIn and checkOut must be given together"), or a value past a source ceiling ({"error":"months: Must be -1 (the maximum) or between 1 and 12"}on/listing-availability). - 401 — missing or invalid key.
- 404 — on
/listing-detailand/listing-availability, an id that names no listing:{"error":"no listing was found for id \"1\""}. A 404 with a message, not a 503 — so it is a wrong request to fix rather than a degraded service to retry.