Docs/Documentation/Guest data

Reviews

Cross-channel guest and host reviews — Airbnb, Booking.com, VRBO — in one unified feed. Filter by platform, listing, rating range, response status, or reviewer role. Both directions (guest → host and host → guest) are returned through the same shape.

What you get

  • One feed across channels— Airbnb, Booking, VRBO reviews land in the same shape. Filter by platform when you only care about one.
  • Both directionsreviewer_role 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=needs_responseto triage.
  • Review windowexpires_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 / Booking / VRBO 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.next_cursorback as cursor on the next call.

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

Query parameters

limitintegerDefault: 50

Max reviews per page. 1-200.

cursorstring

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

platformstring

Filter by source channel. One of airbnb, booking, vrbo.

listing_idinteger

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 needs_response, responded, all. Defaults to all.

reviewer_rolestring

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

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": {
    "next_cursor": "eyJsYXN0SWQiOjc3ODIwMn0",
    "has_more": true
  }
}
idinteger

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

external_idstring

ID in the source channel. Stable across syncs.

platformstringnullable

airbnb, booking, vrbo.

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
  }
}

Common patterns

Unresponded reviews to follow up

Filter status=needs_response and reviewer_role=guest— host-side reviews don't get host responses. Walk all pages, sort by expires_atto triage the ones closest to the channel deadline.

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

do {
  const page = await repull.reviews.list({
    status: 'needs_response',
    reviewer_role: 'guest',
    cursor: cursor ?? undefined,
    limit: 200,
  })
  queue.push(...page.data)
  cursor = page.pagination.next_cursor
} while (cursor)

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

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

Low-rating reviews this month

Combine rating_max=3 with reviewer_role=guest, then filter on submitted_at client-side to scope to the current month. Useful for an ops-quality dashboard.

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

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

do {
  const page = await repull.reviews.list({
    rating_max: 3,
    reviewer_role: 'guest',
    cursor: cursor ?? undefined,
    limit: 200,
  })
  for (const r of page.data) {
    if (r.submitted_at && r.submitted_at >= cutoff) lowRatings.push(r)
  }
  cursor = page.pagination.next_cursor
} 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).

const reviews: Review[] = []
let cursor: string | null | undefined

do {
  const page = await repull.reviews.list({
    listing_id: 4118,
    reviewer_role: 'guest',
    cursor: cursor ?? undefined,
    limit: 200,
  })
  reviews.push(...page.data)
  cursor = page.pagination.next_cursor
} 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 / Booking / VRBO 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