Skip to main content

Availability & Rates (ARI)

Once a listing is onboarded, its availability and rates are pushed to Channex. ARI is keyed by the stored room_type_id / rate_plan_id, so the listing must be onboarded first. Pushes happen three ways: event-driven (the normal path), a periodic drift correction, and a manual admin call.

Event-driven push (normal path)

The PMS already emits inventory.changed / rate.changed events to the distribution-events SNS topic when inventory or rates change. ChannexAriEventConsumer subscribes via its own SQS queue and is a batch listener: a poll's worth of messages is coalesced per (channel, listing) — touched properties unioned, date range widened — and pushed as one availability + one restrictions call, with values re-read from the database at push time.

Crash-safe: the SQS batch is acked only after the pushes succeed. If the instance dies mid-push, the messages were never acked and SQS redelivers them, so no ARI change is lost when an instance goes down. A push failure fails the whole batch for redelivery; pushes are idempotent, so reprocessing is safe.

Enable per environment: channex.sqs.enabled=true + channex.sqs.queue-name (defaults to the sandbox queue). See Deployment & Config.

Drift correction

ChannexDriftSyncJob (opt-in, channex.drift.enabled=true; hourly by default) pushes a full ARI window for every onboarded listing, so no missed event can desynchronize Channex forever.

Manual push

POST /api/v1/admin/channex/listings/{listingId}/ari?channelId=chnl_X&from=2026-06-10&to=2026-09-08
  • from defaults to today; past dates are clamped to today (Channex rejects past dates).
  • to defaults to from + 90 days; the window is capped at 365 days.
{
"listingId": "lst_0853ie",
"status": "SYNCED",
"messages": [
"availability ranges pushed: 6",
"restriction ranges pushed: 6",
"room types: 2",
"window: 2026-06-10 → 2026-09-08"
]
}

Range compression

Sending one value per date for a year is thousands of entries. Consecutive dates with identical values are run-length encoded into a single date_from/date_to range, so a year of unchanged availability is a handful of entries:

{ "values": [
{ "property_id": "5f8a…", "room_type_id": "7c2b…",
"date_from": "2026-06-10", "date_to": "2026-09-08", "availability": 3 }
] }

Availability

Built from Inventory and sent to POST /availability:

  • availability = inventory.quantity minus active cart holds for that property/date (cart_inventory_hold_unit), clamped at 0 — so a unit held in someone's cart is not also sold on an OTA.
  • A stop-sell day (inventory.stopSell = true) is pushed as availability: 0.

Restrictions (rate + stay rules)

Built from ChannelRate (+ Inventory) and sent to POST /restrictions, also range-compressed:

{ "values": [
{ "property_id": "5f8a…", "rate_plan_id": "a91d…",
"date_from": "2026-06-10", "date_to": "2026-06-14",
"rate": 12000, "min_stay_arrival": 2, "stop_sell": false }
] }
  • rate = channelRate.nightlyRate (falls back to rate), in the smallest currency unit (paise for INR).
  • min_stay_arrival = inventory.minimumStay; stop_sell = inventory.stopSell.
  • Channex applies partial updates, so a rate-only edit can send just {rate} without clobbering the stay rules.

Batching

Each values array is chunked at 500 entries per request, so large windows split across calls automatically.

Verifying

Don't trust the 200. The doctor command reads ARI back from Channex (GET /availability, GET /restrictions?...&filter[restrictions]=rate) and compares it against the locally computed values, flagging any drift.