Updated Oct 2026

Guesty reviews API in Python: Airbnb and Booking.com guest reviews, replies

For a property manager on Guesty, the Open API is the route to guest reviews from Airbnb, Booking.com and Vrbo, and to replying on Airbnb and Booking.com. Two things trip people up: Guesty allows only five tokens a day, and the review text for the big channels isn’t in the obvious field. This guide handles both with requests, following open-api-docs.guesty.com, which is also how Prisma’s Guesty connector works (in beta, built from the docs). Hostaway and Hospitable have their own guides; the sector view is on hotel reviews API.

Shell
pip install requests

Credentials: an Open API key’s client id and client secret (Guesty → Integrations → Open API).

1. Get a token, and keep it

Python
import os, json, time, requests

BASE = "https://open-api.guesty.com"
CACHE = "guesty_token.json"

def token():
    try:
        t = json.load(open(CACHE))
        if t["expires_at"] > time.time() + 300:
            return t["access_token"]
    except FileNotFoundError:
        pass
    r = requests.post(f"{BASE}/oauth2/token", headers={"Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded"},
                      data={"grant_type": "client_credentials", "scope": "open-api", "client_id": os.environ["GUESTY_CLIENT_ID"], "client_secret": os.environ["GUESTY_CLIENT_SECRET"]}, timeout=30)
    r.raise_for_status()
    body = r.json()
    json.dump({"access_token": body["access_token"], "expires_at": time.time() + body.get("expires_in", 86400)}, open(CACHE, "w"))
    return body["access_token"]

def h(): return {"Authorization": f"Bearer {token()}", "Accept": "application/json", "Content-Type": "application/json"}

Five tokens a day per client, 24 hours each, 120 requests a minute. Cache the token somewhere durable (a file here; a database in production).

2. Listings

Python
listings, skip = [], 0
while True:
    body = requests.get(f"{BASE}/v1/listings", headers=h(), params={"limit": 100, "skip": skip, "fields": "_id title nickname address"}, timeout=30).json()
    listings += body.get("results", [])
    skip += 100
    if not body.get("results") or skip >= body.get("count", 0):
        break
for l in listings:
    print(l["_id"], l.get("nickname") or l.get("title"), (l.get("address") or {}).get("full"))
LISTING = listings[0]["_id"]

3. Reviews for a listing, and where the text really is

Python
def reviews(listing_id, start_date=None, limit=100):
    skip = 0
    while True:
        params = {"listingId": listing_id, "limit": limit, "skip": skip}
        if start_date:
            params["startDate"] = start_date         # ISO 8601, on updatedAt
        body = requests.get(f"{BASE}/v1/reviews", headers=h(), params=params, timeout=30).json()
        rows = body.get("data") if isinstance(body.get("data"), list) else (body.get("data") or {}).get("data") or []
        if not rows:
            break
        yield from rows
        skip += limit

def content(r):
    """Rating (1-5), title, text, author from `contents` or the channel's raw review."""
    c = r.get("contents") or {}
    if c.get("body") or c.get("rating") is not None:
        return {"rating": round(c["rating"] / 2) if c.get("rating") else None, "title": c.get("title"), "text": c.get("body"), "author": c.get("reviewerName")}
    raw = r.get("rawReview") or {}
    if r.get("channelId") == "bookingCom" or "scoring" in raw:                        # Booking.com: score out of 10, positive/negative
        rc = raw.get("content") or raw
        score = (raw.get("scoring") or {}).get("review_score")
        text = "\n".join(t for t in [rc.get("positive"), rc.get("negative") and f"Negative: {rc['negative']}"] if t)
        return {"rating": round(score / 2) if score else None, "title": rc.get("headline"), "text": text or None, "author": (raw.get("reviewer") or {}).get("name")}
    rating = raw.get("overall_rating") or raw.get("rating")                               # Airbnb and Vrbo: 1-5
    return {"rating": int(rating) if rating else None, "title": raw.get("title"), "text": raw.get("public_review") or raw.get("body") or raw.get("comments"), "author": raw.get("reviewer_name")}

for r in reviews(LISTING):
    c = content(r)
    replies = [x for x in (r.get("reviewReplies") or []) if x.get("reviewReply")]
    print(r["_id"], r.get("channelId"), c["rating"], c["title"], (c["text"] or "")[:80], c["author"], "| replied" if replies else "")

What Prisma learned building this: contents is populated for Guesty’s own channels; for Airbnb, Booking.com and Vrbo the review lives in rawReview in the channel’s own shape. The mapping above follows those formats as documented; check it against your own account’s raw records before relying on it, because Guesty’s docs don’t pin every field. reviewReplies holds your replies with status and replyAt.

4. Reply

Python
requests.put(f"{BASE}/v1/reviews/{r['_id']}/reply", headers=h(), json={"reviewReply": "Thank you, Laura! The new coffee machine is in for your next stay."}, timeout=30).raise_for_status()

One reply per review, inside the channel’s window. On Airbnb another PUT edits it; on Booking.com the first one is final.

5. The same job through Prisma

Prisma connects a Guesty account with the client id and secret (checked before storing, encrypted), respects the five-token limit by reusing one, imports listings as locations, reads contents or the raw review per channel, and publishes replies with the PUT; it reads the review back after a dropped request instead of replying twice. One key covers Guesty, Hostaway and Hospitable plus Google, Tripadvisor, Expedia and 50+ other platforms.

Python
H = {"Authorization": f"Bearer {os.environ['PRISMA_API_KEY']}"}
r = requests.get("https://api.prismareviews.com/v1/reviews", headers=H, params={"platform": "guesty", "has_reply": "false", "limit": 100}, timeout=30)
for rev in r.json()["data"]:
    print(rev["rating"], rev["author"], rev["text"])
requests.post(f"https://api.prismareviews.com/v1/reviews/{rev['id']}/reply", headers=H, json={"text": "Thank you, Laura! The new coffee machine is in for your next stay."}, timeout=30).raise_for_status()

Guesty is in beta in Prisma. Connection fields: Platforms. A test key returns sample reviews so the code runs now.

FAQ

How many tokens can I request from the Guesty Open API?

Five a day per API client, each valid 24 hours. Store the token and reuse it until it expires; requesting one per call will lock you out by mid-morning. Calls are limited to 120 a minute.

Can I reply to Airbnb and Booking.com reviews through Guesty?

Yes: PUT /v1/reviews/{id}/reply with reviewReply, one reply per review. Airbnb lets it be edited with another PUT; Booking.com doesn’t. The reply has to go out inside the channel’s response window.

Why is the review text empty in the Guesty API?

Guesty fills contents only for its own channels; for Airbnb, Booking.com and Vrbo the text and score sit in the channel’s rawReview in that channel’s own format (for example public_review and overall_rating on Airbnb, scoring.review_score and positive/negative on Booking.com). Read both.

Guesty, Hostaway, Hospitable and Google, one key.

Get API key

2 locations free. Test keys return sample reviews from every platform.

Get your API key in a minute

Free Spark plan: 2 locations, no card. Test keys return sample data on all 62 platforms, so you can build before connecting a real one.