zudo-slack-wisdom
GitHub repository

Type to search...

to open search from anywhere

Rate Limits

The Tier 1-4 model, Retry-After handling, per-channel posting limits, and the 2025-2026 non-Marketplace app changes — verified 2026-08-06

Verified against docs.slack.dev as of 2026-08-06. Rate limits are one of the more actively-changing parts of the platform (see the non-Marketplace section below) — re-check the primary sources before depending on exact numbers in production.

The Tier Model

Every Web API method is assigned one of four rate-limit tiers, plus a special tier for methods with bespoke rules. All Slack plans get the same tier for a given method — a method's Facts section on its reference page states which tier applies (Rate limits):

TierRateCharacter
11+ per minuteInfrequent access, small bursts tolerated
220+ per minuteMost methods; occasional bursts allowed
350+ per minuteLarger quota; sporadic bursts welcome
4100+ per minuteGenerous burst behavior
SpecialVaries per methodMethod-specific rules — chat.postMessage is one of these (see below)

The limit is scoped per API method, per workspace/team, per app — hitting a limit on one method against one workspace doesn't throttle other methods or other workspaces the same app is installed in.

Retry-After Handling

A rate-limited call returns HTTP 429 Too Many Requests with a Retry-After header giving the number of seconds to wait before retrying. Treat this as authoritative when it's present rather than rolling your own backoff schedule from scratch — sleep for (at least) the stated duration, then retry the exact same call. Every official SDK's built-in retry handler already implements this; reach for it before writing a custom retry loop.

"Authoritative" doesn't mean "always present" or "safe to obey unbounded," though. The header can be absent or unparseable on a 429, and it is never present on a 5xx — a retry loop that only knows how to read Retry-After has nothing to act on in either case and, with no fallback, ends up retrying back-to-back against an endpoint that just asked it to slow down. Pair header-based waiting with an exponential fallback (1 second base, doubling on each subsequent failure) for the cases where no usable header value is present, and clamp both the header's value and the fallback's computed value to a per-call-path ceiling — an unusually large or corrupt Retry-After value shouldn't be able to stall a call path indefinitely any more than an unbounded exponential fallback should.

Message-Posting Limits: ~1/sec/Channel

chat.postMessage carries special rate-limit behavior rather than a plain tier: apps may post no more than one message per second, per channel, regardless of whether the message goes out via chat.postMessage, an incoming webhook, or any other path into a channel. Short bursts above that rate are tolerated, but Slack doesn't guarantee every message in a burst will display, and a sustained excess can still draw the standard HTTP 429 + Retry-After response described above — implement that handling for posting calls too, not only for read methods. Space out high-volume posting (or batch content into fewer, larger messages) rather than relying on burst tolerance.

The 2025–2026 Rate-Limit Changes for Non-Marketplace Apps

This is the change most likely to bite a bot built and distributed outside the official Slack Marketplace, and it directly affects conversations.history and conversations.replies — methods a bot commonly uses to read channel/thread content, not to post it.

What changed: for apps that are commercially distributed but not approved for the Slack Marketplace, conversations.history and conversations.replies were cut from their normal Tier 3 allowance (50+ requests/minute, up to 999 objects per request) down to 1 request per minute, returning a maximum of 15 objects per request. Marketplace-approved apps and internal customer-built apps (single-workspace apps built for your own org, not commercially distributed) keep the original Tier 3 limits (conversations.history).

Rollout timeline:

  • 2025-05-29 — effective immediately for any app created on or after this date, and for any new installation of an existing non-Marketplace app. Slack's stated rationale: "unvetted applications have the potential to exfiltrate large amounts of sensitive conversational data," and the changes are meant to prevent bulk data exfiltration through these two methods.

  • 2026-03-03 — the grace period for already-installed non-Marketplace apps ended on this date; existing installations that had been unaffected by the May 2025 change became subject to the same 1-request-per-minute / 15-object limit from this date forward.

As of this page's verification date (2026-08-06), that grace period has already elapsed — any non-Marketplace, non-internal app's existing installations are now subject to the reduced limit, not just newly-created ones. A bot that reads channel or thread history and isn't Marketplace- approved should assume the 1-request-per-minute ceiling applies today, regardless of when it was first installed.

Sources: Rate limit changes for non-Marketplace apps (original changelog + FAQ), Clarifying rate limit changes for non-Marketplace apps (follow-up clarification), and the conversations.history method reference, which documents the tier split directly on the method page.

If bulk history reads are a hard requirement

Slack points at the Real-time Search APIas the sanctioned alternative for searching message/file content without large exports — but as of 2026-08-06 it is scoped to "select partners," not a drop-in replacement any app can adopt. Getting Marketplace-approved is the path that keeps the original Tier 3 limits onconversations.history/conversations.replies directly.

Field Notes from a Production Deployment

Two operational realities from running this rate-limit model in production, worth carrying into a runbook rather than rediscovering under load.

Read budgets settle above the raw floor

A production reference integration operating under the non-Marketplace regime above raises itseffective Retry-After ceiling on the read path to roughly 60 seconds rather than clamping to something tighter — 1 request/minute is a hard floor, not a target, and a self-imposed ceiling below what the platform actually enforces just adds failed calls without buying anything. Budget how many conversations.history/conversations.replies calls a single cron tick is allowed to spend, and cache anything read-heavy and reusable — permalinks in particular — server-side rather than re-fetching it on every pass. See Reading Channel Historyfor the full per-tick budget breakdown, the two-phase coverage pattern, and the replace-on-read design this ceiling shapes.

The internal-app exemption is live, not one-time

An app stays exempt from the non-Marketplace limits above only as long as it stays "internal" — a single-workspace app never submitted for commercial distribution. Enabling Manage Distributionreclassifies the app out of that exemption immediately, silently droppingconversations.history/conversations.replies onto the reduced 1-request-per-minute ceiling for a read design that was sized for Tier 3. "Manage Distribution stays OFF" belongs in the runbook as a standing operational invariant, not a one-time setup decision — anyone with admin access can flip it later without realizing what it breaks.

Applying This to a Dashboard Bot

The pinned-dashboard pattern — one chat.update call per refresh — sits comfortably inside every limit on this page: it's a single special-tier chat.update call (which shares the same per-channel posting-rate family as chat.postMessage), refreshing on a cron interval far looser than once per second. The non-Marketplace change above matters for the read side instead — a sync job that pulls source data via conversations.history/conversations.replies on a non-Marketplace app needs to budget for 1 request/minute, 15 objects/request, not the Tier 3 numbers the method's base documentation still shows as the default case.

Revision History

CreatedUpdated