AirbnbAnswer

Does Airbnb have a public API? Listings and calendars

No. Airbnb's API is closed to new partners. Our Airbnb API searches stays and returns 12-month calendars as JSON, and a Python script saves a city to CSV.

7 min read

Short answer

  • Airbnb has no public API. Its API is for property-management and channel-management software, and Airbnb is not accepting new access requests.
  • ToolzerHub's Airbnb API searches stays in a place, returns a listing's details and up to 12 months of its availability calendar, as JSON, at 1 credit per call.
  • Searches mix 2 to 12 hotels into 20 stays; hotel calendars were 16-27% closed over the next 30 nights against 42-74% for other stays, so track fixed stay ids.
On this page

Airbnb has no public API. Its API is for property-management and channel-management software that hosts connect their listings to, and Airbnb isn't taking new partners. To read listings and calendars you don't manage, you read Airbnb's public pages. Our API does that, and the script below turns a city into a CSV of how booked its listings look.

What Airbnb's API is for

Airbnb's partner API lets hospitality software push rates, availability and listing content for the hosts it manages, and receive their bookings and messages. Its FAQ says Airbnb is "not accepting new access requests". Nothing in it searches listings, so a search or a calendar for a stay you don't manage has to come from Airbnb's public pages.

Airbnb listings with ToolzerHub

We make ToolzerHub. Our Airbnb API has three routes, each a GET with the x-api-key header at 1 credit a call:

  • /v1/airbnb/stays/search?destination=... returns stays in a place, with optional checkin, checkout and guest counts.
  • /v1/airbnb/stay/detail?stay_id=... returns a listing's property type, capacity, amenities, host stats and rating per category.
  • /v1/airbnb/stay/availability?stay_id=... returns each day for up to 12 months.
bash
export TOOLZERHUB_API_KEY=your_key
curl -G -H "x-api-key: $TOOLZERHUB_API_KEY" \
  --data-urlencode "destination=Kyoto, Japan" -d count=10 \
  "https://api.toolzerhub.com/v1/airbnb/stays/search"

The first stay in data.stays, without its photo list:

json
{
  "id": "1744088620623845596",
  "url": "https://www.airbnb.com/rooms/1744088620623845596",
  "name": "LuckyaKotako|Shijo&Nishiki|Shigaraki Open-Air Bath",
  "title": "Townhouse in Kyoto",
  "subtitle": "112 m² machiya with a private Japanese garden and a semi-open-air Shigaraki bath",
  "coordinate": { "latitude": 35.0061, "longitude": 135.7554 },
  "price": { "label": "$1,477 for 5 nights", "amount": "$1,477", "original_amount": null, "qualifier": "for 5 nights" },
  "rating": { "score": 5, "reviews": 8, "label": "5.0 (8)" }
}

With no dates, every price is for a 5-night stay; send checkin and checkout for your own. A day in the same listing's calendar:

json
{ "date": "2026-10-24", "available": true, "bookable": true, "available_for_checkin": true, "available_for_checkout": false, "min_nights": 1, "max_nights": 1125 }

The calendar starts on the 1st of the current month and days before today come back as available: false, so count from today. It has no prices, and a closed day can be a booking or a day the host blocked, so "closed" is an upper bound on bookings.

Check how booked a set of listings is

The script takes a place, or a file of stay ids, and for each stay pulls 4 months of calendar plus its property type. It appends one row per stay: closed nights in the next 30 and 90 days, today's minimum stay, rating, price and link. The detail call doubles the cost but is the reliable way to spot hotels. Save it as airbnb_calendar.py and install requests (pip install requests).

python
import csv
import os
import sys
import time
from datetime import date, timedelta

import requests

BASE = "https://api.toolzerhub.com/v1/airbnb"
HEADERS = {"x-api-key": os.environ.get("TOOLZERHUB_API_KEY", "")}
FIELDS = ["checked_on", "stay_id", "type", "title", "name", "rating", "reviews", "price",
          "closed_next_30", "closed_next_90", "min_nights_today", "url"]


