VDR Uploads and Downloads with the API

Upload Manifest VDR documents, check their processing status, and download uploaded VDR files with the Manifest API.

A VDR upload imports a Manifest vulnerability disclosure report (VDR) file into your organization so its triage statuses are applied to the product or asset it describes. The endpoints on this page are the API equivalent of the VDR tab on the Uploads page, described in Product and Vulnerability Sharing.

MethodEndpointPurpose
POST/v1/upload/vdr/initiateStart an upload and get a signed upload URL
POST/v1/upload/vdr/complete/{vdrUploadId}Confirm the file was uploaded and start processing
GET/v1/upload/vdrList your organization's VDR uploads and their status
GET/v1/download/vdr/{vulnerabilityReportUploadId}Download a VDR file that was uploaded to your organization

All paths are relative to https://api.manifestcyber.com.


Before you start

VDR uploads must be enabled for your organization

VDR uploads and downloads are enabled per organization by Manifest. Organization admins cannot turn them on from the app. To enable them, contact [email protected].

Until they are enabled, these endpoints respond with "success": false and one of these messages:

  • VDR upload is not enabled for this organization. (initiate, complete, and list)
  • VDR download is not enabled for this organization. (download)

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

TaskRequired permissionScope that grants it
List VDR uploads and download filesread:asset-vdr-reportview-all (required on every token)
Upload a VDR (initiate and complete)create:product-vdr-reportexport-reports (Export reports)

A token that only lists and downloads VDR files needs only view-all. Because an uploaded VDR can change triage statuses, add export-reports only to tokens that should be able to import VDRs.

See the API Token Scopes Reference for the full list of scopes.

Check success, not only the HTTP status

Responses are JSON with success and errors fields, plus data on most responses. Authentication and permission errors (HTTP 401 and 403) have no data field. Many errors on these endpoints are returned with HTTP 200 and "success": false, so always check the success field:

  • Initiate, complete, and list return HTTP 200 for every error they handle, including the "not enabled" error.
  • Download returns HTTP 400, 404, or 500 for its own errors, but its "not enabled" error is also HTTP 200.
  • Token and permission errors return HTTP 401 or 403 on all four endpoints. These checks run before the "not enabled" check, so a token or scope problem is reported first.

Upload a VDR

Uploading a VDR takes three requests: start the upload, send the file to the signed URL, and complete the upload. Processing then runs in the background.

Step 1: Start the upload

curl -X POST "https://api.manifestcyber.com/v1/upload/vdr/initiate" \
  -H "Authorization: Bearer <API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"filename": "my-product.mfst.vdr.json", "fileSize": 48213}'
FieldRequiredDescription
filenameYesThe name of the VDR file. It must end with .mfst.vdr.json in lowercase. Only VDR files exported from Manifest are supported.
fileSizeNoThe file size in bytes. It is stored on the upload record and is not checked against the uploaded file.

You do not pass a product or asset ID. Manifest reads the target product or asset from the contents of the VDR file.

A successful response looks like this:

{
  "success": true,
  "data": [
    {
      "vdrUploadId": "<VDR_UPLOAD_ID>",
      "uploadUrl": "<UPLOAD_URL>"
    }
  ],
  "errors": []
}

Keep the vdrUploadId. You need it to complete the upload, and it is also the ID the download endpoint takes. The uploadUrl expires after 30 minutes.

Step 2: Upload the file

Send the raw file to uploadUrl with an HTTP PUT before the URL expires:

curl -X PUT --upload-file ./my-product.mfst.vdr.json "<UPLOAD_URL>"

The URL is already signed, so do not add an Authorization header to this request. Keep the URL in quotes, because it contains & characters.

Step 3: Complete the upload

curl -X POST "https://api.manifestcyber.com/v1/upload/vdr/complete/<VDR_UPLOAD_ID>" \
  -H "Authorization: Bearer <API_TOKEN>"

This request has no body. Manifest checks that the uploaded file is present and not empty, then queues it for processing. A successful response looks like this:

{
  "success": true,
  "data": [],
  "errors": []
}

Call complete once per upload. Each call queues the file for processing again.

Step 4: Check the processing status

Poll the list endpoint until your upload's status is completed or failed. The pending filter returns uploads that are neither completed nor failed:

curl -G "https://api.manifestcyber.com/v1/upload/vdr" \
  -H "Authorization: Bearer <API_TOKEN>" \
  --data-urlencode "status=pending"

Look for the item whose _id matches your vdrUploadId. When it no longer appears in the pending results, list without the filter (or with status=completed or status=failed) to read the final result.


List VDR uploads

GET /v1/upload/vdr returns your organization's VDR uploads, newest first. Use it to check processing status and to find the ID of an upload you did not start yourself.

curl -G "https://api.manifestcyber.com/v1/upload/vdr" \
  -H "Authorization: Bearer <API_TOKEN>" \
  --data-urlencode "page=1" \
  --data-urlencode "limit=25"
Query parameterDescription
pagePage number, starting at 1. Defaults to 1.
limitResults per page. Defaults to 100.
statusOne of initialized, processing, completed, failed, or pending (any upload that is not completed or failed). Other values are ignored.
searchTextReturns uploads whose file name contains this text. Not case-sensitive.
sortA URL-encoded JSON object with a field name and 1 (ascending) or -1 (descending), for example {"dateCreated":1}. Omit it to sort newest first. If you send sort, it must be a JSON object whose directions are 1 or -1. Otherwise, for example with invalid JSON, {}, 0, 2, or "asc", the request returns Unable to fetch VDR uploads.

