# Rookery API reference

> Base URL: https://app.userookery.com/api/v1. Send an API key as `Authorization: Bearer rk_…`. Errors are `{ "error": "…" }`. Each key may make 120 requests a minute. The OpenAPI 3.1 spec is at https://userookery.com/developers/openapi.json.

## Check your key

`GET /v1/me` · needs `read` · safe to retry

Returns the agent this key acts as and the scopes it has. A quick way to check a key works.

Example:

```bash
curl 'https://app.userookery.com/api/v1/me' \
  -H "Authorization: Bearer $ROOKERY_KEY"
```

Response (200):

```json
{
  "agent": {
    "id": "agt_3n7q2wv9k4hxm8tr",
    "name": "Support bot"
  },
  "scopes": [
    "read",
    "draft"
  ]
}
```

Errors: `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `read` scope. `429` More than 120 requests in a minute with this key. Wait, then try again.

## List conversations

`GET /v1/conversations` · needs `read` · safe to retry · MCP tool `list_conversations`

Lists conversations, newest first. Use `view` for open, waiting, snoozed or closed; `query` searches all mail text.

You only see what the person who made the key can see: every team inbox, plus their own private inboxes.

Results are newest first, up to `limit` (at most 100). There's no cursor yet: narrow the list with `view`, `mailbox` or `q`.

Query parameters:

- `view` (string; One of `open`, `waiting`, `snoozed`, `closed`; Default `open`): Which conversations to list. `waiting` means waiting on the customer.
- `mailbox` (string): A mailbox id from list_mailboxes.
- `q` (string; Up to 200 characters): Full-text search.
- `assigned` (string; One of `me`, `unassigned`): `me` means assigned to this agent.
- `limit` (integer; Default `25`; 1 to 100): How many to return, newest first.

Example:

```bash
curl 'https://app.userookery.com/api/v1/conversations?view=open&limit=1' \
  -H "Authorization: Bearer $ROOKERY_KEY"
```

Response (200):

```json
[
  {
    "id": "cnv_8k2m4q7rx9tbw3hd",
    "subject": "Where is my order?",
    "snippet": "Order 4502 hasn't arrived and the tracking hasn't moved since Monday.",
    "status": "open",
    "contact": {
      "name": "Lee Park",
      "email": "lee@example.com"
    },
    "mailbox": "Support",
    "lastMessageAt": "2026-10-09T14:32:05.000Z",
    "messageCount": 2,
    "assignee": null,
    "tags": [
      "shipping"
    ],
    "topic": "order status",
    "urgency": "normal",
    "risk": null
  }
]
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `read` scope. `429` More than 120 requests in a minute with this key. Wait, then try again.

## Read a conversation

`GET /v1/conversations/{id}` · needs `read` · safe to retry · MCP tool `get_conversation`

Reads a conversation: messages (quoted history removed), internal notes, events, tags and who's on the team.

The timeline is oldest first and mixes three kinds of item: `message`, `note` and `event`. Message text is plain text, with quoted history removed and anything past 8,000 characters cut.

`team` lists the people and agents you can assign it to, with the ids `assign_conversation` takes.

Path:

- `id` (string, required; Matches `^cnv_[0-9a-z]+$`): The conversation's id, like `cnv_…`.

Example:

```bash
curl 'https://app.userookery.com/api/v1/conversations/cnv_8k2m4q7rx9tbw3hd' \
  -H "Authorization: Bearer $ROOKERY_KEY"
```

Response (200):