def get(path, params, tries=3):
    # 429, 5xx and dropped connections are retried; failed calls cost no credits.
    for attempt in range(tries):
        last = attempt == tries - 1
        try:
            r = requests.get(f"{BASE}/{path}", headers=HEADERS, params=params, timeout=120)
        except requests.RequestException:
            if last:
                raise
            time.sleep(10 * (attempt + 1))
            continue
        if r.status_code in (429, 500, 502, 503, 504) and not last:
            time.sleep(10 * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json()["data"]


def stays_for(place, count):
    if os.path.isfile(place):  # a file of stay ids, one per line: the same stays every run
        ids = [line.strip() for line in open(place) if line.strip()]
        return [{"id": i, "url": f"https://www.airbnb.com/rooms/{i}"} for i in ids]
    found = get("stays/search", {"destination": place, "count": int(count)})
    if not found or not found["stays"]:
        sys.exit(f"Airbnb found no stays for {place!r}")
    if not found["location"]["resolved"]:  # unrecognised places can return unrelated stays
        sys.exit(f"Airbnb did not recognise {place!r}; try 'City, Country'")
    print(f"{len(found['stays'])} stays in {found['location']['canonical_location']}")
    return found["stays"]


def closed(days, start, n):
    # Unavailable nights in the n days from start: booked or blocked by the host.
    window = {(start + timedelta(i)).isoformat() for i in range(n)}
    seen = [d for d in days if d["date"] in window]
    return sum(not d["available"] for d in seen), len(seen)


def main(place, count="20", out="airbnb_calendar.csv"):
    stays = stays_for(place, count)
    today, written = date.today(), 0
    new_file = not os.path.exists(out)
    with open(out, "a", newline="", encoding="utf-8-sig" if new_file else "utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        if new_file:
            writer.writeheader()
        for s in stays:
            try:  # 4 months from the 1st of this month covers the next 90 days
                cal = get("stay/availability", {"stay_id": s["id"], "months": 4})
            except requests.RequestException as e:
                print(f"{s['id']}: calendar failed ({e})")
                continue
            if not cal:
                print(f"{s['id']}: no calendar")
                continue
            try:  # search mixes in hotel rooms; property_type says which (HOTEL)
                d = get("stay/detail", {"stay_id": s["id"]}) or {}
            except requests.RequestException:
                d = {}
            c30, n30 = closed(cal["days"], today, 30)
            c90, n90 = closed(cal["days"], today, 90)
            first = next((x for x in cal["days"] if x["date"] == today.isoformat()), {})
            rating = s.get("rating") or d.get("rating") or {}
            writer.writerow({
                "checked_on": today.isoformat(), "stay_id": s["id"],
                "type": d.get("property_type"), "title": s.get("title"),
                "name": s.get("name") or d.get("name"), "rating": rating.get("score"),
                "reviews": rating.get("reviews"), "price": (s.get("price") or {}).get("label"),
                "closed_next_30": f"{c30}/{n30}", "closed_next_90": f"{c90}/{n90}",
                "min_nights_today": first.get("min_nights"), "url": s["url"],
            })
            written += 1
    print(f"{written} of {len(stays)} stays appended to {out}")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit('usage: python airbnb_calendar.py "Lisbon, Portugal" [count] [file.csv]\n'
                 '   or: python airbnb_calendar.py stay_ids.txt 0 [file.csv]')
    if not HEADERS["x-api-key"]:
        sys.exit("set TOOLZERHUB_API_KEY first")
    main(*sys.argv[1:])

python airbnb_calendar.py "Lisbon, Portugal" 20 lisbon.csv makes 41 calls, one search plus two per stay, and takes 44 to 54 seconds. We ran it on Lisbon and Austin three times each.

What the calendars showed

The share of the next 30 nights that was closed, per search:

Search Hotels in top 20 Closed, other stays Closed, hotels
Lisbon, run 1 4 62.5% 25.8%
Lisbon, run 2 4 64.0% 25.8%
Lisbon, run 3 12 74.2% 16.1%
Austin, run 1 11 50.7% 27.0%
Austin, run 2 2 55.7% 21.7%
Austin, run 3 11 42.2% 27.3%

"Other stays" are everything the detail call didn't mark HOTEL: apartments, houses and the odd hostel. In every run the hotels' calendars were far emptier, 16% to 27% closed against 42% to 74%. Over 90 days the gap held: 50.3% to 57.2% closed for Lisbon's other stays and 6.8% to 13.3% for its hotels.

These are the top 20 results of a search, not a sample of a market, and the top 20 moves: two searches of the same city a few minutes apart shared 7 of 20 stays, in both cities. One search's average says little about a city.

Keep hotels and reshuffles out of the numbers

Drop rows where type is HOTEL before you average. The search subtitle ("Hotel in …") catches most hotels, but one Austin hotel titled "Room in Austin" only showed in the detail call's property_type.

Then save the ids of the stays you track, one per line, and run python airbnb_calendar.py stay_ids.txt 0 tracked.csv each week. A rerun of 20 saved Austin ids returned all 20, and the checked_on column shows how the same calendars fill up.

What it costs

A city search of 20 stays is 41 credits and a weekly run of 20 fixed stays is 40, about 175 credits a month. The 100 free credits a month cover the first two weeks; $19 buys 10,000 credits that never expire, and plans for higher volume cover more cities. Set your key and run the script on your city; the first CSV is ready in under a minute.

For prices from other public pages, see our Skyscanner API post and Zillow listings to CSV.

We are not affiliated with Airbnb.

Get this data from your own code

The Airbnb API returns it as JSON with one x-api-key header. 100 free credits monthly.