Docs/API Reference/Compatibility

Channex-compatible API

Repull serves the Channex API as a drop-in replacement. Point an integration written against Channex at Repull's base URL, send a Repull API key, and it keeps working: the same paths, request and response bodies, filters, pagination, envelopes, error shapes, webhooks and HMAC signatures. Nothing has to be rewritten. Every operation in Channex's published API has a route; the coverage table shows which work fully, which work with a named limit, and which return a Channex-shaped error.

Base URLhttps://api.repull.dev/channex/api/v1 (instead of https://app.channex.io/api/v1)
AuthYour Repull API key in the user-api-key header
ChannelsBooking.com, Airbnb and Vrbo (beta). Creating any other Channex channel returns a Channex-shaped error.
Moving an existing Channex accountSwitch from Channex: one import call keeps your Channex ids

Quick start

  1. Create an API key in the Repull dashboard.
  2. Replace the base URL. https://app.channex.io/api/v1 becomes https://api.repull.dev/channex/api/v1.
  3. Send the Repull key in the user-api-key header, where your Channex key was.
BASE=https://api.repull.dev/channex/api/v1

curl "$BASE/properties?pagination[page]=1&pagination[limit]=10" \
  -H "user-api-key: sk_live_YOUR_KEY"

Everything else is unchanged: filter[...], pagination[...] and order[...] query parameters, { data, meta } on success, { errors: { code, title, details } } on failure with the same HTTP status. A missing or unknown key returns Channex's 401; an unknown path returns 404 resource_not_found; a wrong method returns 405 with an Allow header. The base URL without /api (https://api.repull.dev/channex/v1) works too.

Read meta.warnings

When Repull accepts a write but cannot apply all of it, the response says so in meta.warnings, for example a rate that a channel cannot sell. Log them; they are how a partial endpoint tells you what it did.

How Channex concepts map

ChannexOn Repull
PropertyA hotel or a home. A hotel groups its room types; a multi-room Booking.com property is one property; a single home is its own property. One rule assigns the property_id, so bookings, revisions, messages, reviews, webhooks and channels all print the same id for the same place.
Room typeA sellable unit type. count_of_rooms is how many identical units it has.
Rate planReal rate plans. Every room type has a default plan, and you can add more: manual, derived from a parent (with modifiers), cascade or auto, with occupancy options, meals and a cancellation policy. Each plan can be mapped to a Booking.com rate. Airbnb and Vrbo sell one price per night, taken from the primary plan.
ARIPOST /availability sets rooms to sell per night, as a count. POST /restrictions sets rate, min_stay_arrival, min_stay_through, max_stay, closed_to_arrival, closed_to_departure and stop_sell per plan and date. A derived plan is computed from its parent.
TaskEvery ARI or inventory write returns a task. GET /tasks/:id shows the dates written, queued and refused, with an error per refused item.
curl -X POST "$BASE/restrictions" \
  -H "user-api-key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "values": [
        { "property_id": "PROPERTY_ID", "rate_plan_id": "RATE_PLAN_ID",
          "date_from": "2026-12-01", "date_to": "2026-12-14",
          "rate": "189.00", "min_stay_arrival": 3, "closed_to_arrival": false }
      ] }'

Channels API and the mapping iframe

The Channels API (/channels, /channels/:id/mappings, activate, deactivate, /channels/list, /channels/codes, test_connection, mapping_details) is available for Booking.com (BDC), Airbnb (ABB) and Vrbo (VRB). Every connection in your workspace appears as a channel, including ones made from the dashboard.

If your app embeds Channex's channel mapping screen, keep the button and change the server. Request a one-time token as before, then load https://api.repull.dev/channex/auth/exchange with the same query string. The token is single-use and lasts 15 minutes. It opens the hosted mapping screen for that workspace, limited to the channels in channels or available_channels. Only redirect_to=/channels exists; other pages are refused before the token is spent.

curl -X POST "$BASE/auth/one_time_token" \
  -H "user-api-key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "one_time_token": { "property_id": "PROPERTY_ID", "username": "jane@pms.example.com" } }'

