Docs/Documentation/Guest data

Reviews

Every review on Airbnb and Booking.com in one API: read them in one feed, reply to what guests wrote, and write your review of a guest — without knowing which channel a review came from. Both directions (guest → host and host → guest) come back in the same shape.

What you get

  • One feed across channels— Airbnb and Booking reviews land in the same shape. Filter by platform when you only care about one.
  • Both directions — reviewerRole distinguishes guest reviews (about your listing) from host reviews (about the guest).
  • Sub-category ratings— cleanliness, communication, accuracy, etc. surfaced in categories[]. Categories vary by channel.
  • Response visibility — the response block shows whether a host reply has been posted and when. Filter status=unansweredto triage.
  • Reply and review from here — reply to a review on any channel, and review a guest (Airbnb), with the same review id.
  • Review window — expires_at tells you when the channel-side response deadline closes (Airbnb is 14 days post-checkout).

How channels get into this feed

A review only shows up here once the channel itself is connected. See Connect (multi-channel) for how Airbnb and Booking accounts get linked. After connection, the per-channel backfill runs on a schedule — expect first reviews within minutes.

List reviews

Cursor-paginated. Pass the previous response's pagination.nextCursorback as cursor on the next call.

GET/v1/reviews
curl 'https://api.repull.dev/v1/reviews?platform=airbnb&listingId=4118&rating_min=1&rating_max=3&status=unanswered' \
  -H 'Authorization: Bearer sk_live_...'

Query parameters

limitintegerDefault: 25

Max reviews per page. 1-100; over 100 returns 422.

cursorstring

Opaque cursor from the previous response. Omit on the first call.

platformstring

Filter by source channel. One of airbnb or booking.

listingIdinteger

Only return reviews attached to this listing.

rating_minnumber

Inclusive lower bound on the overall rating (typically 1..5).

rating_maxnumber

Inclusive upper bound on the overall rating.

statusstring

Response status. One of responded, unanswered, all. Defaults to all.

reviewerRolestringDefault: guest

Direction. One of guest (about your property), host (about the guest), all.

Response

{
  "data": [
    {
      "id": 778201,
      "external_id": "abnb_rev_xyz789",
      "platform": "airbnb",
      "listing_id": 4118,
      "reservation_id": 216039,
      "reservation_confirmation_code": "HMABCDE123",
      "guest_id": 88341,
      "guest_name": "Sarah Mitchell",
      "guest_avatar": "https://images.repull.dev/g/88341.jpg",
      "reviewer_role": "guest",
      "rating": 2,
      "categories": [
        { "category": "cleanliness",   "rating": 1, "comment": "Found hair in the bathroom." },
        { "category": "communication", "rating": 5, "comment": null },
        { "category": "accuracy",      "rating": 3, "comment": null },
        { "category": "location",      "rating": 5, "comment": null },
        { "category": "check_in",      "rating": 4, "comment": null },
        { "category": "value",         "rating": 2, "comment": null }
      ],
      "public_review": "Beautiful location but the cleaning was below standard.",
      "private_feedback": "The bathroom needs a deeper clean — found hair in the shower.",
      "is_reviewee_recommended": null,
      "response": null,
      "submitted_at": "2026-04-29T11:14:09.000Z",
      "updated_at": "2026-04-29T11:14:09.000Z",
      "expires_at": "2026-05-13T11:14:09.000Z",
      "hidden": false
    },
    {
      "id": 778202,
      "external_id": "abnb_rev_xyz790",
      "platform": "airbnb",
      "listing_id": 4118,
      "reservation_id": 216039,
      "reservation_confirmation_code": "HMABCDE123",
      "guest_id": 88341,
      "guest_name": "Sarah Mitchell",
      "guest_avatar": "https://images.repull.dev/g/88341.jpg",
      "reviewer_role": "host",
      "rating": null,
      "categories": [],
      "public_review": "Sarah was a respectful guest and left the place tidy.",
      "private_feedback": null,
      "is_reviewee_recommended": true,
      "response": null,
      "submitted_at": "2026-04-29T09:02:11.000Z",
      "updated_at": "2026-04-29T09:02:11.000Z",
      "expires_at": null,
      "hidden": false
    }
  ],
  "pagination": {
    "nextCursor": "eyJsYXN0SWQiOjc3ODIwMn0",
    "hasMore": true
  }
}
idinteger

Stable internal review id. Pass to /v1/reviews/{id}.

external_idstring

