Slink.API (Slink v0.6.0)

Copy Markdown View Source

A thin Slack Web API client built on Req.

Slack Web API methods are POSTs to https://slack.com/api/<method> with a bearer token. Note that Slack returns HTTP 200 even on logical failures — the real status is in the "ok" field of the JSON body, which the helpers here surface as {:error, reason}.

Genuine rate limiting is different: Slack answers with HTTP 429 and a Retry-After header. call/3 retries those automatically, honouring the header, so a burst backs off instead of dropping. Only 429 is retried — messages aren't idempotent, so we never re-POST on a transport error or a 5xx.

Tokens:

  • apps.connections.open needs an app-level token (xapp-…).
  • Everything else (e.g. chat.postMessage) needs a bot token (xoxb-…).

Summary

Functions

Add an emoji reaction (name, no colons) to the message at channel/timestamp via reactions.add. Needs the reactions:write scope.

Append markdown to a streaming message with chat.appendStream.

Who this token authenticates as, via auth.test.

Call any Web API method. Returns {:ok, body} when Slack replies ok: true, otherwise {:error, reason} (the Slack error string, or a transport error).

Delete a message with chat.delete. Needs the chat:write scope.

Get a permalink to the message at channel/timestamp via chat.getPermalink.

Fetch one page of a channel's message history with conversations.history.

Join a public channel with conversations.join. Needs the channels:join scope.

Exchange an OAuth code for a workspace's tokens, via oauth.v2.access.

Open a Socket Mode WebSocket URL via apps.connections.open.

Open (or resume) a direct message with user via conversations.open.

Open a modal with views.open.

Post a message only user can see, with chat.postEphemeral.

Post a message with chat.postMessage.

Publish a Home tab view for user with views.publish.

Push a new modal onto the stack of an open modal with views.push.

Remove a reaction added with add_reaction/4, via reactions.remove.

Reply to a slash command or interaction via its response_url.

Schedule a message for later with chat.scheduleMessage.

Offer tappable prompts in an assistant thread, via assistant.threads.setSuggestedPrompts.

Show a status line ("is thinking…") in an assistant thread, via assistant.threads.setStatus. Pass "" to clear it.

Name an assistant thread, via assistant.threads.setTitle.

Start a streaming message with chat.startStream.

Finish a streaming message with chat.stopStream.

Lazily stream every page of a cursor-paginated Web API method.

Update an existing message with chat.update.

Replace the contents of an open modal with views.update.

Upload a file in one call, hiding Slack's three-step external upload flow (files.getUploadURLExternal → upload the bytes → files.completeUploadExternal).

Look up a user's profile with users.info. Needs the users:read scope.

Functions

add_reaction(bot_token, channel, timestamp, name)

Add an emoji reaction (name, no colons) to the message at channel/timestamp via reactions.add. Needs the reactions:write scope.

append_stream(bot_token, channel, ts, markdown_text)

Append markdown to a streaming message with chat.appendStream.

ts is the streamed message's timestamp from start_stream/4. markdown_text is capped at 12,000 characters per call by Slack.

auth_test(token)

Who this token authenticates as, via auth.test.

The body's "user_id" is the bot's own user id (Slink.Identity caches this to power context.bot_user_id and Slink.mentions_me?/1).

call(token, method, params)

Call any Web API method. Returns {:ok, body} when Slack replies ok: true, otherwise {:error, reason} (the Slack error string, or a transport error).

delete_message(bot_token, channel, timestamp)

Delete a message with chat.delete. Needs the chat:write scope.

get_permalink(bot_token, channel, timestamp)

Get a permalink to the message at channel/timestamp via chat.getPermalink.

Returns {:ok, url}.

history(bot_token, channel, opts \\ %{})

Fetch one page of a channel's message history with conversations.history.

opts is merged into the body — most usefully %{limit: ..., cursor: ...} for paging (the next cursor is in body["response_metadata"]["next_cursor"]). Needs the relevant *:history scope for the conversation type.

join_channel(bot_token, channel)

Join a public channel with conversations.join. Needs the channels:join scope.

oauth_access(client_id, client_secret, code, redirect_uri \\ nil)

Exchange an OAuth code for a workspace's tokens, via oauth.v2.access.

This is the round-trip behind an "Add to Slack" install (see Slink.OAuth). Unlike other Web API methods it authenticates with the app's client credentials and a form-encoded body — no bearer token. redirect_uri must match the one used in the authorize URL, when one was used there.

open_connection(app_token)

Open a Socket Mode WebSocket URL via apps.connections.open.

Pass an app-level token (xapp-…) with the connections:write scope.

open_dm(bot_token, user)

Open (or resume) a direct message with user via conversations.open.

Returns {:ok, channel_id} — the DM channel to post into (see Slink.send_dm/4 for the one-call version). Needs the im:write scope.

open_view(bot_token, trigger_id, view)

Open a modal with views.open.

