> ## Documentation Index
> Fetch the complete documentation index at: https://developers.novala.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Bookings API — Resources, Availability, and Reservations

> List bookable resources, check available time slots for a date range, create bookings, retrieve booking details, and cancel reservations programmatically.

The bookings API lets you embed scheduling into any external surface — a website, a mobile app, or an AI agent. You list available resources, query open slots for a date range, and confirm a booking by passing a slot ID back to the create endpoint. Slot IDs are opaque and encode both the resource and the start time, so you never need to reconstruct them.

## Required scopes

| Operation                                         | Required scope              |
| ------------------------------------------------- | --------------------------- |
| List resources, check availability, list bookings | `bookings.read`             |
| Create and cancel bookings                        | `bookings.manage`           |
| Create and update resources                       | `bookings.resources.manage` |

## Resource types

A bookable resource can represent any schedulable entity:

`location` · `service` · `room` · `equipment` · `person`

## Booking statuses

| Status      | Meaning                     |
| ----------- | --------------------------- |
| `confirmed` | Booking is active.          |
| `cancelled` | Booking was cancelled.      |
| `no_show`   | Guest did not appear.       |
| `completed` | Booking has been completed. |

***

## List resources

`GET /api/bookings/resources`

Returns all active bookable resources for the tenant.

### Query parameters

<ParamField query="type" type="string">
  Filter by resource type: `location`, `service`, `room`, `equipment`, or `person`.
</ParamField>

<ParamField query="query" type="string">
  Search by resource name.
</ParamField>

<ParamField query="includeInactive" type="boolean" default="false">
  When `true`, includes resources where `isActive` is `false`.
</ParamField>

<ParamField query="limit" type="integer">
  Maximum number of results.
</ParamField>

<ParamField query="offset" type="integer" default="0">
  Results to skip for pagination.
</ParamField>

### Response

```json theme={null}
{
  "data": [
    {
      "id": "res-uuid-001",
      "type": "location",
      "name": "Main Campus Tour",
      "slug": "main-campus-tour",
      "description": "Guided tour of the main campus.",
      "addressLine1": "500 Campus Drive",
      "city": "Salt Lake City",
      "state": "UT",
      "postalCode": "84101",
      "timezone": "America/Denver",
      "defaultDurationMinutes": 60,
      "maxConcurrent": 10,
      "isActive": true,
      "published": true
    }
  ]
}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://app.novala.ai/api/bookings/resources?type=location" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(
    'https://app.novala.ai/api/bookings/resources?type=location',
    { headers: { 'Authorization': `Bearer ${apiKey}` } }
  );
  const { data } = await response.json();
  ```
</CodeGroup>

***

## Get available slots

`GET /api/bookings/resources/{id}/availability`

Returns available time slots for a resource within a date range. Use this before creating a booking to present real-time availability to a user.

### Path parameters

<ParamField path="id" type="string" required>
  UUID of the bookable resource.
</ParamField>

### Query parameters

<ParamField query="from" type="string">
  Start of the date range (ISO 8601, for example `2024-08-01` or `2024-08-01T00:00:00Z`). Defaults to now.
</ParamField>

<ParamField query="to" type="string">
  End of the date range (ISO 8601). Defaults to 7 days from `from`.
</ParamField>

### Response

```json theme={null}
{
  "resourceId": "res-uuid-001",
  "slots": [
    {
      "slotId": "cmVzLXV1aWQtMDAxXzIwMjQtMDgtMDFUMDk6MDA6MDBa",
      "startsAt": "2024-08-01T09:00:00.000Z",
      "endsAt": "2024-08-01T10:00:00.000Z",
      "remaining": 8
    },
    {
      "slotId": "cmVzLXV1aWQtMDAxXzIwMjQtMDgtMDFUMTA6MDA6MDBa",
      "startsAt": "2024-08-01T10:00:00.000Z",
      "endsAt": "2024-08-01T11:00:00.000Z",
      "remaining": 10
    }
  ]
}
```

`remaining` reflects the number of additional bookings that can be made for the slot, based on `maxConcurrent`.

<Warning>
  Slot IDs are opaque strings. Do not parse or construct them manually — always pass back the exact `slotId` value returned by this endpoint when creating a booking.
