Discord 429 Too Many Requests: rate limits and Cloudflare bans explained

A 429 Too Many Requests error is Discord telling your bot to wait the time in the Retry-After header before trying again. Retrying anyway can get your IP temporarily blocked.

Updated 6 min read

Learning objectives

After reading this article you will be able to:

  • Understand the three kinds of limits: per route, global and invalid requests
  • Read the headers and body of a 429 response
  • Fix and prevent rate limits in discord.js and discord.py
On this page

You check your bot's console and see this:

json
{
  "message": "You are being rate limited.",
  "retry_after": 1.337,
  "global": false
}

That is the body of an HTTP response with status 429 Too Many Requests. Discord limits how many calls a bot can make to its API, and yours went over. Most of the time your library already waits it out and the bot keeps working.

The serious case is when the bot ignores the warning, piles up error responses and Discord blocks the IP for a while. That temporary block is what Discord's own documentation calls a "Cloudflare ban".

BotrequestDiscordAPI200 OK429Retry-AfterWaitthe given timeretriesInvalid requests: 401, 403, 42910,000 in 10 minIPblocked
Waiting out Retry-After and retrying solves a 429; retrying without waiting fills the invalid counter until the IP is blocked.

What are Discord's rate limits?#

There are three separate mechanisms, and mixing them up causes most of the confusion.

  • Per-route limit. Every endpoint has its own bucket. Sending messages in a channel has one limit, editing a channel has another. Values change, and Discord asks you not to hard-code them: read the headers.
  • Global limit. Up to 50 requests per second per bot. Requests without an Authorization header are limited per IP. Interaction endpoints (replies to slash commands) are not counted.
  • Invalid request limit. Responses with status 401, 403 and 429 count as invalid. Past 10,000 in 10 minutes, your IP is temporarily blocked. A 429 with X-RateLimit-Scope: shared (a limit on a shared resource) does not count.

There is also a Gateway limit, on the WebSocket connection: 120 events sent per connection every 60 seconds. Go over it and Discord disconnects the bot immediately.

How do you read a 429 response?#

The headers that matter:

Header What it tells you
Retry-After How long to wait before retrying
X-RateLimit-Remaining Requests left in the bucket
X-RateLimit-Reset-After Seconds until the bucket resets (may be fractional)
X-RateLimit-Bucket Bucket ID, useful for grouping routes
X-RateLimit-Global Present only when the global limit was hit
X-RateLimit-Scope user, global or shared, only on 429 responses

The JSON body has message, retry_after (seconds, fractional) and global. If global is true, the whole bot is paused, not just one route.

Why does my bot get 429s?#

From most to least common:

  1. Loops with no pause. Renaming 200 channels, giving a role to every member or deleting messages one by one, all at once.
  2. Constant message edits. A "live counter" or progress bar that edits every second hits the message route limit.
  3. Too many events triggering calls. A bot that replies to every message in a busy server, or answers each event with an API call.
  4. Blind retries. A call fails, the code retries immediately, and every attempt becomes another 429.
  5. Invalid token in a restart loop. The bot starts, gets a 401, crashes, restarts and repeats. Each cycle adds invalid requests. See the article on the bot token.
  6. Too many Gateway reconnections. The limit is 1,000 IDENTIFY calls per 24 hours. Hit it and Discord ends the bot's sessions and resets the token.

How do you fix a 429 error?#

In discord.js (v14)#

The library already waits out each rate limit. Your job is to watch for limits and cut volume. This snippet logs every limit hit and warns when invalid responses pile up:

js
import { Client, GatewayIntentBits, RESTEvents } from "discord.js";

const client = new Client({
  intents: [GatewayIntentBits.Guilds],
  rest: {
    // warn every 500 401/403/429 responses within the 10-minute window
    invalidRequestWarningInterval: 500,
  },
});

client.rest.on(RESTEvents.RateLimited, (info) => {
  console.warn(
    `429 on ${info.method} ${info.route}: waiting ${info.timeToReset} ms (global: ${info.global})`,
  );
});

client.rest.on(RESTEvents.InvalidRequestWarning, (info) => {
  console.warn("Too many invalid requests:", info);
});

await client.login(process.env.DISCORD_TOKEN);

The rejectOnRateLimit option (a function that receives the limit data and returns true, such as () => true, or a list of routes) makes the library throw a RateLimitError instead of waiting, useful for a command that would rather answer "try again shortly" than sit in a queue. Two defaults worth knowing: globalRequestsPerSecond is 50, Discord's global limit, and the invalidRequestWarningInterval warning is off (0).

