External Integrations
Property-scoped API-key routes for channel managers and third-party booking integrations.
External Integrations
NapX PMS exposes a dedicated external API surface under /v1/external/* for channel managers, membership systems, kiosks, and other third-party property integrations. These routes are isolated from the session-authenticated operator API and always derive tenant and property access from the API key itself.
Authentication
- Use
Authorization: Bearer <napx_live_...>orX-Api-Key: <napx_live_...>. - Do not send
x-tenant-idon this surface. - External keys must be scoped to a property.
- Keys can optionally include scopes such as
reservations:readorpricing:read. - Keys with an empty
scopesarray retain full access for backwards compatibility.
What Changed
The current external contract enforces several important safety rules:
- Cross-property reservation and space-type lookups return
404 Not Foundinstead of leaking that the resource exists elsewhere in the tenant. - Coupon listing returns property-visible active coupons for the current property: inside the validity window, below max-usage, and either tenant-wide or property-scoped to the property.
- Reservation creation validates nested product items, including UUIDs and quantity bounds.
- Guest file attachment and external payment recording now validate their request bodies through DTOs instead of ad-hoc object checks.
- Adding a product to a reservation now rejects products from another property.
- Stay-date and rate-plan-specific coupon restrictions still require reservation context and are not fully resolved by the listing route alone.
POST /reservations/:id/confirmnow auto-advances through all intermediate states (draft → pending_payment → confirmed) so callers do not need to know the current state.POST /reservations/:id/cancelnow accepts an optional body{ reason?: string, sendEmail?: boolean }. OmittingsendEmailor setting it totruesends the default cancellation email; passingfalsesuppresses it.GET /reservationsnow accepts optionalfromandtoISO-8601 date filters for scoped listing.- Space types now return
description,maxOccupancy,imageUrls[],hourlyRate, andnightlyRatein all listing and detail responses. - Products now return a permanent
imageUrlwhen one has been stored directly; temporary presigned URLs are only used as a fallback for legacy products withimageFileId.
Route Summary
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /v1/external/availability | availability:read | Availability by space type for the key's property |
| GET | /v1/external/coupons | coupons:read | Property-visible active coupons |
| GET | /v1/external/coupons/:code/preview | coupons:read | Coupon preview without redemption |
| POST | /v1/external/guests | guests:write | Guest creation |
| GET | /v1/external/guests | guests:read | Property-visible guests |
| GET | /v1/external/guests/:id | guests:read | Guest detail |
| POST | /v1/external/guests/:id/files | guests:write | Guest file attachment |
| GET | /v1/external/products | products:read | Active products |
| GET | /v1/external/products/:id | products:read | Product detail with optional presigned image URL |
| GET | /v1/external/price-quote | pricing:read | Quote without reservation creation |
| GET | /v1/external/rate-plans | rate-plans:read | Active rate plans |
| POST | /v1/external/reservations | reservations:write | Reservation creation |
| GET | /v1/external/reservations | reservations:read | Reservation listing |
| GET | /v1/external/reservations/:id | reservations:read | Reservation detail |
| POST | /v1/external/reservations/:id/check-in | reservations:write | Check-in |
| POST | /v1/external/reservations/:id/check-out | reservations:write | Check-out |
| GET | /v1/external/reservations/:id/products | reservations:read | Reservation products |
| POST | /v1/external/reservations/:id/products | reservations:write | Add reservation product |
| POST | /v1/external/reservations/:id/payment-intent | reservations:write | Stripe PaymentIntent creation |
| POST | /v1/external/reservations/:id/confirm | reservations:write | Confirm reservation (auto-advances draft → pending_payment → confirmed) |
| POST | /v1/external/reservations/:id/cancel | reservations:write | Cancel reservation (accepts { reason?, sendEmail? } body) |
| GET | /v1/external/reservations/:id/notes | reservations:read | Reservation notes |
| POST | /v1/external/reservations/:id/notes | reservations:write | Add reservation note |
| POST | /v1/external/reservations/:id/payments | reservations:write | Record external payment |
| GET | /v1/external/reservations/:id/access-codes | reservations:read | Guest-facing access codes (room + shared) from Seam |
| POST | /v1/external/reservations/:id/files | reservations:write | Attach a pre-uploaded S3 file to a reservation |
| GET | /v1/external/space-types | space-types:read | Space-type listing (includes description, maxOccupancy, imageUrls, rates) |
| GET | /v1/external/space-types/:id | space-types:read | Space-type detail |
| GET | /v1/external/spaces | spaces:read | Space listing |
Common Request Shapes
Reservation creation
{
"spaceCategoryId": "11111111-1111-4111-8111-111111111111",
"ratePlanId": "22222222-2222-4222-8222-222222222222",
"checkIn": "2026-06-01T14:00:00.000Z",
"checkOut": "2026-06-02T10:00:00.000Z",
"type": "overnight",
"products": [
{
"productId": "44444444-4444-4444-8444-444444444444",
"quantity": 2
}
]
}
Guest file attachment
{
"fileId": "55555555-5555-4555-8555-555555555555"
}
External payment recording
{
"amount": 100,
"currency": "usd",
"description": "External checkout payment"
}
Error Semantics
401 Unauthorizedmeans the API key is missing, expired, or revoked.403 Forbiddenmeans the key is valid but lacks property scope or the required route scope.404 Not Foundhides records that belong to another property as well as genuinely missing ids.400 Bad Requestcovers validation failures, unusable coupons, restricted rate plans, and inactive or mismatched products.
The repository API reference for this surface lives in docs/api/endpoints/external-integrations.md.