# Inbox

Comments, mentions and direct messages from every connected account.

Comments, mentions and DMs on your connected accounts, as they arrive.

```json
{
  "id": "4c7e1a90-3b26-4f5d-8e03-a9d1c6b27f54",
  "account_id": "a3d9f1c4-7b2e-4c60-9e85-1f4b6d2a0c73",
  "network": "instagram",
  "kind": "comment",
  "direction": "inbound",
  "thread_key": "17912345678901234",
  "post_external_id": "17998765432109876",
  "author_handle": "roadrunner",
  "author_name": "R. Runner",
  "body": "Does this come in blue?",
  "permalink": "https://www.example.com/p/…",
  "status": "open",
  "read_at": null,
  "replied_at": null,
  "occurred_at": "2026-10-01T09:14:22+00:00",
  "automation_id": null
}
```

`kind` is `comment`, `mention` or `dm`. `direction` is `outbound` for your own
replies, which appear in the thread. `status` is `open`, `done` or `hidden`.
`automation_id` is set when an automation answered it.

Inbox items are kept for 180 days.

## Operations

### `GET /v1/inbox`

**List the inbox**

Scope `social:read`.

Comments, mentions and messages across connected accounts, newest first. Open items by default.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `status` | query | `string` | (one of `open`, `done`, `hidden`, `all`) |
| `kind` | query | `InboxKind` | (one of `comment`, `dm`, `mention`) |
| `account_id` | query | `uuid` |  |
| `limit` | query | `integer` | Page size, 1 to 200. Above 200 is clamped; anything else is the default. (default `50`; >= 1; <= 200) |
| `cursor` | query | `string` | The `next_cursor` from the previous page. Opaque; an invalid one is `400`. (max 512) |

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `InboxList` | A page of items. |
| 400 | `ErrorEnvelope` | `VALIDATION_ERROR`: a body that is not JSON, a field that breaks its rule (named in `fields`), a bad query value or cursor, or a malformed `Idempotency-Key`. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |

**The social inbox** — answers `200`

Comments, mentions and DMs from every connected account in one list. The person who wrote is their public handle, never the network's internal id for them.

```bash
curl "https://api.boltclaw.ai/v1/inbox?limit=50" \
  -H "Authorization: Bearer bc_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```js
const res = await fetch("https://api.boltclaw.ai/v1/inbox?limit=50", {
  headers: {
    Authorization: `Bearer ${process.env.BOLTCLAW_KEY}`,
  },
});

// 200
const data = await res.json();
```

### `GET /v1/inbox/{id}`

**Read an inbox thread**

Scope `social:read`. Another workspace's answers `404`.

The item and the rest of its thread, oldest first.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `id` | path | `uuid` | An id this API gave you. Anything that is not one of yours is `404`. (**required**) |

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `InboxThread` | The thread. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | `ErrorEnvelope` | `NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |

### `PATCH /v1/inbox/{id}`

**Set an item's status**

Scope `social:write`. Another workspace's answers `404`.

Open or done, in your inbox only. Hiding is `POST /inbox/{id}/hide`.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `id` | path | `uuid` | An id this API gave you. Anything that is not one of yours is `404`. (**required**) |

**Request body** (required)

| Field | Type |  |
|---|---|---|
| `status` | `string` | (**required**; one of `open`, `done`) |

```json
{
  "status": "open"
}
```

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `InboxItem` | The item. |
| 400 | `ErrorEnvelope` | `VALIDATION_ERROR`: a body that is not JSON, a field that breaks its rule (named in `fields`), a bad query value or cursor, or a malformed `Idempotency-Key`. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | `ErrorEnvelope` | `NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 413 | `ErrorEnvelope` | `PAYLOAD_TOO_LARGE`: the body is over 64 KB. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |

### `POST /v1/inbox/{id}/reply`

**Reply to an item**

Scope `social:write`. Accepts `Idempotency-Key`. Another workspace's answers `404`. **Can make something publicly visible.** **Can record a platform charge.**

Replies on the network as the connected account: in public to a comment, privately to a
message. The network's own refusal is `UPSTREAM_ERROR` (422, 502 or 503) with a sentence
that says what happened. Replying on X is billed per call on top of the plan.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `id` | path | `uuid` | An id this API gave you. Anything that is not one of yours is `404`. (**required**) |

**Request body** (required)

| Field | Type |  |
|---|---|---|
| `text` | `string` | Trimmed; not blank. (**required**; min 1; max 5000) |

```json
{
  "text": "<text>"
}
```

**Answers**

| Status | Body |  |
|---|---|---|
| 201 | `InboxItem` | The reply, as an outbound item. |
| 400 | `ErrorEnvelope` | `VALIDATION_ERROR`: a body that is not JSON, a field that breaks its rule (named in `fields`), a bad query value or cursor, or a malformed `Idempotency-Key`. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | `ErrorEnvelope` | `PLAN_LIMIT`: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | `ErrorEnvelope` | `NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | `ErrorEnvelope` | `CONFLICT`: the request clashes with the object's current state (the message says how). `IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_FLIGHT`: the `Idempotency-Key` was used with another request, or its first request is still running. |
| 413 | `ErrorEnvelope` | `PAYLOAD_TOO_LARGE`: the body is over 64 KB. |
| 422 | `ErrorEnvelope` | `VALIDATION_ERROR`: the request is well formed but cannot be done as asked, such as a network that does not support it. Or `UPSTREAM_ERROR`: the network refused it. |
| 428 | `ErrorEnvelope` | `APPROVAL_REQUIRED` on the first try, `APPROVAL_PENDING` on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries `approval` with a `url`: show it to the person, and once they approve, send the identical request again with the approval id in `BoltClaw-Approval`. Only an AI assistant's request is ever answered this way. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |
| 502 | `ErrorEnvelope` | `UPSTREAM_ERROR`: the social network failed the call. The message says what happened. |
| 503 | `ErrorEnvelope` | `UPSTREAM_ERROR`: the social network is temporarily unavailable; retrying later may work. |

