---
title: "Posts"
description: "Posts to one or more connected accounts, now, scheduled, or as drafts."
canonical: "https://boltclaw.ai/docs/api/posts"
last-updated: "2026-11-05"
---

# Posts

Posts to one or more connected accounts, now, scheduled, or as drafts.

A post is written once and goes out to one or more accounts. Each account it
goes to is a **target** with its own status, link and error — a post can
publish on Instagram and fail on X.

```json
{
  "id": "5e0c2b7a-1d94-4f38-a6e2-7b3c9d10f4a8",
  "body": "Autumn catalogue is out. Link in bio.",
  "media": [
    {
      "id": "e71b0f3d-2c85-4a9e-b604-93d2f7a1c5e8",
      "mime_type": "image/jpeg",
      "bytes": 482113,
      "url": "https://.../social-media/e71b0f3d-.../catalogue.jpg",
      "status": "ready",
      "created_at": "2026-09-30T10:05:40.551020+00:00"
    }
  ],
  "options": { "instagram": { "type": "feed" } },
  "status": "scheduled",
  "scheduled_for": "2026-10-01T09:00:00+00:00",
  "published_at": null,
  "source": "api",
  "created_at": "2026-09-30T10:06:02.118904+00:00",
  "updated_at": "2026-09-30T10:06:02.118904+00:00",
  "targets": [
    {
      "id": "b8f24c61-0e3d-4a97-8c15-2d6e9f7a3b40",
      "account_id": "a3d9f1c4-7b2e-4c60-9e85-1f4b6d2a0c73",
      "network": "instagram",
      "handle": "acme",
      "status": "pending",
      "external_url": null,
      "last_error": null,
      "published_at": null,
      "metrics": null
    }
  ]
}
```

A post's `status` is `draft`, `scheduled`, `publishing`, `published`, `partial`
(some targets published, some failed), `failed` or `cancelled`. A target's is
`pending`, `publishing`, `processing` (the network accepted it and is still
working — a video transcoding), `published`, `failed` or `cancelled`.
`external_url` is the live post once it exists. `source` is `dashboard`, `api`
or `mcp`.

`options` and `metrics` are passed through in each network's own vocabulary,
so their keys are not renamed to snake_case the way every other key is.

## Operations

### `GET /v1/posts`

**List posts**

Scope `social:read`.

The workspace's posts, newest first, optionally filtered.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `status` | query | `PostStatus` | (one of `draft`, `scheduled`, `publishing`, `published`, `partial`, `failed`, `cancelled`) |
| `from` | query | `date-time` | An ISO-8601 timestamp with an offset. |
| `to` | query | `date-time` | An ISO-8601 timestamp with an offset. |
| `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 | `PostList` | A page of posts. |
| 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. |

**Scheduled and published posts** — answers `200`

Ordered by when the post is scheduled, newest first. `status` filters to one state.

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

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

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

### `POST /v1/posts`

**Create a post**

Scope `social:write`. Accepts `Idempotency-Key`. **Can make something publicly visible.** **Can record a platform charge.**

One post to one or more connected accounts. Say when with exactly one of `scheduled_for`,
`publish_now: true` or `draft: true`. A post needs a `body` or at least one media id. The
organisation's monthly post allowance applies (`402 PLAN_LIMIT`). Posting to X is billed
per call on top of the plan.

**Request body** (required)

| Field | Type |  |
|---|---|---|
| `body` | `string` | (max 5000) |
| `media_ids` | `uuid[]` | (<= 20 items) |
| `account_ids` | `uuid[]` | (**required**; <= 50 items; >= 1 items) |
| `scheduled_for` | `date-time` |  |
| `publish_now` | `boolean` |  |
| `draft` | `boolean` |  |
| `options` | `PostOptions` | Per-network options, keyed by network, in each network's own vocabulary. |

```json
{
  "account_ids": "3f8c1e42-9b7a-4d61-a0f5-2c9e1b7d4a83"
}
```

**Answers**

| Status | Body |  |
|---|---|---|
| 201 | `Post` | Created. |
| 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. |

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

**Read a post**

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

The post, its media and where it stands on each account.

**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 | `Post` | The post. |
| 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/posts/{id}`

**Update a post**

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

Changes a draft or scheduled post. Any other is `409 CONFLICT`.

