Reference
Channel manager
For a PMS that uses ZimmerStack purely as its channel manager: push absolute room counts, nightly prices and stay rules; we send them to every connected channel. OTA bookings come back through the webhooks.
The model
- Writes are absolute. absolute values, last write wins within a request, unchanged cells are dropped before anything is written
- Dates. YYYY-MM-DD, calendar dates in the property's timezone (GET /property); a stay occupies [checkIn, checkOut) — the checkout day is not a night
- Money. rates are SENT in major units with the property's currency and RETURNED in minor units (rateMinor); stored as whole major units
- No reservation endpoints for a channel-manager-only tenant. A walk-in booked in your PMS becomes a lower room count here. Mirroring your bookings would create a shadow PMS; the industry pattern is “the PMS pushes counts, the channel manager pushes bookings”.
Routes
| Route | Scope | What it does |
|---|---|---|
| GET /availability | ari:read | Availability as our record holds it |
| GET /batches/{batchId} | ari:read | One accepted write and whether it has landed |
| GET /catalog | any | Room types and rate plans with the numeric ids every write uses |
| GET /property | any | The property this key is for |
| GET /rates | ari:read | Rates as our record holds them (minor units) |
| GET /restrictions | ari:read | Restrictions as our record holds them |
| GET /sync | ari:read | Has what you sent reached the channels? |
| PUT /availability | ari:write | Set absolute availability |
| PUT /rates | ari:write | Set nightly sell prices |
| PUT /restrictions | ari:write | Set stay restrictions |
Base URL https://api.zimmerstack.com/api/v1/connect-api/v1
GET/availabilityari:read
Availability as our record holds it
Served from our record, never from the channel manager.
- from (query)
- Default: today in the property's timezone
- to (query)
- Default: from + 30 days; at most 500 days after from
200OK400Bad range (data.code RANGE_INVALID)401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
GET/batches/{batchId}ari:read
One accepted write and whether it has landed
- batchId (path, required)
{
"batchId": "7f1d2c4e-5a6b-4c8d-9e0f-112233445566",
"resource": "rates",
"createdAt": "2026-10-12T09:14:03.201Z",
"requestedCells": 151,
"acceptedCells": 43,
"noopCells": 108,
"rejected": [],
"status": "pending",
"pendingCells": 12,
"lastError": null
}200OK401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)404No such batch on this property409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
GET/catalog
Room types and rate plans with the numeric ids every write uses
200OK401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
GET/property
The property this key is for
200OK401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
GET/ratesari:read
Rates as our record holds them (minor units)
Served from our record, never from the channel manager.
- from (query)
- Default: today in the property's timezone
- to (query)
- Default: from + 30 days; at most 500 days after from
200OK400Bad range (data.code RANGE_INVALID)401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
GET/restrictionsari:read
Restrictions as our record holds them
null = we hold no opinion for that field on that night.
- from (query)
- Default: today in the property's timezone
- to (query)
- Default: from + 30 days; at most 500 days after from
200OK400Bad range (data.code RANGE_INVALID)401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
GET/syncari:read
Has what you sent reached the channels?
Poll this instead of asking the channel manager: inventory pipe state, rate cells still queued, the projector's last outcome and the newest batch still pending.
200OK401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
PUT/availabilityari:write
Set absolute availability
Each update sets the ABSOLUTE number of rooms for sale for one room type over a date range. Lands in our record and rides the inventory drain to the channels.
- x-idempotency-key (header)
- Optional. Same key + same body within 7 days replays the stored answer (X-Idempotent-Replay: true); same key + different body is refused (409 IDEMPOTENCY_KEY_REUSED).
{
"updates": [
{
"roomTypeId": 412,
"dateFrom": "2026-11-01",
"dateTo": "2026-11-30",
"available": 5
},
{
"roomTypeId": 412,
"dateFrom": "2026-11-14",
"dateTo": "2026-11-14",
"available": 2
}
]
}{
"batchId": "7f1d2c4e-5a6b-4c8d-9e0f-112233445566",
"resource": "rates",
"requestedCells": 151,
"acceptedCells": 43,
"noopCells": 108,
"rejected": [],
"pushedBy": "ari-projector",
"poll": "/api/v1/connect-api/v1/batches/7f1d2c4e-5a6b-4c8d-9e0f-112233445566"
}202Accepted. The cells that differed from our record are written and queued; the push to the channels is asynchronous (typically seconds, minutes under load, bounded by the channel manager's budget, never lost). Poll `poll` or GET /sync.400data.code is one of VALIDATION | RANGE_INVALID | HORIZON | TOO_MANY_UPDATES | TOO_MANY_CELLS | UNKNOWN_ROOM_TYPE401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)413Body larger than 1048576 bytes (x-zimmerstack.limits.maxBodyBytes); nothing was read422Every group was refused by the property's rate policy; data.details.rejected says why429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
PUT/ratesari:write
Set nightly sell prices
Each update sets the price of one rate plan over a date range, optionally masked to weekdays (`days`). `currency` must equal the property's. Lands in our record and rides the ARI projector to the channels.
- x-idempotency-key (header)
- Optional. Same key + same body within 7 days replays the stored answer (X-Idempotent-Replay: true); same key + different body is refused (409 IDEMPOTENCY_KEY_REUSED).
{
"updates": [
{
"ratePlanId": 901,
"dateFrom": "2026-11-01",
"dateTo": "2027-03-31",
"rate": "4500.00",
"currency": "INR"
},
{
"ratePlanId": 901,
"dateFrom": "2026-11-01",
"dateTo": "2027-03-31",
"days": [
"fr",
"sa"
],
"rate": 5200,
"currency": "INR"
}
]
}{
"batchId": "7f1d2c4e-5a6b-4c8d-9e0f-112233445566",
"resource": "rates",
"requestedCells": 151,
"acceptedCells": 43,
"noopCells": 108,
"rejected": [],
"pushedBy": "ari-projector",
"poll": "/api/v1/connect-api/v1/batches/7f1d2c4e-5a6b-4c8d-9e0f-112233445566"
}202Accepted. The cells that differed from our record are written and queued; the push to the channels is asynchronous (typically seconds, minutes under load, bounded by the channel manager's budget, never lost). Poll `poll` or GET /sync.400data.code is one of VALIDATION | RANGE_INVALID | HORIZON | TOO_MANY_UPDATES | TOO_MANY_CELLS | UNKNOWN_RATE_PLAN | CURRENCY_MISMATCH401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)413Body larger than 1048576 bytes (x-zimmerstack.limits.maxBodyBytes); nothing was read422Every group was refused by the property's rate policy; data.details.rejected says why429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
PUT/restrictionsari:write
Set stay restrictions
Each update sets ANY subset of stopSell, closedToArrival, closedToDeparture, minStayArrival, minStayThrough, maxStay for one rate plan over a date range; fields you omit are left as they are.
- x-idempotency-key (header)
- Optional. Same key + same body within 7 days replays the stored answer (X-Idempotent-Replay: true); same key + different body is refused (409 IDEMPOTENCY_KEY_REUSED).
{
"updates": [
{
"ratePlanId": 901,
"dateFrom": "2026-12-24",
"dateTo": "2026-12-26",
"minStayArrival": 2,
"closedToArrival": false
},
{
"ratePlanId": 901,
"dateFrom": "2026-12-31",
"dateTo": "2026-12-31",
"stopSell": true
}
]
}{
"batchId": "7f1d2c4e-5a6b-4c8d-9e0f-112233445566",
"resource": "rates",
"requestedCells": 151,
"acceptedCells": 43,
"noopCells": 108,
"rejected": [],
"pushedBy": "ari-projector",
"poll": "/api/v1/connect-api/v1/batches/7f1d2c4e-5a6b-4c8d-9e0f-112233445566"
}202Accepted. The cells that differed from our record are written and queued; the push to the channels is asynchronous (typically seconds, minutes under load, bounded by the channel manager's budget, never lost). Poll `poll` or GET /sync.400data.code is one of VALIDATION | RANGE_INVALID | HORIZON | TOO_MANY_UPDATES | TOO_MANY_CELLS | UNKNOWN_RATE_PLAN401Missing, malformed, unknown or revoked key (data.code API_KEY_INVALID | API_KEY_REVOKED)403The key lacks the scope this route needs (SCOPE_MISSING, details.needed / details.held), or the request came from outside the key's address allowlist (IP_NOT_ALLOWED)409The property is deleted or demo (PROPERTY_UNAVAILABLE); on an ARI route its channel manager is off (CHANNEL_MANAGER_DISABLED); on a booking route it has no PMS (PMS_NOT_AVAILABLE)413Body larger than 1048576 bytes (x-zimmerstack.limits.maxBodyBytes); nothing was read422Every group was refused by the property's rate policy; data.details.rejected says why429A budget was exhausted — data.scope names it (ip · ip-auth · requests · writes · property · property-writes · cells · global); honour Retry-After and pace on the RateLimit header (x-zimmerstack.rateLimits)
What a write answers
{
"batchId": "7f1d2c4e-5a6b-4c8d-9e0f-112233445566",
"resource": "rates",
"requestedCells": 151,
"acceptedCells": 43,
"noopCells": 108,
"rejected": [],
"pushedBy": "ari-projector",
"poll": "/api/v1/connect-api/v1/batches/7f1d2c4e-5a6b-4c8d-9e0f-112233445566"
}acceptedCells differed from our record and were written; noopCells already held the value and cost nothing; rejected names groups our record refused (a plan whose policy forbids exceptions). Poll poll or GET /sync.
{
"batchId": "7f1d2c4e-5a6b-4c8d-9e0f-112233445566",
"resource": "rates",
"createdAt": "2026-10-12T09:14:03.201Z",
"requestedCells": 151,
"acceptedCells": 43,
"noopCells": 108,
"rejected": [],
"status": "pending",
"pendingCells": 12,
"lastError": null
}Pacing
- Batch: one PUT per resource carrying ranges (dateFrom..dateTo, optional days) — a 365-night calendar for three plans is a handful of updates, not a thousand
- Send on change, not on a timer: unchanged cells are dropped and cost nothing, but they still spend your request budget
- Read RateLimit on every answer and slow down when r approaches 0, instead of waiting for a 429
- Reuse x-idempotency-key on a retry so a timed-out request can never land twice
- Poll GET /batches/{batchId} and GET /sync no more than every 15 s; the push to the channels is asynchronous and bounded by the channel manager's own budget
Facts on this page (routes, samples, limits, codes) come from the API's public contract read live, Tue, 20 Oct 2026 13:10:40 GMT.