Provider Capability Matrix
Exactly what each channel supports today, capability by capability, with a real request for every supported operation. Honest cells: supported means shipped and callable, partial means it works with caveats, unsupported means the route returns 501 or has no write path yet.
| Capability | Airbnb | Booking.com |
|---|---|---|
| Connect (hosted flow) | Supported | Supported |
| Listing discovery (read) | Supported | Supported |
| Create a new channel listing | Supported Publish with hostId | Partial Connect (claim) then publish |
| Link an existing channel listing | Supported POST …/listings/map | Partial Room mapping in Connect |
| Content update (canonical PATCH) | Unsupported PATCH only toggles active | Unsupported PATCH only toggles active |
| Publish / push listing content | Partial Pushes already-owned content | Partial Needs existing mapping |
| Granular content read | Supported amenities · descriptions · rooms · settings · quality | Partial Single content GET |
| Granular content write | Supported rooms · photos · check-in guide | Partial Push via …/content |
| Availability write (per-channel) | Supported | Supported |
| Pricing write (per-channel) | Supported | Supported |
| Availability read (generic) | Supported Projected from Airbnb calendar | Partial Default calendar until synced |
| Generic ARI write (/v1/availability) | Unsupported 501 — use per-channel | Unsupported 501 — use per-channel |
| Reservations (read) | Supported | Supported |
| Reservation alterations | Supported List · read · create | Unsupported |
| Reservation acknowledge | Unsupported | Supported Clear the new-queue |
| Messaging | Supported Read thread · edit/react/mark | Partial List / send only |
| Special offers & pre-approvals | Supported Create · withdraw | Unsupported |
| Financials | Partial Transactions mirror not yet synced | Supported Charges read + write |
| Webhooks | Supported | Supported Per-channel CNS subscriptions |
| Usage & quota | Supported Account-level — all channels | Supported Account-level — all channels |
Read-only & specialty channels
Two more providers are live with a narrower surface. Plumguide exposes reads plus availability/pricing writes and a webhook config. VRBO is connect + read-only today — no content, mapping, or pricing writes.
| Capability | Plumguide | VRBO |
|---|---|---|
| Connect (hosted flow) | Supported | Supported |
| Listing discovery (read) | Supported GET …/plumguide/listings | Supported GET …/vrbo/listings |
| Reservations / bookings (read) | Supported GET …/plumguide/bookings | Supported GET …/vrbo/reservations |
| Availability write | Supported PUT …/plumguide/availability | Unsupported |
| Pricing write | Supported PUT …/plumguide/pricing | Unsupported |
| Content / mapping write | Unsupported | Unsupported Read-only today |
| Webhooks | Supported GET · PUT (replace) · DELETE | Unsupported |
One id everywhere
4118). Channel-specific ids (Airbnb airbnbId/hostId, Booking hotelId) live under it — see IDs & External IDs.Connect
Both providers connect through the same hosted flow. Start a session, redirect the user to the returned connect.repull.dev URL, then confirm with the status endpoint.
POST /v1/connect/airbnb # or /v1/connect/booking
{ "redirectUrl": "https://your-app.com/done", "accessType": "full_access" }
# →
{
"url": "https://connect.repull.dev/cs_abc123",
"provider": "airbnb",
"sessionId": "cs_abc123",
"expiresAt": "2026-07-30T12:00:00.000Z"
}
# Confirm
GET /v1/connect/airbnb
{ "connected": true, "provider": "airbnb", "id": 12, "status": "active", "externalAccountId": "112233" }Listing discovery (read)
Read the listings each connection exposes. Both are pure reads — no live provider call.
Airbnb — supported
GET /v1/channels/airbnb/listings
{
"data": [
{
"listingId": 4118,
"name": "R-Sable 1302",
"connections": [
{ "id": 55, "airbnbId": "12345678", "hostId": "112233", "active": true, "primary": true }
]
}
],
"pagination": { "next_cursor": null, "has_more": false, "total": 1 }
}Booking.com — supported
Returns a bare array (no data wrapper).
GET /v1/channels/booking/properties
[
{
"listingId": 4118,
"name": "R-Sable 1302",
"connections": [
{ "id": 12, "hotelId": "998877", "active": true, "syncCategory": "full" }
]
}
]Create vs link a channel listing
Two different operations, often confused. Create makes a brand-new listing on the channel from a canonical Repull property. Link points an existing channel listing (one the host already has on Airbnb or Booking.com) at a canonical Repull property so the two are kept in sync. Pick by which side the listing already exists on.
| You want to… | Airbnb | Booking.com |
|---|---|---|
| Create a new channel listing from a Repull property | POST /v1/listings/{id}/publish/airbnb with hostId | Run Connect (claim) to open the property, then POST /v1/listings/{id}/publish/booking |
| Link an existing channel listing to a Repull property | POST /v1/channels/airbnb/listings/map | POST /v1/connect/booking/map-rooms (during Connect) |
Create — Airbnb supported
Publish in create mode — pass a hostId (no airbnbConnectionId) and Repull creates a brand-new Airbnb listing under that host, pushing the content it already holds for the property. Pass exactly one of the two: with hostId it creates, with airbnbConnectionId it updates an existing listing (see Publish below).
POST /v1/listings/4118/publish/airbnb
{ "hostId": "112233" } // create — omit airbnbConnectionId
# →
{ "listingId": 4118, "channel": "airbnb", "result": { /* push result */ } }Create — Booking.com partial
Booking has no single create call. First run the Connect (claim) flow, which opens the property on Booking and maps its rooms to your Repull listings, then publish to push descriptions, amenities, and photos.
# 1. Connect (claim) — opens the Booking property + maps rooms
# 2. Push content to the now-mapped listing:
POST /v1/listings/4118/publish/booking
# → { "listingId": 4118, "channel": "booking", "result": { /* ... */ } }Link — Airbnb supported
The host already has this listing on Airbnb and you discovered it via GET /v1/channels/airbnb/listings (which surfaces airbnbId + hostId per connection). Link points that Airbnb listing at the canonical Repull listingId of your choice — the consolidation / de-dup case. API-key-scoped, and idempotent (re-linking to the same listing is a 200 no-op).
POST /v1/channels/airbnb/listings/map
{ "airbnbId": "12345678", "listingId": 4118 } // optional "hostId", "syncEnabled"
# →
{ "success": true, "alreadyMapped": false, "airbnbId": "12345678",
"listingId": 4118, "previousListingId": 9001, "platformLinkId": 77 }Link — Booking.com rooms partial
During Connect you map Booking.com room types to Repull listings. This is session-scoped — the sessionId from the connect flow authorizes the call (no API key). Set listingId: null to unmap a room.
POST /v1/connect/booking/map-rooms
{
"sessionId": "cs_abc123",
"mappings": [
{ "roomId": 456, "listingId": 4118 },
{ "roomId": 457, "listingId": null }
]
}
# →
{ "success": true, "mapped": 2, "sessionId": "cs_abc123", "connectionId": 12 }Link is not a generic mapping write
…/listings/map); Booking links its rooms inside the Connect session. Each targets the canonical Repull listingId.Listing content
Canonical content update is unsupported on both
PATCH /v1/listings/{id} only toggles the active flag — it does not write title, description, amenities, or photos. There is no unified content-update endpoint yet.PATCH /v1/listings/4118
{ "active": false }
# →
{ "id": 4118, "active": false }Airbnb publish — partial
Publishing pushes content Repull already holds for the listing to Airbnb. The request carries no content — only which connection (or host) to publish under. Provide exactly one of airbnbConnectionId (update an existing listing) or hostId (create a new one).
POST /v1/listings/4118/publish/airbnb
{ "airbnbConnectionId": 55 } // or { "hostId": "112233" }, optional "force": true
# →
{ "listingId": 4118, "channel": "airbnb", "result": { /* push result */ } }Airbnb channel action endpoint is not implemented
POST /v1/channels/airbnb/listings/{id} for push / publish / unlist returns 501 not_implemented. Use the publish endpoint above.Booking.com content — partial
Booking content pushes require a pre-existing Booking mapping and target a specific property_id (hotel id), optionally a room_id.
POST /v1/channels/booking/content
{ "type": "descriptions", "property_id": "998877", "room_id": 456, "descriptions": { /* ... */ } }
# →
{ "updated": true }
# Go live for an already-mapped listing (no body):
POST /v1/listings/4118/publish/booking
# → { "listingId": 4118, "channel": "booking", "result": { /* ... */ } }Granular Airbnb content — supported
Airbnb content is addressable field-group by field-group. The read side is a pure DB read from the local mirrors (no upstream call); the write side (rooms, photos, check-in guide) calls Airbnb upstream and reconciles the mirror via the sync worker. All target the canonical Repull listingId.
# Reads (pure DB — never call Airbnb) GET /v1/channels/airbnb/listings/4118/amenities # regular + accessibility amenities GET /v1/channels/airbnb/listings/4118/descriptions GET /v1/channels/airbnb/listings/4118/settings GET /v1/channels/airbnb/listings/4118/rooms GET /v1/channels/airbnb/listings/4118/checkin-guide # ?locale=en GET /v1/channels/airbnb/listings/4118/checkout-guide GET /v1/channels/airbnb/listings/4118/quality # ?type=all|standards|issues|stats # Writes (call Airbnb upstream) POST /v1/channels/airbnb/listings/4118/rooms # body = room object (minus room_id) DELETE /v1/channels/airbnb/listings/4118/rooms?roomId=456 PUT /v1/channels/airbnb/listings/4118/checkin-guide?locale=en DELETE /v1/channels/airbnb/listings/4118/photos?photoId=789
Booking content is coarser
GET/POST /v1/channels/booking/content endpoint (typed by type), not per-field-group routes — see Listing content above.Availability & pricing (ARI)
Availability read (generic) — supported
One channel-agnostic read returns a day-by-day calendar for a property. from and to (ISO YYYY-MM-DD) are required. The calendar is projected from the connected channel (currently Airbnb).
GET /v1/availability/4118?from=2026-08-01&to=2026-08-07
{
"propertyId": 4118,
"calendar": [
{ "date": "2026-08-01", "available": true, "price": 245, "minNights": 2 }
]
}Default calendar until a channel calendar is present
price/minNightsas “not yet synced,” not as authoritative.Generic ARI write is unsupported
PUT against the generic route is not implemented. Use the per-channel routes below.Airbnb — supported
A calendar operation sets availability, nightly price, and stay limits together. Note the real field names: availability is a tri-state enum (not a boolean), price is daily_price, and stay limits are min_nights/max_nights. Airbnb also supports closed_to_arrival/closed_to_departure.
PUT /v1/channels/airbnb/listings/4118/availability
{
"type": "calendar",
"operations": [
{
"start_date": "2026-08-01",
"end_date": "2026-08-07",
"availability": "available", // "available" | "unavailable" | "default"
"daily_price": 245,
"min_nights": 2,
"closed_to_arrival": false
}
]
}Pricing has its own typed endpoint (base price, length-of-stay, rate plans, fees):
PUT /v1/channels/airbnb/listings/4118/pricing
{ "type": "standard", "daily_price": 245, "currency": "USD" }Booking.com — supported
Booking's per-channel schema is the only one that carries maxStay and closed-to-arrival / closed-to-departure restrictions. Updates target the property_id (hotel id) and its rooms.
PUT /v1/channels/booking/availability
{
"type": "availability",
"property_id": "998877",
"updates": [
{
"roomId": 456,
"dateRange": { "start": "2026-08-01", "end": "2026-08-07" },
"roomsToSell": 3,
"restrictions": {
"minStay": 2,
"maxStay": 14,
"closedToArrival": false,
"closedToDeparture": false
}
}
]
}PUT /v1/channels/booking/listings/4118/pricing
{
"updates": [
{ "roomId": 456, "rateId": 789, "dateRange": { "start": "2026-08-01", "end": "2026-08-07" }, "price": 245, "currency": "USD" }
]
}
# →
{ "hotel_id": "998877", "listing_id": 4118, "pushed": 3, "requested": 3, "errors": [] }Field support differs by channel
closed_to_arrival/closed_to_departure per calendar operation but no max-stay ceiling in the same shape).Reservations (read)
Reservations from both channels read through the same normalized shape. Booking.com also exposes an acknowledge write — POST /v1/channels/booking/reservations with a reservation_ids array clears them from the new queue (acknowledge only after you have durably persisted each one). The read side is GET /v1/channels/booking/reservations.
GET /v1/reservations
{
"data": [
{
"id": 214303,
"listingId": 4118,
"checkIn": "2026-08-01",
"checkOut": "2026-08-05",
"status": "confirmed",
"platform": "airbnb",
"confirmationCode": "HMABC123",
"totalPrice": "540.00",
"currency": "USD",
"primaryGuest": { "firstName": "Jane", "lastName": "Doe", "language": "en" },
"createdAt": "2026-01-01T00:00:00.000Z",
"bookedAt": "2026-01-01T00:00:05.000Z"
}
],
"pagination": { "next_cursor": null, "has_more": false }
}No updatedAt — reconcile carefully
createdAt and bookedAt, but no updatedAt or revision field. You cannot detect a change by polling a timestamp — reconcile against the reservation.updated webhook or re-fetch and diff against your last-known state.Reservation alterations (Airbnb)
List, read, and create alteration requests (change dates, guest count, or price) on an Airbnb reservation. Create is write-side and calls Airbnb upstream; it needs a connected Airbnb host, else 404 no_connection. Booking.com has no equivalent alteration route.
GET /v1/channels/airbnb/alterations # list
GET /v1/channels/airbnb/alterations/{id} # single
POST /v1/channels/airbnb/alterations
{ "reservationCode": "HMABC123", "startDate": "2026-08-02", "endDate": "2026-08-06", "guests": 3 }Messaging
Read an Airbnb thread (pure DB read from the local mirror), then act on a single message. The message PATCH is write-side — the action discriminator selects edit / unsend / read / react. Booking.com exposes list/send via GET/POST /v1/channels/booking/messaging.
GET /v1/channels/airbnb/messaging/{threadId} # read a thread
PATCH /v1/channels/airbnb/messaging/{threadId}/messages/{messageId}
{ "action": "edit", "message": "Updated reply text" } # or unsend | read | reactSpecial offers & pre-approvals (Airbnb)
Create a special offer or pre-approve an inquiry thread, and withdraw an offer. Both are write-side Airbnb calls. The type discriminator picks offer (custom terms) or preapproval (needs threadId).
POST /v1/channels/airbnb/offers
{ "type": "offer", "reservationCode": "HMABC123", "price": 220, "currency": "USD" }
# preapproval: { "type": "preapproval", "threadId": "..." }
DELETE /v1/channels/airbnb/offers?offerId=555Financials
Airbnb host transactions (payouts, adjustments, resolutions) and Booking.com extra-charge sets (cleaning fee, resort fee, city tax…).
Airbnb transactions — partial
Pure DB read — customer-facing reads never call Airbnb upstream. The transactions mirror is not yet synced into this surface, so today it returns an empty array with data_freshness.stale = true. The route is live and shape-stable; data lands once the mirror syncs.
GET /v1/channels/airbnb/transactions
{ "data": [], "data_freshness": { "stale": true } }Booking.com charges — supported
Read and replace the extra-charge set for a property. PUT is a full replacement — include every charge you want to keep. property_id is required.
GET /v1/channels/booking/charges?property_id=998877
PUT /v1/channels/booking/charges
{ "property_id": "998877", "charges": [ { "type": "cleaning_fee", "amount": 60, "currency": "USD" } ] }Booking.com property & setup
Read the Booking.com connection record(s) for a Repull listing, and drive property onboarding through the setup action-router (legal entity, status polling, and the remaining onboarding steps).
GET /v1/channels/booking/properties/{id} # connection record(s) for a Repull listing
POST /v1/channels/booking/setup
{ "action": "create-legal-entity", /* … */ } # or check-legal-status (?leid=…), etc.Webhooks
The canonical, cross-channel subscription is /v1/webhooks: both channels emit account.* and reservation.* events. Subscribe with the events array (not event_types). Deliveries are signed Stripe-style and retried up to 5 times over 7 days.
POST /v1/webhooks
{
"url": "https://your-app.com/webhooks",
"events": ["reservation.created", "reservation.updated", "account.disconnected"]
}Per-channel webhook config
Some channels also carry their own native notification config, managed directly:
# Booking.com CNS subscriptions (per notification type)
GET /v1/channels/booking/webhooks
POST /v1/channels/booking/webhooks { "notification_type": "...", "callback_url": "https://..." }
DELETE /v1/channels/booking/webhooks
# Plumguide webhook config (PUT is a full replace)
GET /v1/channels/plumguide/webhooks
PUT /v1/channels/plumguide/webhooks { /* full webhook config */ }
DELETE /v1/channels/plumguide/webhooksSee Event Types, Verify Signatures, and Retries & Replay.
Plumguide
Read listings and bookings, and push availability + pricing. Filter bookings to one listing with listing_id, or fetch a single booking with booking_code. Webhook config is covered under Webhooks.
GET /v1/channels/plumguide/listings GET /v1/channels/plumguide/bookings # ?listing_id=… | ?booking_code=… PUT /v1/channels/plumguide/availability PUT /v1/channels/plumguide/pricing
Usage & quota
Account-level, not channel-specific. A summary aggregates usage over a range (tier + plan limits, quota used/remaining, next reset, per-operation breakdown), a lightweight tier snapshot powers status badges and quota meters, and the raw request log is cursor-paginated. null limits mean unlimited on that dimension.
GET /v1/usage/summary # ?range=… — aggregated usage + per-operation breakdown GET /v1/usage/tier # current tier, limits, used, remaining, next reset GET /v1/usage/logs # cursor-paginated raw request log, newest first