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.