Public Booking API

REST API for integrating availability, services, and bookings into external websites and applications. Uses API key authentication. All endpoints are prefixed with /api/v1.

Authentication

Every request must include a valid API key. You can pass it in either of two ways:

  • X-API-Key header (recommended)
  • Authorization: Bearer header
X-API-Key header
curl -H "X-API-Key: qv_pk_abc12345_0123456789abcdef0123456789abcdef" \
  https://api.qvian.com/api/v1/services
Authorization header
curl -H "Authorization: Bearer qv_pk_abc12345_0123456789abcdef0123456789abcdef" \
  https://api.qvian.com/api/v1/services

To create an API key, see API Keys.

Response Format

All responses use a consistent JSON envelope.

Success Response

{
  "success": true,
  "data": { ... },
  "meta": { "requestId": "abc123" }
}

Error Response

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      { "message": "startDate must be a valid date string" }
    ]
  },
  "meta": { "requestId": "abc123" }
}

Error Codes

CodeHTTP StatusDescription
VALIDATION_ERROR400Invalid request parameters or body
UNAUTHORIZED401Missing, invalid, revoked, or expired API key
FORBIDDEN403Key lacks required scope or origin not allowed
NOT_FOUND404Resource not found
RATE_LIMIT_EXCEEDED429Too many requests for this API key

Endpoints

GET/api/v1/availability

Search availability across the organization or a specific business unit.

Scope: read

Query Parameters

ParameterTypeRequiredDescription
startDatestringYesStart date (YYYY-MM-DD)
endDatestringYesEnd date (YYYY-MM-DD)
adultsnumberYesNumber of adults (min 1)
childrennumberYesNumber of children (min 0)
businessUnitIdstringNoFilter to a specific business unit
Example request
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.qvian.com/api/v1/availability?startDate=2026-03-01&endDate=2026-03-05&adults=2&children=0"
Example response (with businessUnitId)
{
  "success": true,
  "data": {
    "businessUnitId": "bu-abc123",
    "services": [
      {
        "serviceId": "svc-001",
        "name": "Beach Villa",
        "description": "Beachfront villa with ocean view",
        "pricingModel": "PerNight",
        "defaultPrice": 350.00,
        "currency": "USD",
        "bookingType": "Online",
        "category": "Accommodation",
        "durationMinutes": null,
        "availableResources": [
          {
            "resourceId": "res-001",
            "name": "Villa 1",
            "code": "VIL-001",
            "capacity": 4,
            "status": "Available"
          }
        ]
      }
    ]
  },
  "meta": { "requestId": "abc123" }
}

Only resources with no existing bookings in the requested date range are returned. Resources with any active reservation (reserved, confirmed, or checked-in) are excluded. If businessUnitId is omitted, the response groups results by business unit under a businessUnits array.

GET/api/v1/services

List active services for the organization.

Scope: read

Query Parameters

ParameterTypeRequiredDescription
businessUnitIdstringNoFilter by business unit
categorystringNoFilter by category name
Example request
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.qvian.com/api/v1/services"
Example response
{
  "success": true,
  "data": [
    {
      "id": "svc-001",
      "name": "Beach Villa",
      "description": "Beachfront villa with ocean view",
      "serviceType": "Accommodation",
      "pricingModel": "PerNight",
      "defaultPrice": 350.00,
      "currency": "USD",
      "bookingType": "Online",
      "category": "Accommodation",
      "durationMinutes": null,
      "businessUnitId": "bu-abc123",
      "businessUnitName": "Island Resort",
      "primaryImageUrl": null
    }
  ],
  "meta": { "requestId": "abc123" }
}

GET/api/v1/services/:id

Get a single service by ID.

Scope: read

Example request
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.qvian.com/api/v1/services/svc-001"

Returns the same service object format as the list endpoint. Returns 404 NOT_FOUND if the service does not exist or is not in the organization.

GET/api/v1/packages

List active packages for the organization.

Scope: read

