---
title: Bookings
description: Read, record, update and cancel meetings booked through Signal's Scheduler.
product: signal
section: api
order: 21
---

# Bookings

A booking is a meeting on the calendar of someone in your workspace, usually made through a Scheduler booking page. Bookings can link to an Account, a Contact, a prospect contact and a Deal.

## The booking object

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | 25-character ID. |
| `title` | string or null | |
| `invitee_name` | string | The person the meeting is with. |
| `invitee_email` | string or null | |
| `invitee_phone` | string or null | |
| `start_at` | string or null | ISO 8601 timestamp. |
| `end_at` | string or null | ISO 8601 timestamp. |
| `duration` | integer or null | Length in minutes. |
| `status` | string or null | `scheduled`, `rescheduled` or `cancelled`. |
| `notes` | string or null | |
| `event_type_id` | string or null | The Scheduler event type it was booked through. |
| `owner_id` | string or null | User ID of the host. |
| `account_id` | string or null | |
| `contact_id` | string or null | |
| `prospect_id` | string or null | |
| `deal_id` | string or null | |
| `timezone` | string or null | IANA time zone, such as `America/New_York`. |
| `location_type` | string or null | For example `zoom`, `google_meet`, `teams`, `phone`, `in_person` or `other`. |
| `location_details` | string or null | A link, phone number or address. |
| `cancelled_at` | string or null | When it was canceled. |
| `cancel_reason` | string or null | |
| `created_at` | string | ISO 8601 timestamp. |
| `updated_at` | string | ISO 8601 timestamp. |

```json
{
  "id": "cm3ka3c8u0007ab7c4d5e6f7g",
  "title": "Product demo",
  "invitee_name": "Sam Lee",
  "invitee_email": "sam.lee@fabrikam.example",
  "invitee_phone": null,
  "start_at": "2026-11-18T16:00:00.000Z",
  "end_at": "2026-11-18T16:30:00.000Z",
  "duration": 30,
  "status": "scheduled",
  "notes": null,
  "event_type_id": null,
  "owner_id": "5b8d1f3a-7e2c-4a9b-b6d4-1c3e5f7a9b20",
  "account_id": null,
  "contact_id": null,
  "prospect_id": "e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63",
  "deal_id": null,
  "timezone": "America/New_York",
  "location_type": "zoom",
  "location_details": "https://zoom.us/j/0000000000",
  "cancelled_at": null,
  "cancel_reason": null,
  "created_at": "2026-11-12T09:30:00.000Z",
  "updated_at": "2026-11-12T09:30:00.000Z"
}
```

---

## List bookings

`GET /bookings` · Scope: `bookings:read`

Returns bookings newest first (by when they were made, not when they take place).

### Query parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `status` | string | No | Such as `scheduled` or `cancelled`. |
| `contact_id` | string | No | |
| `deal_id` | string | No | |
| `owner_id` | string | No | |
| `limit` | integer | No | 1 to 100. Default 20. |
| `cursor` | string | No | `meta.cursor` from the previous page. |

### Example

```bash
curl "https://crm.empiraa.app/api/v1/bookings?status=scheduled" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
const res = await fetch("https://crm.empiraa.app/api/v1/bookings?status=scheduled", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const { data, meta } = await res.json();
```

### Response `200`

