# CRM · Reservations

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /crm/reservations/definitions

**Get reservation definitions**

`operationId: ReservationsController_getReservationDefinitions`

The reservation types the org offers, with their schemas — read this to build a booking form generically rather than hard-coding appointment types.

#### Signature

```http
GET /crm/reservations/definitions () -> Reservation definitions
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/reservations/slots`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Reservation definitions |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/slots

**Generate appointment slots**

`operationId: ReservationsController_generateAppointmentSlots`

Computes the bookable slots for a period from the service point's availability and existing reservations.

Slots are calculated, not reserved — one returned here can be taken by someone else before you book it.

The definition record must be datatype **`reservation_definition`**. Creating it as `crm_reservation_definition` succeeds and then every slot request answers "Reservation Definition not found", because the generator looks in the other collection.

Slot times come back in UTC with `businessTimezone` alongside. Format for display in the business timezone, not the visitor's — a customer abroad shown their own local time books an appointment nobody turns up to.

#### Signature

```http
POST /crm/reservations/slots (body) -> The available slots
```

#### Access

Public — no credentials required.

#### Notes

- No hold is placed. Handle the race between showing a slot and booking it.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/reservations/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The criteria to generate slots for.

```json
{
  "servicePointId": "sp_t7",
  "startDate": "2026-09-01T09:00:00.000Z",
  "endDate": "2026-09-01T17:00:00.000Z",
  "duration": 30
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The available slots |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/reservations/by-email/{email}/{reservationNumber}

**Get reservations by email**

`operationId: ReservationsController_getReservationByEmail`

A customer's reservations, keyed on their email address rather than an account — the self-service "my bookings" read.

#### Signature

```http
GET /crm/reservations/by-email/{email}/{reservationNumber} (email: string, reservationNumber: string) -> The customer's reservations
```

#### Access

Public — no credentials required.

#### Notes

- Keyed on email alone.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `DELETE /crm/reservations/cancel/{email}/{reservationNumber}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `reservationNumber` | path | string | yes | Reservation number. Omit to list all for the email. |
| `email` | path | string | yes | Customer email address. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The customer's reservations |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/reservations/cancel/{email}/{reservationNumber}

**Cancel a reservation**

`operationId: ReservationsController_cancelReservationEntry`

Cancels a customer's reservation, matched on both email and reservation number so one alone is not enough. Prefer this to deletion — it frees the slot while keeping the record.

#### Signature

```http
DELETE /crm/reservations/cancel/{email}/{reservationNumber} (email: string, reservationNumber: string) -> Cancellation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `DELETE /crm/reservations/delete/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `x-client-info` | header | string | yes |  |
| `reservationNumber` | path | string | yes | Reservation number. |
| `email` | path | string | yes | Customer email address. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Cancellation result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/reservations/mine

**My hosted bookings**

`operationId: ReservationsController_getMyHostedReservations`

Reservations the signed-in staff member hosts — directly, or through a service they host. Up to 1000.

#### Signature

```http
GET /crm/reservations/mine () -> Paged reservations (`{ data, total }`)
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in user. | — |

Plus the standard platform errors: `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Paged reservations (`{ data, total }`) |
| `401` | Unauthorized — No signed-in user. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/reservations/get/{id}

**Get reservations**

`operationId: ReservationsController_getReservationEntries`

Fetches one reservation by id, or lists them when the segment is omitted.

#### Signature

```http
GET /crm/reservations/get/{id} (id: string) -> The reservation, or all reservations
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `GET /crm/reservations/by-email/{email}/{reservationNumber}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Reservation id. Omit to list. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The reservation, or all reservations |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/reservations/delete/{id}

**Delete a reservation**

`operationId: ReservationsController_deleteReservationEntry`

Deletes a reservation outright. Cancelling instead keeps the record and notifies the customer.

#### Signature

```http
DELETE /crm/reservations/delete/{id} (id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `DELETE /crm/reservations/cancel/{email}/{reservationNumber}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `x-client-info` | header | string | yes |  |
| `id` | path | string | yes | Reservation id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/create

**Create a reservation**

`operationId: ReservationsController_createReservationEntry`

Books a reservation. Availability is not held between generating slots and creating one, so a slot can be lost in between — handle a conflict on create.

#### Signature