Example request
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.qvian.com/api/v1/packages"
Example response
{
  "success": true,
  "data": [
    {
      "id": "pkg-001",
      "name": "Island Getaway",
      "description": "3-night stay with diving and meals",
      "packageType": "Accommodation",
      "pricingMode": "Dynamic",
      "basePrice": 1500.00,
      "currency": "USD",
      "minGuests": 2,
      "maxGuests": 6,
      "minNights": 3,
      "maxNights": 7,
      "validFrom": "2026-01-01",
      "validTo": "2026-12-31",
      "components": [
        {
          "id": "comp-001",
          "serviceName": "Beach Villa",
          "serviceType": "Accommodation",
          "isOptional": false,
          "quantity": 1
        },
        {
          "id": "comp-002",
          "serviceName": "Guided Dive",
          "serviceType": "Activity",
          "isOptional": true,
          "quantity": 2
        }
      ]
    }
  ],
  "meta": { "requestId": "abc123" }
}

GET/api/v1/packages/:id/pricing

Calculate pricing for a package based on dates and guest count.

Scope: read

Query Parameters

ParameterTypeRequiredDescription
startDatestringYesStart date (YYYY-MM-DD)
endDatestringYesEnd date (YYYY-MM-DD)
guestsnumberYesNumber of guests (min 1)
Example request
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.qvian.com/api/v1/packages/pkg-001/pricing?startDate=2026-03-01&endDate=2026-03-04&guests=2"
Example response
{
  "success": true,
  "data": {
    "packageId": "pkg-001",
    "packageName": "Island Getaway",
    "totalBasePrice": 1500.00,
    "packagePrice": 1500.00,
    "discountAmount": 0.00,
    "guestCount": 2,
    "nightCount": 3,
    "components": [
      {
        "componentId": "comp-001",
        "serviceName": "Beach Villa",
        "basePrice": 1050.00,
        "allocatedPrice": 1050.00,
        "businessUnitId": "bu-abc123"
      },
      {
        "componentId": "comp-002",
        "serviceName": "Guided Dive",
        "basePrice": 450.00,
        "allocatedPrice": 450.00,
        "businessUnitId": "bu-abc123"
      }
    ]
  },
  "meta": { "requestId": "abc123" }
}

POST/api/v1/reservations

Create a new reservation.

Scope: write

Request Body

FieldTypeRequiredDescription
businessUnitIdstringYesTarget business unit ID
customerobjectYesCustomer details (see below)
startDatestringYesCheck-in date (YYYY-MM-DD)
endDatestringYesCheck-out date (YYYY-MM-DD)
guestsobjectYes{ adults: number, children: number }
totalAmountnumberYesTotal price
primaryCurrencystringYesCurrency code (e.g. "USD")
selectedPackagestringNoPackage ID
selectedServicesarrayNoIndividual services to book (see below)
selectedAddonsarrayNoArray of addon IDs
packageComponentResourcesarrayNoPer-component resource assignments for package bookings (see below)
discountobjectNoDiscount to apply (see below)
notesstringNoBooking notes or special requests

Customer object:

FieldTypeRequired
firstNamestringYes
lastNamestringYes
emailstringYes
phonestringNo
titlestringNo

selectedServices[] object:

FieldTypeRequiredDescription
serviceIdstringYesService ID
businessUnitIdstringNoBusiness unit for cross-BU bookings
startDatestringYesService start date (YYYY-MM-DD)
endDatestringYesService end date (YYYY-MM-DD)
quantitynumberNoTotal guest count. For accommodation: total number of guests staying (e.g. 2 adults + 1 child = 3). For activities: number of seats/participants. Defaults to 1.
selectedResourceIdsstring[]NoSpecific resource IDs (e.g. room IDs)
resourceScheduleIdstringNoLink to a specific time slot for activities (diving, spa, etc.)
serviceDetailsobjectNoService metadata (booking type, departure times, meal selections, etc.)

packageComponentResources[] object:

