Use the DHI API
The DHI API is a GraphQL API for querying Docker Hardened Images data programmatically, for use cases like building automation or dashboards on top of DHI data.
Endpoint
Send requests as POST requests to:
https://api.dso.docker.com/v1/graphqlRequest format
The API accepts standard GraphQL requests: a JSON body with a query and,
optionally, variables.
$ curl https://api.dso.docker.com/v1/graphql \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"query": "...", "variables": { ... }}'
Every query takes a Context argument (conventionally named ctx in the
variables object) alongside its query-specific arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
ctx | Context | Yes | Scopes the request to an organization. |
ctx.organization | String | Yes | The Docker organization the token belongs to. |
Authentication
An organization access token (OAT) or personal access token (PAT) isn't used directly as the bearer token. Exchange it first for an access token:
$ curl -X POST https://hub.docker.com/v2/auth/token \
-H "Content-Type: application/json" \
-d '{"identifier": "<identifier>", "secret": "<token>"}'
For identifier, use your Docker Hub username with a PAT, or the
organization name with an OAT. The response contains the access token:
{ "access_token": "..." }Pass that access_token as Authorization: Bearer <access_token>. Also set
ctx.organization in variables to the organization the token belongs to
(see Request format).
Response format
Responses follow the standard GraphQL envelope:
| Key | Description |
|---|---|
data | The requested fields. A field is null if it couldn't be resolved, for example due to an authorization failure. |
errors | Present when a field failed to resolve. Includes a message and a path identifying which field failed. |
extensions | Metadata such as a correlation_id, useful when reporting an issue. |
For example, an unauthenticated request, or a request for data your token
can't access, returns a null result under data alongside an authorization
error in errors, rather than an HTTP-level failure:
{
"errors": [
{
"message": "You are not allowed to read data for this team",
"path": ["someQuery"],
"extensions": { "code": "DOWNSTREAM_SERVICE_ERROR", "status": 403 }
}
],
"data": { "someQuery": null },
"extensions": { "correlation_id": "..." }
}Queries
imagePackagesForImageCoords
Fetches every package in an image, every CVE reported against it, and whether Docker suppresses that CVE, by digest. See Query VEX for a Docker Hardened Image for a guided example.
| Argument | Type | Required | Description |
|---|---|---|---|
digest | String | Yes | The image's platform manifest digest, not the multi-arch index digest. |
hostName | String | Yes | hub.docker.com or docker.io. |
repoName | String | Yes | Repository name, with or without the namespace prefix. |
includeExcepted | Boolean | No | Include suppressed CVEs in the response alongside the reason for suppression. Without it, the response only shows the netted list, with no visibility into what was suppressed. |
includeNodsa | Boolean | No | Include Debian NODSA exclusions, which make up most suppressions on a Debian-based image. |
includePublic | Boolean | No | Also include public images when ctx.organization scopes the request to an organization. Not needed for a typical lookup. |
Keep the requested response fields limited to what you plan to render.
Fields such as locations, description, vulnerableRange, and epss
increase response size substantially and aren't needed for a CVE-count or
suppressed-CVE view.
Response fields
vulnerabilityExceptions only contains records that actually suppress a
CVE, so it always lines up with isExcepted: an empty array means the CVE
is live. Use isExcepted as your filter for "is this CVE suppressed."
| Field | Meaning |
|---|---|
isExcepted | Docker suppresses this CVE for this image. Use this to filter. |
sourceType | EXTERNAL (Debian NODSA), MANUAL_EXCEPTION (Docker analyst exception), or VEX_STATEMENT (an ingested VEX document). |
type | FALSE_POSITIVE and ACCEPTED_RISK suppress the CVE. UNDER_INVESTIGATION and AFFECTED don't. |
justification | The OpenVEX justification value. Always null for NODSA exclusions. |
additionalDetails | Free-text rationale for the suppression. |
isDhiStatement | Whether the statement is inherited from the DHI base image. |
id | Stable identifier for the statement. |
Mapping to OpenVEX
If your pipeline consumes OpenVEX documents (for example, Trivy's --vex
flag), each suppressed record maps as follows:
| OpenVEX field | Source |
|---|---|
vulnerability.name | sourceId |
products[].@id | The parent package's purl |
status | not_affected (from type: FALSE_POSITIVE) |
justification | justification, defaulting to vulnerable_code_cannot_be_controlled_by_adversary for NODSA exclusions |
status_notes | additionalDetails |
@id | id |