</Warning>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://app.novala.ai/api/bookings/resources/res-uuid-001/availability?from=2024-08-01&to=2024-08-07" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const params = new URLSearchParams({ from: '2024-08-01', to: '2024-08-07' });
  const response = await fetch(
    `https://app.novala.ai/api/bookings/resources/res-uuid-001/availability?${params}`,
    { headers: { 'Authorization': `Bearer ${apiKey}` } }
  );
  const { slots } = await response.json();
  ```
</CodeGroup>

***

## List bookings

`GET /api/bookings`

Returns a list of bookings for the tenant, with optional filters.

### Query parameters

<ParamField query="resourceId" type="string">
  Filter by resource UUID.
</ParamField>

<ParamField query="status" type="string">
  Filter by booking status: `confirmed`, `cancelled`, `no_show`, `completed`.
</ParamField>

<ParamField query="leadId" type="string">
  Filter by the lead UUID associated with the booking.
</ParamField>

<ParamField query="from" type="string">
  Return bookings starting on or after this date (ISO 8601).
</ParamField>

<ParamField query="to" type="string">
  Return bookings starting on or before this date (ISO 8601).
</ParamField>

<ParamField query="limit" type="integer">
  Maximum number of results.
</ParamField>

<ParamField query="offset" type="integer" default="0">
  Offset for pagination.
</ParamField>

***

## Create a booking

`POST /api/bookings`

Confirms a booking for a slot. The slot is validated against the resource's operating hours and closures at write time. If the slot is no longer available, the request returns `409 Conflict`.

### Request body

<ParamField body="slotId" type="string" required>
  Opaque slot ID obtained from the availability endpoint.
</ParamField>

<ParamField body="firstName" type="string" required>
  Guest's first name.
</ParamField>

<ParamField body="lastName" type="string" required>
  Guest's last name.
</ParamField>

<ParamField body="email" type="string" required>
  Guest's email address. Used for confirmation emails.
</ParamField>

<ParamField body="phone" type="string">
  Guest's phone number.
</ParamField>

<ParamField body="notes" type="string">
  Additional notes from the guest.
</ParamField>

<ParamField body="interest" type="string">
  What the guest is interested in (for example, a specific program or service).
</ParamField>

<ParamField body="source" type="string" default="api">
  Attribution for the booking source.
</ParamField>

### Response

Returns `201 Created` with the confirmed booking object. Returns `409 Conflict` if the slot has been taken.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.novala.ai/api/bookings \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "slotId": "cmVzLXV1aWQtMDAxXzIwMjQtMDgtMDFUMDk6MDA6MDBa",
      "firstName": "Morgan",
      "lastName": "Lee",
      "email": "morgan.lee@example.com",
      "phone": "+15559876543",
      "interest": "Fall semester enrollment"
    }'
  ```

  ```typescript TypeScript theme={null}
  // 1. Fetch availability
  const availRes = await fetch(
    'https://app.novala.ai/api/bookings/resources/res-uuid-001/availability?from=2024-08-01',
    { headers: { Authorization: `Bearer ${apiKey}` } }
  );
  const { slots } = await availRes.json();
  const slot = slots[0]; // pick a slot to show the user

  // 2. Confirm the booking
  const bookRes = await fetch('https://app.novala.ai/api/bookings', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      slotId: slot.slotId,
      firstName: 'Morgan',
      lastName: 'Lee',
      email: 'morgan.lee@example.com',
      interest: 'Fall semester enrollment',
    }),
  });

  if (bookRes.status === 409) {
    // Slot was taken between availability check and booking
    throw new Error('Slot no longer available');
  }
  const booking = await bookRes.json();
  ```
</CodeGroup>

***

## Get a booking

`GET /api/bookings/{id}`

Retrieves a single booking by ID.

### Path parameters

<ParamField path="id" type="string" required>
  UUID of the booking.
</ParamField>

### Response

Returns `200 OK` with `{ "booking": { booking object } }`.

***

## Cancel a booking

`DELETE /api/bookings/{id}`

Cancels a booking. The booking record is retained; its status changes to `cancelled`. Returns `404` if the booking is already cancelled.

### Path parameters

<ParamField path="id" type="string" required>
  UUID of the booking to cancel.
</ParamField>

### Response

Returns `200 OK` with `{ "success": true }`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE https://app.novala.ai/api/bookings/booking-uuid-001 \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch('https://app.novala.ai/api/bookings/booking-uuid-001', {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${apiKey}` },
  });
  // 200 { success: true }
  ```
</CodeGroup>
