NVD API Compatibility

Errors and rate limits

Status codes, error body format, and the request budget

Error body format

Errors return JSON, with the status repeated in the body:

{
  "message": "parameter \"keywordSearch\" is not supported in v1; deferred to v2",
  "status": 400
}
This is the one deliberate departure from NVD's wire behaviour. NVD returns plain-text bodies on 4xx and 5xx. If your client logs or parses error bodies, that code needs adjusting; if it only reads status codes, nothing changes.

Status codes

StatusMeaningWhat to do
400Invalid parameter, malformed date range, malformed cursor, or a v1-deferred parameterRead message — it distinguishes a deferred parameter from a typo. Do not retry unchanged
401Missing or invalid credentialsCheck the apiKey or Authorization header
403Authenticated, but not entitled to this resourceContact VulnCheck about your entitlement
404Unknown pathCheck the endpoint spelling against the mapping table
405Method not allowedThese endpoints are GET only
429Rate limit exceededBack off for the number of seconds in Retry-After
503Service temporarily unavailableRetry with exponential backoff

Rate limits

NVD has a very restrictive rate limit so we aligned ours to the much more expansive 1,000 requests per minute per token e.g., the same community API rate limit that applies to the rest of the VulnCheck API. There is no separate, stricter budget for these endpoints.

That is twenty times NVD's own cap of 50 requests per 30 seconds, so a client that was pacing itself against NVD's limit will stay comfortably inside this one without changing anything.

NVD's unauthenticated tier of 5 requests per 30 seconds has no equivalent here: every request needs valid credentials, so there is no anonymous bucket to fall into.

A 429 carries a Retry-After header with the number of seconds to wait:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{ "message": "rate limit exceeded", "status": 429 }
X-RateLimit-* headers are not emitted. NVD does not document any either, so a client written against NVD will not be looking for them. Use Retry-After on a 429.

Staying inside the budget

  • Request full pages. resultsPerPage defaults to the endpoint maximum, so one large request costs the same against the budget as one small one.
  • For a whole corpus, use /v3/backup/{index} rather than paging (one download instead of hundreds of requests).
  • Use lastModStartDate and lastModEndDate for incremental syncs instead of re-walking everything.