Conversations

Host-guest messaging threads linked to reservations.

List conversations

GET/v1/conversations

Request

curl https://api.repull.dev/v1/conversations?limit=20 \
  -H "Authorization: Bearer $REPULL_API_KEY"

Response

{
  "data": [
    {
      "id": "123",
      "platform": "airbnb",
      "guestId": "123",
      "listingId": "123",
      "reservationId": "123",
      "subject": "Example text",
      "lastMessageAt": "2026-06-01T12:00:00.000Z",
      "lastMessagePreview": "Example text",
      "unreadCount": 0,
      "status": "open",
      "createdAt": "2026-06-01T12:00:00.000Z",
      "updatedAt": "2026-06-01T12:00:00.000Z",
      "externalThreadId": "123"
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6MTIzfQ==",
    "hasMore": true,
    "total": 0
  }
}

Captured from a real response. Regenerated on every release, and CI fails if this shape stops matching the API.

Query Parameters

accountstring

Only one connected account's records, as `provider:externalAccountId` (e.g. `airbnb:79730216`, `booking:10167991`, `vrbo:36`, `hostaway:…`) — the pair on every webhook `account` block, on `connect.session.completed`, and on each record's `account`. See /for-platforms.

cursorstring

Opaque cursor returned in the previous response's `pagination.nextCursor`. Omit to fetch the first page.

offsetinteger

First-class alias for cursor-based pagination. Mutually exclusive with `cursor` — passing both returns 422. Accepts integers in `[0, 10000]`; deeper walks must use `cursor` (constant per-page cost). The response always includes `pagination.nextCursor` so consumers can switch from offset → cursor mid-walk for deep pagination without re-keying.

limitinteger

Max items per page. Hard cap is 100.

platformstring

Restrict to threads on a single channel.

statusstring

Filter by archive status. `archived` currently always returns an empty page — kept for forward-compat.

Get messages in a conversation

GET/v1/conversations/:id/messages

Request

curl https://api.repull.dev/v1/conversations/123/messages?limit=20 \
  -H "Authorization: Bearer $REPULL_API_KEY"

Response

{
  "data": [
    {
      "id": "123",
      "externalMessageId": "123",
      "direction": "outbound",
      "senderType": "user",
      "senderName": "Example",
      "senderAvatar": null,
      "channel": "airbnb",
      "body": "Example text",
      "translatedBody": null,
      "attachments": [],
      "isAutomated": false,
      "aiGenerated": false,
      "sentAt": "2026-06-01T12:00:00.000Z",
      "deliveredAt": "2026-06-01T12:00:00.000Z",
      "readAt": null
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6MTIzfQ==",
    "hasMore": true
  }
}

Captured from a real response. Regenerated on every release, and CI fails if this shape stops matching the API.

Send a message in a conversation

POST/v1/conversations/:id/messages

Note

Send `message`, `attachments`, or both. Airbnb takes JPEG/PNG/GIF/WebP/MP4/QuickTime and delivers each file as its own message; Booking.com takes JPEG/PNG on one message with text; SMS, email and site chat return 422 attachments_not_supported. The response then adds `attachments` and `parts`. On a conversation a connected PMS relays (Guesty, Hostaway), the message is sent through the PMS and `channel` may be the PMS's own name — Guesty airbnb2, platform, email, sms, whatsapp or note; Hostaway channel, email, sms, whatsapp — or one of Repull's, which the PMS maps. Neither PMS can send attachments: 422 pms_write_unsupported, nothing sent. Full rules: /docs/send-message#attachments, /docs/pms-writes#messages

Request

curl -X POST https://api.repull.dev/v1/conversations/123/messages \
  -H "Authorization: Bearer $REPULL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "message": "Welcome to your stay!"
}'

Response

{
  "id": "1847911",
  "conversationId": "164743",
  "externalMessageId": "32837172381",
  "channel": "airbnb",
  "status": "sent",
  "direction": "outbound",
  "contentRewritten": false,
  "submittedContent": "Your check-in details are ready.",
  "deliveredContent": "Your check-in details are ready.",
  "statusReason": null,
  "attachments": []
}

Body Parameters

messagestring

The text to send the guest, up to 4000 characters. Required unless you send attachments, and always required on Booking.com.

channelstring

Force a channel. Omit to send on whichever channel the conversation already uses, which is the right default. `airbnb`, `booking`, `vrbo`, `sms`, `email` or `website` — or, on a conversation a connected PMS relays, the PMS's own channel name.

attachmentsarray

1 to 5 files as `{ url, contentType?, filename? }`. Each `url` must be a public https URL, up to 10 MB. Supported on Airbnb and Booking.com; other channels return 422 attachments_not_supported and nothing is sent.

List Airbnb inquiries

GET/v1/inquiries

Note

Guests asking about dates before booking, newest first. Answer with the pre-approval or special-offer endpoints using conversationId. Booking requests are not inquiries: they are reservations with status pending. Guide: /docs/channels/airbnb/inquiries-and-requests

Request

curl https://api.repull.dev/v1/inquiries?status=open \
  -H "Authorization: Bearer $REPULL_API_KEY"

Response

{
  "data": [
    {
      "id": "25173",
      "conversationId": "164743",
      "listingId": "23892",
      "channel": "airbnb",
      "status": "open",
      "checkIn": "2026-10-01",
      "checkOut": "2026-10-05",
      "guests": { "total": 2, "adults": 2, "children": 0, "infants": 0, "pets": 0 },
      "expectedPayout": { "amount": 812.4, "currency": "USD" },
      "reservationId": null,
      "relayedBy": null,
      "respondBy": "2026-09-23T17:40:38.000Z",
      "respondedAt": null,
      "createdAt": "2026-09-22T17:40:38.000Z",
      "updatedAt": "2026-09-22T17:40:38.000Z"
    }
  ],
  "pagination": { "nextCursor": null, "hasMore": false }
}

Query Parameters

statusstringDefault: open

open, pre_approved, special_offer_sent, booked, expired, declined, not_possible, or all.

listing_idinteger

Only inquiries about this Repull listing.

conversation_idinteger

The inquiry on one conversation. Combine with status=all.

limitintegerDefault: 50

Max 100.

cursorstring

pagination.nextCursor from the previous page.

offsetinteger

Alias for shallow paging, 0 to 10000. Mutually exclusive with cursor.

include_totalboolean

Adds pagination.total.

Pre-approve an inquiry (Airbnb, VRBO, or through a PMS)

POST/v1/conversations/:id/pre-approval

Note

Airbnb (listings connected directly) and VRBO. The guest may book the dates they asked about at the listed price. An inquiry relayed by a connected PMS is pre-approved in that PMS and the response carries `pms`: Guesty pre-approves Airbnb inquiries (blockInstantBooking: true is refused — Guesty has no such switch); Hostaway has no pre-approval API (422 pms_write_unsupported). capabilities.pms.reservations.preapprove on GET /v1/connect/{provider} says so beforehand. Guides: /docs/channels/airbnb/inquiries-and-requests, /docs/pms-writes#requests

Request

curl -X POST https://api.repull.dev/v1/conversations/164743/pre-approval \
  -H "Authorization: Bearer $REPULL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Response

{
  "conversationId": "164743",
  "status": "pre_approved",
  "blockInstantBooking": false,
  "expiresAt": "2026-09-23T17:40:38Z"
}

Body Parameters

blockInstantBookingboolean

true makes the guest book through the pre-approval rather than Instant Book. Default false. The body is optional.

Send an Airbnb special offer

POST/v1/conversations/:id/special-offers

Note

The guest has 24 hours to book it. Airbnb refusals are 422 airbnb_rejected with Airbnb's reason. Guide: /docs/channels/airbnb/inquiries-and-requests

Request

curl -X POST https://api.repull.dev/v1/conversations/164743/special-offers \
  -H "Authorization: Bearer $REPULL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "checkIn": "2026-10-01",
  "checkOut": "2026-10-05",
  "guests": { "adults": 2, "children": 1 },
  "totalPrice": 880
}'

