Errors
The API returns conventional HTTP status codes. The body is always JSON in
the shape { "error": "<machine_readable_code>" }. Human descriptions are
intentionally not returned, as they can leak which keys exist, which IPs are
allowlisted, and which credentials are valid.
Status code reference
| Code | error value | Meaning |
|---|---|---|
| 200 | n/a | Success. |
| 400 | invalid_cve_id | The path didn't match CVE-YYYY-N{4,7}. |
| 401 | unauthorized | Credential failure: missing, malformed, expired, or revoked key, or IP not allowed. Intentionally uniform. |
| 402 | insufficient_credits | Organisation balance below 1 credit. |
| 404 | not_found | Route doesn't exist, or the CPE vendor, product, or version isn't tracked. |
| 405 | method_not_allowed | Only GET (and OPTIONS) are supported. |
| 500 | internal | Unexpected server error. Email support with the x-request-id. |
Why all credential errors look the same
Bad token, revoked key, expired key, IP not on the allowlist: all return:
{ "error": "unauthorized" }…with no extra detail. This stops an attacker holding a leaked key from enumerating which credentials and IP rules you have in place.
If you're integrating and can't tell why you're getting 401s, the dashboard's Audit log shows the real reason for every authentication failure on your keys.
Always provided
Every error response also includes:
x-request-idheader (quote this in support tickets).x-credits-remainingonce a charge has been attempted.
Retry strategy
| Code | Retry? |
|---|---|
402 insufficient_credits | No: top up credits first. |
401 unauthorized | No: fix the credential or IP allowlist, then retry once. |
5xx internal | Yes: exponential back-off, max 3 attempts. |
Retrying a 401 or 402 in a tight loop will never succeed. Fix the
credential or top up credits before retrying.