# { "data": { "token": "..." }, "meta": { "message": "..." } }

Webhooks

Register webhooks with POST /webhooks exactly as on Channex: callback_url, event_mask, property_id or is_global, send_data, headers, request_params, is_signed and signing_key_id, with Channex's defaults. Deliveries use Channex's event names and envelope. A property webhook wins over a global one on the same URL, so the endpoint gets one delivery, not two. Delivery is retried, and GET /webhooks/:id/logs shows every attempt.

{
  "event": "booking",
  "payload": {
    "booking_id": "9c1a2b3c-...",
    "property_id": "4f6e7d8c-...",
    "revision_id": "e2d4f6a8-..."
  },
  "property_id": "4f6e7d8c-...",
  "user_id": null,
  "timestamp": "2026-10-22T14:03:11.204Z"
}

HMAC signing

Signing is byte-for-byte Channex's, so your existing verifier works unchanged. A signed delivery carries:

  • x-hmac-timestamp: the Unix time used to sign.
  • x-hmac-signature-256: lowercase hex HMAC-SHA256 of <timestamp>.<raw body>. During a rotation it holds two signatures, comma-separated, current first.
  • x-hmac-keys-256: ... plus the last six characters of each secret, in the same order.

Your secrets carry over. The import recreates each signing key under its Channex id; install the secret your endpoints already verify with through POST /signing_keys/:id/rotate and { "signing_key": { "secret": "..." } }. POST /signing_keys and generate accept a secret too, of 32 to 256 characters; leave it out and Repull generates one, shown once. generate adds a pending secret and rotate promotes it. Where Channex asks for billing_account_id, use your workspace id; the response names it if you send your old one.

import crypto from 'node:crypto'

// rawBody must be the exact bytes received, before JSON parsing.
export function verifyChannexWebhook(rawBody: string, headers: Headers, secret: string): boolean {
  const ts = headers.get('x-hmac-timestamp')
  const sigs = (headers.get('x-hmac-signature-256') ?? '').split(',')
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false

  const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')
  return sigs.some(s =>
    s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)),
  )
}

Events

Every event Channex lists is accepted in event_mask (GET /webhooks/events). These are accepted but not sent, and registering one returns a warning:

  • published_review: review fires when a review is first seen; the publish moment is not observed.
  • updated_channel: No event is sent when a connected account’s settings change.
  • activate_channel, deactivate_channel: Use new_channel and disconnect_channel.
  • booking_invalid_guarantee: Cards are not validated on arrival.
  • airbnb_notification: Airbnb platform notifications are not relayed.
  • booking_unmapped_rate: Accepted, but not produced yet.

Bookings

The revision feed works as on Channex. A booking webhook carries booking_id, property_id and revision_id, and the revision exists before the webhook is sent, so the loop needs no change: receive the webhook, fetch the revision, store it, ack it.

# 1. Fetch the revision named by the webhook
curl "$BASE/booking_revisions/REVISION_ID" -H "user-api-key: sk_live_YOUR_KEY"

# 2. Acknowledge it once stored
curl -X POST "$BASE/booking_revisions/REVISION_ID/ack" -H "user-api-key: sk_live_YOUR_KEY"

# Catch-up: every revision not yet acknowledged
curl "$BASE/booking_revisions/feed" -H "user-api-key: sk_live_YOUR_KEY"
  • booking_new, booking_modification and booking_cancellationfollow the revision's status, so a cancellation always arrives as booking_cancellation.
  • A change that leaves the booking's content unchanged creates no revision and sends no webhook. non_acked_booking fires for revisions left unacknowledged, and is dropped if you ack first.
  • A revision shows the booking's current content. Revisions are kept for 90 days; ack is idempotent.
  • amount is null when the channel reported no money, never "0.00". The booking's guarantee carries the card where the channel provides one; no PCI app is needed. Webhooks never carry a card.
  • reservation_request, accepted_reservation and declined_reservation carry bms in the booking revision structure.

