What error code does the API return when rate-limited?
When a Valid Email Checker API request crosses a rate-limit threshold — either the per-minute window or the daily envelope — the server responds with HTTP 429 Too Many Requests. The response body and headers carry everything your retry logic needs to back off cleanly, without guessing the wait time or polling blindly.
Per-minute limit response
Triggered when the rolling 60-second counter exceeds the endpoint cap (60 for verify-single/verify-bulk, 120 for get-results):
{
"error": "Rate limit exceeded",
"limit": 60,
"window": "1 minute",
"current": 61,
"retry_after_seconds": 45
}Headers attached to the response:
Retry-After: 45
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716156000| Header | Meaning |
|---|---|
Retry-After | Number of seconds to wait before retrying. Standard HTTP header — many client libraries respect it automatically. |
X-RateLimit-Limit | The cap for this endpoint (60 or 120). |
X-RateLimit-Remaining | 0 on a 429 response; otherwise the number of calls you have left in the current minute. |
X-RateLimit-Reset | Unix timestamp (seconds) when the rolling window clears completely. |
Daily limit response
Triggered when the account-wide daily counter on the endpoint exceeds 10,000 requests. The body is slightly different because there is no per-minute retry — you wait until midnight UTC:
{
"error": "Daily limit exceeded",
"limit": 10000,
"current": 10001,
"resets_at": "midnight UTC"
}retry_after_seconds field — the wait is until 00:00 UTC. Your retry loop should detect the resets_at field and pause rather than spinning. Hitting daily 10K consistently is usually a sign you should switch from synchronous single-verify to bulk verification, which does not count per-email against the limit.How to back off cleanly
async function verifyWithBackoff(email, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(
'https://app.validemailchecker.com/api/verify-single',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VEC_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
}
);
if (response.status === 429) {
const data = await response.json();
if (data.error === 'Daily limit exceeded') {
throw new Error('Daily quota reached — pause until midnight UTC');
}
const wait = data.retry_after_seconds || 60;
console.log(`Rate limited. Waiting ${wait}s before retry…`);
await new Promise(r => setTimeout(r, wait * 1000));
continue;
}
return response.json();
}
throw new Error('Max retries exceeded after rate-limit retries');
}Three rules for client-side retry handling:
- Respect
Retry-Afterexactly. Faster retries get you blocked harder; the abuse-protection counter notices repeated 429s. - Differentiate per-minute from daily based on the
errorfield, not the status code. Both are 429. - Cap your retries. Three attempts with proper backoff is plenty. Endless loops eventually hit the daily limit and stay there.
Watching the rate-limit headers on every response
The X-RateLimit-Limit, X-RateLimit-Remaining, and X-DailyLimit-Remaining headers come back on every response, not just 429s. Read them on successful responses too. When X-RateLimit-Remaining drops below ten, slow your traffic before you hit the wall. See the rate limit article for the full header set and the rolling-window mechanics.
Other error codes for context
- 402 when you run out of credits — see out-of-credits error.
- 403 when the key is revoked or suspended — see revoked-key error.
- 401 when the Authorization header is missing or the key is invalid.
Next steps
Related questions
Still stuck? Email support
