Skip to content

Channel Manager Overview

The RestroLab Channel Manager — PMS Integration API is a normalized, PMS-agnostic contract for connecting a property management system to the RestroLab Channel Manager for OTA distribution. RestroLab’s own hotel module is the first — and today, only — implementation of this contract: a future external PMS would authenticate the same way and see the same shapes without needing to understand RestroLab’s internal models.

This is a separate, independent API contract from the Third-Party Integration API documented elsewhere on this site — different credentials, different base path (/v1/channel-manager/pms/*), different OpenAPI document, own interactive reference.

Every endpoint requires X-PMS-API-Key / X-PMS-API-Secret authentication and is rate-limited — see Rate limit below.

Returns every property for this credential’s tenant.

[
{
"id": "3fae7c1a-2f5b-4b7b-9b2f-1a2b3c4d5e6f",
"name": "Hillside Retreat",
"address": "12 Ridge Road",
"city": "Pokhara",
"country_code": "NP"
}
]
Field Type Description
id uuid Property identifier — use this as property_id below.
name, address, city, country_code string

GET /v1/channel-manager/pms/properties/{property_id}/room-types/

Section titled “GET /v1/channel-manager/pms/properties/{property_id}/room-types/”

Room types for a property, including its active rate plans.

[
{
"id": "9a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
"name": "Deluxe King",
"max_occupancy": 2,
"base_price": "120.00",
"rate_plans": [
{
"id": "1b2c3d4e-5f60-7182-93a4-b5c6d7e8f9a0",
"name": "Standard",
"base_price": "120.00",
"is_refundable": true,
"min_nights": 1
}
]
}
]
Field Type Description
id uuid Room type identifier — use this as room_type_id on inventory/availability calls.
name string
max_occupancy integer
base_price decimal string, nullable
rate_plans array Only this room type’s active rate plans. Each has id, name, base_price, is_refundable (boolean), min_nights (integer).

404 if property_id doesn’t belong to this credential’s tenant.

This PMS’s own current inventory — the same service the front desk uses. Not clamped at 0 — an oversell is reflected, not hidden.

Query parameters (all required): room_type_id (uuid), check_in (YYYY-MM-DD), check_out (YYYY-MM-DD).

Terminal window
curl "https://api.restrolab.com/v1/channel-manager/pms/inventory/?room_type_id=9a1b2c3d-...&check_in=2026-10-01&check_out=2026-10-05" \
-H "X-PMS-API-Key: YOUR_PMS_API_KEY" -H "X-PMS-API-Secret: YOUR_PMS_API_SECRET"
[
{ "date": "2026-10-01", "physical_capacity": 10, "sellable": 7 },
{ "date": "2026-10-02", "physical_capacity": 10, "sellable": 6 }
]
Field Type Description
date date One entry per night in [check_in, check_out).
physical_capacity integer Total physical rooms of this type.
sellable integer Remaining sellable count — can go negative on an oversell, deliberately not clamped to 0.

404 if room_type_id doesn’t resolve. 400 if check_in/check_out are missing or not valid YYYY-MM-DD dates.

Read this PMS’s own rate-calendar entries for a rate plan, one entry per night. A date with no explicit override falls back to the rate plan’s base_price, not sold out.

Query parameters (all required): rate_plan_id (uuid), check_in (YYYY-MM-DD), check_out (YYYY-MM-DD).

[
{ "date": "2026-10-01", "price": "120.00", "is_sold_out": false },
{ "date": "2026-10-02", "price": "135.00", "is_sold_out": false }
]

404 if rate_plan_id doesn’t resolve. 400 if check_in/check_out are missing or invalid.

Push rate changes into the same table the internal rate-plan UI edits.

Request body:

{
"rate_plan_id": "1b2c3d4e-5f60-7182-93a4-b5c6d7e8f9a0",
"rates": [
{ "date": "2026-10-01", "price": "140.00", "is_sold_out": false }
]
}
Field Type Required Description
rate_plan_id uuid yes
rates array yes Each entry: date (required), price (required, decimal), is_sold_out (optional, boolean, default false).

Response: the same shape as GET /rates/, for every date you just wrote (200 OK).

PUT-only. Writes stop_sell onto that date’s rate-calendar is_sold_out flag. Writes min_nights onto the whole rate plan’s floor — RestroLab has no per-date minimum-stay override, so the last min_nights value in your request array wins for the entire plan, not just that date.

Request body:

{
"rate_plan_id": "1b2c3d4e-5f60-7182-93a4-b5c6d7e8f9a0",
"restrictions": [
{ "date": "2026-10-01", "stop_sell": true, "min_nights": 2 }
]
}
Field Type Required Description
rate_plan_id uuid yes
restrictions array yes Each entry: date (required), stop_sell (optional boolean, default false), min_nights (optional integer, nullable).

Response: {"updated": <count>} — just a count, not the updated rows.

Reservations for this PMS’s own tenant, newest check-in first, capped at 200 results (no pagination). Optional ?status= query parameter to filter by status.

[
{
"id": "5d6e7f80-91a2-43b4-c5d6-e7f8a9b0c1d2",
"natural_id": "BKG-000123",
"status": "confirmed",
"check_in_date": "2026-10-01T14:00:00Z",
"check_out_date": "2026-10-05T11:00:00Z",
"room_type_id": "9a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
"total_amount": "480.00",
"external_source": "mock_ota",
"external_id": "OTA-98765",
"guest": { "full_name": "Jane Doe", "phone": "+977...", "email": "jane@example.com" }
}
]
Field Type Description
id uuid RestroLab’s internal booking id.
natural_id string Human-readable booking reference.
status string One of pending, confirmed, cancelled, no_show, completed, expired.
check_in_date, check_out_date datetime
room_type_id uuid, nullable
total_amount decimal string
external_source, external_id string, nullable Set when the reservation originated from an OTA via the Channel Manager.
guest.full_name string
guest.phone, guest.email string, nullable

Creating a reservation is not exposed on this API. The create direction always flows OTA → Channel Manager → PMS (via a separate internal webhook receiver), never a PMS pushing a new reservation into the Channel Manager.

GET /v1/channel-manager/pms/reservations/{booking_id}/

Section titled “GET /v1/channel-manager/pms/reservations/{booking_id}/”

A single reservation, same shape as above. 404 if booking_id doesn’t belong to this credential’s tenant.

Errors use the same {"code": ..., "message": ...} envelope as the Third-Party API, but this API is less mature: it does not yet have specific per-case codes like room_type_not_found404/400 responses use the generic fallback:

{ "code": "error", "message": "Unknown room_type_id." }
{ "code": "invalid_request", "message": "check_in and check_out (YYYY-MM-DD) are required." }
Status code Example message
401 invalid_credentials "Invalid PMS credentials."
400 invalid_request Varies — missing/invalid query params, or array-level validation errors on PUT rates/PUT restrictions.
404 error "Unknown room_type_id." / "Unknown rate_plan_id." / "Unknown property." — message text is endpoint-specific.
429 rate_limited "Too many requests."

300 requests per minute, per X-PMS-API-Key credential — same mechanism and number as the Third-Party API’s rate limit, confirmed from the backend’s throttle configuration (not yet in the spec). It’s a single shared budget across all /pms/* endpoints, not separate per endpoint.

Status
Endpoint paths, methods Confirmed — from openapi.json
Request/response field shapes above Confirmed — from the backend’s views and serializers, not yet in openapi.json
Error codes and rate limit Confirmed — from the backend’s exception handler and throttle config, not yet in openapi.json
Authentication See Authentication

The interactive Channel Manager API Reference renders openapi.json directly — it will pick up this same detail automatically once the backend team adds @extend_schema annotations, with no changes needed on this site.