**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 |  |
|---|---|---|
| `body` | `string` | (max 5000) |
| `media_ids` | `uuid[]` | (<= 20 items) |
| `account_ids` | `uuid[]` | (<= 50 items; >= 1 items) |
| `scheduled_for` | `date-time \| null` |  |
| `options` | `PostOptions` | Per-network options, keyed by network, in each network's own vocabulary. |

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `Post` | The updated post. |
| 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. |
| 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. |

### `DELETE /v1/posts/{id}`

**Delete a post**

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

Deletes the post from BoltClaw.

**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 | `Deleted` | Deleted. |
| 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. |
| 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. |

### `POST /v1/posts/{id}/cancel`

**Cancel a scheduled post**

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

Stops a post that has not published yet. Takes no body.

**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 | `Post` | The cancelled post. |
| 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. |
| 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. |

## Shapes

The objects these operations send and answer.

### Deleted

| Field | Type |  |
|---|---|---|
| `deleted` | `true` | (**required**) |
| `id` | `uuid` | (**required**) |

### Media

| Field | Type |  |
|---|---|---|
| `id` | `uuid` | (**required**) |
| `mime_type` | `string` | (**required**) |
| `bytes` | `integer \| null` | (**required**) |
| `url` | `string \| null` | (**required**) |
| `status` | `string` | (**required**; one of `pending`, `ready`) |
| `created_at` | `date-time` | (**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`.

### Post

| Field | Type |  |
|---|---|---|
| `id` | `uuid` | (**required**) |
| `body` | `string` | (**required**) |
| `media` | `Media[]` | (**required**) |
| `options` | `PostOptions` | Per-network options, keyed by network, in each network's own vocabulary. (**required**) |
| `status` | `PostStatus` | (**required**; one of `draft`, `scheduled`, `publishing`, `published`, `partial`, `failed`, `cancelled`) |
| `scheduled_for` | `date-time \| null` | (**required**) |
| `published_at` | `date-time \| null` | (**required**) |
| `source` | `string` | (**required**; one of `dashboard`, `api`, `mcp`) |
| `created_at` | `date-time` | (**required**) |
| `updated_at` | `date-time` | (**required**) |
| `targets` | `Target[]` | (**required**) |

### PostCreate

Exactly one of `scheduled_for`, `publish_now: true` or `draft: true`, and a `body` or at
least one media id.

| Field | Type |  |
|---|---|---|
| `body` | `string` | (max 5000) |
| `media_ids` | `uuid[]` | (<= 20 items) |
| `account_ids` | `uuid[]` | (**required**; <= 50 items; >= 1 items) |
| `scheduled_for` | `date-time` |  |
| `publish_now` | `boolean` |  |
| `draft` | `boolean` |  |
| `options` | `PostOptions` | Per-network options, keyed by network, in each network's own vocabulary. |

### PostList

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

### PostOptions

Per-network options, keyed by network, in each network's own vocabulary.

`object`.

### PostStatus

One of: `draft`, `scheduled`, `publishing`, `published`, `partial`, `failed`, `cancelled`.

### PostUpdate

| Field | Type |  |
|---|---|---|
| `body` | `string` | (max 5000) |
| `media_ids` | `uuid[]` | (<= 20 items) |
| `account_ids` | `uuid[]` | (<= 50 items; >= 1 items) |
| `scheduled_for` | `date-time \| null` |  |
| `options` | `PostOptions` | Per-network options, keyed by network, in each network's own vocabulary. |

### Target

| Field | Type |  |
|---|---|---|
| `id` | `uuid` | (**required**) |
| `account_id` | `uuid` | (**required**) |
| `network` | `Network` | (**required**; one of `instagram`, `facebook`, `threads`, `bluesky`, `x`, `tiktok`, `linkedin`, `youtube`, `pinterest`) |
| `handle` | `string \| null` | (**required**) |
| `status` | `string` | (**required**; one of `pending`, `publishing`, `processing`, `published`, `failed`, `cancelled`) |
| `external_url` | `string \| null` | (**required**) |
| `last_error` | `string \| null` | (**required**) |
| `published_at` | `date-time \| null` | (**required**) |
| `metrics` | `object \| null` | The network's own counters, by the network's own names. (**required**) |

<!-- https://boltclaw.ai/docs/api/posts 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. -->