FieldTypeRequiredDescription
componentIdstringYesPackage component ID (from GET /packages)
selectedResourceIdsstring[]YesResource IDs to assign to this component
resourceScheduleIdstringNoTime slot for activity components
scheduleSelectionsarrayNoMultiple schedule+resource pairs: [{ scheduleId, resourceId }]
startDatestringNoOverride start date for this component (YYYY-MM-DD)
endDatestringNoOverride end date for this component (YYYY-MM-DD)

discount object:

FieldTypeRequiredDescription
typestringYes"amount" (fixed) or "percent"
valuenumberYesDiscount value (e.g. 50 for $50 off or 10 for 10%)
reasonstringNoReason for the discount
Example: Package with room customization
curl -X POST -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "businessUnitId": "bu-abc123",
    "customer": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john@example.com"
    },
    "startDate": "2026-03-01",
    "endDate": "2026-03-05",
    "guests": { "adults": 2, "children": 0 },
    "selectedPackage": "pkg-001",
    "packageComponentResources": [
      {
        "componentId": "comp-001",
        "selectedResourceIds": ["res-villa-3"]
      },
      {
        "componentId": "comp-002",
        "selectedResourceIds": ["res-dive-boat-1"],
        "resourceScheduleId": "sched-morning-dive"
      }
    ],
    "discount": { "type": "percent", "value": 10, "reason": "Returning guest" },
    "totalAmount": 1350.00,
    "primaryCurrency": "USD"
  }' \
  "https://api.qvian.com/api/v1/reservations"
Example: Simple booking (minimal fields)
curl -X POST -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "businessUnitId": "bu-abc123",
    "customer": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john@example.com",
      "phone": "+1234567890"
    },
    "startDate": "2026-03-01",
    "endDate": "2026-03-05",
    "guests": { "adults": 2, "children": 0 },
    "selectedPackage": "pkg-001",
    "totalAmount": 1500.00,
    "primaryCurrency": "USD",
    "notes": "Late arrival, after 10 PM"
  }' \
  "https://api.qvian.com/api/v1/reservations"
Example response
{
  "success": true,
  "data": {
    "reference": "RES-000123",
    "status": "Draft",
    "startDate": "2026-03-01",
    "endDate": "2026-03-05",
    "adults": 2,
    "children": 0,
    "totalAmount": 1500.00,
    "primaryCurrency": "USD",
    "createdAt": "2026-02-28T10:00:00.000Z"
  },
  "meta": { "requestId": "abc123" }
}

Reservations are created as Draft

Reservations created via the API have Draft status. Staff must review and confirm the reservation in the Qvian Suite application. This is by design to allow the team to verify availability and details before committing.

GET/api/v1/reservations/:reference

Get reservation details by reference number.

Scope: read

Example request
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.qvian.com/api/v1/reservations/RES-000123"
Example response
{
  "success": true,
  "data": {
    "reference": "RES-000123",
    "status": "Confirmed",
    "startDate": "2026-03-01",
    "endDate": "2026-03-05",
    "adults": 2,
    "children": 0,
    "totalAmount": 1500.00,
    "subtotal": 1500.00,
    "totalDiscount": 0.00,
    "totalTax": 0.00,
    "primaryCurrency": "USD",
    "paymentStatus": "Pending",
    "customer": {
      "name": "John Doe",
      "email": "john@example.com",
      "phone": "+1234567890"
    },
    "items": [
      {
        "id": "item-001",
        "itemType": "Package",
        "startDate": "2026-03-01T00:00:00.000Z",
        "endDate": "2026-03-05T00:00:00.000Z",
        "amount": 1500.00,
        "status": "Unserved",
        "businessUnit": "Island Resort",
        "services": [
          { "serviceName": "Beach Villa", "quantity": 1 },
          { "serviceName": "Guided Dive", "quantity": 2 }
        ]
      }
    ],
    "createdAt": "2026-02-28T10:00:00.000Z"
  },
  "meta": { "requestId": "abc123" }
}

Integration Guide