ID in the source channel. Stable across syncs.

platformstringnullable

airbnb or booking.

listing_idintegernullable

Internal listing id this review is attached to.

reservation_idintegernullable

Internal reservation id this review is about.

reservation_confirmation_codestringnullable

Channel-side confirmation code for the reservation.

guest_idintegernullable

Internal guest id.

guest_namestringnullable

Display name of the guest.

reviewer_rolestring

guest (review of host/property) or host (review of guest).

ratingnumbernullable

Overall rating on the platform's scale (typically 1..5). Null for review types without a numeric overall.

categoriesarray

Sub-category ratings — cleanliness, communication, accuracy, etc. Categories vary by channel.

public_reviewstringnullable

Public-facing text shown on the listing page.

private_feedbackstringnullable

Private feedback the reviewer sent only to the host.

is_reviewee_recommendedbooleannullable

Whether the reviewer recommended the reviewee. Used on host-side reviews.

responseobject

Host response when present.

submitted_atstringnullable

ISO 8601 timestamp the review was submitted on the channel.

expires_atstringnullable

When the channel response window closes (Airbnb: 14 days).

hiddenboolean

True when the review is hidden from public view on the channel.

Get one review

Returns a single Review wrapped in { data }. Use after a list call to refresh a single record (for example, when polling for a host response that just landed).

GET/v1/reviews/{id}
curl 'https://api.repull.dev/v1/reviews/778201' \
  -H 'Authorization: Bearer sk_live_...'

Response

{
  "data": {
    "id": 778201,
    "external_id": "abnb_rev_xyz789",
    "platform": "airbnb",
    "listing_id": 4118,
    "reservation_id": 216039,
    "reservation_confirmation_code": "HMABCDE123",
    "guest_id": 88341,
    "guest_name": "Sarah Mitchell",
    "guest_avatar": "https://images.repull.dev/g/88341.jpg",
    "reviewer_role": "guest",
    "rating": 2,
    "categories": [
      { "category": "cleanliness", "rating": 1, "comment": "Found hair in the bathroom." }
    ],
    "public_review": "Beautiful location but the cleaning was below standard.",
    "private_feedback": "The bathroom needs a deeper clean — found hair in the shower.",
    "is_reviewee_recommended": null,
    "response": {
      "body": "Sarah, thank you for the feedback — we've already retrained the cleaning team. Hope you'll give us another chance.",
      "submitted_at": "2026-04-30T08:14:00.000Z"
    },
    "submitted_at": "2026-04-29T11:14:09.000Z",
    "updated_at": "2026-04-30T08:14:00.000Z",
    "expires_at": "2026-05-13T11:14:09.000Z",
    "hidden": false
  }
}

Reply to a review

Posts your public reply under a review a guest wrote about you (reviewerRole: "guest"). Works on Airbnb and Booking.com — Repull reads the review's channel and sends the reply there. Each channel accepts one reply per review.

POST/v1/reviews/{id}/reply

Body

messagestringRequired

Your reply, shown publicly under the guest's review. response is accepted as an alias.

curl -X POST 'https://api.repull.dev/v1/reviews/778201/reply'   -H 'Authorization: Bearer sk_live_...'   -H 'Content-Type: application/json'   -d '{"message": "Thank you for staying with us — come back any time!"}'
  • 201 — the reply was posted on the channel.
  • 422 unsupported_channel — the review is from a channel without a reply API; the error names the channels that work.
  • 403 listing_inactive — the review's listing is inactive; nothing was sent.
  • 422 pms_write_unsupported — the review came through a PMS whose API cannot reply (Hostaway); reply in the PMS.

Reviews from a connected PMS. A review read through Guesty or Hostaway carries pms ("guesty", "hostaway"); platform is still the channel the guest wrote it on. Replying to it goes through that PMS, and the response names it in pms. Guesty replies on Airbnb and Booking.com reviews; Hostaway's review API is read-only. See Writing through a PMS.

Review a guest

After checkout Airbnb opens a review of the guest for you. It is listed here with reviewerRole: "host" and an expiresAt deadline (14 days after checkout). Booking.com does not let hosts review guests.

Submitting publishes it, and it is final

Airbnb has no draft and does not allow edits. A second submission is refused with 409 review_already_submitted. Send it when it is ready.
POST/v1/reviews/{id}/guest-review

Body

publicReviewstringRequired

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

isRevieweeRecommendedbooleanRequired

