Slack
SlackService sends messages to Slack channels through a configured Connection, resolving channel names to IDs server-side from the bot’s channel membership so callers never page Slack’s workspace directory themselves. It also exposes a connection health check and a channel listing.
Service: firetiger.slack.v1.SlackService
Access: Read-write (requires a Slack Connection, CONNECTION_TYPE_SLACK)
Why this service
Slack has no name→ID lookup API. Paging the entire workspace directory (conversations.list) to find a channel is rate-limited (Tier 2, ~20 req/min) and, on a large workspace, cannot reach a deep channel within any reasonable budget. So SlackService resolves names against users.conversations — the (small) set of channels the bot is a member of — instead. A channel a human has invited the Firetiger app into resolves in ~1 page; a channel the bot is not in returns CHANNEL_NOT_FOUND with instructions to invite the app. Membership is the source of truth for what the bot may post to — there is no configured allowlist. The service still hard-blocks #general and DMs server-side, and makes sends idempotent so retries don’t double-post.
SendSlackMessage
Resolves each target channel (against the bot’s member channels), enforces the #general/no-DM rules, and posts. Provide an idempotency_key to make a retried request replay the original result instead of re-posting.
curl -X POST "https://api.cloud.firetiger.com/firetiger.slack.v1.SlackService/SendSlackMessage" \
-u "$USERNAME:$PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"connection": "connections/slack-prod",
"targets": [{ "channel": "#alerts" }],
"text": "Deploy v1.2.3 completed",
"idempotency_key": "deploy-v1.2.3-notify"
}'
{
"results": [
{
"channel": "#alerts",
"channelId": "C0123ABCD",
"ts": "1718384000.000100",
"permalink": "https://acme.slack.com/archives/C0123ABCD/p1718384000000100"
}
]
}
Targets carry the channel plus optional threading: { "channel": "#alerts", "thread_ts": "...", "reply_broadcast": true }. Pass blocks (Slack Block Kit JSON) instead of, or alongside, text.
Per-channel errors are returned inline rather than failing the whole call. Each failed result carries a typed error.kind:
kind |
Meaning |
|---|---|
CHANNEL_NOT_FOUND |
The bot is not a member of any channel with that name (or no such channel exists). The detail tells the user to invite the Firetiger app to the channel (e.g. /invite @Firetiger). |
NOT_ALLOWED |
Channel is #general or a DM (both are never allowed). |
RATE_LIMITED |
Slack rate-limited the operation after retries. |
THREAD_STALE |
thread_ts referenced a deleted parent; resend without it. |
SLACK_API_ERROR |
Any other Slack API error. |
The #general ban and the no-DM rule are enforced server-side and cannot be bypassed by passing a raw channel ID. Beyond those, channel membership governs what the bot can post to — a channel the bot is not in simply does not resolve.
TestSlackConnection
Health-checks a connection without walking the channel directory — auth.test plus a scope diff against the app’s required scopes.
curl -X POST "https://api.cloud.firetiger.com/firetiger.slack.v1.SlackService/TestSlackConnection" \
-u "$USERNAME:$PASSWORD" \
-H "Content-Type: application/json" \
-d '{ "connection": "connections/slack-prod" }'
{
"ok": true,
"botUser": "firetiger",
"team": "Acme",
"grantedScopes": ["chat:write", "channels:read", "..."],
"missingScopes": []
}
ok is true only when auth.test succeeds and no required scopes are missing; otherwise error describes the failure.
ListSlackChannels
Lists the channels the connection’s bot is a member of (users.conversations, cursor-paginated, mirroring Slack). Does not walk the full workspace directory.
curl -X POST "https://api.cloud.firetiger.com/firetiger.slack.v1.SlackService/ListSlackChannels" \
-u "$USERNAME:$PASSWORD" \
-H "Content-Type: application/json" \
-d '{ "connection": "connections/slack-prod", "page_size": 200 }'
{
"channels": [
{ "id": "C0123ABCD", "name": "alerts", "isPrivate": false, "isArchived": false }
],
"nextPageToken": "dXNlcjpVMDYxTkZUVDI="
}