In discord.py (2.x)#

discord.py also waits on its own, with no cap on the wait by default. You can set one with max_ratelimit_timeout (minimum 30 seconds, non-global limits only); beyond that, the library raises discord.RateLimited, with the wait in retry_after:

python
import asyncio
import os

import discord

intents = discord.Intents.default()
client = discord.Client(intents=intents, max_ratelimit_timeout=30.0)


async def rename_channels(channels: list[discord.TextChannel]) -> None:
    for channel in channels:
        try:
            await channel.edit(name=f"{channel.name}-archive")
        except discord.RateLimited as error:
            print(f"Rate limited, waiting {error.retry_after:.1f}s")
            await asyncio.sleep(error.retry_after)
            await channel.edit(name=f"{channel.name}-archive")  # retry once
        await asyncio.sleep(1)  # pause between batch items


client.run(os.environ["DISCORD_TOKEN"])

What applies to any library#

  • Respect Retry-After. Do not retry early. If you make HTTP calls by hand, read the header and wait.
  • Process batches with a delay. A queue with a pause between items costs seconds and avoids the problem.
  • Edit less. Refresh a dashboard message every 10 or 30 seconds, not every second.
  • Use bulk endpoints when they exist. Deleting many messages has its own endpoint (bulk delete) instead of one call per message.
  • Never retry 401 or 403. An invalid token or missing permission will not fix itself. Fix the cause.

What is the temporary Cloudflare ban?#

If your IP goes past 10,000 invalid requests in 10 minutes, Discord temporarily blocks that IP on the API. That works out to about 16 to 17 invalid requests per second, sustained, which happens with restart loops and retries without a pause, not with normal use.

If you suspect a block:

  1. Stop the bot. Every new attempt only feeds the counter.
  2. Find what produced the 401, 403 and 429 responses. Your logs show the route.
  3. Fix the loop and wait for the block to expire.
  4. Start the bot again with invalidRequestWarningInterval on, as in the example above.

How do you tell a rate limit from another problem?#

  • 429 with a small retry_after: a normal rate limit, the library handles it.
  • Repeated 429 with global: true: you went past 50 requests per second. Look for the loop.
  • Repeated 401: wrong or reset token.
  • Repeated 403: the bot lacks permission in that channel or role. Check before calling.
  • Every call fails at once: possible IP block.
  • Bot crashes and restarts non-stop: see why your bot goes offline.

How to do this on Square Cloud#

Rate limiting is a Discord API rule and applies on any host. What changes from one host to another is how early you spot the problem. On Square Cloud, logs stream in real time in the dashboard, the CLI and VS Code, so an invalid request warning shows up while the bot is running.

Automatic restart, optional and on by default when you deploy from the dashboard, brings the bot back when the process exits with an error. That keeps the bot up, but with an invalid token each restart can produce another 401, as in the loop described above. Handle the login error in your code: log a clear message, do not retry, and fix the token in the environment variables before restarting.

Discord bots run in isolated containers with discord.js, discord.py, JDA, Discord.Net or any other Gateway library, and the average latency to the Discord Gateway is around 30 ms. You deploy by ZIP upload, CLI, GitHub or the VS Code extension, and the token lives in the application's environment variables. Details are on the Discord bot hosting page. To get your first bot up, follow the guide on hosting a discord.js bot.

Bot hosting

Keep your bot online 24/7 with automatic restarts

Learn more

FAQs

How long does a Discord 429 last?

The response tells you, in the Retry-After header and the retry_after JSON field, in seconds. Wait that long and retry.

What is Discord's global rate limit?

Each bot can make up to 50 requests per second to the REST API. Every route also has its own limit, which you read from the X-RateLimit-* headers.

Can a bot get banned for 429 errors?

Yes. Responses with status 401, 403 and 429 count as invalid requests, and 10,000 of them in 10 minutes get your IP temporarily blocked. A 429 with X-RateLimit-Scope set to shared does not count.

Do discord.js and discord.py handle 429 on their own?

Yes, both wait out rate limits by default. Trouble starts when your code generates too much volume, retries failing calls blindly or restarts in a loop with an invalid token.

Next articleHow to host a discord.js botTo host a discord.js bot, create the app in the Developer Portal, keep the token in an environment variable and run the process on an always-on machine.