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 API has no operation for it on that channel, and n/a means the channel itself has no such thing (Booking.com has no inquiries, and hosts there do not review guests).

Every cell names the endpoints behind it, and the grades are checked against the API's own OpenAPI description before this page can ship: a capability marked unsupported here cannot be one the API is able to do. Where a cell reads partial, the section below it says exactly which part.

Vrbo is in beta: the host signs in with their Vrbo account and Repull reads and answers for it. The Booking.com column is the connectivity-partner connection; the Extranet login covers reservations, messages and reviews and leaves listings, rates and availability to the host's own channel manager — see Works alongside an existing PMS.

CapabilityAirbnbBooking.comVrbo
Connect (hosted flow)
Supported
Supported
Supported

Sign in with the host account (beta)

Works alongside an existing PMS
Supported

Messaging access — the PMS keeps listing management

Supported

Extranet login (beta) — reservations, messages, reviews

Supported

Messaging access — the calendar is never pushed

Listing discovery (read)
Supported
Supported
Supported
Create a new channel listing
Supported

Publish with hostId

Supported

setup → create-property

Unsupported

Link listings the host already has

Link an existing channel listing
Supported

POST …/listings/map

Supported

POST …/booking/listings/map · or map rooms during Connect

Partial

Map units on the hosted Connect page

Canonical content write
Supported

PUT …/listings/{id}/content — one write for every channel; to edit Airbnb alone, see Granular content write

Supported

Same write, every channel; to edit Booking.com alone, see Granular content write

Unsupported

Content stays on Vrbo

Publish / push listing content
Supported

Pushes the content Repull holds

Supported

Pushes the content Repull holds: description, facilities, rooms and beds, photos, policies, cancellation, check-in, fees

Unsupported
Granular content read
Supported

Every field group, plus settings · quality · check-out guide

Supported

photos · facilities · description · settings · policies · licences · check-in · contacts, read straight from Booking.com

Unsupported
Granular content write
Supported

11 field groups — amenities · descriptions · details · rooms · photos (+ cover, order) · check-in guide · booking settings · permits · safety disclosures

Supported

photos · facilities · description · settings · policies · licences · check-in · contacts, property and room level

Unsupported
Listing state action
Supported

push · publish · unlist · relist · delete

Supported

unlist · relist (closes and reopens availability)

Unsupported
Availability write (per-channel)
Supported
Supported
Unsupported

Use the generic ARI write

Pricing write (per-channel)
Supported
Supported
Unsupported

Use the generic ARI write

Channel markup
Supported

Per listing · prices re-sent on change

Supported

Per property, shared by its listings · prices re-sent on change

Unsupported

Airbnb and Booking.com only

Calendar read
Supported

The listing's calendar, before markup — the same on every channel

Supported

The listing's calendar, before markup — the same on every channel

Supported

The listing's calendar, before markup — the same on every channel

Generic ARI write
Supported

Pushed on write

Supported

Pushed on write

Partial

Pushed on write with full access · prices, availability, minimum stay

Reservations (read)
Supported
Supported
Supported
Accept · decline · cancel a reservation
Supported

By confirmation code, or by Repull reservation id

Unsupported

Bookings arrive confirmed

Unsupported

Manage bookings on Vrbo

Reservation alterations
Supported

List · read · create · accept · decline · cancel

Unsupported
Unsupported
Messaging
Supported

Threads · send · edit/unsend/react/mark read

Supported

Conversations · send · attachments

Supported

Conversations · send (text)

Inquiry: special offers & pre-approvals
Supported

Create · read · withdraw

n/a

Booking.com has no inquiries

Supported

Pre-approve · edit the quote · withdraw

Guest reviews
Supported

List · reply · review a guest

Supported

List · reply

Supported

List · reply

Host reviews
Supported

Review the guest · edit it

n/a

Hosts do not review guests on Booking.com

Unsupported

Airbnb only

Financials
Supported

Payout ledger: every payout and its lines, stable ids, refresh on demand

