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.
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /v1/upload/vdr/initiate | Start 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/vdr | List 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
| Task | Required permission | Scope that grants it |
|---|---|---|
| List VDR uploads and download files | read:asset-vdr-report | view-all (required on every token) |
| Upload a VDR (initiate and complete) | create:product-vdr-report | export-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
success, not only the HTTP statusResponses 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}'| Field | Required | Description |
|---|---|---|
filename | Yes | The name of the VDR file. It must end with .mfst.vdr.json in lowercase. Only VDR files exported from Manifest are supported. |
fileSize | No | The 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 parameter | Description |
|---|---|
page | Page number, starting at 1. Defaults to 1. |
limit | Results per page. Defaults to 100. |
status | One of initialized, processing, completed, failed, or pending (any upload that is not completed or failed). Other values are ignored. |
searchText | Returns uploads whose file name contains this text. Not case-sensitive. |
sort | A 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.
| Field | Description |
|---|---|
_id | The upload ID. It is the same value as the vdrUploadId returned by initiate, and it is the vulnerabilityReportUploadId the download endpoint takes. |
filename | The file name sent when the upload was started. |
fileSize | The file size sent when the upload was started, if any. |
status | initialized (started, not processed yet), processing, completed, or failed. |
targetType | product or asset, depending on what the VDR file describes. Set during processing. |
targetProductId, targetAssetId | The ID of the product or asset the VDR file was applied to, or null. |
totalVulnerabilities, matchedVulnerabilities, unmatchedVulnerabilities | Vulnerability counts recorded during processing. |
processingErrors | Problems recorded during processing. Each entry has a targetEntityType (asset, product, or vulnerability) and an errorContext describing the problem. |
dateCreated, dateModified | When the upload record was created and last updated. |
uploadUser | The 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 status | Message | Meaning |
|---|---|---|
| 400 | Invalid Vulnerability Report Upload ID provided. | The ID is not a valid 24-character hexadecimal ID. |
| 404 | Vulnerability report upload not found. | Your organization has no VDR upload with that ID. |
| 500 | An unexpected error message | The download URL could not be created. Retry, then contact support. |
Downloading SBOMs vs. VDRs:
GET /v1/download/bundledownloads SBOMs only. It ignores VDR upload IDs, and it returns HTTP 400 if none of the IDs you pass is an SBOM. UseGET /v1/download/vdr/{vulnerabilityReportUploadId}for VDR files.
Troubleshooting
| Response | Meaning | What 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 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 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 permissions | The permission check could not be completed. | Retry the request. If it keeps failing, contact [email protected]. |
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 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
- Product and Vulnerability Sharing
- Getting Started: Manifest API
- Managing API Tokens
- API Token Scopes Reference
- Manifest API Documentation
Updated 3 days ago