A successful response looks like this:

{
  "success": true,
  "errors": [],
  "queryInfo": {
    "page": 1,
    "limit": 25,
    "totalReturn": 1,
    "totalCount": 1
  },
  "data": [
    {
      "_id": "<VDR_UPLOAD_ID>",
      "filename": "my-product.mfst.vdr.json",
      "fileSize": 48213,
      "status": "completed",
      "targetType": "product",
      "targetProductId": "<PRODUCT_ID>",
      "targetAssetId": null,
      "totalVulnerabilities": 42,
      "matchedVulnerabilities": 40,
      "unmatchedVulnerabilities": 2,
      "dateCreated": "2026-10-06T14:03:11.512Z",
      "dateModified": "2026-10-06T14:04:02.087Z",
      "uploadUser": {
        "firstName": "Jane",
        "lastName": "Doe"
      }
    }
  ]
}

Items can include other internal fields. Rely only on the fields below.

FieldDescription
_idThe upload ID. It is the same value as the vdrUploadId returned by initiate, and it is the vulnerabilityReportUploadId the download endpoint takes.
filenameThe file name sent when the upload was started.
fileSizeThe file size sent when the upload was started, if any.
statusinitialized (started, not processed yet), processing, completed, or failed.
targetTypeproduct or asset, depending on what the VDR file describes. Set during processing.
targetProductId, targetAssetIdThe ID of the product or asset the VDR file was applied to, or null.
totalVulnerabilities, matchedVulnerabilities, unmatchedVulnerabilitiesVulnerability counts recorded during processing.
processingErrorsProblems recorded during processing. Each entry has a targetEntityType (asset, product, or vulnerability) and an errorContext describing the problem.
dateCreated, dateModifiedWhen the upload record was created and last updated.
uploadUserThe firstName and lastName of the user who started the upload, or null.

Download a VDR file

GET /v1/download/vdr/{vulnerabilityReportUploadId} downloads a VDR file exactly as it was uploaded. It does not generate a new VDR export. To export a current VDR for a product, use the Download menu on the product page, as described in Product and Vulnerability Sharing.

The vulnerabilityReportUploadId is the vdrUploadId returned by initiate, or the _id of an item returned by the list endpoint.

Get a download URL

By default, the endpoint returns a signed URL instead of the file:

curl "https://api.manifestcyber.com/v1/download/vdr/<VDR_UPLOAD_ID>" \
  -H "Authorization: Bearer <API_TOKEN>"
{
  "success": true,
  "errors": [],
  "data": [
    {
      "url": "<DOWNLOAD_URL>",
      "vulnerabilityReportUploadId": "<VDR_UPLOAD_ID>"
    }
  ]
}

The URL is valid for one hour and serves the file as an attachment with its original file name. Fetch it without an Authorization header:

curl -o my-product.mfst.vdr.json "<DOWNLOAD_URL>"

Download the file directly

Add redirect=true or redirect=1 to get an HTTP 302 redirect to the file instead of JSON. Any other value, including TRUE or yes, returns the JSON response.

curl -L -o my-product.mfst.vdr.json \
  -H "Authorization: Bearer <API_TOKEN>" \
  "https://api.manifestcyber.com/v1/download/vdr/<VDR_UPLOAD_ID>?redirect=1"

The download endpoint does not check the upload's status. If the file was never sent to uploadUrl, a URL is still returned, but fetching it fails.

Download errors

HTTP statusMessageMeaning
400Invalid Vulnerability Report Upload ID provided.The ID is not a valid 24-character hexadecimal ID.
404Vulnerability report upload not found.Your organization has no VDR upload with that ID.
500An unexpected error messageThe download URL could not be created. Retry, then contact support.

Downloading SBOMs vs. VDRs: GET /v1/download/bundle downloads SBOMs only. It ignores VDR upload IDs, and it returns HTTP 400 if none of the IDs you pass is an SBOM. Use GET /v1/download/vdr/{vulnerabilityReportUploadId} for VDR files.


Troubleshooting

ResponseMeaningWhat to do
HTTP 200, VDR upload is not enabled for this organization. or VDR download is not enabled for this organization.VDR uploads are not enabled for your organization.Contact [email protected] to enable them.
HTTP 403, 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 required permission.The permissionDetails.missingPermissions field lists what is missing. Use a token with the scopes listed above. 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].
HTTP 401, Unauthorized, or HTTP 403, ForbiddenThe token is missing, invalid, or expired.Check the Authorization header and the token's expiration date.
HTTP 200, Missing filename for VDR upload.Initiate was called without a filename.Send a filename ending in .mfst.vdr.json.
HTTP 200, Invalid VDR file type (must be .mfst.vdr.json).The filename does not end in .mfst.vdr.json.Use a VDR file exported from Manifest, with its original extension.
HTTP 200, Uploaded file is empty or missing (did you successfully PUT the file to the uploadUrl?)Complete was called before the file reached uploadUrl, or the file is empty.PUT the file to uploadUrl and call complete again. If the URL has expired, start a new upload.
HTTP 200, Invalid VDR Upload ID provided.Complete was called without an ID in the path, or with an ID that does not exist in your organization.Use the vdrUploadId returned by initiate.
HTTP 200, The vdrUploadId portion of the URL should be a valid objectId (24 character hexadecimal string)The ID in the complete path is malformed.Use the vdrUploadId returned by initiate exactly as given.

When initiate rejects a request because of the filename, the errors array also contains a second, generic upload error after the specific message. The specific message is the one to act on.


Related docs



Did this page help you?