# Ivanti / RiskSense API Reference Base URL: `https://platform4.risksense.com/api/v1` Swagger: `https://platform4.risksense.com/doc/swagger.json` Auth: `x-api-key` header. Error codes: 401 bad key, 419 insufficient privileges, 429 rate limited. ## Endpoints Used ### Search Workflow Batches ``` POST /client/{clientId}/workflowBatch/search Content-Type: application/json ``` Standard JSON body with filters, projection, sort, page, size. Used by `ivantiWorkflows.js` for the daily sync. ### Create False Positive Workflow ``` POST /client/{clientId}/workflowBatch/falsePositive/request Content-Type: multipart/form-data ``` This endpoint does NOT accept JSON. It requires `multipart/form-data` with the following fields: | Field | Type | Required | Notes | |-------|------|----------|-------| | `name` | string | yes | Workflow batch name (max 255) | | `reason` | string | yes | Reason for the FP determination | | `description` | string | yes | Description (can be empty string but field must be present) | | `expirationDate` | string | yes | ISO-8601 date, e.g. `2026-06-01` | | `overrideControl` | string | yes | `AUTHORIZED`, `NONE`, or `AUTOMATED`. Use `AUTHORIZED` for standard FP workflows. `NONE` with `isEmptyWorkflow=true` is rejected (400). | | `isEmptyWorkflow` | boolean | yes | `true` if no findings attached, `false` otherwise | | `subjectFilterRequest` | string | yes | Stringified JSON (see format below) | | `files` | file | no | Attachments sent inline in the same request | #### subjectFilterRequest format This is the critical field. It must be a stringified JSON object with this exact structure: ```json { "subject": "hostFinding", "filterRequest": { "filters": [ { "field": "id", "exclusive": false, "operator": "IN", "value": "2283734550,2283734551" } ] } } ``` Key details: - `subject` must be `"hostFinding"` — without this, the API returns 500 - `filters` is nested inside `filterRequest`, NOT at the top level — `{"filters":[]}` at the top level returns 500 - `value` for multiple IDs is comma-separated as a single string, not an array - `operator` values: `EXACT`, `IN`, `LIKE`, `WILDCARD`, `RANGE`, `CIDR` - For empty workflows, use `{"subject":"hostFinding","filterRequest":{"filters":[]}}` with `isEmptyWorkflow=true` #### Response (200/202) ```json { "id": 33418832, "created": "2026-04-08T18:16:08" } ``` Returns HTTP 200 or 202 (Accepted — async job creation). Response contains a numeric `id` (the workflow batch job ID) and `created` timestamp. No `generatedId` or `uuid` in this response. ### Map Findings to Existing Workflow (tested 2026-04-13) ``` POST /client/{clientId}/workflowBatch/falsePositive/{workflowBatchUuid}/map Content-Type: application/json ``` Maps additional host findings to an existing FP workflow batch. Used by the FP submission editing feature to add findings after initial creation. **Critical: one finding per call.** The map endpoint only reliably maps one finding per request. Sending multiple finding IDs via the `IN` operator or comma-separated values results in only the first finding being mapped. The multipart/form-data format (used by the create endpoint) returns 500 on this endpoint. #### Request body ```json { "subject": "hostFinding", "filterRequest": { "filters": [ { "field": "id", "exclusive": false, "operator": "EXACT", "value": "2283734550" } ] } } ``` Key details: - Must be `application/json` (NOT multipart/form-data — returns 500) - Use `EXACT` operator with a single finding ID per call - `IN` operator with comma-separated IDs only maps the first finding - Loop through findings and make one API call per finding - The `workflowBatchUuid` in the URL is the UUID from the search endpoint (not the numeric batch ID from create) #### Response (200) Returns the updated workflow batch object on success. #### UUID resolution The `workflowBatchUuid` required in the URL is NOT returned by the create endpoint. To obtain it: 1. Search via `POST /client/{clientId}/workflowBatch/search` with `{ field: 'name', operator: 'EXACT', value: '' }` 2. Use `projection: 'internal'` to get full batch objects 3. The UUID is in the `uuid` field of the returned batch object 4. Cache the UUID locally after first resolution (stored in `ivanti_fp_submissions.ivanti_workflow_batch_uuid`) #### Implementation in dashboard The `resolveWorkflowBatchUuid()` helper in `backend/routes/ivantiFpWorkflow.js` handles UUID resolution: - Returns cached UUID if available in the local submission record - Otherwise searches Ivanti by workflow name, extracts `batch.uuid`, and caches it for future use The findings map loop in the `POST /submissions/:id/findings` endpoint: - Iterates through each finding ID individually - Makes one JSON POST per finding with `EXACT` operator - Tracks which findings succeeded vs failed - Only marks queue items as complete for successfully mapped findings - Returns both `addedFindings` and `failedFindings` arrays in the response ### Other Workflow Endpoints (from Swagger) These are available but not all are currently used by the dashboard: | Endpoint | Purpose | Status | |----------|---------|--------| | `/workflowBatch/acceptance/request` | Risk acceptance workflow | Not used | | `/workflowBatch/remediation/request` | Remediation workflow | Not used | | `/workflowBatch/severityChange/request` | Severity change workflow | Not used | | `/workflowBatch/{workflowType}/approve` | Approve a workflow (needs `workflowBatchUuid`) | Not used | | `/workflowBatch/{workflowType}/reject` | Reject a workflow | Not used | | `/workflowBatch/{workflowType}/rework` | Send back for rework | Not used | | `/workflowBatch/{workflowType}/update` | Update a workflow | Not used | | `/workflowBatch/{workflowType}/{workflowBatchUuid}/map` | Map findings to workflow | Used (FP editing) | | `/workflowBatch/{workflowType}/{workflowBatchUuid}/unmap` | Unmap findings | Not used | | `/workflowBatch/{workflowType}/{workflowBatchUuid}/attach` | Attach file to existing workflow | **Broken — see note** | | `/workflowBatch/{workflowType}/{workflowBatchUuid}/detach` | Detach file | Not used | | `/workflowBatch/model` | Get model/schema | Not used | | `/workflowBatch/filter` | Get available filter fields | Not used | | `/workflowBatch/suggest` | Get suggested values for a filter field | Not used | ### Known Limitations #### Attach endpoint does not work (tested 2026-04-13) The `/workflowBatch/{workflowType}/{workflowBatchUuid}/attach` endpoint is listed in the Swagger spec but returns HTTP 400 (Bad Request) for all tested request formats: - `multipart/form-data` with field name `file` (singular) — 400 - `multipart/form-data` with field name `files` (plural) — 400 - Tested with `Content-Type: application/octet-stream` and `image/png` — both 400 - Tested with both `ivantiMultipartPost` and `ivantiFormPost` helpers — both 400 The Ivanti response is a generic Spring Boot error with no detail message: ```json {"timestamp":"...","status":400,"error":"Bad Request","path":"/api/v1/client/1550/workflowBatch/falsePositive/{uuid}/attach"} ``` **Workaround:** File attachments can only be uploaded during the initial workflow creation (sent inline with the `/workflowBatch/falsePositive/request` endpoint). To add attachments to an existing workflow, users must upload them directly in the Ivanti platform UI. #### Search by numeric batch ID does not work The `/workflowBatch/search` endpoint does not support filtering by the numeric `id` returned from the create endpoint. Searching with `{ field: 'id', operator: 'EXACT', value: '33432541' }` returns 0 results. Searching by `name` field works and returns the workflow batch object including the `uuid` field needed for map/attach operations. #### UUID not returned by create endpoint The `/workflowBatch/falsePositive/request` create endpoint returns only `{ id: , created: }`. The `uuid` needed for map/attach/approve/reject operations must be obtained separately via the search endpoint. ## Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `IVANTI_API_KEY` | — | Required. API key for authentication | | `IVANTI_CLIENT_ID` | `1550` | Client ID in the Ivanti platform | | `IVANTI_SKIP_TLS` | `false` | Set `true` to skip TLS verification | | `IVANTI_FIRST_NAME` | — | Used for workflow search filter (sync) | | `IVANTI_LAST_NAME` | — | Used for workflow search filter (sync) | | `IVANTI_MANAGED_BUS` | `NTS-AEO-ACCESS-ENG,NTS-AEO-STEAM` | Comma-separated list of BUs considered "managed" for archive drift classification. Findings leaving these BUs are classified as bu_reassignment. |