Booking CRS

POST /bookings and PUT /bookings/:idcreate and change real reservations, which appear in the feed as CRS revisions. One room per booking: send a multi-room booking as one booking per room. The stay is priced by the property's rates; if your days total differs, the response names both amounts in meta.warnings. Fields such as meta, services, deposits and notes are stored and read back as written.

Messages and reviews

/message_threads, /message_threads/:id/messages, /bookings/:id/messages, /attachments, /reviews, /reviews/:id/reply and /scores work as on Channex, with message, review and updated_review webhooks. Replies go out on the channel the guest wrote on. The limits are in the table below.

Endpoint coverage

Every operation in Channex's published API has a route. Full behaves as on Channex. Partial works, with the limit named. Not available returns a Channex-shaped error with the reason, never a fake success. A value Repull does not hold is null, never 0, false or [].

Inventory and ARI

EndpointStatusNotes
/properties (CRUD, options)FullA property is a hotel, a multi-room Booking.com property or a home. DELETE deactivates it. email, phone, website and logo_url are not stored and read as null. A property assembled from a Booking.com hotel is edited per room type (422 otherwise).
/room_types (CRUD, options)Fullcount_of_rooms is the number of units. Room counts owned by a hotel PMS refuse writes with 409. Dorm beds are not sold individually.
/rate_plans (CRUD, options)FullEvery room type has a default plan. Extra plans support manual, derived, cascade and auto modes, a parent plan, modifiers, occupancy options, meals and cancellation policy. DELETE archives.
GET/POST /availabilityFullRooms to sell are stored and pushed as counts, never collapsed to open/closed.
GET/POST /restrictionsPartialRates, min stay (arrival and through), max stay, CTA, CTD and stop-sell per plan and date. Airbnb and Vrbo take one nightly price, from the primary plan. An extra plan not mapped to a Booking.com rate is stored but not sold.
GET /tasks, /tasks/:idFullEvery ARI and inventory write: dates written, queued and refused, with per-item errors.
/property_facilities, /room_facilitiesPartialChannex does not publish its catalogue ids, so map facilities by title. Facilities with no equivalent are not listed.
/hotel_policies (CRUD, options)PartialCheck-in and check-out times, pets, smoking, adults-only and max guests reach the listing. Internet, parking, pet fees, child ages and cleaning practices are stored and read back only.
/taxes, /tax_sets (CRUD, options, test)PartialStored and calculated, but not pushed to any channel. Airbnb pass-through taxes are read-only.
/photos (CRUD, upload)FullPhotos of a listing managed by a connected PMS cannot be replaced as a set.
/currencies, /timezones, /whoamiFullwhoami returns the API key acting for the workspace (system_role: api).

Bookings

EndpointStatusNotes
GET /bookings, /bookings/:idFullamount and currency are null when the channel reported no money. guarantee carries the card where the channel provides one.
POST /bookings, PUT /bookings/:id (Booking CRS)PartialCreates or changes a real reservation and a CRS revision. One room per booking (422 otherwise). Priced by the property’s rates; a different days total is named in meta.warnings.
/booking_revisions, /feed, /:id, POST /:id/ackFullA revision is a content change. Kept 90 days. It shows the booking’s current content; historical snapshots are not kept. Ack is idempotent.
GET /bookings/:id/paymentsFullCharges, holds and refunds.
price_breakdown, payout_details, bank_transfer_payoutPartialBooking.com bookings only. Other channels return 422 method_not_supported.
charge_payment, pre_auth_payment, settle_payment, void_payment, refund_paymentNot availableCard charging is not available through this API (403).
stripe_payment_method, stripe_token, authorize_net_customerPartialReturns the payment method already on the property’s connected account; otherwise 422 or 403.
no_show, invalid_card, cancel_due_invalid_card, guest_misconduct, stay_change, misconduct_categoriesNot availableBooking.com guest reporting is not connected (422 method_not_supported).

Messages and reviews

