Channel Partner API
Push rooms, rates and availability from your system. Receive bookings back in real time.
your_source_code stands in for the source code Glacial assigns you at onboarding. Requests to any other host will not reach the gateway.
Overview
The Channel Partner API is a two-way interface between Glacial Adventures and channel managers, property management systems and central reservation systems. Your system pushes rooms, rates and availability to Glacial; Glacial delivers confirmed bookings, modifications and cancellations back to your system in real time.
Inbound, you call four endpoints: room and rate-plan discovery, rate push, availability push, and no-show reporting. Outbound, Glacial POSTs booking events to an HTTPS endpoint you register with us.
Every inbound request is an HTTP POST with a JSON body and a Content-Type of application/json. There are no GET endpoints and no query parameters. The trailing slash on each path is optional.
Every request below is copy-pasteable and runs as written. your_source_code, GLH101, R55 and secret_token_value are placeholders — substitute the values Glacial issues you. Run an example unchanged and you will get a well-formed error envelope back, which is the quickest way to confirm you can reach the gateway.
Getting connected
Credentials are issued by Glacial. There is no self-service signup and no sandbox key you can mint yourself — every integration starts with an application and ends with a certification pass.
- 1Apply
Submit the Become-a-Partner form below. It's the only queue.
- 2We review
Our partnerships team replies within 2 working days, on WhatsApp or email.
- 3Your partner account
Approved partners receive an email invite. Set your password and sign in to the partner extranet.
- 4Credentials & integration
Your test-property hotel code and AuthCode are on your API Integration page, viewable whenever you need them. Integrate the flows in this documentation against the base URL shown above; your credentials identify you in the request body — there is nothing partner-specific in the URLs.
- 5Certification & go live
We verify rate, availability and booking flows together on your test property, then enable production pushes across your hotels. Endpoints, payloads and credentials do not change between test and production.
How credentials work
Two credentials, two channels.
Your sign-in is a personal account — we email you an activation link when you're approved, and you choose your own password. Your API credentials (hotel code + AuthCode) are never emailed and never sent over WhatsApp: they live on the API Integration page of your partner account — masked by default, viewable by you whenever you need them, with every reveal logged. Nobody from Glacial will ever ask you for your AuthCode. If a code is ever compromised, one message to us rotates it instantly without affecting any other property.
Authentication
Inbound requests authenticate in the JSON body, not in headers. Every request carries an auth block with the property's hotel code and API key. There is no Authorization header, no signature and no OAuth exchange on the inbound leg.
{
"auth": {
"hotel_code": "GLH101",
"api_key": "secret_token_value"
}
}The source code in the URL identifies you as a partner; the auth block identifies the property. Both are checked on every request, and a valid key for one property will not authenticate another.
An unknown hotel code, an inactive property and an incorrect API key all return the same AUTH_FAILED code. This is deliberate — the response must not confirm which property codes exist. When you get AUTH_FAILED, check both values against what was issued rather than assuming the key is at fault.
API keys are shown once, at issue. Glacial does not store a form you can read back, so keep the key in your own secret store. Keys can be rotated on request; the previous key stops working the moment the new one is issued, so schedule a rotation for a quiet window.
Outbound booking delivery authenticates the other way round. Glacial sends a bearer token you nominate, in a standard Authorization header, so you can verify that a booking event genuinely came from us.
Authorization: Bearer <your_outbound_token>
Content-Type: application/json
Accept: application/jsonIdentifiers
Three identifier formats appear across the API. All of them are case-sensitive, and all of them are assigned by Glacial — never construct one yourself.
| Identifier | Format | Example | Where it appears |
|---|---|---|---|
| Hotel code | GLH<id> | GLH101 | auth.hotel_code, and hotel_code on outbound events |
| Room code | R<id> | R55 | room_code on rate and inventory rows |
| Rate plan code | R<id>_<MEAL> | R55_CP | rate_plan_code on rate rows |
The meal component of a rate plan code is one of five values, always upper case:
| Meal code | Plan | Included |
|---|---|---|
| EP | European Plan | Room only |
| CP | Continental Plan | Room and breakfast |
| MAP | Modified American Plan | Room, breakfast and one further meal |
| AP | American Plan | Room and all meals |
| JP | Jain Plan | Room with Jain meals |
A property does not necessarily offer every meal plan, and room codes change when a property reconfigures its inventory. Push a rate plan a room does not offer and the row fails with INVALID_RATE_PLAN even though the code is well-formed. Call /roominfo/ to read the live set, cache the result, and refresh it on a schedule rather than deriving codes from a room id.
Bookings carry a reservation reference in the form GL-<booking>-<property>, for example GL-2026-001-H101. Treat it as an opaque string: it is the value you echo back in /noshow/ and the key you store against your own booking record.
Conventions
Every inbound response is HTTP 200, including failures. The body carries the outcome in a fixed envelope; branch on success, never on the HTTP status. A non-200 status means the request never reached the gateway — a proxy, a network fault or a malformed body — and should be retried rather than parsed.
{
"success": false,
"code": "AUTH_FAILED",
"message": "invalid api_key"
}On success, code is an empty string and message is a human-readable summary. Discovery adds a rooms array beside the envelope; the batch endpoints add nothing.
Rate and inventory pushes are batches, and a batch is not all-or-nothing. Rows are applied independently: valid rows are written, invalid rows are itemised in the message by their zero-based index in the array you sent. The batch reports success as long as at least one row applied, so a 200 with success true still deserves a look at message.
{
"success": true,
"code": "",
"message": "2 rows applied; 14 dates; 1 rows failed: #2 INVALID_ROOM"
}When every row fails, success is false and code carries the first failing row's code. Because indexes are zero-based, #2 is the third element of the array.
Dates are YYYY-MM-DD and every range is inclusive of both ends: start_date 2026-09-01 with end_date 2026-09-07 writes seven nights. A range whose end_date precedes its start_date fails the row with INVALID_RANGE.
Dates in the past are skipped, not failed. A range that straddles today writes the future half, reports the skipped count in message, and still counts as applied. A range entirely in the past writes nothing and applies no row — which is a success with zero rows applied, not an error.
A single row may span at most 400 days; a longer range fails that row with RANGE_TOO_LONG. There is no hard cap on rows per request, but keep a request to roughly 500 written dates in total — rows multiplied by nights — and split larger updates across several calls. Long single ranges are cheaper than many one-night rows covering the same period.
Omitting an optional field, or sending null, leaves the stored value untouched — the previous value survives. Sending 0 sets the value to zero. Omitting extra_adult_amount on an update does not clear an existing extra-adult surcharge; to remove one, send 0 explicitly.
When a rate lands on a date that has no inventory record yet, that date is created closed — zero rooms, stop-sale on. Rates set the price; availability comes only from /inventory/. An integration that pushes rates and never pushes inventory produces a property that is fully priced and entirely unbookable, with no error to signal it.
Rate rows may carry an ota_code to target a single channel. Leave it null or omit it and the rate applies to every channel — this is what almost every integration wants. Send a value and the row is applied only if it matches the OTA code assigned to your link; a row naming any other channel is silently skipped and counted in message as foreign-OTA skipped, not reported as a failure.
Amounts are in INR and are per room, per night, for the room's default occupancy. Extra adult and child amounts are surcharges on top of that base, not replacements for it.
Room and rate plan discovery
/channel/v1/your_source_code/roominfo/Returns the active rooms for a property and the rate plans each one supports. Call this first, and call it again whenever a push starts failing with INVALID_ROOM or INVALID_RATE_PLAN — it is the mapping you build every other request against.
Request body
authObject. Present on every inbound request.| Field | Type | Description |
|---|---|---|
| hotel_code * | string | The property identifier Glacial issued for this link, e.g. GLH101. |
| api_key * | string | The secret key issued alongside the hotel code. |
curl -X POST 'https://api.glacialadventures.in/apiV1/channel/v1/your_source_code/roominfo/' \
-H 'Content-Type: application/json' \
-d '{
"auth": {
"hotel_code": "GLH101",
"api_key": "secret_token_value"
}
}'{
"success": true,
"code": "",
"message": "Success",
"rooms": [
{
"room_code": "R55",
"room_name": "Deluxe Room",
"rate_plans": [
{
"code": "R55_EP",
"name": "Deluxe Room EP"
},
{
"code": "R55_CP",
"name": "Deluxe Room CP"
},
{
"code": "R55_MAP",
"name": "Deluxe Room MAP"
},
{
"code": "R55_AP",
"name": "Deluxe Room AP"
}
]
}
]
}- The rooms array is always present, including on failures, where it is empty — you can read it without branching on success first.
- Only rooms currently active on the property are returned. A room that disappears between calls has been removed or deactivated; stop pushing to it.
- Discovery is not affected by a paused push link: it answers even when rate and inventory pushes are disabled.
Rate push
/channel/v1/your_source_code/rates/Sets nightly rates for a room and rate plan across a date range, with optional extra adult and extra child surcharges. Send one row per room, rate plan and contiguous range.
Request body
authObject. Present on every inbound request.| Field | Type | Description |
|---|---|---|
| hotel_code * | string | The property identifier Glacial issued for this link, e.g. GLH101. |
| api_key * | string | The secret key issued alongside the hotel code. |
rates[]Array of rate rows. Each row is applied independently.| Field | Type | Description |
|---|---|---|
| room_code * | string | Room code from /roominfo/, e.g. R55. |
| rate_plan_code * | string | Rate plan code from /roominfo/, e.g. R55_CP. Must belong to room_code and be a plan the room actually offers. |
| amount * | number | Nightly rate in INR for the room's default occupancy. |
| start_date * | string (YYYY-MM-DD) | First night the rate applies to, inclusive. |
| end_date * | string (YYYY-MM-DD) | Last night the rate applies to, inclusive. At most 400 days after start_date. |
| currency | string | Accepted for readability; amounts are settled in INR regardless. |
| ota_code | string | null | Restricts the row to one channel. Null or omitted applies it to all channels — the usual case. |
| extra_adult_amount | number | null | Surcharge per extra adult. Null or omitted leaves the stored value unchanged; send 0 to clear it. |
| extra_child_amount | number | null | Surcharge per extra child. Null or omitted leaves the stored value unchanged; send 0 to clear it. |
curl -X POST 'https://api.glacialadventures.in/apiV1/channel/v1/your_source_code/rates/' \
-H 'Content-Type: application/json' \
-d '{
"auth": {
"hotel_code": "GLH101",
"api_key": "secret_token_value"
},
"rates": [
{
"room_code": "R55",
"rate_plan_code": "R55_CP",
"amount": 4500,
"currency": "INR",
"start_date": "2026-09-01",
"end_date": "2026-09-07",
"ota_code": null,
"extra_adult_amount": 800,
"extra_child_amount": 500
}
]
}'{
"success": true,
"code": "",
"message": "1 rows applied; 7 dates"
}Dates created by a rate push start closed. Until /inventory/ sets a room count for the same dates, the rate is stored but the room cannot be booked.
- A row is rejected with INVALID_RATE_PLAN if the plan does not belong to the room or the room does not offer that meal plan, even when the code is correctly formed.
- Rows targeting another channel's OTA code are skipped silently and counted in message — they are not failures.
- Re-sending a row overwrites the rate for those dates. Pushes are idempotent, so a full refresh is a safe recovery from a missed update.
Availability push
/channel/v1/your_source_code/inventory/Sets the number of rooms available to sell across a date range. This is what opens a room for sale, and it is also how you close one.
Request body
authObject. Present on every inbound request.| Field | Type | Description |
|---|---|---|
| hotel_code * | string | The property identifier Glacial issued for this link, e.g. GLH101. |
| api_key * | string | The secret key issued alongside the hotel code. |
inventory[]Array of availability rows. Each row is applied independently.| Field | Type | Description |
|---|---|---|
| room_code * | string | Room code from /roominfo/, e.g. R55. |
| available_rooms * | integer | Rooms available to sell per night. Must be zero or greater; 0 applies a stop-sale. |
| start_date * | string (YYYY-MM-DD) | First night of the range, inclusive. |
| end_date * | string (YYYY-MM-DD) | Last night of the range, inclusive. At most 400 days after start_date. |
curl -X POST 'https://api.glacialadventures.in/apiV1/channel/v1/your_source_code/inventory/' \
-H 'Content-Type: application/json' \
-d '{
"auth": {
"hotel_code": "GLH101",
"api_key": "secret_token_value"
},
"inventory": [
{
"room_code": "R55",
"available_rooms": 8,
"start_date": "2026-09-01",
"end_date": "2026-09-07"
},
{
"room_code": "R55",
"available_rooms": 0,
"start_date": "2026-09-08",
"end_date": "2026-09-08"
}
]
}'{
"success": true,
"code": "",
"message": "2 rows applied; 8 dates"
}- available_rooms is an absolute count, not a delta. Send the number you want on sale, not the change since the last push.
- Sending 0 closes the room for those dates. Sending a positive count again reopens it — there is no separate stop-sale endpoint.
- A negative or non-numeric count fails the row with BAD_COUNT.
- Availability is per room, not per rate plan. All of a room's rate plans draw on the same pool.
No-show reporting
/channel/v1/your_source_code/noshow/Reports that a guest did not arrive. Marks the reservation as a no-show on the Glacial side and notifies the reservations team.
Request body
authObject. Present on every inbound request.| Field | Type | Description |
|---|---|---|
| hotel_code * | string | The property identifier Glacial issued for this link, e.g. GLH101. |
| api_key * | string | The secret key issued alongside the hotel code. |
Body| Field | Type | Description |
|---|---|---|
| reservation_ref * | string | The reservation reference from the booking event, e.g. GL-2026-001-H101. Your own booking id is accepted here too, if you returned one when the booking was delivered. |
curl -X POST 'https://api.glacialadventures.in/apiV1/channel/v1/your_source_code/noshow/' \
-H 'Content-Type: application/json' \
-d '{
"auth": {
"hotel_code": "GLH101",
"api_key": "secret_token_value"
},
"reservation_ref": "GL-2026-001-H101"
}'{
"success": true,
"code": "",
"message": "reservation marked no_show"
}- The reference must belong to the property in the auth block. A reference from another property returns NOT_FOUND.
- The call is idempotent: reporting a reservation that is already marked no-show succeeds and does not raise a second notification.
- Reporting a no-show does not cancel the booking or trigger a refund. Commercial resolution is handled by the reservations team.
Outbound booking delivery
When a booking is confirmed, changed or cancelled at Glacial, we POST a JSON event to the bookings endpoint you register during onboarding. The endpoint must be HTTPS and reachable from the public internet.
Each request carries your bearer token in an Authorization header and expects a JSON acknowledgement. Requests time out after ten seconds by default.
{
"event": "create",
"reservation_ref": "GL-2026-001-H101",
"hotel_code": "GLH101",
"sent_at": "2026-09-01T10:15:00+05:30",
"booking": {
"reference": "GL-2026-001",
"created_at": "2026-09-01T10:12:00+05:30",
"guest": {
"given_name": "Asha",
"surname": "Menon",
"salutation": "Ms",
"phone": "+910000000000",
"email": "guest@example.com"
},
"comments": [
"Late check-in requested",
"Channel booking GL-2026-001"
]
},
"stays": [
{
"stay_ref": "5001",
"room_code": "R55",
"room_name": "Deluxe Room",
"rate_plan_code": "R55_CP",
"rate_plan_name": "Deluxe Room CP",
"rooms": 1,
"checkin": "2026-09-01",
"checkout": "2026-09-05",
"guests": {
"adults": 2,
"children": 1,
"infants": 0
},
"amount": "18000.00",
"currency": "INR",
"resolution": "hint"
}
],
"payment": {
"status": "prepaid",
"paid_amount": "18000.00",
"total_amount": "18000.00"
}
}| event | Meaning | Payload notes |
|---|---|---|
| create | A new reservation has been confirmed. | Full payload with at least one stay. |
| modify | Dates, rooms, occupancy, amounts or guest details changed. | Full current state, not a diff. Replace what you hold against reservation_ref. |
| cancel | The reservation has been cancelled. | stays is empty; reservation_ref identifies what to cancel. |
reservation_ref is stable across all three events for a given reservation, so it is the key to store and match on. sent_at and event are stamped at delivery time; everything else describes the reservation's current state.
No card data is ever sent. The payment block reports what has been collected and what is owed, nothing more.
To acknowledge an event, return HTTP 200 or 201 with a JSON body containing success true and your own reference for the booking:
{
"success": true,
"booking_id": "PMS-REF-789012"
}All three of a 2xx status, success: true, and a non-empty booking_id are required. A 200 carrying only success: true is recorded as a failed delivery and the create is retried — which, in a system that stores the booking on receipt, produces duplicates. Return the id your system assigned, on every event including modify and cancel. If you have no natural id, echo reservation_ref back.
To reject a booking your system genuinely cannot accept — an unmapped room category, a closed property — return HTTP 400 or 422 with success false and a message. The event is recorded as BOOKING_REJECTED for the operations team to resolve, and is not retried.
{
"success": false,
"message": "Room category R55 is not mapped in the PMS"
}Transport failures and the statuses 429, 500, 502, 503 and 504 are retried up to three attempts with an increasing delay, honouring Retry-After when you send one. Anything else — a non-JSON body, a missing booking_id, another 4xx — is recorded as a failed delivery and surfaced to the operations team rather than retried automatically.
Because retries exist, the same event can arrive more than once. Key on reservation_ref and treat a repeat as an update to what you already hold. Acknowledge a duplicate with the same booking_id and HTTP 200 rather than an error.
Error codes
Inbound codes arrive in the code field of an HTTP 200 envelope. The outbound code describes how Glacial records a booking your endpoint refused.
| Code | Leg | Cause | Resolution |
|---|---|---|---|
| AUTH_FAILED | Inbound | The auth block is missing, the hotel code is unknown, the property is inactive, or the API key does not match. The response does not distinguish between these. | Check hotel_code and api_key against what was issued for this property. If both look right, the property link may have been deactivated — contact partners@glacialadventures.in. |
| UNKNOWN_PARTNER | Inbound | The source code in the URL is not recognised, or the partner record is not active. | Use the source code Glacial issued you, lower case, exactly as it appears in your onboarding email. This is a URL problem, not a credential problem. |
| PARTNER_PAUSED | Inbound | Inbound pushes are paused for your integration as a whole, across every property. | Stop pushing and contact Glacial. A pause is set deliberately by the operations team; retrying will not clear it. |
| PUSH_DISABLED | Inbound | Pushes are disabled for this one property link, while your other properties continue to work. | The property is likely being reconfigured or has moved off channel management. Confirm with Glacial before resuming pushes for it. Discovery still works. |
| CONFIG_GUARD | Inbound | The gateway declined to serve the request because it was not reached through the correct production host. | Send requests to https://api.glacialadventures.in. If you are already using that host, report the failure to Glacial — no retry will succeed until it is fixed on our side. |
| INVALID_ROOM | Inbound | A row's room_code does not correspond to an active room on the property. | Call /roominfo/ and refresh your mapping. The room has been renamed, removed or deactivated since you last read it. |
| INVALID_RATE_PLAN | Inbound | A row's rate_plan_code is malformed, belongs to a different room, or names a meal plan this room does not offer. | Use a rate plan code exactly as returned by /roominfo/ for that room. A well-formed code such as R55_AP still fails if room R55 does not sell AP. |
| BAD_RATE | Inbound | A rate row's amount is missing, non-numeric or unparseable. | Send amount as a JSON number, not a formatted string. Strip currency symbols and thousands separators before sending. |
| BAD_DATE | Inbound | start_date or end_date is missing, or is not a valid YYYY-MM-DD date. | Send plain calendar dates with no time component and no timezone suffix. 2026-09-01 is valid; 01/09/2026 and 2026-09-01T00:00:00Z are not. |
| INVALID_RANGE | Inbound | end_date falls before start_date. | Ranges are inclusive of both ends. For a single night, set both fields to the same date. |
| RANGE_TOO_LONG | Inbound | A single row spans more than 400 days. | Split the row into consecutive ranges of 400 days or fewer. The limit is per row, not per request. |
| BAD_COUNT | Inbound | available_rooms is missing, negative or not a number. | Send a whole number of zero or more. Use 0 to close the room rather than a negative value or null. |
| NOT_FOUND | Inbound | The reservation_ref sent to /noshow/ does not match any reservation on this property. | Use the reservation_ref from the booking event, or the booking id you returned when acknowledging it. References are scoped to the property in the auth block. |
| BAD_REQUEST | Inbound | The body could not be processed — an array element that is not an object, a missing rates or inventory array, or an unexpected error while applying the batch. | Validate the payload against the examples on this page. Check that rates and inventory are arrays of objects and that Content-Type is application/json. |
| BOOKING_REJECTED | Outbound | Your endpoint answered a booking event with HTTP 400 or 422, or with success false — a business rejection rather than a transport fault. | Nothing to do on the wire; the event is not retried. Glacial's operations team follows up on the reason you returned in message, so make it specific. |
Changelog
- v1.02026-08-22
- Initial public release of the Channel Partner API reference.
Contact
The application form is how integrations start — there is no separate queue. Already a partner? Your account manager and partners@glacialadventures.in are your channels, and WhatsApp is fastest.