Overview
The Pipeline API lets you automate scan uploads into a Paramify assessment. Instead of uploading scanner output through the Paramify UI, you can send a file directly from your CI pipeline, have Paramify turn the results into issues, and optionally close out the assessment cycle, all with REST calls.
This article covers the current version of the API, available at the Paramify API documentation, the full interactive reference, with every endpoint, request/response schema, and the ability to try calls directly.
Who can use this
Access is controlled by two API key permissions:
| API key permission | Covers |
|---|---|
| View Evidence | Querying assessments (needed to look up a pipeline ID before you can call any pipeline endpoint) |
| Queue Pipeline Processing | All six pipeline endpoints: intake (with or without an operation), process, and close, in both their current-cycle and named-cycle forms |
TIP
A key that only looks up assessments to find a pipeline ID doesn't need Queue Pipeline Processing; only grant it once that key is also uploading, processing, or closing cycles.
Key concepts
A pipeline is an assessment. Every assessment moves through cycles, and each of the three actions below is available in two forms:
| Action | Current cycle | Named cycle |
|---|---|---|
| Upload a file | POST /pipelines/{pipelineId}/intake |
POST /pipelines/{pipelineId}/cycles/{cycleId}/intake |
| Turn artifacts into issues | POST /pipelines/{pipelineId}/process |
POST /pipelines/{pipelineId}/cycles/{cycleId}/process |
| Close the cycle | POST /pipelines/{pipelineId}/close |
POST /pipelines/{pipelineId}/cycles/{cycleId}/close |
*pipelineId is the assessment's ID.
The current-cycle form resolves the cycle automatically from the pipeline's own state; you can't hand it a cycle ID. Only intake will open a new cycle (starting today) if none is currently open; calling process or close this way when there's no open cycle fails with a 409 instead. Use the named-cycle form when you need to target a specific cycle explicitly, such as backfilling a prior month.
How to use the Pipeline API
The examples below use a sample scan file, title, note, and IDs to illustrate the calls. Swap in your own filename, metadata, and identifiers: $PIPELINE_ID and $PARAMIFY_API_KEY are environment variables you're expected to set, not literal text to paste in.
1. Find your pipeline ID
A pipeline is identified by its assessment ID. If you don't already know it, list your workspace's assessments and grab the id of the one you want. This call needs the View Evidence permission on your API key.
curl -s "https://app.paramify.com/api/v0/assessment" \
-H "Authorization: Bearer $PARAMIFY_API_KEY" \
| jq '.assessments[] | {id, name, type}'With many assessments in a workspace, narrow the list with the type or frequency query parameters instead of scanning through everything:
curl -s "https://app.paramify.com/api/v0/assessment?type=VULNERABILITY&frequency=MONTHLY" \
-H "Authorization: Bearer $PARAMIFY_API_KEY" \
| jq '.assessments[] | {id, name, type}'type accepts VULNERABILITY or CONFIGURATION; frequency accepts DAILY, EVERY_THREE_DAYS, WEEKLY, MONTHLY, QUARTERLY, BIANNUALLY, or ANNUALLY. Both accept multiple values, and you can already supply a known id the same way to fetch one or more specific assessments directly.
Save the one you want as PIPELINE_ID for the calls that follow:
export PIPELINE_ID='<assessment id>'
2. Upload a scan
Send a multipart/form-data request with two required parts: file (the binary scan output) and artifact (a JSON object describing it).
curl -X POST "https://app.paramify.com/api/v0/pipelines/$PIPELINE_ID/intake" \
-H "Authorization: Bearer $PARAMIFY_API_KEY" \
-F "file=@scan-2026-09-15.nessus" \
-F 'artifact={
"title": "Weekly infra scan 2026-09-15",
"note": "Nightly Nessus run, prod VPC",
"effectiveDate": "2026-09-15T00:00:00.000Z",
"operation": "PROCESS"
};type=application/json'Every field on artifact is optional. Omit title and Paramify uses the original filename; omit effectiveDate and it defaults to today. Omit operation entirely and the file is only parked on the cycle's intake; nothing is read until you call Process separately.
operation accepts PROCESS or PROCESS_CLOSE right here at intake, same as the standalone Process step below. Either way, it doesn't just process the file you just uploaded; it processes every artifact on the cycle that no run has processed yet and no other job is already mid-processing.
NOTE
Accepted file types are CSV, XML, JSON, and Nessus only. If your scanner writes filenames with spaces, colons, or brackets, URL-encode the filename before sending, since certain characters otherwise cause upload errors.
BEFORE YOUR FIRST UPLOAD
The assessment's file intake preset must already be configured in the app, or intake will return a 400 error. This is app-side configuration and can't be set through the API.
The response is 201 when you didn't request an operation, or 202 when you did (because processing is queued as a background job):
{
"artifact": { "id": "...", "title": "Weekly infra scan 2026-09-15", "...": "..." },
"job": {
"id": "e3977e3e-0b77-4e78-81de-f90e3e37e43c",
"type": "PROCESS",
"status": "QUEUED",
"assessmentId": "...",
"cycleId": "...",
"artifactIds": ["..."],
"blockedByJobId": null,
"counts": null
}
}job is null whenever no operation was supplied at intake.
3. Process the artifacts
Only needed if you skipped operation at intake, or you're processing files that were uploaded some other way.
curl -X POST "https://app.paramify.com/api/v0/pipelines/$PIPELINE_ID/process" \
-H "Authorization: Bearer $PARAMIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "artifactIds": ["123e4567-e89b-12d3-a456-426614174000"], "operation": "PROCESS" }'Both fields are optional. Omit artifactIds and Paramify queues every artifact on the cycle that hasn't already been processed and isn't already mid-processing; re-running the call is safe and won't create duplicate work. If nothing on the cycle qualifies, the request is rejected rather than queuing a no-op. operation defaults to PROCESS; set it to PROCESS_CLOSE to process and close the cycle in the same run.
4. Close the cycle
curl -X POST "https://app.paramify.com/api/v0/pipelines/$PIPELINE_ID/close" \
-H "Authorization: Bearer $PARAMIFY_API_KEY"Closing applies "if this cycle never saw it, it's fixed" logic: any open issue the cycle never rediscovered is automatically closed, unless it's been flagged to prevent auto-close.
5. Target a specific cycle
The plain routes above always act on the pipeline's current open cycle. To name one explicitly instead (for backfilling, or checking a past cycle), list the cycles on the assessment and grab the id of the one you want:
curl -s "https://app.paramify.com/api/v0/assessment/$PIPELINE_ID/cycle" \
-H "Authorization: Bearer $PARAMIFY_API_KEY" \
| jq '.cycles[] | {id, name, startDate}'
export CYCLE_ID='<cycle id>'Then use the named-cycle form of any pipeline endpoint with that ID:
curl -X POST "https://app.paramify.com/api/v0/pipelines/$PIPELINE_ID/cycles/$CYCLE_ID/intake" \
-H "Authorization: Bearer $PARAMIFY_API_KEY" \
-F "file=@scan-2026-08-15.nessus" \
-F 'artifact={"title":"Backfill"};type=application/json'Remember that queued work is confined to the pipeline's current cycle, so passing an operation here is refused unless $CYCLE_ID happens to be the current one; see backfilling below.
Backfilling a prior cycle
The named-cycle form of intake accepts an upload into any cycle that isn't closed or closing, but queued processing work is confined to the pipeline's current cycle. Passing an operation while targeting any other cycle is refused with a 409. To backfill an old cycle, upload the file bare (no operation), then process it separately using the named-cycle process endpoint.
Reading the results
The counts field in the response is null until processing finishes, and its shape depends on what you called:
| PROCESS field | Meaning |
|---|---|
| recordsProcessed | Records read from the artifacts |
| issuesCreated | New issues |
| issuesUpdated | Existing issues touched |
| issuesSeenClosed | Open issues closed because the scan showed them resolved |
| issuesCreatedAndClosed | Issues created already closed |
| issuesExcuseClosed | Records skipped because the issue they would have updated was already closed by an excuse (deviation) |
| CLOSE field | Meaning |
|---|---|
| issuesAutoClosed | Open issues auto-closed because this cycle never discovered them |
| legacyOperationsApplied | Pending issue changes on the cycle that the close applied |
*A PROCESS_CLOSE job returns all eight fields.
NOTE
recordsProcessed: 0 on a completed job means the job succeeded but found nothing to read; check your file and the intake preset configuration.
Error handling
All six pipeline endpoints return the same set of error codes. Requests are validated when submitted, not at run time: a request that can't run is rejected immediately with a 4xx rather than failing later. Treat a 4xx as something to fix in your request; treat a FAILED job as a data problem (an unparseable file or bad records).
| Code | Common cause |
|---|---|
| 400 | Intake preset not configured on the assessment, an unsupported file type, or nothing on the cycle qualifies to process |
| 403 | API key is missing View Evidence (for querying assessments) or Queue Pipeline Processing (for intake, process, and close calls) |
| 404 | Wrong pipeline ID (remember, it's the assessment ID), or a cycle that isn't on that pipeline |
| 409 | Conflicting state, for example an operation aimed at a non-current cycle, or a cycle that's already closed or closing |
Comments
0 comments
Please sign in to leave a comment.