Skip to content

A message has three optional parts: text, blocks and attachments. Send at least one.

  • text — plain text with mrkdwn formatting. With blocks, it becomes the notification and search fallback, as on Slack.
  • blocks — up to 50 Block Kit blocks. This is what people see.
  • attachments — Slack’s legacy secondary attachments with a coloured side bar, shown after the blocks.
POST /api/bot/chat.postMessage
Authorization: Bearer foxa-…
Content-Type: application/json
{
"channel": "support",
"text": "Ticket #4821 was escalated",
"blocks": [
{ "type": "header", "text": { "type": "plain_text", "text": "Ticket #4821 escalated" } },
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Customer*\nAcme Ltd" },
{ "type": "mrkdwn", "text": "*Priority*\nUrgent" }
]
}
]
}
Field Description
channel Channel id or short name. Required.
text Message text, or the fallback when blocks are sent.
blocks Array of blocks, or the same array as a JSON string (form-encoded requests).
attachments Array of attachments, or a JSON string.
thread_ts Reply in a thread: the parent message’s ts.
idempotency_key Or the Idempotency-Key header. Retrying with the same key returns the first message instead of posting twice.

The answer follows Slack:

{
"ok": true,
"ts": "3a77c808-55af-410c-aac3-32f02c0c782f",
"channel": "e0fd46e9-86de-4104-a1d0-f7a1ed30f403",
"message": { "text": "Ticket #4821 was escalated", "ts": "3a77c808-…", "bot_id": "75385d0c-…", "blocks": [ … ] }
}

ts is the message id: keep it to update, delete or reply to the message.

Workstation never rejects a message because of its blocks. Missing block_id / action_id values are generated, unsupported blocks are skipped, and anything that doesn’t match the Block Kit rules is reported back in response_metadata.warnings (bot API) or warnings (REST API) so you can fix it:

{
"ok": true,
"ts": "…",
"response_metadata": {
"warnings": ["blocks[1].elements[0].text: longer than 75 characters"]
}
}
POST /api/bot/chat.update
{ "ts": "3a77c808-…", "text": "Ticket #4821 resolved", "blocks": [ … ] }

Sending blocks or attachments replaces them; sending only text replaces the whole message with that text. People see the edit immediately. Delete with POST /api/bot/chat.delete and { "ts": "…" }. An app can only edit and delete its own messages.

The same messages through the REST API:

POST /api/v1/chat/channels/{channelId}/messages
{
"body": "Ticket #4821 was escalated",
"blocks": [ … ],
"attachments": [ … ],
"parentMessageId": "…",
"clientMessageId": "9b2f…"
}

PATCH /api/v1/chat/messages/{id} takes body, blocks and attachments; DELETE /api/v1/chat/messages/{id} deletes. The REST API needs the chat_messages:write scope.

error Meaning
channel_not_found No such channel, or the bot can’t post there (private channel it isn’t in, or not on its allowlist).
is_archived The channel is archived.
not_in_channel An AI-agent bot must be a member of the channel.
no_text No text, blocks or attachments.
message_not_found chat.update / chat.delete on a message that isn’t the bot’s or no longer exists.
missing_scope The bot lacks a scope; needed names it.