Errors and plan-gated answers
The error body shapes and HTTP status codes the VDB API returns, which ones to retry, and how answers above your plan come back as 200 with nulled fields or a planLocked marker.
Error bodies
Most errors have this shape:
{ "success": false, "error": "Invalid API key" }
Some add details with the underlying cause. Input errors name the parameter at fault:
{ "error": "cel expression is longer than 4096 bytes", "field": "cel" }
A path that matches no route returns 404 with a different body, listing a few starting points:
{
"success": false,
"message": "VDB API - Invalid endpoint",
"documentation": "/v1/spec/ui",
"openapi": "/v1/spec",
"availableEndpoints": ["GET /v1/spec - OpenAPI specification", "..."]
}
Status codes
| Status | Meaning | Retry? |
|---|---|---|
| 200 | Success. May still be plan-gated | |
| 400 | A parameter is missing or malformed, for example Invalid month. Use YYYY-MM |
No |
| 401 | No credential, or it is invalid or expired. See Authentication | No; fix the credential or fetch a new JWT |
| 403 | The organisation is inactive | No |
| 404 | The advisory, package or record is not in the database, or the path matches no route. Also Organization not found when the key’s organisation has been removed |
No |
| 409 | An API token whose account belongs to several organisations | No; contact support |
| 413 | An input field over 512 characters, a CEL expression over 4 KiB, or more than 100 advisory ids | No |
| 422 | An input that cannot be resolved: a SWID tag id without the tag, an unsupported TEI type | No |
| 429 | Daily quota used. See Rate limits | After the window moves |
| 499 | The client closed the connection before the answer; recorded server-side, never seen by the client | |
| 500 | Server error | Yes, with backoff |
| 502, 504 | Gateway error or timeout | Yes, with backoff |
| 503 | authentication backend temporarily unavailable (with Retry-After: 2), or maintenance mode |
Yes, after Retry-After |
The CLI retries 429, 502, 503, 504 and connection timeouts or resets, at most twice, waiting Retry-After when the API sends it.
Maintenance mode
During maintenance, write requests (anything other than GET, HEAD and OPTIONS) are refused and reads keep working:
HTTP/2 503
Retry-After: 120
X-Maintenance-Mode: true
X-Vulnetix-Maintenance: true
{ "success": false, "error": "maintenance_mode", "message": "...", "retry_after_seconds": 120 }
Most of the public VDB API is reads, so this mainly affects the POST routes (/scan/*, /packages/refresh, bulk and search bodies) and the CLI’s upload routes.
Plan-gated answers
The VDB API does not answer 402 or 403 for a feature above your plan. It answers 200 and tells you what was withheld, in one of two ways.
Fields withheld. On about 26 routes (exploit detail, fixes, KEV, IOCs, sightings, detection rules, virtual patches, remediation plans and others), fields above your plan are set to null and two markers are added:
{
"...": "the normal answer, with gated fields set to null",
"_pro_data": ["$.exploits"],
"_entitlements": {
"plan": "community",
"gated": [
{ "feature": "full-enrichment", "requiredPlan": "pro", "paths": ["$.exploits"] }
]
}
}
That is what a Community key gets from GET /v2/exploits/{identifier}: the counts and summary, with the exploit records themselves nulled. full-enrichment is the feature name for any rule that does not name its own.
_pro_data lists only the paths that need Pro; _entitlements.gated lists every gated feature, including those that need Teams or Enterprise. Both are absent when nothing was withheld.
Whole answer withheld. Routes that are a paid feature end to end (Safe Harbour safety matrix and refresh, exploit and raw advisory archive downloads, some defence and YARA content) return a marker body instead:
{
"planLocked": true,
"feature": "safe-harbour",
"requiredPlan": "pro",
"upgrade": true,
"_entitlements": { "plan": "community", "gated": [{ "feature": "safe-harbour", "requiredPlan": "pro" }] }
}
Check for planLocked before you read the rest of the body. The packages in GET /v2/packages/free-demo get Safe Harbour data on every plan.
Plan-dependent answers are always sent Cache-Control: private, no-store, because the same URL answers different plans differently.