Docs/Channels/Airbnb

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.

PUT/v1/channels/airbnb/reviews/:id

Parameters

idstringRequired

The review — its id or externalReviewId, both from GET /v1/reviews?reviewerRole=host.

publicReviewstringRequired

Your review of the guest, shown publicly on their Airbnb profile. Up to 1000 characters. comment is accepted as an alias.

ratinginteger

A 1–5 score used for every category you do not rate in categoryRatings. Send this, categoryRatings covering all three categories, or both.

categoryRatingsarray

Per-category scores: [{ "category": "cleanliness", "rating": 5, "comment": "…" }]. Categories are cleanliness, communication and respect_house_rules; comment is optional, up to 50 characters.

isRevieweeRecommendedbooleanRequired

Whether you would host this guest again. Airbnb requires it; Repull never assumes it.

privateFeedbackstring

A 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 rating to give all three the same score, categoryRatings to score them one by one, or both — rating fills any category you did not score individually. Each category can carry an optional comment of 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; expiresAt on the review is the deadline.
  • Anything missing is refused with 422 invalid_params naming 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; field names 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 (see expiresAt); 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 in message.

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: true means 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: false with reason: "partial_account_staleness" means some accounts are fine and some are not. The response is usable; read accounts[] to see which rows to distrust. The reason is emitted deliberately even though stale is false, so a consumer reading only the aggregate is never told everything is fine while one account is down.
  • Top-level stale: false with no reason means every account is current.
  • With ?account_id=, accounts[] holds exactly that account and the top-level fields mirror it.
  • reason is one of host_disconnected_since_<iso>, host_disconnected, host_not_activated, sync_lag_>_24h, never_synced, or partial_account_staleness. fixUrl is 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
}
AI