Discord rate limits are the reason busy bots suddenly slow down, return 429 Too Many Requests, or appear broken during bulk actions. The API is strict by design: if your bot sends too many requests too quickly, Discord tells you exactly how long to wait with retry_after.
This guide explains route limits, global limits, webhook limits, discord.js automatic queues, and the patterns that keep a production bot stable on a VPS.
How Discord Rate Limits Work
Discord applies rate limits at multiple granularities:
Per-route limits: Each API endpoint has its own bucket. Sending 5 messages to channel A doesn't affect your ability to send messages to channel B.
Global limit: 50 requests per second globally across all routes.
Interaction response limit: 3 seconds to respond to a slash command (not a rate limit, but a timeout).
When your bot exceeds a bucket's limit, Discord returns:
{
"message": "You are being rate limited.",
"retry_after": 1.337,
"global": false
}
How discord.js Handles Rate Limits Automatically
discord.js implements automatic rate limiting. It queues requests that would hit a limit and releases them after the retry_after period. For most bots, this is invisible - requests queue briefly and succeed without errors.
You can observe this:
client.rest.on('rateLimited', (rateLimitInfo) => {
console.log(`Rate limited: ${rateLimitInfo.method} ${rateLimitInfo.url}, retry in ${rateLimitInfo.timeToReset}ms`);
});
When Automatic Handling Is Not Enough
Problems arise when:
- Your bot sends bulk messages in loops - Even with queuing, processing is slower than expected:
// BAD: Fires 100 API calls almost simultaneously
for (let i = 0; i < 100; i++) {
await channel.send(`Message ${i}`)
}
// BETTER: Add delays between bulk operations
for (let i = 0; i < 100; i++) {
await channel.send(`Message ${i}`)
await new Promise(resolve => setTimeout(resolve, 100))
}
-
Ban/kick storms - Mass moderation operations. Use rate-limited queues with delay.
-
Webhook spam - Webhooks have their own limits separate from bot limits. 30 messages per minute per webhook. For webhook-only workflows, see our Discord webhook rate limits guide.
Global Ban Prevention
If your bot receives more than a specific number of 429 responses in a short period, Discord may issue a temporary global ban (CloudFlare ban). This blocks your VPS IP from the API for minutes to hours.
Prevention:
- Never use raw HTTP clients for Discord - always use a library that respects rate limits
- Add logging on rate limit events
- Implement exponential backoff for any retry logic
Host your Discord bot 24/7 on Space-Node
What Discord rate-limits and where
| Limit | Scope | Default | Hit when |
|---|---|---|---|
| Per-route HTTP | route + major param | varies, ~5/5s typical | spamming /messages |
| Per-user message edit | channel | 5 / 5 s | edit-spam from one user |
| Channel send | channel | 5 / 5 s | high-traffic channel |
| Global HTTP | bot token | 50 / s | rare but fatal |
| Gateway connect | token | 1000 / day | many restarts |
| Sharded gateway | shard | 1 identify / 5 s | scaling issues |
The dangerous one is global. Once you trip it, all your routes are throttled for ~30-60 s.
Reading the rate-limit headers
Every Discord HTTP response includes:
X-RateLimit-Bucket: abc123
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset-After: 1.234
discord.js and discord.py handle these automatically. If you call the API directly with fetch, you must respect them yourself; otherwise you trigger 429s and the global lock.
Right way to handle a 429
async function send(channelId, body) {
while (true) {
const r = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
method: 'POST',
headers: { Authorization: `Bot ${TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (r.status !== 429) return r;
const j = await r.json();
const wait = (j.retry_after ?? 1) * 1000;
if (j.global) console.warn('GLOBAL 429, backing off');
await new Promise(s => setTimeout(s, wait));
}
}
Burst patterns that get you globally throttled
- Sending 100 DMs in a loop without backoff.
- Editing the same message in a tight loop (status counters, "current online count").
- Mass-inviting users via API.
- Spamming
channel.create/channel.delete(cleanup scripts).
The fix for status counters: edit at most once every 30-60 seconds. Discord shows the same counter to users either way.
Internal queue patterns that work
For a leaderboard bot that updates 50 channels every minute:
- Spread updates across the minute, not in a 1-second burst.
- Use one persistent message per channel;
PATCHis cheaper thanPOST+ delete. - Skip the update if the value didn't change.
Detection: log everything 429
Every 429 response should be logged with route + retry_after. A spike of 429s on one route is your fingerprint of which feature is misbehaving.
