Airbnb Reviews
What is specific to Airbnb reviews: the three ratings, private feedback, the recommendation and the 14-day window. Reading, replying and reviewing a guest all work from the unified Reviews API with the same review id — reviewerRole: "host" is your review of the guest, reviewerRole: "guest" is theirs of you.
/v1/channels/airbnb/reviews/:idParameters
idstringRequiredThe review — its id or externalReviewId, both from GET /v1/reviews?reviewerRole=host.
publicReviewstringRequiredYour review of the guest, shown publicly on their Airbnb profile. Up to 1000 characters. comment is accepted as an alias.
ratingintegerA 1–5 score used for every category you do not rate in categoryRatings. Send this, categoryRatings covering all three categories, or both.
categoryRatingsarrayPer-category scores: [{ "category": "cleanliness", "rating": 5, "comment": "…" }]. Categories are cleanliness, communication and respect_house_rules; comment is optional, up to 50 characters.
isRevieweeRecommendedbooleanRequiredWhether you would host this guest again. Airbnb requires it; Repull never assumes it.
privateFeedbackstringA note to the guest that is not published. Up to 1000 characters.
Your review of a guest
After checkout Airbnb opens a review of the guest for you, listed in GET /v1/reviews with reviewerRole: "host" and an expiresAt. Submit it with POST /v1/reviews/{id}/guest-review (Reviews API) or PUT /v1/channels/airbnb/reviews/{id} — the two behave identically. Submitting publishes it and is final — Airbnb has no draft and does not allow edits, so send it when it is ready.
- The public review (
publicReview, required) — what other hosts read on the guest's profile. - Three ratings, all required — each a whole number from 1 to 5:
cleanliness— the state the guest left the place in.communication— how responsive and clear the guest was.respect_house_rules— whether the guest followed your house rules.- Send
ratingto give all three the same score,categoryRatingsto score them one by one, or both —ratingfills any category you did not score individually. Each category can carry an optionalcommentof up to 50 characters. - Private feedback (
privateFeedback, optional) — a note to the guest that is not published. Use it for things you would tell them but not other hosts. - Recommendation (
isRevieweeRecommended, required) — whether you would host this guest again. Airbnb refuses a review without it, and Repull never fills it in for you. - Both reviews stay hidden until both sides have submitted, or until the window closes — whichever comes first. The window is 14 days after checkout;
expiresAton the review is the deadline. - Anything missing is refused with
422 invalid_paramsnaming the field, before anything is sent to Airbnb — so a rejected request never uses up the review.
curl -X PUT https://api.repull.dev/v1/channels/airbnb/reviews/181567 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"publicReview": "Joanne was a great guest — the place was left spotless and she followed every house rule.",
"rating": 5,
"categoryRatings": [
{ "category": "communication", "rating": 4, "comment": "A little slow to reply" }
],
"privateFeedback": "Thanks for taking such good care of the place!",
"isRevieweeRecommended": true
}'Errors
422 invalid_params— a required piece is missing or out of range;fieldnames it (publicReview,isRevieweeRecommended,rating,categoryRatings).409 review_already_submitted— this review was already submitted. Airbnb accepts one submission and no edits.409 review_window_closed— the window closed (seeexpiresAt); the review can no longer be written.409 not_host_review— the id is the guest's review of you. Reply to it instead (below).404 not_found— no review with that id in this workspace.422 airbnb_rejected— Airbnb refused it; its reason is inmessage.
Reply to a guest's review
POST /v1/reviews/{id}/reply with { "message": "…" } posts your public reply (up to 1000 characters) under a review a guest wrote about you (reviewerRole: "guest"). Airbnb allows one reply per review. The same call replies on Booking.com — see Reply to a review. POST /v1/channels/airbnb/reviews/{id}/respond still works but is deprecated.
curl -X POST https://api.repull.dev/v1/reviews/186761/reply \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Thank you for staying with us — come back any time!"}'Several Airbnb accounts
A workspace can connect more than one Airbnb account. By default this route returns every connected account's rows; pass ?account_id=<airbnb host id> to scope to one. Every row carries accountId + accountName either way (the older hostId / hostName remain as aliases), so you can group without a second call. Airbnb host ids exceed 2^53 — keep them as strings and never parse them as numbers: Number("1000000000000000002") is 1772489413932732200, a different account, and returns 404. A two-account walkthrough lives on the Account scope page (/docs/scoping).
# Every connected account — the default, unchanged
curl 'https://api.repull.dev/v1/channels/airbnb/reviews' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# Just one account
curl 'https://api.repull.dev/v1/channels/airbnb/reviews?account_id=1000000000000000002' \
-H "Authorization: Bearer sk_live_YOUR_KEY"
# An id this workspace has not connected — 404, with your own ids to copy
# {
# "error": {
# "code": "not_found",
# "message": "No connected Airbnb account `1772489413932732200` in this workspace.",
# "field": "account_id",
# "value_received": "1772489413932732200",
# "valid_values": ["1000000000000000002", "80000001"]
# }
# }Data freshness
Airbnb reads are served from Repull's local mirror and never call Airbnb upstream, so every response carries a dataFreshness envelope telling you whether to trust it. lastSyncedAt is the last import that actually landed data — a run that failed or was rate-limited never moves it. accounts[] gives the verdict per connected account; it is omitted when the workspace has no Airbnb account to attribute.
- Top-level
stale: truemeans every connected account is stale — nothing in the response is current. For the single-account workspace this is exactly the old behaviour. - Top-level
stale: falsewithreason: "partial_account_staleness"means some accounts are fine and some are not. The response is usable; readaccounts[]to see which rows to distrust. Thereasonis emitted deliberately even thoughstaleisfalse, so a consumer reading only the aggregate is never told everything is fine while one account is down. - Top-level
stale: falsewith noreasonmeans every account is current. - With
?account_id=,accounts[]holds exactly that account and the top-level fields mirror it. reasonis one ofhost_disconnected_since_<iso>,host_disconnected,host_not_activated,sync_lag_>_24h,never_synced, orpartial_account_staleness.fixUrlis the dashboard screen that resolves it.
{
"dataFreshness": {
"lastSyncedAt": "2026-09-18T04:12:09.000Z",
"stale": false,
"reason": "partial_account_staleness",
"fixUrl": "https://repull.dev/dashboard/connections",
"accounts": [
{
"accountId": "1000000000000000002",
"accountName": "Seaside Stays",
"lastSyncedAt": "2026-09-18T04:12:09.000Z",
"stale": false
},
{
"accountId": "80000001",
"accountName": "Casey",
"lastSyncedAt": null,
"stale": true,
"reason": "host_disconnected",
"fixUrl": "https://repull.dev/dashboard/connections"
}
]
}
}Example
curl -X PUT https://api.repull.dev/v1/channels/airbnb/reviews/181567 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"publicReview": "Joanne was a great guest.", "rating": 5, "isRevieweeRecommended": true}'Response
{
"id": "181567",
"externalReviewId": "1776076418138684216",
"submitted": true
}