NapX PMS Docs

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_...> or X-Api-Key: <napx_live_...>.
  • Do not send x-tenant-id on this surface.
  • External keys must be scoped to a property.
  • Keys can optionally include scopes such as reservations:read or pricing:read.
  • Keys with an empty scopes array 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 Found instead 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/confirm now auto-advances through all intermediate states (draft → pending_payment → confirmed) so callers do not need to know the current state.
  • POST /reservations/:id/cancel now accepts an optional body { reason?: string, sendEmail?: boolean }. Omitting sendEmail or setting it to true sends the default cancellation email; passing false suppresses it.
  • GET /reservations now accepts optional from and to ISO-8601 date filters for scoped listing.
  • Space types now return description, maxOccupancy, imageUrls[], hourlyRate, and nightlyRate in all listing and detail responses.
  • Products now return a permanent imageUrl when one has been stored directly; temporary presigned URLs are only used as a fallback for legacy products with imageFileId.

Route Summary

MethodPathScopeDescription
GET/v1/external/availabilityavailability:readAvailability by space type for the key's property
GET/v1/external/couponscoupons:readProperty-visible active coupons
GET/v1/external/coupons/:code/previewcoupons:readCoupon preview without redemption
POST/v1/external/guestsguests:writeGuest creation
GET/v1/external/guestsguests:readProperty-visible guests
GET/v1/external/guests/:idguests:readGuest detail
POST/v1/external/guests/:id/filesguests:writeGuest file attachment
GET/v1/external/productsproducts:readActive products
GET/v1/external/products/:idproducts:readProduct detail with optional presigned image URL
GET/v1/external/price-quotepricing:readQuote without reservation creation
GET/v1/external/rate-plansrate-plans:readActive rate plans
POST/v1/external/reservationsreservations:writeReservation creation
GET/v1/external/reservationsreservations:readReservation listing
GET/v1/external/reservations/:idreservations:readReservation detail
POST/v1/external/reservations/:id/check-inreservations:writeCheck-in
POST/v1/external/reservations/:id/check-outreservations:writeCheck-out
GET/v1/external/reservations/:id/productsreservations:readReservation products
POST/v1/external/reservations/:id/productsreservations:writeAdd reservation product
POST/v1/external/reservations/:id/payment-intentreservations:writeStripe PaymentIntent creation
POST/v1/external/reservations/:id/confirmreservations:writeConfirm reservation (auto-advances draft → pending_payment → confirmed)
POST/v1/external/reservations/:id/cancelreservations:writeCancel reservation (accepts { reason?, sendEmail? } body)
GET/v1/external/reservations/:id/notesreservations:readReservation notes
POST/v1/external/reservations/:id/notesreservations:writeAdd reservation note
POST/v1/external/reservations/:id/paymentsreservations:writeRecord external payment
GET/v1/external/reservations/:id/access-codesreservations:readGuest-facing access codes (room + shared) from Seam
POST/v1/external/reservations/:id/filesreservations:writeAttach a pre-uploaded S3 file to a reservation
GET/v1/external/space-typesspace-types:readSpace-type listing (includes description, maxOccupancy, imageUrls, rates)
GET/v1/external/space-types/:idspace-types:readSpace-type detail
GET/v1/external/spacesspaces:readSpace 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 Unauthorized means the API key is missing, expired, or revoked.
  • 403 Forbidden means the key is valid but lacks property scope or the required route scope.
  • 404 Not Found hides records that belong to another property as well as genuinely missing ids.
  • 400 Bad Request covers 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.

On this page