### `POST /v1/inbox/{id}/hide`

**Hide or unhide a comment**

Scope `social:write`. Accepts `Idempotency-Key`. Another workspace's answers `404`.

Hides the comment on the network (an empty body means hide), or `{ "hidden": false }`
unhides it. A network that cannot hide comments is `422`.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `id` | path | `uuid` | An id this API gave you. Anything that is not one of yours is `404`. (**required**) |

**Request body** (optional)

| Field | Type |  |
|---|---|---|
| `hidden` | `boolean` | (default `true`) |

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `InboxItem` | The item. |
| 400 | `ErrorEnvelope` | `VALIDATION_ERROR`: a body that is not JSON, a field that breaks its rule (named in `fields`), a bad query value or cursor, or a malformed `Idempotency-Key`. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | `ErrorEnvelope` | `NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | `ErrorEnvelope` | `CONFLICT`: the request clashes with the object's current state (the message says how). `IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_FLIGHT`: the `Idempotency-Key` was used with another request, or its first request is still running. |
| 413 | `ErrorEnvelope` | `PAYLOAD_TOO_LARGE`: the body is over 64 KB. |
| 422 | `ErrorEnvelope` | `VALIDATION_ERROR`: the request is well formed but cannot be done as asked, such as a network that does not support it. Or `UPSTREAM_ERROR`: the network refused it. |
| 428 | `ErrorEnvelope` | `APPROVAL_REQUIRED` on the first try, `APPROVAL_PENDING` on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries `approval` with a `url`: show it to the person, and once they approve, send the identical request again with the approval id in `BoltClaw-Approval`. Only an AI assistant's request is ever answered this way. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |
| 502 | `ErrorEnvelope` | `UPSTREAM_ERROR`: the social network failed the call. The message says what happened. |
| 503 | `ErrorEnvelope` | `UPSTREAM_ERROR`: the social network is temporarily unavailable; retrying later may work. |

## Shapes

The objects these operations send and answer.

### InboxHide

| Field | Type |  |
|---|---|---|
| `hidden` | `boolean` | (default `true`) |

### InboxItem

| Field | Type |  |
|---|---|---|
| `id` | `uuid` | (**required**) |
| `account_id` | `uuid` | (**required**) |
| `network` | `Network` | (**required**; one of `instagram`, `facebook`, `threads`, `bluesky`, `x`, `tiktok`, `linkedin`, `youtube`, `pinterest`) |
| `kind` | `InboxKind` | (**required**; one of `comment`, `dm`, `mention`) |
| `direction` | `string` | (**required**; one of `inbound`, `outbound`) |
| `thread_key` | `string` | (**required**) |
| `post_external_id` | `string \| null` | (**required**) |
| `author_handle` | `string \| null` | (**required**) |
| `author_name` | `string \| null` | (**required**) |
| `body` | `string` | (**required**) |
| `permalink` | `string \| null` | (**required**) |
| `status` | `string` | (**required**; one of `open`, `done`, `hidden`) |
| `read_at` | `date-time \| null` | (**required**) |
| `replied_at` | `date-time \| null` | (**required**) |
| `occurred_at` | `date-time` | (**required**) |
| `automation_id` | `uuid \| null` | (**required**) |

### InboxKind

One of: `comment`, `dm`, `mention`.

### InboxList

| Field | Type |  |
|---|---|---|
| `data` | `InboxItem[]` | (**required**) |
| `next_cursor` | `NextCursor` | Pass back as `cursor` for the next page; null on the last page. (**required**) |

### InboxReply

| Field | Type |  |
|---|---|---|
| `text` | `string` | Trimmed; not blank. (**required**; min 1; max 5000) |

### InboxStatusUpdate

| Field | Type |  |
|---|---|---|
| `status` | `string` | (**required**; one of `open`, `done`) |

### InboxThread

| Field | Type |  |
|---|---|---|
| `item` | `InboxItem` | (**required**) |
| `thread` | `InboxItem[]` | (**required**) |

### Network

One of: `instagram`, `facebook`, `threads`, `bluesky`, `x`, `tiktok`, `linkedin`, `youtube`, `pinterest`.

### NextCursor

Pass back as `cursor` for the next page; null on the last page.

`string | null`.

<!-- https://boltclaw.ai/docs/api/inbox as markdown. Generated from openapi.yaml; API version 1.22.0. -->
<!-- Every /docs page answers as markdown: add .md to its path, or send Accept: text/markdown. -->