Response

{
  "id": "1459920384",
  "conversationId": "164743",
  "status": "active",
  "listingId": "23892",
  "airbnbListingId": "955656266214757921",
  "checkIn": "2026-10-01",
  "checkOut": "2026-10-05",
  "nights": 4,
  "guests": { "total": 3, "adults": 2, "children": 1, "infants": 0, "pets": 0 },
  "totalPrice": 880,
  "createdAt": "2026-09-22T18:00:00Z",
  "expiresAt": "2026-09-23T18:00:00Z"
}

Body Parameters

checkIndateRequired

YYYY-MM-DD.

checkOutdateRequired

YYYY-MM-DD, after checkIn.

guestsobjectRequired

{ adults (at least 1), children?, infants?, pets? }.

totalPricenumberRequired

Total for the whole stay, in the listing's Airbnb currency.

listingIdinteger

Repull listing id to offer. Omit to offer the listing the guest asked about.

Get a special offer

GET/v1/conversations/:id/special-offers/:offerId

Note

Read live from Airbnb. Same shape as the create response. The offer must belong to the conversation in the path.

Request

curl https://api.repull.dev/v1/conversations/164743/special-offers/1459920384 \
  -H "Authorization: Bearer $REPULL_API_KEY"

Response

{
  "id": "1459920384",
  "conversationId": "164743",
  "status": "active",
  "listingId": "23892",
  "airbnbListingId": "955656266214757921",
  "checkIn": "2026-10-01",
  "checkOut": "2026-10-05",
  "nights": 4,
  "guests": { "total": 3, "adults": 2, "children": 1, "infants": 0, "pets": 0 },
  "totalPrice": 880,
  "createdAt": "2026-09-22T18:00:00Z",
  "expiresAt": "2026-09-23T18:00:00Z"
}

Withdraw a special offer

DELETE/v1/conversations/:id/special-offers/:offerId

Note

Only an offer the guest has not booked. A booked or expired one is 409 inquiry_no_longer_open.

Request

curl -X DELETE https://api.repull.dev/v1/conversations/164743/special-offers/1459920384 \
  -H "Authorization: Bearer $REPULL_API_KEY"

Response

{ "id": "1459920384", "conversationId": "164743", "status": "withdrawn" }
AI