Sending messages
A message has three optional parts: text, blocks and attachments. Send at least one.
text— plain text with mrkdwn formatting. Withblocks, 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 a message
Section titled “Post a message”POST /api/bot/chat.postMessageAuthorization: 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.
Warnings, never rejections
Section titled “Warnings, never rejections”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"] }}Update and delete
Section titled “Update and delete”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.
Using the REST API instead
Section titled “Using the REST API instead”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.
Errors
Section titled “Errors”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. |