trigger_id comes from the interaction that opened it (see Slink.Event.trigger_id/1) and is only valid for ~3 seconds, so open the modal promptly. view is a Block Kit view payload (a map).

post_ephemeral(bot_token, channel, user, text, opts \\ %{})

Post a message only user can see, with chat.postEphemeral.

Shows up for that one user in channel and vanishes on reload — good for private acknowledgements. opts is merged into the body. Needs chat:write.

post_message(bot_token, channel, text, opts \\ %{})

Post a message with chat.postMessage.

opts is merged into the request body (e.g. %{thread_ts: ..., blocks: ...}).

publish_view(bot_token, user, view)

Publish a Home tab view for user with views.publish.

This is what populates a bot's App Home tab; call it from an :app_home_opened handler. Needs the App Home tab enabled for the app.

push_view(bot_token, trigger_id, view)

Push a new modal onto the stack of an open modal with views.push.

remove_reaction(bot_token, channel, timestamp, name)

Remove a reaction added with add_reaction/4, via reactions.remove.

respond(response_url, params)

Reply to a slash command or interaction via its response_url.

Slash commands and interactive payloads carry a short-lived response_url (valid ~30 minutes, up to 5 uses) — see Slink.Event.response_url/1. Unlike the Web API methods this is a plain POST to that URL with no bearer token. params is the message body, e.g. %{response_type: "ephemeral", text: "…"} ("ephemeral" — only the invoker sees it — or "in_channel"), optionally with blocks, replace_original, or delete_original.

schedule_message(bot_token, channel, post_at, text, opts \\ %{})

Schedule a message for later with chat.scheduleMessage.

post_at is a Unix timestamp (seconds). opts is merged into the body (e.g. %{thread_ts: ...}). Needs the chat:write scope.

set_suggested_prompts(bot_token, channel, thread_ts, prompts, opts \\ %{})

Offer tappable prompts in an assistant thread, via assistant.threads.setSuggestedPrompts.

prompts is a list of %{title: ..., message: ...} maps (the message is what gets sent when tapped). opts merges into the body (e.g. %{title: "Try one of these"} for the section header).

set_thread_status(bot_token, channel, thread_ts, status)

Show a status line ("is thinking…") in an assistant thread, via assistant.threads.setStatus. Pass "" to clear it.

set_thread_title(bot_token, channel, thread_ts, title)

Name an assistant thread, via assistant.threads.setTitle.

start_stream(bot_token, channel, thread_ts, opts \\ %{})

Start a streaming message with chat.startStream.

Streamed messages are always thread replies, so thread_ts is required. Returns the started message in the body — its "ts" is what append_stream/4 and stop_stream/4 take. When streaming into a channel (not the app's DM), Slack requires recipient_user_id and recipient_team_id in opts. See Slink.stream_reply/3 for the high-level helper.

stop_stream(bot_token, channel, ts, opts \\ %{})

Finish a streaming message with chat.stopStream.

opts merges into the body — a final markdown_text: chunk, or blocks: (allowed only here, not on start/append).

stream(token, method, params \\ %{})

Lazily stream every page of a cursor-paginated Web API method.

Emits each page's whole body, fetching the next page on demand by following response_metadata.next_cursor until Slack returns none. Extract the method's list field to stream items:

Slink.API.stream(token, "conversations.history", %{channel: "C123"})
|> Stream.flat_map(& &1["messages"])
|> Enum.take(500)

params gets limit: 200 (Slack's recommended page size) unless you pass your own. Rate limiting is handled the same as call/3 — a 429 waits out Retry-After and retries. A page fetch that still fails raises Slink.API.Error (a lazy stream can't return an error tuple), so wrap enumeration in a rescue if you need to handle it.

update_message(bot_token, channel, timestamp, text, opts \\ %{})

Update an existing message with chat.update.

timestamp is the ts of the message to edit. opts is merged into the body (e.g. %{blocks: ...}). Needs the chat:write scope.

update_view(bot_token, view_id, view)

Replace the contents of an open modal with views.update.

view_id is the id of the view returned when it was opened.

upload_file(bot_token, content, opts)

Upload a file in one call, hiding Slack's three-step external upload flow (files.getUploadURLExternal → upload the bytes → files.completeUploadExternal).

content is the file's binary content. Options (:filename required):

  • :filename — e.g. "report.csv".
  • :channel — share the file into this channel; without it the file is uploaded private to the bot.
  • :title — display title (defaults to the filename on Slack's side).
  • :initial_comment — message text alongside the shared file.
  • :thread_ts — share into a thread.
  • :alt_text — image description for screen readers.
  • :snippet_type — syntax type for text snippets (e.g. "elixir").

Returns {:ok, body} from the completing call (body["files"] holds the file objects, ids included), or {:error, reason} from whichever step failed. Needs the files:write scope.

user_info(bot_token, user)

Look up a user's profile with users.info. Needs the users:read scope.