For developers
Nuwaya Public API
A REST API over your organization's data: read reservations, guests, room types and daily KPIs; create and cancel bookings; receive signed webhooks. The reference below is in English.
Authentication
Create an API key on Integrations in your Nuwaya back office (permission integrations.manage). The plaintext key — tch_live_… — is shown once; store it securely. Send it on every request:
curl https://nuwaya.app/api/v1/me \ -H "Authorization: Bearer tch_live_..."
Keys can expire, be revoked, and be scoped to a single property — a property-scoped key only ever sees that property's data.
Scopes
Grant scopes when creating the key (comma-separated). The literal scope read grants every read scope, write every write scope, and * everything. A key with no scopes can only call /v1/me.
read:reservationsread:guestsread:room-typesread:kpisread:productsread:menuwrite:reservationswrite:guestswrite:orders
Endpoints
| Endpoint | Scope | Description |
|---|---|---|
GET /api/v1/me | — | Key introspection: organization, prefix, granted scopes. Smoke-test a new key here. |
GET /api/v1/reservations | read:reservations | List reservations. Filters: from / to (arrival window), status, limit ≤ 100, offset. |
GET /api/v1/reservations/{id} | read:reservations | One reservation by uuid or confirmation number, with room stays and nightly prices. |
GET /api/v1/guests | read:guests | Guest profiles (PII). Filters: search (name/email prefix), limit, offset. |
GET /api/v1/room-types | read:room-types | Room types with active room counts and default rates. |
GET /api/v1/daily-kpis | read:kpis | Completed night-audit KPIs: occupancy, ADR, RevPAR, revenue split. Filters: from / to, limit, offset. |
POST /api/v1/reservations | write:reservations | Create a booking through the availability-locked engine (409 when sold out). rate_plan_id defaults to the property's first active plan. |
POST /api/v1/reservations/{id}/cancel | write:reservations | Cancel by uuid or confirmation number. The property's cancellation policy runs — any fee is charged and reported. |
POST /api/v1/guests | write:guests | Create a guest profile. An existing guest with the same email is returned (deduplicated: true) instead of duplicated. |
curl "https://nuwaya.app/api/v1/reservations?from=2026-07-01&status=confirmed&limit=10" \ -H "Authorization: Bearer tch_live_..."
curl -X POST https://nuwaya.app/api/v1/reservations \
-H "Authorization: Bearer tch_live_..." \
-H "Content-Type: application/json" \
-d '{
"property_id": "…", "room_type_id": "…",
"arrival": "2026-08-01", "departure": "2026-08-04", "adults": 2,
"guest": { "first_name": "Aisha", "last_name": "Khan", "email": "aisha@example.com" }
}'MCP server (AI agents)
AI agents connect over the Model Context Protocol at https://nuwaya.app/api/mcp (Streamable HTTP, stateless) using the same Authorization: Bearer tch_live_… key and scopes as the REST API. Tools mirror the API: list_properties, list_room_types, check_availability, list_reservations, create_reservation, cancel_reservation, create_guest, list_products, create_store_order (scope write:orders) and list_menu — bookings and cancellations run through the same availability-locked, server-priced engines as the booking page, and a key only sees the tools its scopes allow. Agentic checkout stays human-settled: create_store_order returns a hosted payment_url — the order confirms only when a person pays it.
Webhooks
Subscribe on the Integrations page: add an endpoint URL and pick events. Nuwaya POSTs each event as JSON with two headers — X-Nuwaya-Event (the event type) and X-Nuwaya-Signature (sha256=<hex>, an HMAC-SHA256 of the raw body using your endpoint's whsec_… secret, shown once at creation). Respond 2xx within 5 seconds. Deliveries triggered by API writes go out right after the response; everything else is swept by a scheduled job (retried up to 5 attempts), and a manual run is available on the Integrations page.
reservation.createdreservation.confirmedreservation.checked_inreservation.checked_outreservation.cancelledreservation.no_showpayment.capturedguest.createdorder.paidinvoice.issuedinvoice.paidinvoice.overdueevent.lead_createdevent.confirmedevent.completedevent.cancelledguest_request.created
Verify the signature before trusting a delivery:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, signatureHeader: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}Build an app (marketplace)
Ship your integration to every Nuwaya property. Any workspace can register an app on its Integrations page: pick a slug, the API scopes you need, and (optionally) the webhook events you want pushed to your endpoint — then submit it for review. Once the platform team approves the listing, it appears in the App marketplace on every tenant's Integrations page. When a hotel installs your app it hands you an API key limited to your approved scopes and a whsec_… signing secret for your webhook feed; uninstalling revokes both instantly. Apps can be priced per install per month (BHD); the platform retains a 15% share, recorded per listing.
OAuth install flow (v2)
An approved app can enable OAuth from its developer card (you get a nwc_… client id and a nws_… secret, shown once). Instead of a tenant copy-pasting a key, send them to /oauth/authorize?client_id=…&redirect_uri=…&scope=…&state=… — a signed-in admin approves the consent screen and Nuwaya redirects back with a single-use code (10-minute expiry). Exchange it server-side at POST /api/oauth/token (form fields grant_type=authorization_code, code, redirect_uri, client_id, client_secret) to receive a tch_live_… access token scoped to the tenant's consent — it works on the whole REST API and MCP server, and uninstalling the app revokes it.
Conventions
- All monetary amounts are integers in minor units — BHD uses 3 decimals, so
75000= BHD 75.000. - Dates are
YYYY-MM-DD; timestamps are ISO 8601 UTC. - Listings paginate with
limit(max 100) andoffset, echoed back underpagination. - Errors return
{ "error": { "code", "message" } }with 401 (bad key), 403 (missing scope), 404, or 400. - Writes return 201 (created), 200 (idempotent match), 409 (sold out) or 422 (rejected by a business rule). Treat unknown response fields as additive.