This section explains how to design a booking website or application that gets the most out of the Qvian Suite reservation system. The API supports everything from simple one-room bookings to complex multi-property packages with activity scheduling and discounts.

Recommended Booking Flow

A well-designed booking website should follow this sequence of API calls to build a complete reservation:

  1. Fetch services and packages — Call GET /api/v1/services and GET /api/v1/packages to populate your catalog. Cache these; they change infrequently.
  2. Check availability — When the user selects dates and guest count, call GET /api/v1/availability to get available services and specific resources (rooms, boats, etc.).
  3. Calculate package pricing — If the user selects a package, call GET /api/v1/packages/:id/pricing with their dates and guest count. This returns the exact price and per-component breakdown with component IDs you will need for resource customization.
  4. Let the user customize — Show available rooms, time slots, and optional add-ons. Map user selections to the fields described below.
  5. Create the reservation — Call POST /api/v1/reservations with all collected data.
  6. Show confirmation — Display the returned reference number (e.g. RES-260301-001). Optionally poll GET /api/v1/reservations/:reference to show status updates.

Booking Scenarios

The reservation endpoint supports several booking patterns. You can combine them in a single request.

1. Package Booking (Most Common)

Packages bundle accommodation, activities, and meals into a single priced product. Set selectedPackage to the package ID. The server creates one reservation item per package component and handles pricing automatically.

Package booking — minimal
{
  "businessUnitId": "bu-resort",
  "customer": { "firstName": "Jane", "lastName": "Smith", "email": "jane@example.com" },
  "startDate": "2026-03-01",
  "endDate": "2026-03-05",
  "guests": { "adults": 2, "children": 1 },
  "selectedPackage": "pkg-island-getaway",
  "totalAmount": 2400.00,
  "primaryCurrency": "USD"
}

Without packageComponentResources, the system auto-assigns the best available room and default time slots. To let the user pick their room or activity time, see the Package Customization section below.

2. A La Carte Services

For bookings without a package, use selectedServices to pick individual services. Create one entry per service (not one per room) — put all room IDs in selectedResourceIds and set quantity to the total guest count for that service.

A la carte — two rooms + one activity
{
  "businessUnitId": "bu-resort",
  "customer": { "firstName": "Jane", "lastName": "Smith", "email": "jane@example.com" },
  "startDate": "2026-03-01",
  "endDate": "2026-03-05",
  "guests": { "adults": 4, "children": 0 },
  "selectedServices": [
    {
      "serviceId": "svc-beach-villa",
      "startDate": "2026-03-01",
      "endDate": "2026-03-05",
      "quantity": 4,
      "selectedResourceIds": ["room-villa-1", "room-villa-2"]
    },
    {
      "serviceId": "svc-guided-dive",
      "startDate": "2026-03-03",
      "endDate": "2026-03-03",
      "quantity": 4,
      "selectedResourceIds": ["vessel-1"],
      "resourceScheduleId": "sched-morning-8am"
    }
  ],
  "totalAmount": 3200.00,
  "primaryCurrency": "USD"
}

3. Mixed: Package + Extra Services

You can combine a package with additional a la carte services and add-ons in a single reservation. The package components are created first, then the extra services, then add-ons. If a discount is applied, it distributes proportionally across all items.

Package + extra spa session + add-on
{
  "businessUnitId": "bu-resort",
  "customer": { "firstName": "Jane", "lastName": "Smith", "email": "jane@example.com" },
  "startDate": "2026-03-01",
  "endDate": "2026-03-05",
  "guests": { "adults": 2, "children": 0 },
  "selectedPackage": "pkg-island-getaway",
  "selectedServices": [
    {
      "serviceId": "svc-spa-massage",
      "startDate": "2026-03-02",
      "endDate": "2026-03-02",
      "quantity": 2,
      "resourceScheduleId": "sched-spa-2pm"
    }
  ],
  "selectedAddons": ["addon-airport-transfer", "addon-honeymoon-setup"],
  "discount": { "type": "percent", "value": 10, "reason": "Returning guest" },
  "totalAmount": 2700.00,
  "primaryCurrency": "USD"
}