Supported

Charges read + replace

Unsupported
Webhooks
Supported
Supported
Supported
Usage & quota
Supported

Account-level — all channels

Supported

Account-level — all channels

Supported

Account-level — all channels

Specialty channels

One more provider is live with a narrower surface. Plumguide exposes reads plus availability/pricing writes and a webhook config. It has no content or mapping write, and the generic calendar write does not reach it — push its calendar with PUT /v1/channels/plumguide/availability.

CapabilityPlumguide
Connect (hosted flow)
Supported
Listing discovery (read)
Supported

GET …/plumguide/listings

Reservations / bookings (read)
Supported

GET …/plumguide/bookings

Availability write
Supported

PUT …/plumguide/availability

Pricing write
Supported

PUT …/plumguide/pricing

Content / mapping write
Unsupported
Webhooks
Supported

GET · PUT (replace) · DELETE

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" }

Works alongside an existing PMS

On all three channels Repull can connect next to the PMS or channel manager the host already uses, without taking over its connection. The PMS keeps listings, rates and availability; Repull brings you the host's reservations, guest messages and reviews, and sends replies.

  • Airbnb — connect with accessType: "messaging".
  • Booking.com — connect with the Extranet login.
  • Vrbo — connect with accessType: "messaging"; the calendar is never pushed.

The full picture, channel by channel: Use Repull alongside a PMS.

Listing discovery (read)

Read the listings each connection exposes. Both are pure reads — no live provider call.

Why Airbnb has listings and Booking.com has properties

Under each channel's routes (/v1/channels/airbnb/listings, /v1/channels/booking/properties) the channel keeps its own words, because the ids and shapes are the channel's. An Airbnb listing is one bookable place. A Booking.com property is a building whose roomsare what guests book — one property often holds several of your listings, one per room. Your own listings, the same on every channel, are /v1/listings; /v1/properties is an older name for the same thing and keeps working.

Airbnb — supported

GET /v1/channels/airbnb/listings

{
  "data": [
    {
      "listingId": 4118,
      "name": "Lakeview Loft",
      "connections": [
        {
          "id": 55,
          "airbnbId": "12345678",
          "accountId": "112233",      // which connected Airbnb account (hostId is an alias)
          "accountName": "Seaside Stays",
          "active": true,
          "primary": true
        }
      ]
    }
  ],
  "pagination": { "nextCursor": null, "hasMore": false, "total": 1 },
  "dataFreshness": {
    "lastSyncedAt": "2026-09-18T04:12:09.000Z",
    "stale": false,
    "accounts": [
      { "accountId": "112233", "accountName": "Seaside Stays", "lastSyncedAt": "2026-09-18T04:12:09.000Z", "stale": false }
    ]
  }
}

Every Airbnb collection route — listings, reservations, messaging, reviews, transactions, alterations — accepts ?account_id=<airbnb host id> to scope the response to one connected account, stamps each row with accountId / accountName, and reports freshness per account in dataFreshness.accounts[]. See Account scope → Several Airbnb accounts.

Booking.com — supported

Returns a bare array (no data wrapper).

GET /v1/channels/booking/properties

