Skip to content

Errors and rate limits

Error responses are JSON with an error message and, in most cases, a requestId:

{
"error": "An app named 'MyApp' already exists.",
"requestId": "f3b2c1d0-4a5e-6789-abcd-ef0123456789"
}

Validation failures add an errors array with one entry per problem. The request id also travels as the X-Request-Id response header on every response, success or error. Quote it when reporting an issue. The release-upload 429 is plain text, not this envelope; see Rate limits.

Three layers apply:

  • A global limit of 100 requests per minute.
  • A per-tenant limit on management API requests, set by your plan: 60, 300, or 1000 per minute. See Plans, quotas, and MAD.
  • Endpoint-specific limits on sensitive routes: registration, login, password reset, and resending a verification email are limited per client address, and release uploads to 100 per 15 minutes.

Everything counted per client address uses your exact address on IPv4. On IPv6 it uses your /64 prefix, so moving between addresses inside the block you were assigned lands in the same counter.

A rate-limited request gets HTTP 429. The body and headers depend on which layer you hit.

On registration, login, password reset, and resending a verification email, the JSON body includes retryAfterSeconds:

{
"error": "Too many login attempts. Please try again later.",
"retryAfterSeconds": 900
}

Wait that many seconds before retrying. The error string is different on each of those routes.

Release uploads are limited to 100 per 15 minutes per client address. That 429 is the plain text Too many requests, please try again later., with headers Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. There is no retryAfterSeconds field.

The per-tenant management limit returns:

{
"error": "Rate limit exceeded",
"message": "Tenant rate limit of 60 requests per minute exceeded. Retry after 60 seconds."
}

and sets Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Wait the number of seconds in Retry-After. There is no retryAfterSeconds field.

The global limit of 100 requests per minute returns { "error": "Too many requests" } with RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, RateLimit-Policy, and Retry-After.

In CI, a 429 on release upload usually means a runaway retry loop rather than real traffic. Wait as long as Retry-After says.

Repeated failed logins ask for a challenge

Section titled “Repeated failed logins ask for a challenge”

Failures against a single account escalate on their own track, separately from the address limits above. After a small number of recent failures, the next attempt has to carry a Cloudflare Turnstile token, and the API answers 403 until it gets one:

{
"error": "Please complete the verification challenge to continue.",
"code": "challenge_required"
}

In the dashboard you do not have to do anything special. The challenge appears under the password field, usually clears on its own, and then you sign in again.

If you call POST /v1/auth/login yourself, branch on the code field rather than the message text, and send the token back as challengeToken in the body. A token works once, so each attempt needs a fresh one.

None of this locks the account. The challenge asks the caller to prove they are a person, so someone else failing logins against your email address cannot keep you out of it.