How Pricing Works

While you must send a totalAmount, the server recalculates the actual total from the selected services, package pricing, and discounts. The value you send serves as a reference but the authoritative price comes from the server. Use GET /api/v1/packages/:id/pricing and the service defaultPrice from the services endpoint to calculate accurate totals on your end.

Services use different pricing models depending on their type:

Pricing ModelCalculationTypical Use
PerNightunit price x rooms x nightsAccommodation (rooms, villas)
PerPersonunit price x participantsActivities (diving, yoga, excursions)
Fixedflat unit priceTransfers, one-time services
PerHourunit price x hoursEquipment rental, guided tours
PerDayunit price x daysEquipment rental, day passes
PerTripflat unit priceExcursions, transfers

Rooms and Resource Selection

Resources are the physical things being booked — rooms, villas, dive boats, spa treatment rooms, yoga decks, etc. The availability endpoint returns availableResources for each service, which gives you the resource IDs to use.

Resource selection is optional but recommended

If you omit selectedResourceIds, the system auto-assigns the best available resource. However, letting the user choose (e.g. "Villa 3 — Beachfront" vs "Villa 7 — Garden View") creates a much better booking experience. Use the availability endpoint to show what's available.

For accommodation (PerNight services), each resource ID in selectedResourceIds represents one room. Use the capacity field from the availability response to determine how many rooms you need (e.g. if each villa has capacity 4, then 6 guests need 2 villas). Create a single selectedServices entry with all room IDs and the total guest count:

Two rooms for a group of 4
{
  "serviceId": "svc-deluxe-room",
  "startDate": "2026-03-01",
  "endDate": "2026-03-05",
  "quantity": 4,
  "selectedResourceIds": ["room-101", "room-102"]
}

One entry per service — quantity = total guests

Do not create separate selectedServices entries for each room. Instead, create one entry per service with all room IDs in selectedResourceIds and quantity set to the total guest count. The system automatically distributes guests across the rooms using each service's capacity configuration. For example, 4 adults across 2 rooms = quantity: 4 with selectedResourceIds: ["room-101", "room-102"].

Activity Time Slots

Activities like diving, spa treatments, and excursions often run on a schedule (e.g. "Morning Dive at 8:00 AM", "Afternoon Spa at 2:00 PM"). The resourceScheduleId field links a booking to a specific time slot. Without it, the system creates a generic period allocation instead of a precise scheduled booking — staff won't see which departure or session the guest chose.

To build a time slot picker, use the availability endpoint to discover services and resources, then let the user pick a slot. The schedule ID comes from your service catalog or availability data.

Activity with time slot — 4 people on the morning dive
{
  "serviceId": "svc-guided-dive",
  "startDate": "2026-03-03",
  "endDate": "2026-03-03",
  "quantity": 4,
  "selectedResourceIds": ["dive-boat-1"],
  "resourceScheduleId": "sched-morning-8am"
}

The quantity field represents seats/participants, not resource count. If 4 guests book a dive, set quantity: 4 — the system allocates 4 seats on the specified vessel.

Service Details (Metadata)

The serviceDetails field is a freeform JSON object attached to each service booking. It is stored as-is and displayed to staff in the reservation detail view. Use it to capture any service-specific information that staff need to fulfill the booking.

Examples of serviceDetails by service type
// Diving activity
"serviceDetails": {
  "certificationLevel": "Advanced Open Water",
  "equipmentNeeded": ["BCD", "Regulator"],
  "departureTime": "08:00"
}

// Spa treatment
"serviceDetails": {
  "treatmentPreference": "Deep Tissue",
  "therapistGender": "Female",
  "allergies": "None"
}

// Meal plan
"serviceDetails": {
  "dietaryRequirements": ["Vegetarian", "Gluten-free"],
  "mealPlan": "Half Board"
}

