Docs/Channels/Channel Manager

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.

CapabilityAirbnbBooking.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.

CapabilityPlumguideVRBO
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

Every payload below keys off the canonical Repull listing id (e.g. 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" }
    ]
  }
]

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…AirbnbBooking.com
Create a new channel listing from a Repull propertyPOST /v1/listings/{id}/publish/airbnb with hostIdRun Connect (claim) to open the property, then POST /v1/listings/{id}/publish/booking
Link an existing channel listing to a Repull propertyPOST /v1/channels/airbnb/listings/mapPOST /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

There's no single cross-channel “map any provider listing” endpoint. Airbnb links with an API-key call (…/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

Booking.com content flows through the single 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

Where no connected-channel calendar data exists yet for the property, the endpoint returns a default calendar (all-available, no price/min-nights) rather than an error — treat missing price/minNightsas “not yet synced,” not as authoritative.

Generic ARI write is unsupported

There is still no generic availability writePUT 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

Common availability fields (available/price/min-nights) work on both. Max-stay and closed-to-arrival/departure are only expressible on Booking.com's schema (Airbnb exposes 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

Reservation rows expose 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 | react

Special 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=555

Financials

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/webhooks

See 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
AI