Shared client for the Slack Admin API: form-encoded requests with the admin user token, ok: false and rate-limit handling, and cursor pagination.

Per-call credentials. Every API-calling function in this package takes an optional trailing opts Map, resolved by resolve: opts first, then the slack.admin.token context variable. Nothing from opts is written to ctx, so one run can act for many organizations and parallel flows don't share credentials. A token from opts is masked in call records. A token present but blank or null is an error, never a fallback to slack.admin.token.

  • token: the org-level admin user token for this call

Context Vars

Optional
  • slack.admin.token

Functions

build-request

fn (method: Str, request: Any): ::hot::http/HttpRequest
fn (method: Str, request: Any, token: Str?): ::hot::http/HttpRequest
fn (method: Str, request: Any, opts: Map): ::hot::http/HttpRequest

Build (without sending) the POST to https://slack.com/api/<method> with a form body and Authorization: Bearer <token>. The 3rd argument is per-call opts (see resolve), or an explicit token instead of slack.admin.token. Returns an Err when the call has no usable token.

call

fn (method: Str, request: Any): Map
fn (method: Str, request: Any, token: Str?): Map
fn (method: Str, request: Any, opts: Map): Map

Call any Slack admin method with a request record or Map and return the response body, or an Err(SlackError). Use it for methods this package doesn't wrap. The 3rd argument is per-call opts (see resolve) or an explicit token.

Example

teams ::slack::admin::api/call("admin.teams.list", {limit: 100})
teams.teams
tenant ::slack::admin::api/call("admin.teams.list", {limit: 100}, {token: "xoxp-tenant-b"})

collect-pages

fn (fetch: Fn, params: Map, items-key: Str, max-items: Int, acc: Vec): Vec

The paging loop behind paginate, with the page fetcher passed in: fetch takes the params Map and returns a response body. A failed page comes back as its Err. Exposed for testing.

form-body

fn (request: Any): Str

Encode a request record as application/x-www-form-urlencoded, dropping unset and null fields. Exposed for testing.

Example

::slack::admin::api/form-body({channel_ids: ["C1", "C2"], cursor: null, limit: 50})
// => "channel_ids=C1%2CC2&limit=50"

form-value

fn (value: Any): Str

Encode one argument the way Slack's admin methods expect in a form body: lists of strings comma-separated (C1,C2), other lists and maps as JSON strings (e.g. prefs), booleans as true/false.

is-rate-limited

fn (error: Any): Bool

True when error is a SlackError for HTTP 429; wait error.retry_after seconds before retrying.

next-cursor

fn (body: Map): Str?

The next page cursor from a response (response_metadata.next_cursor, or a top-level next_cursor), or null on the last page.

non-blank

fn (value: Any): Str?

paginate

fn (method: Str, request: Any, items-key: Str, max-items: Int): Vec
fn (method: Str, request: Any, items-key: Str, max-items: Int, opts: Map): Vec

Call a cursor-paginated admin method repeatedly, following next_cursor, and return the concatenated items-key lists (up to max-items). The per-module *-all helpers use it.

Example

users ::slack::admin::api/paginate("admin.users.list", {team_id: "T0123ABCDEF", limit: 200}, "users", 5000)

parse-response

fn (method: Str, response: Result): Any

Return the body of a successful Slack response, or an Err(SlackError) for HTTP errors, rate limits (429), and ok: false bodies (Slack's usual failure, sent with HTTP 200). Exposed for testing.

resolve

fn (opts: Map): Map

The admin token for one call (see resolve-from). A token from opts is masked in call records with ::hot::ctx/mark-secret, and opts values are never written to ctx.

Example

::slack::admin::api/resolve({token: "xoxp-tenant-b"}).token
Context Vars: slack.admin.token

resolve-from

fn (opts: Map, ctx-values: Map): Map

The one resolver, pure: {token} for a call from opts first, then ctx-values (a Map of context variables). An Err when no token is set, or when token is in opts but blank or null (that never falls back to context). Exposed for testing; resolve feeds it the context.

slack-error

fn (method: Str, response: ::hot::http/HttpResponse): SlackError

Build the SlackError for a failed response to method. Exposed for testing; parse-response uses it.

Types

SlackError

SlackError type {
    method: Str,
    error: Str,
    status: Int,
    messages: Vec<Str>?,
    warning: Str?,
    needed: Str?,
    provided: Str?,
    retry_after: Int?,
    body: Any
}

A failed admin call. error is Slack's error code (not_allowed_token_type, missing_scope, channel_not_found, ...); messages holds response_metadata.messages (per-argument detail for invalid_arguments); needed/provided list scopes on missing_scope. HTTP-level failures use http_<status>, and HTTP 429 uses ratelimited with retry_after seconds from Retry-After.

Example

result ::slack::admin::conversations/archive(::slack::admin::conversations/ArchiveRequest({channel_id: "C0123ABCDEF"}))
if-err(result, (e) { `archive failed: ${e.error} ${join(or(e.messages, []), "; ")}` })