EndpointStatusNotes
/message_threads, /:id, /:id/messagesFullmessage_count is null in the list and exact on a single thread.
/bookings/:id/messages, POST …/messages/pullPartialpull returns the stored thread; it does not trigger a fresh channel fetch. A booking without a conversation returns 422.
POST /attachments, GET /attachments/:id/:fileFullKept for 24 hours.
POST /message_threads/:id/no_reply_neededPartialBooking.com conversations on a connectivity-provider connection only.
POST /message_threads/:id/closeNot availableThreads have no closed state (422).
/reviews, /:id, POST /:id/replyFull—
POST /reviews/:id/guest_reviewPartialAirbnb only.
GET /scores/:property_id, /detailedFullComputed from the reviews held, out of 10 (Airbnb scores doubled).

Channels, iframe and accounts

EndpointStatusNotes
/channels (CRUD, options)PartialEvery connection is a channel: each Booking.com hotel, Extranet login, Airbnb host and Vrbo login. Deleting a Vrbo or Extranet login is done in the dashboard, not the API.
POST /channels/:id/activate, /deactivate, /check_readinessFullTurns rate and availability pushes on or off. Activate is refused, with the reasons, while a channel is unconnected or unmapped.
POST /channels/:id/mappings, DELETE …/mappings/:idPartialAn Airbnb listing cannot be unmapped through the API; deactivate the channel instead.
/channels/:id/execute/:actionNot availableThe actions list is empty: reservations import continuously.
GET /channels/:id/action/listingsFullAirbnb. Other channels return 400, as on Channex.
/channels/list, /adapter, /codes, test_connection, connection_details, mapping_detailsFullBooking.com (BDC), Airbnb (ABB) and Vrbo (VRB).
POST /meta/airbnb/connection_linkFullReturns a hosted Connect URL that runs Airbnb authorisation, then redirects to redirect_uri.
POST /auth/one_time_token, {server}/auth/exchangePartialOpens the hosted channel mapping screen. Only redirect_to=/channels exists.
/channel_availability_rules (CRUD, options)PartialClose-outs are enforced on Booking.com and Airbnb, and stored only on Vrbo and Extranet logins. availability_offset and max_availability are stored, not enforced.
GET /live_feed, /:id, POST /:id/resolvePartialbooking_revision_id is null until that revision has been created.
/groups (CRUD, options)PartialA group is a workspace. A property lives in exactly one, so moving it between groups is refused.
/group_users, /property_usersPartialReal members are read-only. Invitations are stored but create no account, send no email and grant no access.
/applications, /installed, install, uninstallPartialMessages and Booking CRS are always on. PCI is not needed (cards are on the booking). Payment apps and PMS bridges are refused with a reason.
/applications/payment_app/*Not availableThere is no installable payment app (404).
/applications/stripe_tokenization_app/:id/*PartialAnswered from the booking, as stripe_payment_method above.

Webhooks

EndpointStatusNotes
/webhooks (CRUD), GET /webhooks/eventsFullEvery Channex attribute, with Channex’s defaults. A property webhook wins over a global one on the same URL.
POST /webhooks/test, /webhooks/:id/testFull—
GET /webhooks/:id/logsFullOne entry per attempt, newest first. Custom header values are redacted.
/signing_keys (CRUD, options, generate, rotate)FullChannex’s HMAC headers, verified with Channex’s own Node and Python code.

Differences to know

  • Revision ids start fresh. When you import a Channex account, property, room type, rate plan, webhook and channel ids carry over; booking revision ids do not. Unacknowledged Channex revisions should be acked on Channex before you switch.
  • New channels start deactivated. A channel you create is not pushed to until you call activate.
  • Airbnb and Vrbo sell one rate. Extra rate plans reach Booking.com when mapped to a Booking.com rate; on Airbnb and Vrbo the primary plan sets the price.
  • No card charging. Cards are readable on the booking; charge, pre-authorise, settle, void and refund are not available.
  • Rate limits are Repull's. A 429 returns http_too_many_requests with Retry-After. See Rate limits.
AI