// Transfer
"serviceDetails": {
  "flightNumber": "QR 674",
  "arrivalTime": "14:30",
  "pickupLocation": "Velana International Airport"
}

Package Customization

Packages contain multiple components (e.g. a villa + 2 dives + meals). By default, the system auto-assigns resources to each component. Use packageComponentResources to let the user choose which room they want, which dive slot they prefer, etc.

The component IDs come from the GET /api/v1/packages response, where each package has a components[] array with id fields.

Package with room + activity customization
{
  "selectedPackage": "pkg-island-getaway",
  "packageComponentResources": [
    {
      "componentId": "comp-villa",
      "selectedResourceIds": ["room-villa-3"]
    },
    {
      "componentId": "comp-dive",
      "selectedResourceIds": ["dive-boat-1"],
      "resourceScheduleId": "sched-morning-8am",
      "startDate": "2026-03-02",
      "endDate": "2026-03-02"
    },
    {
      "componentId": "comp-second-dive",
      "selectedResourceIds": ["dive-boat-2"],
      "resourceScheduleId": "sched-afternoon-2pm",
      "startDate": "2026-03-04",
      "endDate": "2026-03-04"
    }
  ]
}

Key points:

  • You only need to include components the user wants to customize. Omitted components use auto-assignment.
  • Use startDate/endDate on a component to override when that activity happens within the stay.
  • For components with multiple schedule slots (e.g. a dive that includes morning briefing + afternoon dive), use scheduleSelections instead of a single resourceScheduleId.
Component with multiple schedule selections
{
  "componentId": "comp-full-day-dive",
  "selectedResourceIds": [],
  "scheduleSelections": [
    { "scheduleId": "sched-briefing-7am", "resourceId": "classroom-1" },
    { "scheduleId": "sched-dive-9am", "resourceId": "dive-boat-1" }
  ],
  "startDate": "2026-03-03",
  "endDate": "2026-03-03"
}

Discounts

Apply a discount to the entire reservation using the discount field. The server distributes the discount proportionally across all reservation items (both package components and a la carte services). Each item's discount is capped at its own amount — no item goes below zero.

TypeExampleBehavior
"amount"{ "type": "amount", "value": 200 }$200 off the total, distributed proportionally
"percent"{ "type": "percent", "value": 15 }15% off each item proportionally

The reason field is optional but recommended — it appears in the staff reservation detail view (e.g. "Returning guest", "Early bird promo", "Corporate rate").

Cross-Business-Unit Bookings

If the organization has multiple business units (e.g. a resort and a dive center), a single reservation can span both. The top-level businessUnitId is the primary/receiving property. For a la carte services, use businessUnitId on each service to specify which BU provides it. For packages, cross-BU is handled automatically based on which BU each package component belongs to.

Cross-BU: Resort stay + Dive center activity
{
  "businessUnitId": "bu-resort",
  "selectedServices": [
    {
      "serviceId": "svc-beach-villa",
      "businessUnitId": "bu-resort",
      "startDate": "2026-03-01",
      "endDate": "2026-03-05",
      "selectedResourceIds": ["room-villa-1"]
    },
    {
      "serviceId": "svc-guided-dive",
      "businessUnitId": "bu-dive-center",
      "startDate": "2026-03-03",
      "endDate": "2026-03-03",
      "quantity": 2,
      "resourceScheduleId": "sched-morning-8am"
    }
  ]
}

The system handles inter-business-unit financial settlements automatically when payment is processed by staff.

Customer Matching

The system automatically matches customers by email address. If a guest has booked before, their existing customer profile is linked to the new reservation — staff see the full history. You can also pass tradingPartnerId in the customer object to explicitly link to a known customer record. If no match is found, a new customer profile is created automatically.

Design Tips for Building a Booking Website

Cache your catalog

Services and packages change infrequently. Fetch them once and cache for hours or days. Only availability needs to be checked in real-time.

Show available rooms, not just room types