[
  {
    "listingId": 4118,
    "name": "Lakeview Loft",
    "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 hostIdPOST /v1/channels/booking/setup with create-property, then POST /v1/listings/{id}/publish/booking
Link an existing channel listing to a Repull propertyPOST /v1/channels/airbnb/listings/mapPOST /v1/channels/booking/listings/map, or 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 supported

One call opens a brand-new property from a Repull listing: the property, its first room with the listing's beds, a rate plan and the product that makes that room sellable, the contact and invoice details, the listing's facilities, cancellation policy and prices, then Booking.com's readiness check. You do not supply a legal entity — the one your workspace already contracts under is reused, and a second is never registered. Send a contact (name, email, phone in international form) for Booking.com to reach about the property; without one, the workspace owner is used.

POST /v1/channels/booking/setup
{ "action": "create-property", "listing_id": 4118,
  "contact": { "name": "Ana Silva", "email": "ana@example.com", "phone": "+14155550142" } }

# →
{ "action": "create-property", "listingId": "4118",
  "propertyId": "17325248", "roomId": "1732524801",
  "legalEntity": { "id": "59731", "source": "workspace" },
  "status": "being_built", "sellable": false,
  "warnings": ["not yet bookable — Booking.com's readiness check says: Property (17325248): License required, but not found"],
  "nextSteps": [ /* ... */ ] }

# see what still blocks opening, then check-and-open in one step:
POST /v1/channels/booking/setup  { "action": "check-readiness", "property_id": "17325248" }
# → { "ready": false, "blockers": ["Property (17325248): License required, but not found"] }
POST /v1/channels/booking/setup  { "action": "advance", "property_id": "17325248" }

A new property is not sellable the moment it exists.Booking.com opens it only once its readiness check passes, and the check names each thing still missing — a main photo that is still processing, no availability, or a licence the region requires. check-readiness returns that list as blockers; advance checks and, when nothing is left, opens the property. Give the listing a price before you create it: that is what writes the calendar Booking.com needs.

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 supported

The host already has this property on Booking.com. Booking.com attaches at the room: a property is a building and its rooms are what a guest books, so you map each room to one Repull listing. Find the room ids with GET /v1/channels/booking/properties/{id}/rooms, then map them with an API key — the counterpart of the Airbnb link above. Set listingId: null to unmap a room.

POST /v1/channels/booking/listings/map
{ "roomBookingId": "1557551502", "listingId": 3867 }

# →
{ "success": true, "alreadyMapped": false,
  "roomBookingId": "1557551502", "listingId": "3867", "previousListingId": null,
  "hotelId": "15575515", "roomId": "968", "roomName": "Room 02804",
  "platformLinkId": "3988", "reservationsImported": 1 }

The property's reservations come in with the mapping. Once the room is mapped, every active reservation Booking.com holds for the property is imported onto its listing, and reservationsImportedsays how many were processed. You make no follow-up call — and there is no other moment they would arrive, because the regular sync only picks up new changes on rooms that are already mapped. A reservation already present is left as it is, so re-sending the same mapping never duplicates; it retries the import instead. A property with a long history can take tens of seconds. If the import cannot run, the mapping still stands and the field is null.

The hosted Connect flow maps rooms the same way when a host claims their property there, and imports the same way. One listing can carry several Booking.com rooms — that is a normal arrangement and is not refused.

Listing content

Content is written once, to the canonical listing, and distributed by a separate publish. PUT /v1/listings/{id}/content is that write: one channel-agnostic call for title, description, summary, amenities, address, occupancy, property details and policies. Every field is optional and only the fields you send are written, so a partial update is just a smaller body — with the exception of amenities, which is a full replacement of the amenity set. Send locale to say which language this copy is in; each locale keeps its own row rather than overwriting the English one.

PUT /v1/listings/4118/content
{
  "title": "Sable 1302 — mountain-view suite",
  "summary": "Two bedrooms, hot-spring views, ski shuttle at the door.",
  "amenities": ["wifi", "kitchen", "free_parking"],
  "occupancy": { "maxGuests": 4, "bedrooms": 2, "beds": 3, "bathrooms": 1.5 }
}

# → what was written, and anything that was accepted but not applied
{ "id": "4118", "changed": ["title", "summary", "amenities", "occupancy"], "deferred": [] }

Writing content is not publishing it

This write changes Repull's copy and marks the connected channels as having something to send. It does not reach Airbnb or Booking.com on its own — distribution stays a separate, explicit step, so nothing goes live because you edited a description. Publish when you are ready, with the calls below. Photos are the one field that is accepted and not yet persisted: a photos array comes back in deferred rather than being stored. Upload photos with POST /v1/listings/{id}/photos/upload-url.

PATCH /v1/listings/{id} is a different call with a narrow job: it toggles the active flag and writes no content.

PATCH /v1/listings/4118
{ "active": false }

# →
{ "id": 4118, "active": false }

Airbnb publish — supported

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

Two ways to push, and what a publish leaves out

POST /v1/channels/airbnb/listings/{id} with {"action": "push"} or "publish" does the same push as the call above, alongside unlist, relist and delete — see Listing state actions. A publish also does not carry every field: which sections it pushes, which fields Airbnb locks, and how to read back what took effect are on the Airbnb publication contract.

Booking.com publish — supported

Publishing pushes the content Repull holds for the listing to the Booking.com property it is on. The listing needs a Booking.com property first — one you created or linked. If the listing sits on more than one property, name it with hotelId; without it the call is refused with 409 ambiguous_booking_mapping rather than guessing.

POST /v1/listings/4118/publish/booking
{ "hotelId": "15575515" }          # optional when the listing is on one property

# →
{
  "listingId": 4118,
  "channel": "booking",
  "result": {
    "published": true,
    "hotelId": "15575515",
    "sections": ["photos", "policies", "charges"],
    "errors": []
  }
}

published is true only when every section that had something to send was accepted. A refused section is named in errorswith Booking.com's own reason, and the same text stays readable afterwards on GET /v1/listings/{id}/publish-status.

What Booking.com does with what you send

  • Description— Booking.com rewrites it into its own copy, in every language, and publishes that rather than your text. Allow about three hours.
  • Amenitiesbecome Booking.com facilities. Most common ones have a direct match; a facility Booking.com needs more detail for than a listing holds (a pool's type and season, for instance) is left out rather than guessed.
  • Cancellation policybecomes the nearest Booking.com policy — Booking.com has no grace window, so a strict policy maps to “free until 14 days before arrival, then 50%”.
  • Room nameson Booking.com are its own standard names (“Two-Bedroom Apartment”); the listing's title is kept as the operator-side reference.

Granular Airbnb content — supported

Airbnb content is addressable field-group by field-group, and every group that can be read can also be written except the three that Airbnb itself keeps read-only — settings, quality and the check-out guide. Eleven groups take a write. Reads are served from your synced copy and never wait on Airbnb; writes call Airbnb and are reflected back once it answers. All target the canonical Repull listingId, not the Airbnb listing id. Use these when you want one section changed on the live listing without republishing the whole thing.

# Reads (served from your synced copy — never wait on Airbnb)
GET    /v1/channels/airbnb/listings/4118/amenities            # regular + accessibility amenities
GET    /v1/channels/airbnb/listings/4118/descriptions
GET    /v1/channels/airbnb/listings/4118/details
GET    /v1/channels/airbnb/listings/4118/rooms
GET    /v1/channels/airbnb/listings/4118/photos
GET    /v1/channels/airbnb/listings/4118/checkin-guide        # ?locale=en
GET    /v1/channels/airbnb/listings/4118/booking-settings
GET    /v1/channels/airbnb/listings/4118/permits              # ?source=live for Airbnb's questions
GET    /v1/channels/airbnb/listings/4118/safety-disclosures
GET    /v1/channels/airbnb/listings/4118/settings             # read-only
GET    /v1/channels/airbnb/listings/4118/quality              # read-only, ?type=all|standards|issues|stats
GET    /v1/channels/airbnb/listings/4118/checkout-guide       # read-only

# Writes (call Airbnb)
PUT    /v1/channels/airbnb/listings/4118/amenities
PUT    /v1/channels/airbnb/listings/4118/descriptions         # one locale per call
PUT    /v1/channels/airbnb/listings/4118/details              # property type, room type, quiet hours, check-in method
PUT    /v1/channels/airbnb/listings/4118/booking-settings
PUT    /v1/channels/airbnb/listings/4118/permits              # answer Airbnb's permit questions
PUT    /v1/channels/airbnb/listings/4118/safety-disclosures
PUT    /v1/channels/airbnb/listings/4118/checkin-guide        # ?locale=en

POST   /v1/channels/airbnb/listings/4118/rooms                # body = room object (minus room_id)
PUT    /v1/channels/airbnb/listings/4118/rooms
DELETE /v1/channels/airbnb/listings/4118/rooms?roomId=456

POST   /v1/channels/airbnb/listings/4118/photos               # upload
PATCH  /v1/channels/airbnb/listings/4118/photos               # caption, room assignment
DELETE /v1/channels/airbnb/listings/4118/photos?photoId=789
PUT    /v1/channels/airbnb/listings/4118/photos/cover
PUT    /v1/channels/airbnb/listings/4118/photos/order

Per-field-group writes go straight to Airbnb

These change the live Airbnb listing; they are not the canonical copy. Write to PUT /v1/listings/{id}/content when the change belongs to the property on every channel, and use these when it is Airbnb-specific. Some of them are the only way to set a field — a publish deliberately does not carry permits, safety disclosures, the check-in method, or descriptions in a second language. The publication contract lists what a publish does and does not push.

Granular Booking.com content — supported

One endpoint, GET/POST /v1/channels/booking/content, typed by type, reads and writes one kind of content on a Booking.com property without touching the listing or Airbnb: photos, facilities, description, settings, policies, licences, checkin_methods and contacts. Add room_id for a room's facilities, photos or licence. Reads come straight from Booking.com, and what a read returns can be sent back as it is.

# the rules for this region, then the licence
GET  /v1/channels/booking/content?property_id=17325248&type=licences
POST /v1/channels/booking/content
{ "type": "licences", "property_id": "17325248", "variantId": 58,
  "contentData": [{ "name": "business_tax_receipt", "value": "BTR123456-01-2026" },
                  { "name": "certificate_number", "value": "1234567" }] }

# one room's facilities
POST /v1/channels/booking/content
{ "type": "facilities", "property_id": "17325248", "room_id": "1732524801",
  "facilities": [{ "room_facility_id": 8, "state": "PRESENT" }] }

A write Booking.com refuses answers 422 booking_rejectedwith its reason — including the refusals Booking.com returns inside an HTTP 200. A licence value that does not match the region's format is refused before it is sent, naming the field.

Listing state actions (Airbnb)

One endpoint carries the five state changes, selected by action. The path id is the canonical Repull listing id. push and publish are the same operation as POST /v1/listings/{id}/publish/airbnb above.

POST /v1/channels/airbnb/listings/4118
{ "action": "unlist", "airbnbConnectionId": 77 }

# action:
#   push | publish  push the content Repull holds to Airbnb
#   unlist          take the live Airbnb listing down
#   relist          put it back up
#   delete          deactivate the Repull record only — Airbnb is untouched

Unlisting and deactivating are different operations

delete deactivates the Repull record and keeps it: the Airbnb listing stays live and keeps taking bookings. unlist takes the guest-facing listing down, and it is verified rather than assumed — Airbnb accepting the call is not the same as the listing coming down, so it is read back and you get 422 airbnb_rejected if it is still up. unlist and relist require airbnbConnectionId, because one Repull listing can be connected to more than one Airbnb listing. Side by side on the publication contract.

Booking.com has no per-listing action route. To exclude a Booking.com-connected property from Repull, use the channel-agnostic PATCH /v1/listings/{id} with {"active": false}. Opening a property for sale is a setup action.

Availability & pricing (ARI)

Calendar read — supported

Every listing has one calendar in Repull, whatever channels it is on: availability, the listing's own nightly price and minimum stay, day by day. It is the source each channel is priced from, so it is the same answer for Airbnb and Booking.com. from and to (ISO YYYY-MM-DD) are required. The price here is before any channel markup; to see what a channel holds right now, read it from the channel (GET /v1/channels/booking/availability).

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 the channels Repull can write a calendar to: it sets the Repull calendar and pushes the result to Airbnb and Booking.com in the same step. For Plumguide use PUT /v1/channels/plumguide/availability. 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_not_api_connected (Airbnb API sync is off for that one listing — the host turns it on inside Airbnb; reconnecting the account does 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.

Two things are particular to Booking.com. A rate amount is priced at an occupancy — a party size the rate plan sells at — and a write that names one the rate plan does not price at is accepted and then silently ignored, so the response reports whether the price is really live rather than that it was sent. And dateRange is inclusive at both ends: 2026-08-01 → 2026-08-07 is seven nights. Both are covered on Update Booking.com Pricing.

Where roomId and rateId come from

Every Booking.com update below needs a roomId and a rateId. Read them from GET /v1/channels/booking/properties/{listingId}/rooms, which takes a Repull listing id and returns each room on the property Booking.com publishes that listing under, with every rate plan attached to it.

GET /v1/channels/booking/properties/28028/rooms

{
  "hotelId": "5432505",
  "listingId": "28028",
  "otherHotelIds": [],
  "source": "booking",
  "mirrorReason": null,
  "rooms": [
    {
      "roomId": "543250501",
      "roomName": "Deluxe Villa",
      "maxAdults": 4,
      "rates": [
        { "rateId": "15436997", "rateName": "Standard Rate",   "policy": "General",        "policyId": "195090900", "maxPersons": 4, "pricingType": "RLO",      "isChildRate": null },
        { "rateId": "47885754", "rateName": "No reembolsable", "policy": "Non Refundable", "policyId": "195090903", "maxPersons": 4, "pricingType": "Standard", "isChildRate": null }
      ]
    }
  ]
}

maxPersons is the party size that rate plan prices, and it is the occupancy a rate amount has to be written at — see the occupancy note below. maxAdultsis Booking.com's capacity for the room itself, which is what a write falls back to when a rate plan states no maxPersons.

source tells you how fresh the ids are

source: "booking" means the rooms were read from Booking.com just now. If Booking.com returns nothing usable for the property, the rooms and rate plans recorded at your last import are returned instead, source is "mirror", and mirrorReasonsays what went wrong — the ids are still Booking.com's own and can be written against, but they may be stale and maxPersons, policy, policyId, pricingType and isChildRate come back null because only the live feed states them. An empty rooms array means the property genuinely has no rooms — a read that failed is an error, never an empty list.
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", "occupancy": 4 }
  ]
}

# →
{
  "hotelId": "998877",
  "listingId": "4118",
  "requested": 1,
  "occupancy": [ { "index": 0, "roomId": "456", "rateId": "789", "value": 4, "source": "rate_plan" } ],
  "applied": "verified",
  "verification": { "ran": true, "matched": 7, "mismatched": 0, "rows": [ /* one row per night */ ] },
  "booking": { /* Booking.com's answer, verbatim */ },
  "errors": []
}

A rate amount is written at an occupancy

Booking.com stores a nightly amount against the party size the rate planprices, not against the room's capacity. Sent above that number the price is declined in silence and the night keeps its old amount; sent below it the write returns 400. Send occupancyon the update, or leave it out and Repull resolves it from Booking.com's own numbers and echoes the value it used — and where it came from — back in occupancy[]. If it cannot be resolved the write is refused with 422 rather than sent at a guess. The rate plan's number is the maxPersons on the rooms read above.

applied says what is actually known about the nights, because Booking.com acknowledges the request without any per-date status. The nights are read back: verified — every night carries the amount that was sent; mismatch — some do not, and verification.rows names them; rejected — Booking.com named errors; unverified — no read-back ran (you sent verify: false), which is an unknown, not a success.

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.

Channel markup — supported

A channel can sell a listing for more than its own price: the channel's markup is added when a price is sent there. A $200 night with a 35% Airbnb markup goes to Airbnb as $270; the calendar keeps $200. On Airbnb the markup is per listing. On Booking.com it belongs to the property, so every listing priced through that property shares it. Changing a markup re-sends the affected prices to that channel straight away.

GET /v1/listings/30198/markups
{ "id": "30198",
  "airbnb":  [{ "airbnbId": "1781522409437931174", "markupPercent": 35 }],
  "booking": [{ "hotelId": "17325248", "markupPercent": 18, "listingIds": ["30198"] }] }

PUT /v1/listings/30198/markups
{ "channel": "booking", "markupPercent": 20 }
# → { "channel": "booking", "markupPercent": 20, "changed": true,
#     "hotelId": "17325248", "affectedListingIds": ["30198"], "pricesResent": true }

markupPercent is a percentage (15 for 15%); null removes it. A listing on more than one Booking.com property names which with hotelId— otherwise the request is refused with 409 ambiguous_booking_mapping listing them, rather than repricing a property it guessed.

Reservations (read)

Reservations from both channels read through the same normalized shape, and new ones are imported for you as they arrive — there is nothing to acknowledge. The Booking.com-shaped read is GET /v1/channels/booking/reservations, scoped to your workspace: without a hotel_id it covers only your own Booking.com properties, and a property id not connected to your workspace returns 404 not_found.

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.

Accept, decline or cancel a reservation (Airbnb)

Act on a booking request or a confirmed stay. Every one of these calls Airbnb as the account that owns the booking. decline needs a reason from Airbnb's list and a message to the guest (500 characters at most; Airbnb sends it). cancel needs a host-cancellation reason, and host cancellations carry Airbnb's penalties. The body is validated before anything reaches Airbnb, and an unknown field is refused rather than dropped.

POST /v1/channels/airbnb/reservations/HMABC123
{ "action": "accept" }
{ "action": "decline", "reason": "dates_not_available", "message": "Sorry, those dates are no longer available." }
{ "action": "cancel",  "reason": "calendar_conflict" }

# decline: dates_not_available | not_comfortable | listing_not_ready | different_dates_needed | spam | other
# cancel:  calendar_conflict | maintenance_issue | unable_to_host | other

# The same by Repull reservation id
POST /v1/reservations/214303/accept
POST /v1/reservations/214303/decline

There is no pre-approve action here: a pre-approval answers an inquiry, which has no confirmation code yet — see Inquiry: special offers & pre-approvals. Booking.com reservations arrive confirmed, so there is nothing to accept or decline.

Reservation alterations (Airbnb)

List, read and create alteration requests (change dates, guest count, or price) on an Airbnb reservation, and respond to one the guest proposed: accept, decline, or cancel a request you made yourself. Every write calls Airbnb and needs a connected Airbnb host, else 404 no_connection; an alteration id that belongs to another workspace returns 404 not_found. 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 }

# Respond to a pending alteration (no body required)
POST /v1/channels/airbnb/alterations/{id}/accept
POST /v1/channels/airbnb/alterations/{id}/decline
POST /v1/channels/airbnb/alterations/{id}/cancel

Messaging

List threads, read one, send in it as the host, and act on a single message. Reads are served from your synced copy; sending and the message PATCH call Airbnb. The action discriminator selects edit / unsend / read / react. Booking.com conversations, including sending files, go through the channel-agnostic /v1/conversations— the same calls work for both channels. Editing, unsending and reacting are Airbnb features; Booking.com has no equivalent.

GET   /v1/channels/airbnb/messaging                                  # list threads
GET   /v1/channels/airbnb/messaging/{threadId}                       # read a thread
GET   /v1/channels/airbnb/messaging/{threadId}/messages              # ?cursor= | ?all=true
POST  /v1/channels/airbnb/messaging/{threadId}/messages
{ "message": "Your door code is ready." }                            # or "mediaUrl" for a photo or video

PATCH /v1/channels/airbnb/messaging/{threadId}/messages/{messageId}
{ "action": "edit", "message": "Updated reply text" }                # or unsend | read | react

Airbnb rejects off-platform contact details and links

Airbnb enforces its own content rules on anything you send: a message carrying a phone number, an email address or an external URL is refused upstream and comes back as airbnb_error rather than being delivered. Media is a separate rule — one file per message, and no text on the same message.

Inquiry: special offers & pre-approvals (Airbnb)

An inquiry is a guest's question before they book. Answer it with a special offer (your own terms) or a pre-approval, read an offer back, and withdraw one. Booking.com has no inquiries, so this is Airbnb only. All are write-side or live Airbnb calls, addressed by Airbnb ids. The type discriminator picks offer (custom terms) or preapproval. With Repull ids, use the conversation endpoints instead, and accept or decline booking requests by reservation id — see Inquiries & Booking Requests.

POST   /v1/channels/airbnb/offers
{ "type": "offer", "thread_id": "2675957479", "listing_id": "955656266214757921",
  "start_date": "2026-10-01", "nights": 4, "total_price": 880,
  "guest_details": { "number_of_guests": 2 } }
# preapproval: { "type": "preapproval", "thread_id": "2675957479" }

GET    /v1/channels/airbnb/offers?offerId=1459920384
DELETE /v1/channels/airbnb/offers?offerId=1459920384

# The same by Repull ids
POST   /v1/conversations/{id}/pre-approval
POST   /v1/conversations/{id}/special-offers
POST   /v1/reservations/{id}/accept   |   POST /v1/reservations/{id}/decline

Reviews

Guest reviews— what guests write about a stay — read on both channels, with one public response per review. Host reviews— the host reviewing the guest — are an Airbnb feature: submitting publishes it, and it cannot be edited afterwards. Hosts do not review guests on Booking.com. The Reviews API does all of it with one review id, whatever the channel.

# Reviews API — every channel, one review id
GET  /v1/reviews                         # ?platform=airbnb|booking, ?status=unanswered, ?reviewerRole=host
GET  /v1/reviews/{id}
POST /v1/reviews/{id}/reply              # public reply, Airbnb + Booking.com, one per review
POST /v1/reviews/{id}/guest-review       # review a guest (Airbnb; publishes, final)

# Channel routes — same actions by channel ids
GET  /v1/channels/airbnb/reviews                  # ?account_id= to scope to one connected account
PUT  /v1/channels/airbnb/reviews/{id}             # = /v1/reviews/{id}/guest-review
POST /v1/channels/airbnb/reviews/{id}/respond     # deprecated: use /v1/reviews/{id}/reply
GET  /v1/channels/booking/reviews?property_id=998877
POST /v1/channels/booking/reviews                 # reply by Booking.com property + review id

A second response to the same review is refused, not silently ignored: Airbnb answers 409, Booking.com rejects it upstream. Reviews of inactive listings are left out of the reads and reappear once the listing is active again.

Financials

Airbnb host transactions (payouts, adjustments, resolutions) and Booking.com extra-charge sets (cleaning fee, resort fee, city tax…).

Airbnb transactions — supported

Every payout Airbnb sent the host, followed by the lines it paid, each with a stable id and a signed amount; a payout's lines add up to what it paid out. Reads come from Repull's stored copy and never call Airbnb; POST on the same path refreshes it. Filter with ?payout_id=, ?confirmation_code=, ?status= or a date range. See Airbnb Transactions.

GET /v1/channels/airbnb/transactions?payout_id=M-HQLLNSWKUWK7R

{
  "data": [
    { "transactionId": "M-HQLLNSWKUWK7R", "type": "Payout", "isPayout": true, "amount": 2552.38 },
    { "transactionId": "M-HQLLNSWKUWK7R:adjustment:HMQYRTJ5D8:1", "type": "Adjustment", "amount": -681.53,
      "payout": { "payoutId": "M-HQLLNSWKUWK7R", "lineIndex": 1 } },
    { "transactionId": "M-HQLLNSWKUWK7R:reservation:HMRQ8FC4YN:1", "type": "Reservation", "amount": 40.60,
      "payout": { "payoutId": "M-HQLLNSWKUWK7R", "lineIndex": 2 } }
  ]
}

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