Extracts Airbnb accommodation search results from a destination query via SSR-embedded data, returning listing ID, URL, name, coordinates, rating, price, photos, and badge info for each result, plus pagination cursors for multi-page retrieval. Use when user mentions Airbnb search results, Airbnb lis
Browser
Scavio Airbnb
Try itSearch Airbnb stays with the full discount ledger, pull one listing with its complete amenity list and rating breakdown, and page through real review bodies. 3 endpoints, 1 credit each, structured JSON.
What it does
Search Airbnb stays with the full discount ledger, pull one listing with its complete amenity list and rating breakdown, and page through real review bodies. 3 endpoints, 1 credit each, structured JSON.
The skill document
Airbnb via Scavio
Search Airbnb stays for a location and date window, read one listing in full, and page through its review bodies. All endpoints return structured JSON.
When to trigger
Use this skill when the user asks to:
- Find Airbnb stays in a place for specific dates, with price, rating and capacity filters
- Compare nightly and stay-total prices across listings, including the discount ledger
- Pull one listing in full - description, capacity, the complete grouped amenity list, host profile, house rules, cancellation policy, photo tour
- Read the rating breakdown - six category ratings and the five-bucket star distribution
- Read actual guest review bodies with per-review ratings and dates
- Build short-term-rental market research, comp sets or host analytics
Setup
Get a free API key at scavio.dev (50 free credits to get started, no card required):
export SCAVIO_API_KEY=sk_live_your_key
Every request is a POST with a JSON body and:
Authorization: Bearer $SCAVIO_API_KEY
Endpoints
Base URL: https://api.scavio.dev. Every Airbnb endpoint costs 1 credit.
| Endpoint | Credits | What it returns |
|---|---|---|
POST /api/v1/airbnb/search | 1 | Stays: stay-total and per-night price with the full discount ledger, rating and review count, bedrooms/beds/baths, coordinates, badges, images, dates_are_defaulted |
POST /api/v1/airbnb/listing | 1 | One listing in full: description, property and room type, capacity and room counts, the complete grouped amenity list (including what the place does not have), host profile and stats, house rules with parsed check-in/out times, cancellation policy, sleeping arrangements, photo tour, every image, and the rating breakdown. No nightly price |
POST /api/v1/airbnb/reviews | 1 | Review bodies with per-review rating, date, and reviewer name/photo/location |
Two facts that decide how you call this
Prices are search-only. The room page carries no nightly rate under any parameters - with or without dates, and even under a rendered fetch. If the user wants a price for a specific listing, you must find it in a /search response. /listing will never have one.
The rating breakdown is listing-only. The six category ratings, the five-bucket star distribution and the synthesised review tags are server-rendered on /listing - they are not on /reviews. /reviews gives you the review text.
Workflow
- Search: call
/airbnb/searchwithlocationand, ideally,check_in+check_out. Readlistings[].listing_idandlistings[].price. - One listing: call
/airbnb/listingwith thatlisting_idor a full/rooms/URL. Query parameters on a pasted URL are discarded - they carry somebody else's dates. - Reviews: call
/airbnb/reviewswith the samelisting_id, sending an explicitlimitand steppingoffset.
Dates and currency
check_in and check_out must be sent together. A dateless search defaults to roughly +30 days for 5 nights - and Airbnb A/B tests both the window and the prices on that shape: the same URL has come back split across two different windows with first-row prices of $680, $802 and $2,238. The response flags this as dates_are_defaulted. If it is true, do not quote the price as an answer to a dated question.
currency defaults to USD in the transport. Send one explicitly; without it Airbnb prices off the proxy exit and two identical requests disagree.
Pagination
/searchis 18 listings per page and offers two mutually exclusive controls: a 1-basedpage, or the opaquenext_cursorfrom a previous response passed back ascursor.cursorwins overpage, so sending both is rejected. Pick one.page_countis on the response./reviewspages withlimit(1-50, default 30) andoffset(default 0).countis the listing's total review count;returnedis how many this page holds./listingtakes no paging parameter.
The limit / offset names are load-bearing. The underscored _limit / _offset used elsewhere in Airbnb's API are silently ignored here and pin the response at a fixed 7 rows for every offset - which reads as a paging loop that never advances.
Parameters
Search (/search)
| Parameter | Type | Default | Description |
|---|---|---|---|
location | string | required | City, region, ZIP, or a pasted airbnb.com/s/ URL (1-200 chars). Unresolvable is a 404 |
check_in / check_out | string | +30d / +5 nights | YYYY-MM-DD, sent together and in order |
adults | integer | -- | Adult guests |
children | integer | -- | Ages 2-12 |
infants | integer | -- | Infants |
pets | integer | -- | Pets |
min_price / max_price | number | -- | Whole-stay total, not per night |
room_type | string | -- | entire_home, private_room, shared_room, hotel_room |
min_bedrooms / min_beds / min_bathrooms | integer | -- | Minimum counts |
superhost | boolean | -- | Superhost listings only |
instant_book | boolean | -- | Instant Book only |
guest_favorite | boolean | -- | Guest Favourite only |
free_cancellation | boolean | -- | Free cancellation only |
amenities | string | -- | Comma-separated: wifi, air_conditioning, pool, kitchen, free_parking, washer, self_check_in, tv - or raw numeric Airbnb amenity ids |
currency | string | USD | ISO 4217 |
page | integer | -- | 1-based, 18 listings per page. Cannot be combined with cursor |
cursor | string | -- | next_cursor from a previous response. Wins over page |
room_type and the named amenities are validated before the scrape, on purpose: an unrecognised value makes Airbnb return the unfiltered set under a 200, so the user would pay for a filter that never ran.
Listing (/listing)
| Parameter | Type | Default | Description |
|---|---|---|---|
listing_id | string | required | Listing id or full /rooms/ URL; query params discarded |
check_in / check_out | string | -- | YYYY-MM-DD, sent together and in order |
adults / children / infants / pets | integer | -- | Party composition |
currency | string | USD | ISO 4217 |
Reviews (/reviews)
| Parameter | Type | Default | Description |
|---|---|---|---|
listing_id | string | required | Listing id or full /rooms/ URL |
limit | integer | 30 | 1-50. Send it explicitly - see the paging note |
offset | integer | 0 | Row offset |
currency | string | USD | ISO 4217 |
Examples
import requests
BASE = "https://api.scavio.dev"
# Your key from https://scavio.dev. Load it from your environment or secret
# store in real code - keep it out of source control.
API_KEY = "sk_your_key_here"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 1. Search a real date window - min_price/max_price are the WHOLE-STAY total
found = requests.post(f"{BASE}/api/v1/airbnb/search", headers=HEADERS,
json={"location": "Austin, TX", "check_in": "2026-09-08", "check_out": "2026-09-13",
"adults": 2, "max_price": 1500, "room_type": "entire_home",
"amenities": "wifi,pool", "currency": "USD"}).json()
assert found["data"]["dates_are_defaulted"] is False # you sent dates, so this is False
row = found["data"]["listings"][0]
listing_id, stay_price = row["listing_id"], row["price"]
# 2. Listing detail: amenities, host, house rules, rating breakdown - NO price
listing = requests.post(f"{BASE}/api/v1/airbnb/listing", headers=HEADERS,
json={"listing_id": listing_id}).json()
print(listing["data"]["category_ratings"]) # cleanliness, accuracy, check-in, ...
# 3. Review bodies - always send limit explicitly, then step offset
reviews = requests.post(f"{BASE}/api/v1/airbnb/reviews", headers=HEADERS,
json={"listing_id": listing_id, "limit": 30, "offset": 0}).json()
print(reviews["data"]["count"], reviews["data"]["returned"]) # total vs this page
Paging: cursor or page, never both.
def search_pages(location, max_pages=3, **body):
"""18 listings per page, 1 credit per page."""
out, cursor = [], None
for _ in range(max_pages):
payload = {"location": location, **body}
if cursor:
payload["cursor"] = cursor # never alongside "page"
data = requests.post(f"{BASE}/api/v1/airbnb/search",
headers=HEADERS, json=payload).json()["data"]
out += data["listings"]
cursor = data["next_cursor"]
if not cursor:
break
return out
def all_reviews(listing_id, max_pages=4, limit=50):
out = []
for i in range(max_pages):
data = requests.post(f"{BASE}/api/v1/airbnb/reviews", headers=HEADERS,
json={"listing_id": listing_id,
"limit": limit, "offset": i * limit}).json()["data"]
out += data["reviews"]
if data["returned"] < limit or len(out) >= data["count"]:
break
return out
Response shapes
Every response uses the envelope { data, response_time, credits_used, credits_remaining }. Key data fields:
- search —
url,location,resolved_location,city,state,country,place_precision,coordinates,map_bounds,check_in,check_out,nights,dates_are_defaulted,currency,guests,page,page_count,next_cursor,count,listings[](pos,listing_id,listing_node_id,url,name,title,subtitle,rating,review_count,rating_label,bedrooms,beds,baths,price,badges,is_guest_favorite,is_superhost,coordinates,images). - listing —
listing_id,url,name,headline,description,description_html,summary,property_type,room_type,space_type,person_capacity,overview,bedrooms,beds,baths,rating,review_count,is_guest_favorite,is_new_listing,coordinates,address,neighborhood,breadcrumbs,host,amenities,amenity_groups,highlights,sleeping_arrangements,photo_tour,images,thumbnail,house_rules,safety_and_property,cancellation_policy,additional_house_rules,check_in_time,check_in_end_time,check_out_time,category_ratings[](category,label,rating). No price field of any kind. - reviews —
listing_id,url,count(the listing's total),rating,returned(this page),reviews[](id,text,rating,language,created_at,date,reviewer_id,reviewer_name,reviewer_photo).
{
"data": {
"location": "Austin, TX",
"check_in": "2026-09-08",
"check_out": "2026-09-13",
"nights": 5,
"dates_are_defaulted": false,
"currency": "USD",
"page": 1,
"page_count": 15,
"next_cursor": "eyJzZWN0aW9uX29mZnNldCI6...",
"count": 18,
"listings": [
{
"pos": 1,
"listing_id": "1234567890",
"name": "Sunny East Austin bungalow",
"rating": 4.92,
"review_count": 318,
"bedrooms": 2,
"beds": 3,
"baths": 1,
"is_guest_favorite": true,
"url": "https://www.airbnb.com/rooms/1234567890"
}
]
},
"credits_used": 1,
"credits_remaining": 999
}
Guardrails
- Every call is 1 credit, including one that comes back empty.
- Never quote a nightly rate from
/listing- there is none. Prices come from/searchonly. - Always check
dates_are_defaulted. If it istrue, Airbnb chose the window and A/B tested the prices; say so instead of presenting the figure as the user's dates. min_price/max_priceare the whole-stay total. If the user thinks in nightly terms, convert and tell them which one you filtered on.- Send
currencyon every call. Two identical requests without it can disagree. - Send
pageorcursor, never both - the request is rejected. - On
/reviews, always send an explicitlimit, and use exactlylimit/offset. The underscored variants silently return the same 7 rows forever. - Never invent a
room_typeor amenity name. Unrecognised values are rejected before the scrape precisely so the user is not billed for a filter Airbnb would have ignored. - Never fabricate listing names, prices, ratings, host details or review text. Only return what the API returned.
- Review text and host profiles are personal data about real people. Summarise; do not build profiles of individuals.
Failure handling
400means an invalid or missing parameter - an unpaired or out-of-order date,min_priceabovemax_price,pagesent together withcursor, or an unrecognisedroom_type/ amenity name. Fix and retry.401means the API key is invalid or missing. CheckSCAVIO_API_KEY.404means Airbnb could not resolve the location, or the listing id does not exist.429means rate or usage limit exceeded. Wait before retrying. See rate limits.502/503mean upstream is temporarily unavailable - wait a few seconds and retry, up to a few times. Airbnb occasionally serves a geo domain-switch interstitial; the transport already retries that internally, so a 502 that survives to you is real.- An empty result set is usually the filters - widen
max_price(remember it is the stay total), dropsuperhost/guest_favorite, or shorten the stay. - If
SCAVIO_API_KEYis not set, prompt the user to export it before continuing.
SDKs
pip install scavio==0.15.0
from scavio import ScavioClient
client = ScavioClient() # reads SCAVIO_API_KEY
found = client.airbnb.search("Austin, TX", check_in="2026-09-08", check_out="2026-09-13",
adults=2, max_price=1500, currency="USD")
listing_id = found["data"]["listings"][0]["listing_id"]
listing = client.airbnb.listing(listing_id) # amenities + rating breakdown, no price
reviews = client.airbnb.reviews(listing_id, limit=50, offset=0)
npm install scavio@0.15.0
import { Scavio } from "scavio";
const client = new Scavio(); // reads SCAVIO_API_KEY
const found = await client.airbnb.search({
location: "Austin, TX", check_in: "2026-09-08", check_out: "2026-09-13", currency: "USD",
});
const reviews = await client.airbnb.reviews({ listing_id: "1234567890", limit: 50, offset: 0 });
Related skills
Complete Airbnb toolkit — search, availability, listing detail, price, cross-OTA price comparison and reviews, all in one unified schema. Install this when an agent needs broad Airbnb coverage. Powered by StayingAPI.
Read normalized Airbnb reviews for a listing, with native rating scales preserved. Use when a user wants ratings or guest feedback for a listing on Airbnb. Powered by StayingAPI.
Fetches complete Airbnb listing details for a given numeric listing ID via the internal GraphQL API, returning title, room type, description, amenities, photos, coordinates, city, house rules, highlights, ratings, review count, bedroom configuration, and property overview. Use when user mentions Air
Collect Airbnb Builder data and return results
This skill helps users run the Airbnb Listing Details Scraper BrowserAct template and extract structured public data. Use this skill when users ask to collect airbnb listing details scraper data, scrape airbnb listing details scraper results, monitor public records from this source, export structure