The availability endpoint returns specific resources with names (e.g. "Ocean Villa 3"). Showing named rooms creates a premium experience and ensures the guest gets exactly what they chose.

Let guests pick activity time slots

Always pass resourceScheduleId for activities. Without it, staff see a generic booking with no time information and have to follow up manually.

Use packages when available

Packages handle pricing, revenue allocation, and component bundling automatically. If the property offers packages, prefer them over manually assembling individual services.

Pass serviceDetails generously

Any information that helps staff prepare — flight numbers, dietary needs, certification levels, special occasions — should go in serviceDetails. It saves back-and-forth communication.

Use YYYY-MM-DD dates, never ISO timestamps

All date fields expect YYYY-MM-DD format. Do not use .toISOString() in JavaScript — it can shift dates due to timezone conversion. Use a simple date formatter instead.

Poll for status updates

Reservations are created as Draft. If you want to show guests when their booking is confirmed, poll GET /api/v1/reservations/:reference periodically and update your UI when the status changes.

Complete Example: Full-Featured Reservation

This example uses every capability — package with room customization, activity time slots, extra services, add-ons, discount, service details, and notes:

POST /api/v1/reservations — full-featured example
{
  "businessUnitId": "bu-island-resort",
  "customer": {
    "firstName": "Sarah",
    "lastName": "Chen",
    "email": "sarah.chen@example.com",
    "phone": "+1-555-0123",
    "title": "Ms"
  },
  "startDate": "2026-04-10",
  "endDate": "2026-04-15",
  "guests": { "adults": 2, "children": 1 },

  "selectedPackage": "pkg-family-island-escape",
  "packageComponentResources": [
    {
      "componentId": "comp-family-villa",
      "selectedResourceIds": ["villa-beachfront-2"]
    },
    {
      "componentId": "comp-snorkel-trip",
      "selectedResourceIds": ["snorkel-boat-1"],
      "resourceScheduleId": "sched-snorkel-morning",
      "startDate": "2026-04-12",
      "endDate": "2026-04-12"
    }
  ],

  "selectedServices": [
    {
      "serviceId": "svc-sunset-cruise",
      "startDate": "2026-04-13",
      "endDate": "2026-04-13",
      "quantity": 3,
      "resourceScheduleId": "sched-cruise-5pm",
      "serviceDetails": {
        "dietaryRequirements": ["No shellfish"],
        "celebration": "Wedding anniversary"
      }
    }
  ],

  "selectedAddons": ["addon-airport-transfer-roundtrip"],

  "discount": {
    "type": "percent",
    "value": 10,
    "reason": "Returning guest — 3rd visit"
  },

  "totalAmount": 4320.00,
  "primaryCurrency": "USD",
  "notes": "Arriving on seaplane at 3 PM. Please arrange golf cart pickup at jetty."
}

Rate Limiting

Each API key has a configurable rate limit (default: 60 requests per minute). When the limit is exceeded, the API returns a 429 response with the RATE_LIMIT_EXCEEDED error code.

To increase the limit, create a new API key with a higher Rate Limit value (up to 1000 requests per minute). See API Keys for instructions.

Troubleshooting

401 Unauthorized

Cause: API key is missing, invalid, revoked, or expired.

Fix: Verify the key is included in the X-API-Key or Authorization header. Check the key status in SettingsAPI Keys.

403 Forbidden

Cause: Key lacks the required scope, or the request origin is not in the allowed origins list.

Fix: Ensure the key has the correct scope (Read for GET endpoints, Write for POST). If calling from a browser, add the domain to the key's allowed origins.

429 Too Many Requests

Cause: Rate limit exceeded for this API key.

Fix: Wait and retry, or create a new key with a higher rate limit.

Reservation Created but Not Confirmed

Cause: This is expected behavior.

Explanation: API-created reservations have Draft status. Staff review and confirm them in the Qvian Suite application. Use the GET /api/v1/reservations/:reference endpoint to check the current status.

Related: API Keys, Reservations, Service Management.