Comparing SBOM Versions with the API
Compare two versions of an asset with the Manifest API: summary totals, component, vulnerability, and license changes, and an XLSX export.
The version comparison endpoints return what changed between two versions of the same asset. They are the API equivalent of the Version Comparison drawer on an asset's Versions tab, described in Comparing SBOM Versions. That page also explains what counts as an added or removed component, vulnerability, or license.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /v1/asset/compare/{baselineAssetId}/{comparisonAssetId} | Summary totals for both versions |
| GET | /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/components | Components added or removed |
| GET | /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/vulnerabilities | CVEs caused by component changes |
| GET | /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/licenses | License changes on the changed components |
| GET | /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/export | Every change, as an XLSX file |
All paths are relative to https://api.manifestcyber.com.
Before you start
Version comparison must be enabled for your organization
Version comparison is enabled per organization. Until it is enabled, every endpoint on this page returns HTTP 403 with the plain-text body Forbidden: Feature Not Enabled. To enable it, contact [email protected].
Use a user API token
These endpoints only accept user API tokens. Legacy organization tokens are rejected with HTTP 403 and the message Organization tokens are not allowed for this path. To create a user token, go to Settings → Account → API Tokens (see Managing API Tokens).
Send the token in the Authorization header:
Authorization: Bearer <API_TOKEN>Give the token the right scopes
Each endpoint checks its own set of permissions:
| Endpoint | Required permissions |
|---|---|
| Summary | read:asset |
| Components | read:asset, read:component |
| Vulnerabilities | read:asset, read:vulnerability |
| Licenses | read:asset, read:license, read:component |
| Export | read:asset, read:component, read:vulnerability, read:license |
The view-all scope, which every token has, grants all of these permissions. See the API Token Scopes Reference.
Find the two version IDs
Each version of an asset has its own asset ID. Pass the older version as baselineAssetId and the newer one as comparisonAssetId. Added means present in the comparison version only, and Removed means present in the baseline only. Swapping the IDs swaps added and removed.
To list an asset's versions and their IDs, call the versions endpoint with the ID of any version of the asset:
curl "https://api.manifestcyber.com/v1/asset/<ASSET_ID>/versions" \
-H "Authorization: Bearer <API_TOKEN>"Versions are returned newest first. Each item's _id is the asset ID of that version, and version is its version string.
Both IDs must be different versions of the same asset in your organization. See Errors for what is returned otherwise.
Get the summary
GET /v1/asset/compare/{baselineAssetId}/{comparisonAssetId} returns the stored totals of both versions, as shown on the summary cards in the app.
curl "https://api.manifestcyber.com/v1/asset/compare/<BASELINE_ASSET_ID>/<COMPARISON_ASSET_ID>" \
-H "Authorization: Bearer <API_TOKEN>"{
"success": true,
"data": {
"baseline": {
"organizationAssetId": "<BASELINE_ASSET_ID>",
"name": "my-service",
"version": "2.3.0",
"dateCreated": "2026-09-14T10:21:05.000Z",
"isActive": true,
"riskScore": "medium",
"countComponents": 412,
"countVulnerabilities": {
"total": 37,
"critical": 2,
"high": 9,
"medium": 18,
"low": 8,
"info": 0,
"inKEV": 1
},
"countLicenseIssues": 3
},
"comparison": {
"organizationAssetId": "<COMPARISON_ASSET_ID>",
"name": "my-service",
"version": "2.4.0",
"dateCreated": "2026-10-20T08:02:44.000Z",
"isActive": true,
"riskScore": "low",
"countComponents": 419,
"countVulnerabilities": {
"total": 21,
"critical": 0,
"high": 4,
"medium": 12,
"low": 5,
"info": 0,
"inKEV": 0
},
"countLicenseIssues": 3
}
}
}data is an object, not an array. baseline and comparison have the same fields:
| Field | Description |
|---|---|
organizationAssetId | The asset ID of this version. |
name | The asset name. |
version | The version string, or null. |
dateCreated | When this version was created, as an ISO 8601 timestamp. |
isActive | false if the version was marked inactive. |
riskScore | low, medium, high, or null when the risk has not been calculated. |
countComponents | The number of components in this version. |
countVulnerabilities | The vulnerability totals stored for this version: total, one count per severity, and inKEV. |
countLicenseIssues | The number of license issues stored for this version. |
The vulnerability totals are each version's own counts. The vulnerabilities endpoint lists only the CVEs caused by component changes, so the difference between the two totals is usually not the number of rows it returns. A CVE that affects both versions is counted in both totals but is not a change.
List the changes
The components, vulnerabilities, and licenses endpoints share the same query parameters and response shape. Each request computes the comparison and returns one page of rows.
Query parameters
| Parameter | Description |
|---|---|
page | Page number, starting at 1. Defaults to 1. A page past the last one returns an empty data array. |
limit | Rows per page, from 1 to 100. Defaults to 50. |
search | Comma-separated search terms, up to 10 terms of up to 100 characters each. A row matches when any term appears in any of the endpoint's searched fields. Not case-sensitive. |
change | Comma-separated list of added and removed. Omit it to return both. |
sortBy | The field to sort by. The allowed values depend on the endpoint and are listed below. |
sortOrder | asc (default) or desc. |
severity | Vulnerabilities only. Comma-separated list of CRITICAL, HIGH, MEDIUM, LOW, and NONE, in uppercase. NONE also returns CVEs that have no severity. |
Unknown parameters are ignored. An invalid value returns HTTP 400 (see Errors).
Filters combine: a row is returned only when it matches change, severity, and search.
Sorting
Without sortBy, rows are returned in the default order of each endpoint, listed below. With sortBy, rows are sorted by that field first and ties fall back to the default order. In both directions:
- Missing (
null) values sort last. - Text is compared with numbers in order, so version
1.10sorts after1.9.
The order is stable, so paging through the same comparison returns each row once as long as the underlying data does not change between requests.
Response
{
"success": true,
"data": [],
"queryInfo": {
"page": 1,
"limit": 50,
"totalReturn": 50,
"totalCount": 132
}
}| Field | Description |
|---|---|
data | The rows on this page. The fields depend on the endpoint. |
queryInfo.page | The page returned. |
queryInfo.limit | The page size used. |
queryInfo.totalReturn | The number of rows on this page. |
queryInfo.totalCount | The number of rows that match the filters, across all pages. |
Component reference
Rows on all three endpoints describe components with the same object:
| Field | Description |
|---|---|
organizationComponentId | The component ID. |
name | The component name. |
version | The component version, or null. |
packageUrl | The component's package URL (pURL), or null. |
relationship | direct, transitive, or unknown. |
Components
GET /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/components lists each component version that is present in one version of the asset and not the other. An upgraded package appears twice: its old version as removed and its new version as added.
curl -G "https://api.manifestcyber.com/v1/asset/compare/<BASELINE_ASSET_ID>/<COMPARISON_ASSET_ID>/components" \
-H "Authorization: Bearer <API_TOKEN>" \
--data-urlencode "change=added" \
--data-urlencode "sortBy=name" \
--data-urlencode "limit=100"Each row is a component reference with a change field:
{
"change": "added",
"organizationComponentId": "<COMPONENT_ID>",
"name": "lodash",
"version": "4.17.21",
"packageUrl": "pkg:npm/[email protected]",
"relationship": "transitive"
}| Option | Values |
|---|---|
sortBy | change, name, version, packageUrl, relationship |
| Searched fields | Component name, version, and package URL |
| Default order | change (added first), then name, version, and package URL |
Vulnerabilities
GET /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/vulnerabilities lists the CVEs caused by component changes: CVEs that affect an added or removed component and are found in only one of the two versions. A CVE that is still present in the other version through any component is not returned. A CVE that affects several changed components returns one row per component.
curl -G "https://api.manifestcyber.com/v1/asset/compare/<BASELINE_ASSET_ID>/<COMPARISON_ASSET_ID>/vulnerabilities" \
-H "Authorization: Bearer <API_TOKEN>" \
--data-urlencode "change=added" \
--data-urlencode "severity=CRITICAL,HIGH" \
--data-urlencode "sortBy=cvssScore" \
--data-urlencode "sortOrder=desc"{
"change": "added",
"cveId": "CVE-2026-12345",
"vulnerabilityId": "<VULNERABILITY_ID>",
"severity": "HIGH",
"cvssScore": 8.1,
"epssScore": 0.0213,
"inKEV": false,
"recommendedAction": "mitigate",
"affectedComponent": {
"organizationComponentId": "<COMPONENT_ID>",
"name": "example-parser",
"version": "1.4.0",
"packageUrl": "pkg:npm/[email protected]",
"relationship": "direct"
}
}| Field | Description |
|---|---|
change | added or removed. |
cveId | The CVE ID. |
vulnerabilityId | The Manifest vulnerability ID, or null when Manifest has no details for this CVE. In that case severity, the scores, and recommendedAction are null, and inKEV is false. |
severity | CRITICAL, HIGH, MEDIUM, LOW, NONE, or null. |
cvssScore | The CVSS score, or null. |
epssScore | The EPSS score as a fraction from 0 to 1, or null. |
inKEV | true if the CVE is on the CISA Known Exploited Vulnerabilities (KEV) list. |
recommendedAction | mitigate, monitor, accept, or null. |
affectedComponent | The added or removed component that carries the CVE, as a component reference. |
| Option | Values |
|---|---|
sortBy | change, cveId, severity, cvssScore, epssScore, inKEV, affectedComponent (component name), relationship |
| Searched fields | CVE ID, and the affected component's name, version, and package URL |
| Default order | change (added first), then severity (most severe first), CVE ID, component name, and component version |
sortBy=severity sorts from NONE to CRITICAL in ascending order, so use sortOrder=desc to list the most severe first. sortBy=inKEV sorts false before true in ascending order.
Older versions are not re-scanned. Each version's vulnerabilities come from the last time that version was scanned. A recently published CVE can be returned as
addedon an upgraded package even if the old version of the package was also affected, because the older asset version was scanned before the CVE was published.
Licenses
GET /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/licenses lists license changes on the components that changed: a license that an added or removed component carries in one version and that the same package does not carry in the other version. A package that is upgraded and keeps the same license returns no row.
curl -G "https://api.manifestcyber.com/v1/asset/compare/<BASELINE_ASSET_ID>/<COMPARISON_ASSET_ID>/licenses" \
-H "Authorization: Bearer <API_TOKEN>" \
--data-urlencode "sortBy=risk" \
--data-urlencode "sortOrder=desc"{
"change": "added",
"component": {
"organizationComponentId": "<COMPONENT_ID>",
"name": "example-crypto",
"version": "3.0.0",
"packageUrl": "pkg:npm/[email protected]",
"relationship": "transitive"
},
"license": {
"licenseId": "<LICENSE_ID>",
"name": "GPL-3.0-only"
},
"risk": "denied"
}| Field | Description |
|---|---|
change | added or removed. |
component | The component that carries the license, as a component reference. |
license.licenseId | The Manifest license ID, or null when the license could not be matched to a known license. |
license.name | The license name. For an unmatched license, the name as written in the SBOM. |
risk | allowed, review, or denied, based on your organization's license settings, or null for an unmatched license. The app shows these as Approved, Review, and Forbidden. |
| Option | Values |
|---|---|
sortBy | change, component (component name), license (license name), risk |
| Searched fields | Component name and version, license name, and risk (either the API value or the app label, such as denied or forbidden) |
| Default order | change (added first), then component name, component version, and license name |
sortBy=risk sorts allowed, review, denied in ascending order.
Export to XLSX
GET /v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/export?format=xlsx returns every change between the two versions as an XLSX file. format is required, and xlsx is the only supported value. The other query parameters on this page do not apply: the file always contains every row.
curl -o comparison.xlsx \
-H "Authorization: Bearer <API_TOKEN>" \
"https://api.manifestcyber.com/v1/asset/compare/<BASELINE_ASSET_ID>/<COMPARISON_ASSET_ID>/export?format=xlsx"The response is the file itself, sent as an attachment named <asset>_<baseline version>_vs_<comparison version>.xlsx. Use curl -OJ instead of -o to save it under that name. It has three sheets, each in the default order of the matching endpoint:
| Sheet | Columns |
|---|---|
| Components | Change, Component, Version, Package URL, Relationship |
| Vulnerabilities | Change, CVE, Severity, CVSS, EPSS, KEV, Component, Version, Relationship, Recommendation |
| Licenses | Change, Component, Version, License, Risk |
The file is sent only once it is complete, so a failed export never returns a partial file. When several exports are being built at the same time, the request returns HTTP 503. Wait a few seconds and retry.
Errors
Errors return "success": false and an errorDetails array. Each entry has a code and a message. Check the HTTP status and errorDetails[].code, not the message text.
{
"success": false,
"errorDetails": [
{
"code": "ASSET_COMPARISON_DIFFERENT_ASSETS",
"message": "Both versions must belong to the same asset."
}
]
}Validation errors (INVALID_PARAMETERS) and the organization token error also include an errors array with the same messages. Validation messages have the form <parameter>: <reason>, for example baselineAssetId: Invalid asset ID format, with one entry per invalid parameter.
| HTTP status | Code | Message | Meaning |
|---|---|---|---|
| 400 | INVALID_PARAMETERS | <parameter>: <reason> | A path ID is not a 24-character hexadecimal ID, or a query parameter has an invalid value. |
| 400 | ASSET_COMPARISON_SAME_VERSION | Choose two distinct versions to compare. | baselineAssetId and comparisonAssetId are the same ID. |
| 400 | ASSET_COMPARISON_DIFFERENT_ASSETS | Both versions must belong to the same asset. | The two IDs are versions of different assets. |
| 400 | GENERAL_ERROR | This comparison has more rows than a spreadsheet can hold. Use the API to read it page by page. | Export only. A sheet would exceed the XLSX row limit. Use the list endpoints instead. |
| 404 | NOT_FOUND | One or both versions were not found. | An ID does not exist in your organization, or the version was deleted. |
| 503 | GENERAL_ERROR | Other exports are being prepared right now. Try again in a moment. | Export only. Too many exports are being built at the same time. Retry after a short wait. |
| 500 | UNKNOWN_ERROR | Unexpected internal error occurred. If the issue persists, contact Manifest support. | The comparison could not be completed. Retry, then contact support. |
Authentication, feature, token, and permission errors are returned before the errors above. Except for the plain-text feature error, they put the message in the errors array:
| Response | Meaning | What to do |
|---|---|---|
HTTP 401, Unauthorized, or HTTP 403, Forbidden | The token is missing, invalid, or expired. | Check the Authorization header and the token's expiration date. |
HTTP 403, plain-text body Forbidden: Feature Not Enabled | Version comparison is not enabled for your organization. | Contact [email protected] to enable it. |
HTTP 403, code NOT_AUTHORIZED, Organization tokens are not allowed for this path | The request used a legacy organization token. | Create a user API token and use it instead. |
HTTP 403, You are not authorized to perform this action | The token is missing a permission the endpoint requires. | The permissionDetails.missingPermissions field lists what is missing. See Give the token the right scopes. If missingPermissions is empty, the token may include a permission that your role no longer grants. Create a new token. |
Usually HTTP 403, Failed to check permissions | The permission check could not be completed. | Retry the request. If it keeps failing, contact [email protected]. |
The checks run in this order, so only the first problem is reported: authentication, feature enabled, token type, permissions, query parameters, path IDs, same version, versions found, same asset.
Related docs
- Comparing SBOM Versions
- Getting Started: Manifest API
- Managing API Tokens
- API Token Scopes Reference
- Manifest API Documentation
Updated about 5 hours ago
