Failures use the same envelope as successes. status becomes "error", data becomes null,
and error carries one sentence written for a human:
{
"status": "error",
"error": "The 'email' parameter is required.",
"data": null
}Read the HTTP status to decide what kind of problem it is, and error to decide what to
change. The sentence names the parameter; it is not a generic code you have to look up.
Statuses
| Status | Meaning | Retry? |
|---|---|---|
200 | Success | — |
400 | The request is wrong — bad parameter, missing field, or an upload over the plan's size limit | Not until it changes |
401 | Key is well-formed but not recognised | No |
403 | Key is valid but not allowed to make this call | No |
404 | No such endpoint on this door | No |
429 | Rate limited or out of credits | Sometimes — see below |
500 | Something failed on our side | Yes, with backoff |
503 | Upstream source is unavailable | Yes, with backoff |
Two of these do not mean what their names suggest, and both are worth reading carefully.
An HTML 403 means the request never arrived
Before the status table applies, the request has to reach the API at all. Two things stop it at
the edge, and both look identical: an HTML 403 with no JSON body and no CORS headers.
- No
x-api-keyheader at all. - A key that is not in the expected format. Keys are UUIDs. A truncated key, a placeholder
like
YOUR_API_KEY, or an env var that resolved to empty is rejected on shape alone.
# Malformed key -> Cloudflare's own HTML error page, not the API's JSON
curl -i 'https://api.apiverve.com/v1/dadjokes' -H 'x-api-key: av-test-123'
HTTP/1.1 403 Forbidden
Content-Type: text/html; charset=UTF-8A well-formed but wrong key behaves completely differently — a real 401 with a JSON body:
{ "status": "error", "error": "unauthorized", "data": null }This distinction is worth internalising because of how it surfaces in a browser: the HTML 403
carries no CORS headers, so fetch cannot read it and reports TypeError: Failed to fetch with
no status at all. An undefined environment variable is the usual cause. See CORS.
403 is not an authentication failure
401 is the only status that means "we do not accept this key". If you get one, the credential
is wrong — check it before anything else.
403 means the opposite: the key is real and accepted, but it is not permitted to make this
particular call. There are three causes, and the error sentence tells you which:
error says | Cause | Fix |
|---|---|---|
Access to <api> is blocked for this API key | Key scoping blocks this endpoint | Adjust the key's restrictions |
IP address not allowed for this API key | The caller's IP is not on the key's allow-list | Add the IP, or use a different key |
API key has been revoked. | The key was rotated or deleted | Use the current key |
Treating a 403 as "sign in again" sends someone to fix something that is not broken. See
key scoping.
429 is two different problems
A 429 is either slow down or the month is spent, and they need opposite responses. The
discriminator is the credit header, not the message:
if (res.status === 429) {
if (Number(res.headers.get('x-api-remaining-credits')) === 0) {
// Out of credits. Retrying cannot help.
} else {
// Rate limited. Wait retry-after and repeat.
}
}Full treatment, including backoff that does not make it worse, is on rate limits.
404 means "not on this door"
A 404 is returned both for an endpoint that does not exist and for one that exists but is not
part of this product's catalog. That is deliberate — the response does not reveal that an
endpoint you cannot call exists elsewhere.
If a call worked yesterday and 404s today, check you are calling the host that matches your account rather than assuming the endpoint was removed.
Validation errors
A 400 names what to change:
{
"status": "error",
"error": "The 'dob' parameter must be a valid date in YYYY-MM-DD format.",
"data": null
}Undeclared parameters are a special case worth knowing: a parameter the endpoint does not declare is dropped silently rather than rejected. The call succeeds and quietly ignores your input. If a parameter appears to have no effect, check its spelling against the reference page before assuming it is broken.
Handling errors in code
One handler covers every endpoint, because the envelope never changes:
async function call(endpoint, params, key) {
const url = new URL(`https://api.apiverve.com/v1/${endpoint}`);
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
const res = await fetch(url, { headers: { 'x-api-key': key } });
const body = await res.json().catch(() => null);
if (res.ok && body?.status === 'ok') return body.data;
const message = body?.error || `HTTP ${res.status}`;
const retryable =
res.status >= 500 ||
(res.status === 429 && Number(res.headers.get('x-api-remaining-credits')) !== 0);
throw Object.assign(new Error(message), { status: res.status, retryable });
}Server errors
500 and 503 are ours, not yours. They are safe to retry, with backoff, and they are rare
enough that a persistent one is worth reporting rather than working around.
Every response carries two identifiers that let support find your exact call instead of guessing. Quote either one:
| Header | Identifies |
|---|---|
cf-ray | The edge request. Present on every response, including the ones the edge answers itself. |
x-cloud-trace-context | The origin request, once it reached the API. Absent when the edge answered. |
curl -sS -D - -o /dev/null 'https://api.apiverve.com/v1/weather?city=London' \
-H 'x-api-key: YOUR_API_KEY' | grep -iE 'cf-ray|cloud-trace'cf-ray and x-cloud-trace-context are not in the CORS exposed set, so headers.get() returns
null for both in a browser. Capture them server-side, or read them from the network panel. See
CORS.
What is worth retrying
- Retry with backoff:
500,503, and a429that still has credits left. - Never retry unchanged:
400,401,403,404, and a429with zero credits. Nothing about the next attempt will be different, and repeated identical failures get their own throttle.
Next
Response shape and the premium-field rule are in response format. Ceilings, headers and backoff are in rate limits.
The client-side shape that acts on all of it — timeouts, retries, caching — is in making requests. Per-item failures inside a bulk call behave slightly differently; see batch requests.
If a failure here is not the one you expected, the FAQ covers the common surprises.