An array of [booking objects](#the-booking-object) in `data`; `meta` has `cursor` and `hasMore`.

---

## Record a booking

`POST /bookings` · Scope: `bookings:write` · Supports [`Idempotency-Key`](../conventions.md#idempotency)

Adds a booking to Signal, for example one made in another scheduling tool.

### Request body

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `invitee_name` | string | Yes | |
| `title` | string or null | No | |
| `invitee_email` | string or null | No | |
| `invitee_phone` | string or null | No | |
| `start_at` | string or null | No | ISO 8601 timestamp. |
| `end_at` | string or null | No | ISO 8601 timestamp. |
| `duration` | integer or null | No | Minutes. |
| `status` | string or null | No | Defaults to `scheduled`. |
| `notes` | string or null | No | |
| `event_type_id` | string or null | No | |
| `owner_id` | string or null | No | A user ID from `GET /users`. |
| `account_id` | string or null | No | |
| `contact_id` | string or null | No | |
| `prospect_id` | string or null | No | |
| `deal_id` | string or null | No | |
| `timezone` | string or null | No | Defaults to `UTC`. |
| `location_type` | string or null | No | |
| `location_details` | string or null | No | |

### Example

```bash
curl -X POST https://crm.empiraa.app/api/v1/bookings \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0f2d4b6a-8c1e-4a3b-9d5f-7e9a1c3b5d70" \
  -d '{
    "invitee_name": "Sam Lee",
    "invitee_email": "sam.lee@fabrikam.example",
    "title": "Product demo",
    "start_at": "2026-11-18T16:00:00Z",
    "end_at": "2026-11-18T16:30:00Z",
    "duration": 30,
    "timezone": "America/New_York",
    "owner_id": "5b8d1f3a-7e2c-4a9b-b6d4-1c3e5f7a9b20",
    "prospect_id": "e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63"
  }'
```

```js
await fetch("https://crm.empiraa.app/api/v1/bookings", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    invitee_name: "Sam Lee",
    invitee_email: "sam.lee@fabrikam.example",
    title: "Product demo",
    start_at: "2026-11-18T16:00:00Z",
    end_at: "2026-11-18T16:30:00Z",
    duration: 30,
    timezone: "America/New_York",
    owner_id: "5b8d1f3a-7e2c-4a9b-b6d4-1c3e5f7a9b20",
    prospect_id: "e4b7c1d9-2f6a-4b3e-9c8d-5a1f7e2b4c63",
  }),
});
```

### Response `201`

The new [booking object](#the-booking-object) in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_REQUEST` | `invitee_name` is missing, or `start_at` or `end_at` isn't a date. |
| 404 | `NOT_FOUND` | A linked Account, Contact, Deal or prospect contact, or the `event_type_id`, isn't in this workspace (`title` says which), or `owner_id` isn't an active member of the workspace. |

**Webhook events:** none. Starts any Autopilot workflows that run when a booking is confirmed.

---

## Get a booking

`GET /bookings/{id}` · Scope: `bookings:read`

### Example

```bash
curl https://crm.empiraa.app/api/v1/bookings/cm3ka3c8u0007ab7c4d5e6f7g \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
await fetch("https://crm.empiraa.app/api/v1/bookings/cm3ka3c8u0007ab7c4d5e6f7g", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
```

### Response `200`

The [booking object](#the-booking-object) in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `NOT_FOUND` | No booking with that ID in this workspace. |

---

## Update a booking

`PATCH /bookings/{id}` · Scope: `bookings:write`

Send only the fields you want to change.

### Request body

| Field | Type | Notes |
| --- | --- | --- |
| `title` | string or null | |
| `invitee_name` | string | |
| `invitee_email` | string or null | |
| `invitee_phone` | string or null | |
| `start_at` | string or null | ISO 8601 timestamp. |
| `end_at` | string or null | ISO 8601 timestamp. |
| `duration` | integer or null | Minutes. |
| `status` | string or null | Set `cancelled` to cancel. |
| `notes` | string or null | |
| `owner_id` | string or null | |
| `deal_id` | string or null | |
| `location_type` | string or null | |
| `location_details` | string or null | |
| `cancel_reason` | string or null | A non-empty reason also sets `cancelled_at` to now; `null` or empty clears `cancelled_at`. It doesn't change `status`. |

`account_id`, `contact_id`, `prospect_id`, `event_type_id` and `timezone` can't be changed after creation.

### Example: cancel with a reason

```bash
curl -X PATCH https://crm.empiraa.app/api/v1/bookings/cm3ka3c8u0007ab7c4d5e6f7g \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "cancelled", "cancel_reason": "Invitee asked to move to next quarter"}'
```

```js
await fetch("https://crm.empiraa.app/api/v1/bookings/cm3ka3c8u0007ab7c4d5e6f7g", {
  method: "PATCH",
  headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ status: "cancelled", cancel_reason: "Invitee asked to move to next quarter" }),
});
```

### Response `200`

The updated [booking object](#the-booking-object) in `data`.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_REQUEST` | `start_at` or `end_at` isn't a date. |
| 404 | `NOT_FOUND` | No booking with that ID in this workspace, `deal_id` isn't a Deal in it, or `owner_id` isn't an active member of the workspace. |

**Webhook events:** none. Changing `status` to `cancelled` starts any Autopilot workflows that run when a booking is canceled.

---

## Cancel a booking

`DELETE /bookings/{id}` · Scope: `bookings:write`

Cancels the booking: sets `status` to `cancelled` and `cancelled_at` to now. The booking is kept.

### Example

```bash
curl -X DELETE https://crm.empiraa.app/api/v1/bookings/cm3ka3c8u0007ab7c4d5e6f7g \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```js
await fetch("https://crm.empiraa.app/api/v1/bookings/cm3ka3c8u0007ab7c4d5e6f7g", {
  method: "DELETE",
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
```

### Response `204`

No body.

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `NOT_FOUND` | No booking with that ID in this workspace. |

**Webhook events:** none. Starts any Autopilot workflows that run when a booking is canceled (unless it was already canceled).