```http
POST /crm/reservations/create (body) -> The created reservation
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- The `reservation_definition` it books against needs `workDays` as CAPITALISED full day names — `["Monday","Tuesday"]`. Lower-case or abbreviated names match nothing and that day silently generates no slots.
- REQUIRES AUTH, unlike the rest of the booking flow. `definitions`, `slots`, `by-email` and `cancel` all answer anonymously; this returns 401 without a credential. A public page can therefore SHOW availability but cannot take a booking — call it through the browser runtime on a hosted page, or through your own server. Never by putting an operator token in a page.
- Pass the slot's `startTime` and `endTime` back exactly as `slots` returned them. Rebuilding them from a local Date reintroduces the timezone the generator already resolved, and the booking lands an hour out twice a year.
- A customer record is created from the `customer` object when the email is new, and reused when it is not.
- Confirmation email is billed. An org without credit still books the reservation and sends nothing.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `POST /crm/reservations/update`
- `POST /crm/reservations/slots`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `x-client-info` | header | string | yes |  |

### Request body

The reservation to create.

```json
{
  "data": {
    "email": "ada@example.com",
    "servicePointId": "sp_t7",
    "startDate": "2026-09-01T10:00:00.000Z",
    "endDate": "2026-09-01T10:30:00.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created reservation |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/update

**Update a reservation**

`operationId: ReservationsController_updateReservationEntry`

Updates a reservation's details — customer, party size, notes, status. Include the record `sk`. To move it to another time use `POST /crm/reservations/reschedule/{id}`.

#### Signature

```http
POST /crm/reservations/update (body) -> The updated reservation
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- A time sent here by staff is saved as given — no capacity check, no record of the old time, and the customer gets the generic "updated" message. Reschedule instead.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `POST /crm/reservations/reschedule/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `x-client-info` | header | string | yes |  |

### Request body

The reservation to update.

```json
{
  "sk": "66f1a2b3c4d5e6f708192a3b",
  "data": {
    "startDate": "2026-09-02T10:00:00.000Z"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated reservation |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/availability

**Availability across a run of days**

`operationId: ReservationsController_getAvailability`

What is open for a service over up to 31 days, in one call — the question a caller asks ("anything Friday or Saturday?").

Unlike `slots`, full slots are returned too (`spotsAvailable: 0`), so a screen can show the shape of each day rather than a list with holes in it. A slot already past, or inside a blocked time, is not a slot and is left out. A day outside `workDays` comes back `closed: true` with no slots.

Capacity is the definition's `spots`: how many bookings one slot holds. Any live booking that overlaps the slot at all takes a spot.

Times are UTC; format them in the returned `timezone` (the business's), not the viewer's.

#### Signature

```http
POST /crm/reservations/availability (body) -> Per-day slots, plus the first open one
```

#### Access

Public — no credentials required.

#### Notes

- No hold is placed. Handle the race between showing a slot and booking it.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/reservations/reschedule/{id}`
- `POST /crm/reservations/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

What to look for.

```json
{
  "reservationDefinitionId": "6aa798c44f3cf920a2c64b07",
  "serviceName": "consultation",
  "from": "2026-09-21",
  "days": 7
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Per-day slots, plus the first open one |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

Example response:

```json
{
  "reservationDefinitionId": "6aa798c44f3cf920a2c64b07",
  "service": {
    "name": "consultation",
    "duration": 30,
    "price": 0
  },
  "timezone": "America/New_York",
  "from": "2026-09-21",
  "to": "2026-09-27",
  "nextAvailable": {
    "date": "2026-09-21",
    "startTime": "2026-09-21T18:00:00.000Z",
    "endTime": "2026-09-21T18:30:00.000Z",
    "spotsTotal": 2,
    "spotsAvailable": 1
  },
  "days": [
    {
      "date": "2026-09-21",
      "weekday": "Monday",
      "closed": false,
      "openSlots": 5,
      "slots": [
        {
          "startTime": "2026-09-21T18:00:00.000Z",
          "endTime": "2026-09-21T18:30:00.000Z",
          "spotsTotal": 2,
          "spotsAvailable": 1
        }
      ]
    }
  ]
}
```

## GET /crm/reservations/stats

**Reservation figures**

`operationId: ReservationsController_getReservationStats`

Header figures for the Reservations screen, computed on the server: `total`, `today` (bookings on the current day in each booking's own business timezone), `upcoming` (live bookings still ahead) and `revenue` (sum of live bookings' price). Cancelled and no-show bookings count toward neither upcoming nor revenue. Staff only: a customer credential answers 401.

#### Signature

```http
GET /crm/reservations/stats ()
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` |  |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/slot-bookings

**Bookings in a slot**

`operationId: ReservationsController_getSlotBookings`

Who holds a slot — the live bookings that overlap the given time, earliest first, with the slot's capacity. Staff only: it names customers, so a customer credential answers 401.

#### Signature

```http
POST /crm/reservations/slot-bookings (body) -> `{ spotsTotal, spotsAvailable, bookings[] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `POST /crm/reservations/availability`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The slot, as `availability` returned it.

```json
{
  "reservationDefinitionId": "6aa798c44f3cf920a2c64b07",
  "startTime": "2026-09-25T22:30:00.000Z",
  "endTime": "2026-09-25T23:00:00.000Z"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `{ spotsTotal, spotsAvailable, bookings[] }` |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/reschedule/{id}

**Reschedule a reservation**

`operationId: ReservationsController_rescheduleReservationEntry`

Moves a booking to another time. The end time is derived — the booking keeps its length, or takes the new service's duration when `service` changes with the move.

The new time is checked: not past, inside opening hours on a working day, not blocked, and with a spot free (the booking's own seat does not count against it). The move is appended to `data.rescheduleHistory` (from/to, when, who, reason), the customer is sent `reservation-rescheduled` showing old → new with a fresh calendar invite, and the reminders are re-cut for the new time.

The status is not changed — a moved booking is still pending or confirmed.

#### Signature

```http
POST /crm/reservations/reschedule/{id} (id: string, body) -> The moved reservation
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- A time that cannot take the booking answers 409 with `reason: "slot_unavailable"`, `problems: [{ code, message }]` (codes: `past`, `closed`, `outside_hours`, `blocked`, `full`) and `canOverride`. Show the message; staff may resend with `override: true`.
- A customer may only move their own booking; anyone else's answers 404.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | UNAUTHORIZED | Unauthorized | No signed-in customer or user could be resolved. | Sign in. These routes are scoped to the caller. |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `POST /crm/reservations/availability`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `x-client-info` | header | string | yes |  |
| `id` | path | string | yes | Reservation `sk`. |

### Request body

Where to move it.

```json
{
  "startTime": "2026-09-25T22:30:00.000Z",
  "reason": "Customer called — running late from work"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The moved reservation |
| `401` | Unauthorized — No signed-in customer or user could be resolved. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/reservations/service-point/get/{id}

**Get service points**

`operationId: ReservationsController_getServicePoints`

The bookable service points — rooms, chairs, desks, staff. Omit `id` to list them all.

#### Signature

```http
GET /crm/reservations/service-point/get/{id} (id: string) -> Service points
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/reservations/service-point/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `x-client-info` | header | string | — | Client context, used to tailor which service points are returned. |
| `id` | path | string | yes | Service point id. Omit to list. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Service points |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/reservations/service-point/delete/{id}

**Delete a service point**

`operationId: ReservationsController_deleteServicePoint`

Deletes a service point. Reservations already booked against it are not moved or cancelled — check for them first, or they become unreachable.

#### Signature

```http
DELETE /crm/reservations/service-point/delete/{id} (id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Orphans any reservation still pointing at it.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/reservations/service-point/get/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Service point id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/service-point/create

**Create a service point**

`operationId: ReservationsController_createServicePoint`

Creates a bookable service point. Its availability rules are what the slot generator works from.

#### Signature

```http
POST /crm/reservations/service-point/create (body) -> The created service point
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/reservations/service-point/update`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The service point to create.

```json
{
  "data": {
    "name": "Consulting room 2",
    "capacity": 1
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created service point |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/service-point/update

**Update a service point**

`operationId: ReservationsController_updateServicePoint`

Updates a service point. Changing its availability affects future slot generation but does not move reservations already booked against it.

#### Signature

```http
POST /crm/reservations/service-point/update (body) -> The updated service point
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Existing reservations are not revalidated against new availability rules.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `DELETE /crm/reservations/service-point/delete/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The service point to update.

```json
{
  "sk": "66f1a2b3c4d5e6f708192a3b",
  "data": {
    "name": "Consulting room 2 (accessible)"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated service point |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/reservations/meeting/token

**Get a meeting token**

`operationId: ReservationsController_getMeetingToken`

Issues a token for the caller to join a video meeting. Tokens are short-lived — fetch one when joining rather than storing it.

#### Signature

```http
GET /crm/reservations/meeting/token () -> A meeting token
```

#### Access

Public — no credentials required.

#### Notes

- The token grants meeting access — treat it as a credential.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/reservations/meeting/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A meeting token |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/meeting/create

**Create a meeting room**

`operationId: ReservationsController_createMeeting`

Creates a video meeting room, typically for a reservation that is held remotely.

#### Signature

```http
POST /crm/reservations/meeting/create (body) -> The created meeting
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/reservations/meeting/validate`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The meeting to create.

```json
{
  "name": "Consultation with Ada",
  "reservationId": "RES-4821"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created meeting |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/meeting/validate

**Validate a meeting**

`operationId: ReservationsController_validateMeeting`

Checks that a meeting exists and the caller may join it — the pre-flight before showing a join button.

#### Signature

```http
POST /crm/reservations/meeting/validate (body) -> Whether the meeting is valid and joinable
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/reservations/meeting/token`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The meeting to validate.

```json
{
  "meetingId": "MTG-4821"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Whether the meeting is valid and joinable |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/reservations/send-reminder/{reservationId}

**Send a reservation reminder**

`operationId: ReservationsController_sendReservationReminder`

Sends the customer a reminder about an upcoming reservation. Sends on every call — there is no once-only guard, so repeated calls repeat the reminder.

#### Signature

```http
POST /crm/reservations/send-reminder/{reservationId} (reservationId: string) -> Dispatch result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/reservations/get/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `x-client-info` | header | string | yes |  |
| `reservationId` | path | string | yes | Reservation id. |

### Request body

Notification details (optional - uses reservation definition notifications if not provided)

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Dispatch result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

