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.

MethodEndpointPurpose
GET/v1/asset/compare/{baselineAssetId}/{comparisonAssetId}Summary totals for both versions
GET/v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/componentsComponents added or removed
GET/v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/vulnerabilitiesCVEs caused by component changes
GET/v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/licensesLicense changes on the changed components
GET/v1/asset/compare/{baselineAssetId}/{comparisonAssetId}/exportEvery 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:

EndpointRequired permissions
Summaryread:asset
Componentsread:asset, read:component
Vulnerabilitiesread:asset, read:vulnerability
Licensesread:asset, read:license, read:component
Exportread: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:

FieldDescription
organizationAssetIdThe asset ID of this version.
nameThe asset name.
versionThe version string, or null.
dateCreatedWhen this version was created, as an ISO 8601 timestamp.
isActivefalse if the version was marked inactive.
riskScorelow, medium, high, or null when the risk has not been calculated.
countComponentsThe number of components in this version.
countVulnerabilitiesThe vulnerability totals stored for this version: total, one count per severity, and inKEV.
countLicenseIssuesThe 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

ParameterDescription
pagePage number, starting at 1. Defaults to 1. A page past the last one returns an empty data array.
limitRows per page, from 1 to 100. Defaults to 50.
searchComma-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.
changeComma-separated list of added and removed. Omit it to return both.
sortByThe field to sort by. The allowed values depend on the endpoint and are listed below.
sortOrderasc (default) or desc.
severityVulnerabilities 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.10 sorts after 1.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
  }
}
FieldDescription
dataThe rows on this page. The fields depend on the endpoint.
queryInfo.pageThe page returned.
queryInfo.limitThe page size used.
queryInfo.totalReturnThe number of rows on this page.
queryInfo.totalCountThe number of rows that match the filters, across all pages.

Component reference

Rows on all three endpoints describe components with the same object:

FieldDescription
organizationComponentIdThe component ID.
nameThe component name.
versionThe component version, or null.
packageUrlThe component's package URL (pURL), or null.
relationshipdirect, 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"
}
OptionValues
sortBychange, name, version, packageUrl, relationship
Searched fieldsComponent name, version, and package URL
Default orderchange (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"
  }
}
FieldDescription
changeadded or removed.
cveIdThe CVE ID.
vulnerabilityIdThe 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.
severityCRITICAL, HIGH, MEDIUM, LOW, NONE, or null.
cvssScoreThe CVSS score, or null.
epssScoreThe EPSS score as a fraction from 0 to 1, or null.
inKEVtrue if the CVE is on the CISA Known Exploited Vulnerabilities (KEV) list.
recommendedActionmitigate, monitor, accept, or null.
affectedComponentThe added or removed component that carries the CVE, as a component reference.
OptionValues
sortBychange, cveId, severity, cvssScore, epssScore, inKEV, affectedComponent (component name), relationship
Searched fieldsCVE ID, and the affected component's name, version, and package URL
Default orderchange (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 added on 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"
}
FieldDescription
changeadded or removed.
componentThe component that carries the license, as a component reference.
license.licenseIdThe Manifest license ID, or null when the license could not be matched to a known license.
license.nameThe license name. For an unmatched license, the name as written in the SBOM.
riskallowed, 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.
OptionValues
sortBychange, component (component name), license (license name), risk
Searched fieldsComponent name and version, license name, and risk (either the API value or the app label, such as denied or forbidden)
Default orderchange (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:

SheetColumns
ComponentsChange, Component, Version, Package URL, Relationship
VulnerabilitiesChange, CVE, Severity, CVSS, EPSS, KEV, Component, Version, Relationship, Recommendation
LicensesChange, 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 statusCodeMessageMeaning
400INVALID_PARAMETERS<parameter>: <reason>A path ID is not a 24-character hexadecimal ID, or a query parameter has an invalid value.
400ASSET_COMPARISON_SAME_VERSIONChoose two distinct versions to compare.baselineAssetId and comparisonAssetId are the same ID.
400ASSET_COMPARISON_DIFFERENT_ASSETSBoth versions must belong to the same asset.The two IDs are versions of different assets.
400GENERAL_ERRORThis 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.
404NOT_FOUNDOne or both versions were not found.An ID does not exist in your organization, or the version was deleted.
503GENERAL_ERROROther 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.
500UNKNOWN_ERRORUnexpected 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:

ResponseMeaningWhat to do
HTTP 401, Unauthorized, or HTTP 403, ForbiddenThe token is missing, invalid, or expired.Check the Authorization header and the token's expiration date.
HTTP 403, plain-text body Forbidden: Feature Not EnabledVersion 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 pathThe request used a legacy organization token.Create a user API token and use it instead.
HTTP 403, You are not authorized to perform this actionThe 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 permissionsThe 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



Did this page help you?