Whether you would host this guest again. Airbnb requires it, and Repull never fills it in for you.

ratinginteger

A 1–5 score used for every category you do not rate in categoryRatings.

categoryRatingsarray

Per-category scores, each { category, rating, comment? }. Categories are cleanliness, communication and respect_house_rules; all three must end up rated, by here or by rating. comment is optional, up to 50 characters.

privateFeedbackstring

A note to the guest that is not published. Up to 1000 characters.

curl -X POST 'https://api.repull.dev/v1/reviews/181567/guest-review'   -H 'Authorization: Bearer sk_live_...'   -H 'Content-Type: application/json'   -d '{
    "publicReview": "Joanne was a great guest — the place was left spotless.",
    "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
  }'
  • 200 — published on Airbnb.
  • 422 invalid_params — a required piece is missing; field names it. Nothing was sent, so the review is not used up.
  • 422 unsupported_channel — the review is not from Airbnb.
  • 409 review_already_submitted, 409 review_window_closed, 409 not_host_review — already submitted, past expiresAt, or the id is the guest's review of you (reply to it instead).

What each rating means, and how both reviews stay hidden until both sides submit: Airbnb reviews.

What each channel supports

ChannelReadReplyReview a guest
AirbnbYesYesYes
Booking.comYesYesNo — Booking.com has no host reviews
Guesty (PMS)YesYes — Airbnb and Booking.com reviewsNo
Hostaway (PMS)YesNo — Hostaway's review API is read-onlyNo

Common patterns

Unresponded reviews to follow up

Filter status=unanswered(guest-written reviews are the default — host-side reviews don't get host responses). Walk all pages, sort by expiresAtto triage the ones closest to the channel deadline.

import type { Review } from '@repull/sdk'

const queue: Review[] = []
let cursor: string | undefined

// reviewerRole defaults to guest — host-side reviews don't get host responses
do {
  const page = await repull.reviews.list({
    status: 'unanswered',
    cursor,
    limit: 100,
  })
  queue.push(...page.data)
  cursor = page.pagination.nextCursor ?? undefined
} while (cursor)

queue.sort((a, b) => (a.expiresAt ?? '').localeCompare(b.expiresAt ?? ''))

for (const r of queue.slice(0, 20)) {
  console.log(r.id, r.platform, '— expires', r.expiresAt, '—', r.publicReview?.slice(0, 60))
}

Low-rating reviews this month

Filter on rating_max=3 (guest-written reviews are the default), then filter on submittedAt client-side to scope to the current month. Useful for an ops-quality dashboard.

import type { Review } from '@repull/sdk'

const startOfMonth = new Date()
startOfMonth.setUTCDate(1)
startOfMonth.setUTCHours(0, 0, 0, 0)
const cutoff = startOfMonth.toISOString()

const lowRatings: Review[] = []
let cursor: string | undefined

do {
  const page = await repull.reviews.list({
    rating_max: 3,
    cursor,
    limit: 100,
  })
  for (const r of page.data) {
    if (r.submittedAt && r.submittedAt >= cutoff) lowRatings.push(r)
  }
  cursor = page.pagination.nextCursor ?? undefined
} while (cursor)

console.log(lowRatings.length, 'low-rating reviews this month')

Average rating per listing

Pull guest-side reviews for one listing across all channels and average the ratings (skip nulls — some review types don't score numerically).

// The listing filter is query-string only (`listingId`), so this pattern
// calls the API directly.
const headers = { Authorization: `Bearer ${process.env.REPULL_API_KEY}` }
const reviews: Array<{ rating: number | null }> = []
let cursor: string | null = null

do {
  const url = new URL('https://api.repull.dev/v1/reviews')
  url.searchParams.set('listingId', '4118')
  url.searchParams.set('limit', '100')
  if (cursor) url.searchParams.set('cursor', cursor)

  const page = await fetch(url, { headers }).then((r) => r.json())
  reviews.push(...page.data)
  cursor = page.pagination.nextCursor
} while (cursor)

const scored = reviews.filter((r) => r.rating != null)
const avg = scored.reduce((a, r) => a + (r.rating as number), 0) / scored.length

console.log('Listing 4118 average:', avg.toFixed(2), 'across', scored.length, 'reviews')
  • Connect (multi-channel) — how Airbnb and Booking accounts get linked so reviews start syncing.
  • Conversations — jump to the message thread for the reservation a review is about via reservation_id.
  • Guests — pull the full profile of the guest who left the review via guest_id.
AI