Rate limits
Limits protect the service and keep one noisy client from slowing the rest. They apply per API key, and rejected requests are never charged.
The limit
The default limit is 200 requests per 10 seconds per key. It is a token bucket: a key can burst up to 200 requests at once, and the allowance refills continuously across the window rather than resetting on the clock.
On top of that, an account can have at most 16 lookups running at once across all its keys. Cached results return immediately and do not count toward that cap.
Headers
Every authenticated response carries the state of the key's bucket:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per window for this key. |
X-RateLimit-Remaining | Requests left right now. |
X-RateLimit-Reset | Unix time in seconds when the bucket is full again. |
Retry-After | Seconds to wait before retrying. Sent only on a 429. |
Lookup responses also carry X-Request-Id, X-Credits-Charged and X-Credits-Remaining. A request with a bad key is rejected before the limiter, so a 401 has no rate limit headers.
When you hit the limit
The API answers HTTP 429 with the rate_limited error and a Retry-After header. Wait that many seconds, then retry. The error is retryable and nothing was charged.
{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key.",
"fix": "Wait the number of seconds in the Retry-After header, then retry. Spread requests out to stay under the X-RateLimit-Limit value.",
"docs_url": "/docs/errors#rate_limited",
"retryable": true,
"request_id": "req_..."
}
}
Retry with backoff
async function lookup(path, body, attempts = 5) {
for (let attempt = 1; ; attempt += 1) {
const response = await fetch(`https://YOUR_API_HOST${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LOOKVALID_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (response.status !== 429 || attempt === attempts) return response;
const wait = Number(response.headers.get("Retry-After") ?? 1);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
}
import os
import time
import requests
def lookup(path, body, attempts=5):
for attempt in range(1, attempts + 1):
response = requests.post(
f"https://YOUR_API_HOST{path}",
headers={"Authorization": f"Bearer {os.environ['LOOKVALID_API_KEY']}"},
json=body,
timeout=30,
)
if response.status_code != 429 or attempt == attempts:
return response
time.sleep(float(response.headers.get("Retry-After", 1)))
Staying under the limit
- Spread bursts out instead of firing every request at once.
- Read
X-RateLimit-Remainingand slow down before it reaches zero. - Do not repeat lookups you already have: cached repeats are free in credits, but they still count toward the request limit.
- Higher limits are not self-serve yet. If you need more, email hello@lookvalid.com with your use case.