Slack
Schedule and publish messages to Slack channels with Zernio API - Threads, file uploads, per-message bot identity, and a realtime inbox your agent can reply from
Quick Reference
| Property | Value |
|---|---|
| Content limit | 40,000 characters (Slack truncates silently beyond this) |
| Edit limit | 4,000 characters (longer posts cannot be edited) |
| Media per post | Up to 10 files |
| Formatting | Slack mrkdwn |
| Post types | Channel messages, thread replies |
| Scheduling | Yes (Zernio-side; Slack's native scheduler is not used) |
| Threads | Yes (threadTs) |
| Link unfurling | Configurable per post |
| Per-message identity | Yes (display name + avatar override) |
| Inbox (DMs) | Yes |
| Inbox (channel messages) | Mentions and threads only — not all chatter |
| Contacts & built-in automations | DMs only |
| Analytics | No (Slack has no public message analytics API) |
Before You Start
Slack works through a bot app that Zernio provides. You do not create your own Slack app, host a bot, or manage tokens. You install the Zernio app into your workspace via OAuth and post through the API.
- One connected account = one channel. To publish to several channels, connect each one as its own account.
- Public channels are joined automatically when you connect them.
- Private channels require a member to run
/invite @Zernioin the channel first — the bot cannot join a private channel on its own. A private channel that hasn't been invited simply won't appear in the channel list. - Some workspaces require admin approval for new apps. Slack may show a "request approval" screen, and a workspace can be configured to allow only Slack-Marketplace-listed apps. Zernio's app is distributed but not yet listed, so Slack notes that it "has not been reviewed by Slack" during install. That is expected.
OAuth Scopes
Slack publishing and the inbox run through a single bot installation:
| Scope | What it enables |
|---|---|
chat:write | Post messages as the bot |
chat:write.public | Post to public channels the bot hasn't joined |
chat:write.customize | Per-message display name and avatar override |
channels:join | Join public channels on connect |
files:write | Upload media with a post |
channels:read, groups:read | List channels so you can pick one |
team:read | Read workspace name and icon |
channels:history, groups:history, im:history, mpim:history | Receive inbound messages for the inbox |
im:read, mpim:read, im:write | Direct-message conversations |
users:read | Resolve sender names on inbound messages |
files:read | Inbound message attachments |
Workspaces connected before the inbox shipped must reconnect once. Existing bot tokens keep the scopes they were granted, so inbound messages fail with missing_scope until the workspace re-authorizes from the dashboard.
Quick Start
Post a message to a connected Slack channel:
const { post } = await zernio.posts.createPost({
content: 'Deploy finished :rocket:',
platforms: [
{
platform: 'slack',
accountId: 'YOUR_ACCOUNT_ID'
}
],
publishNow: true
});
console.log('Posted to Slack!', post._id);post = zernio.posts.create_post(
content="Deploy finished :rocket:",
platforms=[{
"platform": "slack",
"accountId": "YOUR_ACCOUNT_ID"
}],
publish_now=True,
)
print("Posted to Slack!", post["_id"])curl -X POST https://zernio.com/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Deploy finished :rocket:",
"platforms": [
{ "platform": "slack", "accountId": "YOUR_ACCOUNT_ID" }
],
"publishNow": true
}'There is no channelId field on a post. The channel is whichever one the account was connected to; passing channelId in platformSpecificData returns a 400.
Platform-Specific Fields
All optional, under platformSpecificData:
| Field | Type | Description |
|---|---|---|
threadTs | string | Post as a reply inside an existing thread. Use the parent message's timestamp. |
unfurlLinks | boolean | Show link previews for URLs in the text. |
unfurlMedia | boolean | Show media previews for media URLs. |
username | string | Override the bot's display name for this message. |
iconUrl | string | Override the bot's avatar for this message. |
{
"content": "Following up on the incident",
"platforms": [{
"platform": "slack",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"threadTs": "1785320613.863000",
"unfurlLinks": false,
"username": "Status Bot",
"iconUrl": "https://example.com/status.png"
}
}]
}The identity override is cosmetic and per message: Slack still shows an APP badge, and the app's identity in the sidebar stays "Zernio".
Connection
Start OAuth, then pick a channel:
GET /v1/connect/slack?profileId=xxx— returns the Slack authorization URL.- The user authorizes the workspace on Slack's consent screen.
- Zernio lists the workspace's channels.
- The user picks a channel.
POST /v1/connect/slacksaves the connection.
Headless is supported: pass headless=true, and the post-OAuth redirect carries a pendingDataToken plus connect_token. List channels with GET /v1/connect/slack?profileId=xxx&pendingDataToken=xxx, then save with POST /v1/connect/slack.
Adding another channel from the same workspace does not require a new OAuth. Call GET /v1/connect/slack?profileId=xxx&accountId=<existing slack accountId> to list channels, and POST with accountId instead of pendingDataToken.
Inbox
Slack is part of the unified Inbox. Inbound messages arrive in realtime through Slack's Events API.
What is ingested:
- Direct messages to the Zernio bot
- Channel messages that @-mention the bot
- Replies in threads the bot is already part of, including under a post Zernio published
- The bot's own messages
What is deliberately not ingested: ordinary channel conversation. The inbox is not a mirror of your Slack workspace, so day-to-day chatter in a connected channel is never stored.
Every incoming message fires the message.received webhook. Its metadata carries:
| Field | Meaning |
|---|---|
threadTs | Present when the message is a thread reply |
mentionsBot | true when the message @-mentions the Zernio bot |
That pair is what an agent needs for the usual rule: answer whenever you're mentioned in a channel, always answer in DMs, and reply inside the thread.
Reply from the Inbox UI or with POST /v1/inbox/conversations/{id}/messages; the message goes out as the bot. Pass replyTo to answer inside a thread.
Contacts and built-in automations (Sequences, Workflows) are DM-only. Someone who DMs the bot becomes a contact; people talking in a connected channel do not, otherwise every colleague in the channel would be added as a contact. Channel automation is done by your own agent reacting to the message.received webhook.
Constraints:
- No history backfill. The inbox starts at connect time (a Slack API restriction for apps that aren't Marketplace-listed).
- Messages from other bots appear for context but never trigger automations or webhooks (loop guard).
- Attachments appear as links that need to be opened in Slack.
- Inbox replies are text-only for now.
Edit & Delete
- Edit: messages up to 4,000 characters can be updated. Longer messages cannot be edited, Slack's edit limit is stricter than its posting limit.
- Delete: unpublishing deletes the Slack message. Workspace retention or compliance settings can block deletion; that is an admin setting, not a Zernio error.
- Media posts occasionally cannot be edited or deleted when Slack did not return a message reference at publish time.
Analytics
Slack has no public message analytics API, so Slack posts always report zero analytics in Zernio. Reactions and replies are visible only inside Slack.
What You Can't Do
- Post to several channels with one connected account (connect each channel separately)
- Set the channel per post via
platformSpecificData.channelId - Retrieve engagement metrics
- Backfill message history from before the connection
- Join a private channel without being invited
Common Errors
| Message | Cause and fix |
|---|---|
| Bot is not a member of this channel | The bot was removed, or the channel is private. Run /invite @Zernio in the channel. |
| Channel was not found or the bot lost access | The channel was deleted or the bot lost visibility. Reconnect or pick another channel. |
| A workspace setting prevents posting | A Slack admin restricted posting in that channel. |
| File uploads are disabled or too large | The workspace's upload policy or its free-plan storage cap. |
| Workspace access was revoked | The app was uninstalled from Slack. Reconnect from the dashboard. |
missing_scope on inbound messages | The workspace was connected before the inbox shipped. Reconnect once. |
Disconnecting one channel account does not revoke the workspace token, sibling channel accounts keep working. Uninstalling the Zernio app from Slack disconnects all of that workspace's channel accounts in Zernio automatically.