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

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.

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

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

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

The generic write carries availability, price, min-nights and max-nights to every connected channel. Closed-to-arrival and closed-to-departure are channel-specific: 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

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