> ## Documentation Index
> Fetch the complete documentation index at: https://developers.workchats.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending messages

> Send, edit and delete your bot's messages

One endpoint sends every message, to a person or a conversation. Requires
`messages:write`.

## Send

```http theme={null}
POST /v1/messages
Authorization: Bearer wc_bot_live_…
Idempotency-Key: evt_88231:usr_9a1…
Content-Type: application/json

{
  "to": { "type": "user", "id": "usr_9a1…" },
  "text": "New lead: Sarah Chen (Acme Ltd) from Meta Lead Ads",
  "blocks": [ "…" ],
  "thread_id": null,
  "unfurl_links": false,
  "metadata": { "source": "leadey", "event": "lead.created", "lead_id": "lead_mul0oe5wo0dgol" }
}
```

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://public-api.workchats.com/v1/messages \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: evt_88231:usr_9a1d2c3e" \
    -d '{
      "to": { "type": "user", "id": "usr_9a1d2c3e-4b5f-4a6b-8c7d-9e0f1a2b3c4d" },
      "text": "New lead: Sarah Chen (Acme Ltd) from Meta Lead Ads"
    }'
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://public-api.workchats.com/v1/messages", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "evt_88231:usr_9a1d2c3e",
    },
    body: JSON.stringify({
      to: { type: "user", id: "usr_9a1d2c3e-4b5f-4a6b-8c7d-9e0f1a2b3c4d" },
      text: "New lead: Sarah Chen (Acme Ltd) from Meta Lead Ads",
    }),
  });
  const { message } = await res.json();
  ```
</CodeGroup>

| Field | Rule |
| - | - |
| `to.type` / `to.id` | `user` + `usr_…` for a DM, or `conversation` + `grp_…` / `chn_…` from `GET /v1/conversations` |
| `text` | Required, 1 to 4,000 characters. Shown to people, and it's what push notifications use — always send it, even when you send `blocks` |
| `blocks` | Optional, at most 50, at most 16 KB total. See [Blocks](/guides/blocks). Stored now, rendered in clients from a later phase |
| `thread_id` | Optional. A message id in the *same destination* — quoted above your new message, not a thread (Workchats has no threads). Anything else is `400 invalid_request` |
| `unfurl_links` | Accepted but has no effect yet: App messages never unfurl links |
| `metadata` | Optional object, at most 4 KB. Stored with the message, returned on later reads and (from Phase 1b) in the `interaction` callback. Not secret — anyone who can read the message can read it |

`Idempotency-Key` is required on every send. See
[Idempotency](/guides/idempotency) before you write retry logic.

**Response `201`:**

```json theme={null}
{
  "ok": true,
  "message": {
    "id": "dmsg_4e2d1c0b-9a8f-4e7d-8c6b-5a4f3e2d1c0b",
    "conversation_id": "dm_77a1b2c3-d4e5-4f60-8a7b-9c0d1e2f3a4b",
    "thread_id": null,
    "created_at": "2026-09-28T11:05:12Z"
  }
}
```

`conversation_id` is `dm_…` for a direct message, or the `grp_…` / `chn_…`
you sent to. Keep it — you'll want it for edits, and it's what `interaction`
callbacks reference in a later phase.

## Edit

```http theme={null}
PATCH /v1/messages/{id}
```

Body is `{ "text": "…", "blocks": […], "metadata": {…} }`. `text` is
required. `blocks` **replaces** the stored blocks — leave it out to clear
them. `metadata`, if present, is merged key-by-key into what's already
stored (it doesn't replace it). `to`, `thread_id` and `unfurl_links` are
ignored; you can't move a message after sending it. You can only edit
messages your own bot sent.

```json 200 theme={null}
{ "ok": true, "message": { "id": "dmsg_4e2d1c0b-…", "conversation_id": "dm_77a1b2c3-…", "thread_id": null, "created_at": "2026-09-28T11:05:12Z" } }
```

## Delete

```http theme={null}
DELETE /v1/messages/{id}
```

Returns `200 {"ok": true}`, or `404 message_not_found` if it's already gone
— treat both as success. This works even after your bot was removed from
the conversation. Deletion removes the message's content immediately from
every client and API; see [Data & security](/guides/data-and-security).

## Errors specific to sending

| HTTP | Code | Meaning |
| - | - | - |
| 400 | `missing_idempotency_key` | No `Idempotency-Key` header |
| 400 | `invalid_blocks` | A block failed validation — see [Blocks](/guides/blocks) |
| 403 | `cannot_dm_user` | The target can't receive a DM from your bot |
| 403 | `not_in_conversation` | Your bot was removed from that group or channel |
| 403 | `not_allowed_in_conversation` | That channel is announcement-only |
| 404 | `conversation_not_found` / `user_not_found` | No such destination, or your bot was never a member |
| 409 | `conversation_archived` / `user_deactivated` | The destination can't receive new messages |
| 413 | `payload_too_large` | Request body too large |
| 415 | `unsupported_media_type` | Body isn't `application/json` |
| 422 | `idempotency_key_reused` | Same key, different body — or the original message was edited or deleted |

Full table on [Errors](/guides/errors).
