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 Uncovered dates reported, not guessed |
| Generic ARI write | Supported Pushed on write | Supported Pushed on write |
| 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 Via /v1/webhooks (CNS subscriptions not available) |
| 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 has no channel-specific write endpoints — no content, mapping, or pricing writes — but the generic calendar write pushes price and availability to a connected VRBO listing.
| 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 | Partial Generic PUT /v1/availability/{propertyId} |
| Pricing write | Supported PUT …/plumguide/pricing | Partial Generic PUT /v1/availability/{propertyId} |
| 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/27826?from=2026-08-01&to=2026-08-07
{
"propertyId": "27826",
"currency": "BRL",
"days": [
{ "date": "2026-08-01", "available": true, "price": 245, "minNights": 2 }
],
"coverage": {
"requestedDays": 7,
"coveredDays": 1,
"missingDates": ["2026-08-02", "2026-08-03", "2026-08-04",
"2026-08-05", "2026-08-06", "2026-08-07"]
}
}A missing date is unknown, never bookable
days carries only the dates backed by a real calendar row. Anything else in the window is listed in coverage.missingDates — availability there is unknown. The endpoint deliberately does not synthesise a default, because a fabricated open date can be double-booked. A property with no calendar still returns a real 200 with days: []; a 404 means the property id does not exist in this workspace.Generic ARI write — supported
This is the default for calendar values. One write covers every connected channel: it sets the Repull calendar and pushes the result to Airbnb, Booking.com and VRBO in the same step. PATCH /v1/availability/batch applies the same settings across up to 500 properties. The body applies one settings object to a list of dates, and at least one of available, price, minNights, maxNights is required — a body with only dates returns 422. Reach for the per-channel routes below only when you need a restriction the generic shape does not carry, such as closed-to-arrival.
PUT /v1/availability/27826
{
"dates": ["2026-08-01", "2026-08-02"],
"available": false,
"price": 320
}
{
"listingIds": ["27826"],
"dates": 2,
"synced": { "attempted": 2, "succeeded": 2, "failed": 0, "authErrors": 0 }
}Airbnb — supported
A calendar operation sets availability, nightly price, and stay limits together. 4118 is the Repull listing id, not the Airbnb listing id. 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 (1–1125). Airbnb also supports closed_to_arrival/closed_to_departure. A blocked date ("availability": "unavailable") is sent with busy_subtype: "BLOCKED_BY_HOST" unless you pass "OUTSIDE_RESERVATION" for a date held by a booking on another channel. Unknown fields are refused with 422 invalid_params.
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", "settings": { "default_daily_price": 245 } }
# Currency is its own sub-resource
{ "type": "currency", "currency": "USD" }Errors: 422 invalid_params (a field failed validation, named in field), 422 airbnb_rejected (Airbnb refused the change; message carries its reason), 403 connection_reauth_required (reconnect at repull.dev/dashboard/connections; retrying will not help), 403 listing_inactive, 404 not_found, 429 airbnb_rate_limited, and 502 airbnb_error (Airbnb outage — retry). Every type and field is on Update Airbnb Pricing.
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",
"rateId": "789",
"dateRange": { "start": "2026-08-01", "end": "2026-08-07" },
"availableRooms": 3,
"closed": false,
"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" }
]
}
# →
{ "hotelId": "998877", "listingId": "4118", "pushed": 1, "requested": 1, "errors": [], "raw": { /* Booking.com's response */ } }Field support differs by channel
closed_to_arrival/closed_to_departure per Airbnb calendar operation, and closedToArrival/closedToDeparture in Booking.com restrictions. Booking.com's dedicated stop-sell flag is closed.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. Booking.com reads are scoped to your workspace: without a hotel_id they cover only your own Booking.com properties, a property id not connected to your workspace returns 404 not_found, and acknowledging a reservation that is not yours acknowledges nothing and returns 404.
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, readiness, contacts, policies, open for sale). Every action that takes a property_id needs a property connected to your workspace; any other id returns 404 not_found. check-legal-status always returns 404 — a legal entity cannot be tied to one workspace, so check its status in the Booking.com Extranet.
GET /v1/channels/booking/properties/{id} # connection record(s) for a Repull listing
POST /v1/channels/booking/setup
{ "action": "create-legal-entity", /* … */ } # returns 201 with the entity's status
POST /v1/channels/booking/setup
{ "action": "check-readiness", "property_id": "998877" }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
Booking.com events for your properties arrive through /v1/webhooks above. GET/POST/DELETE /v1/channels/booking/webhooks is deprecated and always returns 403 forbidden: Booking.com notification subscriptions are shared by every workspace, so they cannot be read or changed through the API. Plumguide has its own config, managed directly:
# 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