API
Discord rate limits, explained
How the buckets work, which headers to read, why the global limit is not what most people think, and what actually gets you banned rather than throttled.
- Key
- API-003
- Section
- API
- Read
- 8 min
- Checked
- 30 August 2026
Discord rate limits per route and major parameter, not per endpoint and not per application. Getting that distinction right removes most of the confusion, and most of the accidental 429s.
Buckets, not endpoints
A bucket is identified by the route plus its major parameter. The major parameters are
channel_id, guild_id and webhook_id.
So posting to channel A and posting to channel B are separate buckets with separate budgets. Two requests to the same channel share one. This is why a bot can appear fine across a hundred servers and get throttled hard in a single busy one.
Discord returns the bucket identity in X-RateLimit-Bucket. Two routes can share a bucket, and a
correct client keys its local limiter on that header rather than on the URL it constructed.
Read the headers, do not hardcode the numbers
The published limits change. Anything you memorise from a tutorial will eventually be wrong, and the response tells you the truth on every request:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in this window |
X-RateLimit-Remaining | Requests left before you are limited |
X-RateLimit-Reset-After | Seconds until the window resets — prefer this over Reset |
X-RateLimit-Bucket | The bucket identity to key your limiter on |
X-RateLimit-Global | Present when you hit the global limit, not a route limit |
Retry-After | On a 429, seconds to wait |
Use Reset-After rather than Reset. Reset is an absolute timestamp and requires your clock to
agree with Discord’s; Reset-After is a duration and does not.
The global limit is per-token
The global limit — commonly cited as 50 requests per second — applies to your bot token across everything, and it is separate from route limits. You can be well within every route bucket and still hit it.
Interaction responses are exempt from the global limit. Webhooks are limited per webhook and do not count toward your bot’s global budget, which is occasionally a useful property.
The route limits that catch people out
Most routes behave as the headers describe. A few carry much tighter limits than their traffic suggests, and they are the ones behind the majority of “my bot randomly stops working” reports.
Channel name and topic edits: two per ten minutes, per channel. This is the single most surprising limit on the platform, and it is why member-count channels, live-status channels and countdown channels stall. A bot updating a counter every minute succeeds twice and is then silent for eight minutes — which looks exactly like a crashed bot. Any design that renames a channel on a schedule must be built around this number, not discovered by it.
Server-wide edits are slower than they look. Bulk role syncs, mass nickname changes and permission rewrites across many channels each consume their own bucket per target. Ten channels is ten buckets, and firing them in parallel is the fastest route to a global limit.
Bulk message delete has its own constraints beyond rate limiting: it refuses messages older than two weeks, and refuses a single-message batch. Both return errors that look like permission faults and are not.
The general rule: if an operation changes something everyone can see, assume it is limited more tightly than an operation that only posts a message.
Invalid requests are the one that bans you
This is the part that surprises people, and it is the only rate limit with a real consequence.
Repeatedly sending requests that return 401, 403 or 429 is tracked separately. Exceed roughly 10,000 invalid requests in ten minutes and Cloudflare bans your IP for an hour — not a 429, an actual block, and it takes your whole application down.
The practical implication: a retry loop on a 403 is dangerous. A 403 means you lack permission, and retrying will never fix it. Treat 401 and 403 as permanent for that request, log them, and stop. Only retry 429 and 5xx.
Telling a Cloudflare block from a Discord 429
They arrive differently, and confusing them wastes hours.
A Discord 429 is JSON, carries retry_after and the X-RateLimit-* headers, and is a normal
part of operation. A Cloudflare block returns an HTML error page with no X-RateLimit-Bucket
header and no JSON body — a client that assumes JSON will throw a parse error and report something
misleading.
If your error handler cannot parse the body of a 429, you are almost certainly banned rather than throttled, and the fix is to stop sending entirely for an hour and find the source of the invalid requests.
Handling a 429 correctly
Read Retry-After and wait exactly that long. Do not retry immediately, do not use your own
backoff curve when the server has told you the number, and do not fire the retry from a different
worker that does not know about the wait.
If X-RateLimit-Global is present, pause every request from the token, not just that route.
A correct client keeps a per-bucket queue plus one global gate. That is more work than a naive
sleep on failure, and it is the difference between a bot that degrades under load and one that
falls over.
The shape of a correct limiter
Four components, and skipping any one of them produces a specific failure.
A queue per bucket, keyed on X-RateLimit-Bucket from the last response for that route. Without
it, two concurrent requests to the same channel each believe they hold the last slot.
A single global gate the whole token passes through. Without it, a global 429 on one route is ignored by every other route and the block extends.
A shared store when you run more than one process. Eight PM2 workers each holding a local limiter believe they each have the full budget, so the effective request rate is eight times what the limiter thinks. This is the most common cause of unexplained 429s at scale, and no amount of per-process tuning fixes it.
A hard stop on 401 and 403, with the failure logged rather than retried.
Gateway limits are different again
The gateway has its own budget: 120 events per 60 seconds per shard, and a separate limit on identify calls that governs how fast a large bot can start up.
Presence updates are the usual culprit — a bot that updates its status frequently can exhaust its gateway budget and start missing events, which looks like a bug somewhere else entirely.
Startup is where sharded bots meet this. The number of shards that may identify concurrently is returned by the gateway-bot endpoint rather than being fixed, and the daily session-start allowance is finite — so a bot in a crash-restart loop can exhaust its ability to connect at all and then appear permanently offline for reasons unrelated to the crash. Read the values from the endpoint at startup rather than hardcoding them, and back off on repeated connection failures.
When you are on the other side of a bot’s own API
Everything above is Discord’s limit on a bot. A bot that itself publishes a developer API imposes a second, independent limit on whoever calls it, and the same habits apply: read what is published, key your client on the documented window, and treat a 429 as a signal to slow down rather than retry immediately.
Vetox’s Vito public API is a concrete example of the tiered-limit shape: 180 requests per minute and 5,000 per hour on a Silver or Gold membership, rising to 600 per minute and 15,000 per hour on Platinum or Diamond. A single IP address is additionally capped at half the project’s per-minute budget, floored at 30 — the anti-abuse equivalent of Discord’s own per-token global limit above, scoped to the caller’s network address rather than its credential. Only five of the thirteen bot developer APIs examined for the API comparison on this site publish their limit as a number at all; most leave it to be discovered in production, which is the exact failure mode the rest of this page exists to help you avoid.
Observing your own rate-limit behaviour
You cannot tune what you do not measure, and three signals cover it.
Log every 429 with its bucket and retry_after. A single number — 429s per thousand requests —
tells you whether a change helped.
Log the remaining count on a sample of requests. A bucket that regularly reaches zero is one burst away from throttling, and it shows up here before it shows up as a failure.
Alert on invalid requests, not just on failures. The count of 401s and 403s is the metric that precedes an IP ban, and it is usually the only warning you get.
What this means in practice
Most bots never think about rate limits until they do something bulk: renaming channels on a schedule, syncing roles across a large member list, posting to many channels at once.
Three habits prevent nearly all of it. Queue bulk work rather than firing it in parallel. Key your limiter on the bucket header, not the URL. Never retry a 4xx that is not a 429.
Bots that serve large numbers of servers handle this with a shared limiter across processes, so that eight workers do not each believe they have the full budget. If you are running at that scale and seeing unexplained 429s, a per-process limiter is the first thing to check.
Frequently asked questions
Why is my counter channel only updating twice per ten minutes? Channel name and topic edits are limited to two per ten minutes per channel. Design around it rather than retrying.
Do webhooks count against my bot’s global limit? No — webhooks are limited per webhook and are independent of the bot token’s budget.
What is the difference between a 429 and a Cloudflare ban? A Discord 429 is JSON with
retry_after and rate-limit headers. A Cloudflare block is an HTML page with neither, and it lasts
an hour.
Should I retry a 403? Never. It means the permission is missing, retrying cannot fix it, and repeated 403s count toward the invalid-request budget that ends in an IP ban.
Why do I get 429s with a limiter in place? Most often because each process has its own limiter. A multi-process bot needs a shared one.
Sources
Checked