Send a single POST to the OSV querybatch endpoint with a list of queries, each naming a package and version or a versioned purl. The response returns only vulnerability IDs and modified timestamps, in the same order as your input. Fetch full records separately for the IDs you need.
What querybatch is
OSV is an open vulnerability database with a public API. The single query endpoint answers one question at a time: is this package at this version affected? That works for a demo and falls apart for an SBOM with 800 components, because you would make 800 round trips.
The querybatch endpoint takes many queries in one request body. Each item can identify a package by ecosystem and name plus a version, by a versioned purl, or by a git commit hash. The documentation says the response order is guaranteed to match the input, so result number 3 belongs to query number 3. You never need to match by name.
A request you can copy
cat << EOF2 | curl -s -d @- "https://api.osv.dev/v1/querybatch"
{
"queries": [
{ "package": { "purl": "pkg:pypi/mlflow@0.4.0" } },
{ "package": { "ecosystem": "PyPI", "name": "jinja2" }, "version": "2.4.1" }
]
}
EOF2
One rule trips people up. Use either a version field or a versioned purl in each item, not both. An item with both returns a 400 error, according to the OSV documentation.
What comes back
Each result holds a list of vulnerabilities, and each entry has only an id and a modified timestamp. That is deliberate. It keeps the response small. To read severity, affected ranges or references, call the get endpoint for a given ID, or load the record from the bulk data dumps.
A useful pattern is to cache full records by ID and modified time. On the next run, call querybatch, compare the modified value to your cache, and fetch only records that changed or are new.
Pagination is per query
A large batch can come back partly paged. The documentation says pagination happens when one query returns more than 1,000 vulnerabilities, or when the whole set returns more than 3,000 in total. It also notes these numbers can vary a little and the page size may change.
Each result that has more data carries its own next_page_token. Your follow-up request should include a page_token only for the queries that returned one. Results without a token are complete. If you ignore this, you will silently drop findings for packages with long advisory histories.
| Situation | What to do |
|---|---|
| Result has no next_page_token | That query is complete |
| Result has a next_page_token | Repeat that query with page_token set |
| Other queries finished | Leave them out of the follow up request |
Try it yourself
Click through it at your own pace, or open it full screen. Look at how a flat component list becomes the input for a vulnerability lookup like the one above.
Where this fits
Querybatch answers "what is known about these exact versions today". It does not know whether your code calls the vulnerable function. Treat the output as candidates for triage, not as a verdict. Safeguard offers SBOM and dependency scanning with reachability analysis, and the free scan is an easy way to see the result on a real project.
For related reading, see how OSV version ranges work and how to match SBOM components to vulnerabilities.
Quick check
Does querybatch return severity and fixed versions?
No. It returns only IDs and modified timestamps. Fetch each record by ID to read severity, ranges and references.
Can I put both a version and a versioned purl in one query?
No. Use one or the other. An item with both returns a 400 Bad Request.
Sources
Last reviewed: 19 October 2026.