```json
{
  "id": "cnv_8k2m4q7rx9tbw3hd",
  "subject": "Where is my order?",
  "status": "open",
  "mailbox": "help@yourshop.com",
  "contact": {
    "name": "Lee Park",
    "email": "lee@example.com"
  },
  "assignee": null,
  "tags": [
    "shipping"
  ],
  "topic": "order status",
  "urgency": "normal",
  "risk": null,
  "riskReasons": [],
  "snoozedUntil": null,
  "timeline": [
    {
      "type": "message",
      "id": "msg_5d9w2k7hq3mx8rtn",
      "at": "2026-10-09T14:32:05.000Z",
      "direction": "inbound",
      "from": {
        "name": "Lee Park",
        "email": "lee@example.com"
      },
      "status": null,
      "text": "Order 4502 hasn't arrived and the tracking hasn't moved since Monday.",
      "attachments": []
    },
    {
      "type": "note",
      "id": "not_2h8q4w7m9kx3drtb",
      "at": "2026-10-09T14:40:11.000Z",
      "author": "Jess",
      "body": "Carrier says it's at the depot. @Sam can you check?"
    }
  ],
  "team": {
    "people": [
      {
        "id": "user:usr_4k8m2q9wx7hd3rtn",
        "name": "Jess"
      }
    ],
    "agents": [
      {
        "id": "agent:agt_3n7q2wv9k4hxm8tr",
        "name": "Support bot"
      }
    ]
  }
}
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `read` scope. `404` There's no such conversation, or the key can't see it (someone's private inbox). `429` More than 120 requests in a minute with this key. Wait, then try again.

## Add an internal note

`POST /v1/conversations/{id}/notes` · needs `write` · not safe to retry blindly · MCP tool `add_note`

Adds an internal note the team sees and the customer never does. Mention people with @FirstName.

Path:

- `id` (string, required; Matches `^cnv_[0-9a-z]+$`): The conversation's id, like `cnv_…`.

Body (JSON):

- `body` (string, required; Up to 10,000 characters): The note, as plain text.

Example:

```bash
curl -X POST https://app.userookery.com/api/v1/conversations/cnv_8k2m4q7rx9tbw3hd/notes \
  -H "Authorization: Bearer $ROOKERY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "body": "Tracking shows it'\''s at the depot. @Jess can you call the carrier?"
}'
```

Response (201):

```json
{
  "ok": true
}
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `write` scope. `404` There's no such conversation, or the key can't see it (someone's private inbox). `429` More than 120 requests in a minute with this key. Wait, then try again.

## Change status

`POST /v1/conversations/{id}/status` · needs `write` · safe to retry · MCP tool `set_status`

Sets a conversation to open, waiting (on the customer) or closed.

Path:

- `id` (string, required; Matches `^cnv_[0-9a-z]+$`): The conversation's id, like `cnv_…`.

Body (JSON):

- `status` (string, required; One of `open`, `waiting`, `closed`): The new status. `waiting` means waiting on the customer.

Example:

```bash
curl -X POST https://app.userookery.com/api/v1/conversations/cnv_8k2m4q7rx9tbw3hd/status \
  -H "Authorization: Bearer $ROOKERY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "status": "waiting"
}'
```

Response (200):

```json
{
  "ok": true
}
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `write` scope. `404` There's no such conversation, or the key can't see it (someone's private inbox). `429` More than 120 requests in a minute with this key. Wait, then try again.

## Tag or untag

`POST /v1/conversations/{id}/tags` · needs `write` · safe to retry · MCP tool `tag_conversation`

Adds tags to a conversation, or removes them. New tag names are created.

Path:

- `id` (string, required; Matches `^cnv_[0-9a-z]+$`): The conversation's id, like `cnv_…`.

Body (JSON):

- `add` (array of strings; Up to 10 items): Tag names to add.
- `remove` (array of strings; Up to 10 items): Tag names to remove.

Example:

```bash
curl -X POST https://app.userookery.com/api/v1/conversations/cnv_8k2m4q7rx9tbw3hd/tags \
  -H "Authorization: Bearer $ROOKERY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "add": [
    "vip"
  ],
  "remove": [
    "new"
  ]
}'
```

Response (200):

```json
{
  "ok": true
}
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `write` scope. `404` There's no such conversation, or the key can't see it (someone's private inbox). `429` More than 120 requests in a minute with this key. Wait, then try again.

## Assign

`POST /v1/conversations/{id}/assign` · needs `write` · safe to retry · MCP tool `assign_conversation`

Assigns a conversation to a person or agent (ids like user:… or agent:… from get_conversation's team), to yourself with `me`, or unassigns with `none`.

Path:

- `id` (string, required; Matches `^cnv_[0-9a-z]+$`): The conversation's id, like `cnv_…`.

Body (JSON):

- `to` (string, required; Up to 80 characters): `user:usr_…`, `agent:agt_…`, `me` or `none`.

Example:

```bash
curl -X POST https://app.userookery.com/api/v1/conversations/cnv_8k2m4q7rx9tbw3hd/assign \
  -H "Authorization: Bearer $ROOKERY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "user:usr_4k8m2q9wx7hd3rtn"
}'
```

Response (200):

```json
{
  "ok": true
}
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. Or: The person or agent isn't on the team, or can't see this inbox. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `write` scope. `404` There's no such conversation, or the key can't see it (someone's private inbox). `429` More than 120 requests in a minute with this key. Wait, then try again.

## Reply to the customer

`POST /v1/conversations/{id}/replies` · needs `draft` (or `send` to send) · not safe to retry blindly · MCP tool `reply`

Writes a reply to the conversation's contact. With send: false (the default) it's a draft a person approves under Needs your yes. With send: true it goes out after a 10-minute undo window; that needs the `send` scope. To ask for documents, list them in `documents`: a private upload link and checklist go under the reply, and reminders follow. Don't write the link yourself.

A draft lands in **Needs your yes**, next to Quill's, with your `summary` on top. Nothing reaches the customer until a person approves it.

With `send: true` the reply is scheduled and leaves after a 10-minute undo window, during which anyone on the team can stop it.

Path:

- `id` (string, required; Matches `^cnv_[0-9a-z]+$`): The conversation's id, like `cnv_…`.

Body (JSON):

- `body` (string, required; Up to 50,000 characters): The reply, as plain text.
- `send` (boolean; Default `false`): False (the default) makes a draft for a person to approve. True sends it, and needs the `send` scope.
- `summary` (string; Up to 500 characters): One line for the person approving: what this reply does.
- `documents` (array of objects; Up to 20 items): Documents to ask the customer to upload.

Example:

```bash
curl -X POST https://app.userookery.com/api/v1/conversations/cnv_8k2m4q7rx9tbw3hd/replies \
  -H "Authorization: Bearer $ROOKERY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "body": "Hi Lee, thanks for your patience. Your parcel is at the local depot and should reach you tomorrow.",
  "send": false,
  "summary": "Tells Lee the parcel arrives tomorrow"
}'
```

Response (201):

```json
{
  "messageId": "msg_7r3k9w2hq8mx4dtn",
  "status": "draft",
  "note": "Waiting for a person to approve it."
}
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `draft` scope (or `send`, to send). `404` There's no such conversation, or the key can't see it (someone's private inbox). `429` More than 120 requests in a minute with this key. Wait, then try again.

## Ask the customer for documents

`POST /v1/conversations/{id}/document-requests` · needs `draft` (or `send` to send) · not safe to retry blindly · MCP tool `request_documents`

Asks the conversation's contact to upload documents through a private page with a checklist; reminders follow on days 2, 4 and 7, and each upload is checked. A draft for approval by default; with send: true (needs the `send` scope) it goes out after a 10-minute undo window.

Path:

- `id` (string, required; Matches `^cnv_[0-9a-z]+$`): The conversation's id, like `cnv_…`.

Body (JSON):

- `documents` (array of objects, required; Up to 20 items): What to ask for: up to 20 items.
- `message` (string; Up to 5,000 characters): A short note above the checklist. Greet the customer.
- `dueDate` (string; Matches `^\d{4}-\d{2}-\d{2}$`): YYYY-MM-DD
- `send` (boolean; Default `false`): False (the default) makes a draft for a person to approve. True sends it, and needs the `send` scope.

Example:

```bash
curl -X POST https://app.userookery.com/api/v1/conversations/cnv_8k2m4q7rx9tbw3hd/document-requests \
  -H "Authorization: Bearer $ROOKERY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "documents": [
    {
      "label": "Proof of purchase"
    },
    {
      "label": "Photo of the damage",
      "required": false
    }
  ],
  "message": "Hi Lee, could you send these so we can sort out the refund?",
  "dueDate": "2026-11-01",
  "send": false
}'
```

Response (201):

```json
{
  "messageId": "msg_9q2w7k4hx3mr8dtb",
  "status": "draft",
  "note": "Waiting for a person to approve it. The upload link is added when it's approved."
}
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `draft` scope (or `send`, to send). `404` There's no such conversation, or the key can't see it (someone's private inbox). `429` More than 120 requests in a minute with this key. Wait, then try again.

## List mailboxes

`GET /v1/mailboxes` · needs `read` · safe to retry · MCP tool `list_mailboxes`

Lists the inboxes, with how many conversations are open in each.

Example:

```bash
curl 'https://app.userookery.com/api/v1/mailboxes' \
  -H "Authorization: Bearer $ROOKERY_KEY"
```

Response (200):

```json
[
  {
    "id": "mbx_6t2k9q4wm8xh3drn",
    "address": "help@yourshop.com",
    "name": "Support",
    "open": 12
  }
]
```

Errors: `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `read` scope. `429` More than 120 requests in a minute with this key. Wait, then try again.

## List tags

`GET /v1/tags` · needs `read` · safe to retry · MCP tool `list_tags`

Lists the tags in use and how many conversations have each.

Example:

```bash
curl 'https://app.userookery.com/api/v1/tags' \
  -H "Authorization: Bearer $ROOKERY_KEY"
```

Response (200):

```json
[
  {
    "id": "tag_2m8k4q9wx3hd7rtn",
    "name": "shipping",
    "count": 18
  },
  {
    "id": "tag_9w3k7q2hm4xd8rtb",
    "name": "vip",
    "count": 4
  }
]
```

Errors: `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `read` scope. `429` More than 120 requests in a minute with this key. Wait, then try again.

## Search documents

`GET /v1/documents` · needs `read` · safe to retry · MCP tool `search_documents`

Searches the document library: invoices, receipts, contracts and other files, with extracted fields.

Query parameters:

- `q` (string; Up to 200 characters): Words in the title, file name or fields.
- `kind` (string; One of `invoice`, `receipt`, `statement`, `contract`, `id`, `tax`, `photo`, `other`): Only this kind of document.
- `limit` (integer; Default `25`; 1 to 100): How many to return, newest first.

Example:

```bash
curl 'https://app.userookery.com/api/v1/documents?q=acme&kind=invoice&limit=1' \
  -H "Authorization: Bearer $ROOKERY_KEY"
```

Response (200):

```json
[
  {
    "id": "doc_4h8q2w9k7mx3drtn",
    "title": "Acme Supplies invoice 1182",
    "filename": "INV-1182.pdf",
    "kind": "invoice",
    "fields": {
      "vendor": "Acme Supplies",
      "amount": 412.5,
      "currency": "USD",
      "dueDate": "2026-10-30",
      "reference": "1182"
    },
    "conversationId": "cnv_8k2m4q7rx9tbw3hd",
    "createdAt": "2026-10-09T14:32:05.000Z"
  }
]
```

Errors: `400` Something in the request is missing or not allowed. The message says which field. `401` No key, or the key is wrong or revoked. `403` The key doesn't have the `read` scope. `429` More than 120 requests in a minute with this key. Wait, then try again.

# Webhook events

Rookery POSTs these to your webhooks, signed the Standard Webhooks way (`webhook-id`, `webhook-timestamp`, `webhook-signature`). Answer 2xx within 10 seconds; anything else is retried after about 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, 3 hours, 6 hours and 12 hours. Guide: https://userookery.com/developers/webhooks.md

## `ask.completed` (version 1)

Everything you asked a client for is in (documents today).

Fields in `data`:

- `ask_id` (string, always there): The Ask, like `req_…` for a document request.
- `kind` (string, always there): What was asked for. `documents` today; more kinds will come.
- `conversation_id` (string, always there): The conversation, like `cnv_…`. Read it with the API.
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "ask.completed",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "ask_id": "req_8k2m4q7rx9tbw3hd",
    "kind": "documents",
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "summary": "Everything you asked for is in",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `client.replied` (version 1)

Someone wrote back on a conversation.

Fields in `data`:

- `conversation_id` (string, always there): The conversation, like `cnv_…`. Read it with the API.
- `message_id` (string, always there): Their message, like `msg_…`.
- `mailbox_id` (string, always there): The inbox it arrived in, like `mbx_…`.
- `secure` (boolean, always there): True when they replied on a secure page, not by email.
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "client.replied",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "message_id": "msg_7h2kq9m3x1c8v5bn",
    "mailbox_id": "mbx_d56zb3gp9g8qj3qt",
    "secure": false,
    "summary": "Dana Wu replied",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `conversation.assigned` (version 1)

A conversation was given to someone, or to nobody.

Fields in `data`:

- `conversation_id` (string, always there): The conversation, like `cnv_…`. Read it with the API.
- `assignee` (object, always there; Can be null): Who has it now, or null when nobody does.
- `assigned_by` (object, always there)
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "conversation.assigned",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "assignee": {
      "type": "user",
      "id": "usr_4k8m2q9wx7hd3rtn",
      "name": "Jess"
    },
    "assigned_by": {
      "type": "agent",
      "id": "agt_3n7q2wv9k4hxm8tr",
      "name": "Support bot"
    },
    "summary": "Assigned to Jess",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `turn.changed` (version 1)

Whose turn it is changed: yours, theirs, or done.

Fields in `data`:

- `conversation_id` (string, always there): The conversation, like `cnv_…`. Read it with the API.
- `turn` (string, always there; One of `ours`, `theirs`, `done`): `ours`: your team's turn. `theirs`: waiting on them. `done`: closed.
- `previous` (string, always there; One of `ours`, `theirs`, `done`; Can be null): The turn before, or null the first time.
- `assignee` (object, always there; Can be null): Who it's assigned to, if anyone.
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "turn.changed",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "turn": "ours",
    "previous": "theirs",
    "assignee": {
      "type": "user",
      "id": "usr_4k8m2q9wx7hd3rtn",
      "name": "Jess"
    },
    "summary": "Jess's turn",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `draft.approved` (version 1)

A person approved a helper's or an agent's draft.

Fields in `data`:

- `conversation_id` (string, always there): The conversation, like `cnv_…`. Read it with the API.
- `message_id` (string, always there): The approved reply, like `msg_…`. It goes out after the undo window.
- `drafted_by` (object, always there)
- `approved_by` (object, always there)
- `edited` (boolean, always there): True when the person changed the draft before approving it.
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "draft.approved",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "message_id": "msg_9q2w7k4hx3mr8dtb",
    "drafted_by": {
      "type": "agent",
      "id": "agt_3n7q2wv9k4hxm8tr"
    },
    "approved_by": {
      "type": "user",
      "id": "usr_4k8m2q9wx7hd3rtn",
      "name": "Jess"
    },
    "edited": true,
    "summary": "Jess approved a draft",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `message.sent` (version 1)

An email went out.

Fields in `data`:

- `conversation_id` (string, always there): The conversation, like `cnv_…`. Read it with the API.
- `message_id` (string, always there): The email that went out, like `msg_…`.
- `mailbox_id` (string, always there): The inbox it went from, like `mbx_…`.
- `author` (object, always there; Can be null): Who wrote it, or null for Rookery's own mail.
- `secure` (boolean, always there): True when it went as a secure link.
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "message.sent",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "message_id": "msg_9q2w7k4hx3mr8dtb",
    "mailbox_id": "mbx_d56zb3gp9g8qj3qt",
    "author": {
      "type": "user",
      "id": "usr_4k8m2q9wx7hd3rtn"
    },
    "secure": false,
    "summary": "Email sent",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `booking.created` (version 1)

Someone booked a time with you.

Fields in `data`:

- `booking_id` (string, always there)
- `booking_type_id` (string, always there)
- `host_user_id` (string, always there): The person they booked with, like `usr_…`.
- `conversation_id` (string, always there; Can be null): The conversation it's part of, if any.
- `starts_at` (timestamp, always there)
- `ends_at` (timestamp, always there)
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "booking.created",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "booking_id": "bkg_2m8k4q9wx3hd7rtn",
    "booking_type_id": "bkt_6t2k9q4wm8xh3drn",
    "host_user_id": "usr_4k8m2q9wx7hd3rtn",
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "starts_at": "2026-10-14T15:00:00.000Z",
    "ends_at": "2026-10-14T15:30:00.000Z",
    "summary": "Dana Wu booked Intro call",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `booking.cancelled` (version 1)

A booking was cancelled.

Fields in `data`:

- `booking_id` (string, always there)
- `booking_type_id` (string, always there)
- `host_user_id` (string, always there): The person they had booked with, like `usr_…`.
- `conversation_id` (string, always there; Can be null): The conversation it's part of, if any.
- `starts_at` (timestamp, always there)
- `ends_at` (timestamp, always there)
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "booking.cancelled",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "booking_id": "bkg_2m8k4q9wx3hd7rtn",
    "booking_type_id": "bkt_6t2k9q4wm8xh3drn",
    "host_user_id": "usr_4k8m2q9wx7hd3rtn",
    "conversation_id": null,
    "starts_at": "2026-10-14T15:00:00.000Z",
    "ends_at": "2026-10-14T15:30:00.000Z",
    "summary": "Dana Wu cancelled Intro call"
  }
}
```

## `scout.held` (version 1)

Scout held a likely scam.

Fields in `data`:

- `conversation_id` (string, always there): The conversation, like `cnv_…`. Read it with the API.
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "scout.held",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "conversation_id": "cnv_zys19vqj63sb1z4v",
    "summary": "Held by Scout: The sender asks you to pay a new bank account.",
    "url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
  }
}
```

## `webhook.test` (version 1)

Sent when you press Send test event.

Fields in `data`:

- `endpoint_id` (string, always there): The webhook it was sent to, like `whk_…`.
- `summary` (string, always there; Up to 200 characters): One line, in plain words. Never the email's own words.
- `url` (string): A link to the conversation in Rookery, when there is one.

Example body:

```json
{
  "id": "evt_4pj6sy63n8spf4h9",
  "type": "webhook.test",
  "version": 1,
  "created_at": "2026-10-13T09:14:02.118Z",
  "workspace": "wsp_0qadwjbmg1dw6wws",
  "data": {
    "endpoint_id": "whk_5d9w2k7hq3mx8rtn",
    "summary": "A test event from Rookery. If you can read this, it works."
  }
}
```
