REST API
Overview
Pensar provides a REST API for programmatic access to your workspace data. It covers the same pentest, issue, and fix capabilities as the MCP Server — plus attack-surface and webhook-endpoint routes that MCP does not expose — and is designed for automation, scripting, and building custom integrations.
If you want findings pushed to you rather than polled for, register a webhook endpoint instead of looping over GET /issues.
The REST API uses API key authentication. For AI-assistant integrations, consider using the MCP Server which supports OAuth-based authentication and works natively with tools like Claude, Cursor, and Windsurf.
Base URL
Authentication
All requests must include a valid Pensar API key. You can provide it in one of two ways:
The workspace is resolved automatically from the API key, so you do not need to send a workspace identifier. (The X-Workspace-Id header is only used by browser/WorkOS-JWT callers, not by API-key callers.)
Creating an API Key
- Navigate to Settings > Integrations > API Keys in the Pensar Console
- Click Create API Key
- Give it a descriptive name and copy the key value
- Store it securely — the key is only shown once
API keys are scoped to a workspace. All API requests authenticate against the workspace associated with the key.
Endpoints
Pentests
List Pentests
Returns all pentests (scans) in the workspace.
Response:
Get Pentest
Returns detailed information about a specific pentest.
Response:
Dispatch Pentest
Queue a hosted pentest in the authenticated workspace. It runs asynchronously; use the returned scanId with GET /pentests/:pentestId to check its status. An identical active launch may be reused, in which case the response identifies that existing scan.
Select exact endpoints:
First list applications with GET /apps, then retrieve their discovered endpoints with GET /apps/:appId/endpoints (paginate with limit and offset). Use the endpoint id values, not URLs, application IDs, or the target IDs returned by a previous pentest. Endpoints from multiple applications may be selected when they belong to the same workspace.
Replace the example UUIDs with IDs from your workspace. scanLevel may be omitted for this request because exact endpoint launches default to full depth.
How scope is selected:
- With
endpointIds, only those discovered endpoints become scan targets. The server derives their applications, removes duplicate IDs, and does not narrow the list using a repository commit or priority depth. Even a list containing every current endpoint remains an explicit list. - Without
endpointIds, the server resolves eligible workspace applications. These launches, including Apexpensar pentests dispatch, are not attributed to a repository and do not narrow targets against a previous pentest, even if a commit SHA is recorded.scanLeveldefaults to"priority"; depth is separate from diff selection. For repository-baseline comparisons, use the CI dispatch integration. To test the changes between two specific commits, sendbaseShaandcommitShatoPOST /ci/dispatch(compare two explicit commits); this endpoint does not accept them. - Selected applications must be ready to test: they need a linked domain with pentesting enabled, domain verification when required, and discovered endpoints. A rejected selected application does not silently reduce the requested scope.
Launch without an explicit endpoint list:
To include branch, supply the configured target branch, for example {"branch":"main","scanLevel":"full"} when that repository is configured to analyze main.
Response (201):
Validation: Malformed JSON, unknown fields, an empty or oversized endpoint list, invalid UUIDs, missing or deleted endpoints, endpoints outside the workspace, and incompatible branch or scanLevel values return 400 before a scan is created. The endpoint list is never replaced with workspace-wide scope after validation fails. For example:
Send only the documented launch fields. Application IDs, custom objectives, model overrides, and credential selections are not accepted by this endpoint.
For command-line examples, including reading endpoint IDs from a file or stdin, see Apex CLI: launch selected endpoints. AI assistants can use MCP dispatch_pentest.
List Pentest Targets
Returns the targets tested during a pentest. A target is a single endpoint of the attack surface that the pentest exercised. Targets are the entry point for querying the pentest’s execution logs, which are persisted per target rather than per issue (see Target Logs).
Response:
Issues
List Issues
Returns security issues in the workspace with optional filtering.
Response:
issueLabel is the human-facing reference shown in the console and url links
straight to the issue. Every :issueId path below accepts either form.
closedAt, closedDisposition and duplicateOf are null on an open
finding. The list carries the disposition because without it every closed
finding reads as resolved; the free-text close fields are on
Get Issue only, since this list is unpaginated.
Get Issue
Returns full details of a specific security issue, including description, CWE classification, proof-of-concept, and affected code location.
title is the fix pull request’s title when one exists; name is always the
finding’s own name. startLineNumber and endLineNumber are the affected lines
in location, and are null when the finding has no line range.
A closed issue also carries the full close record: closedAt,
closedDisposition, duplicateOf, closedMethod (how it was closed —
manual, retest, mcp, api), closedReason, and closedComments. All six
are null while the issue is open, and duplicateOf is set only on a
duplicate close.
Update Issue
Update the status of a security issue — close it, mark it as a false positive, or reopen it.
Request Body:
Response (200): the updated issue, including closedAt, closedReason, closedDisposition, duplicateOf, endpointId, and applicationId.
Setting endpointId also updates applicationId to the endpoint’s application.
A malformed UUID returns 400; an endpoint outside this workspace or missing
returns 404. Another finding with the same name and endpoint in the same
pentest returns 409.
A status change, or a change to duplicateOf, is also applied to the finding’s linked GitHub, Linear and Notion tickets, the same as a change made in the console.
closedDisposition is the structured verdict; closedReason is the free text
beside it. A finding closed without a disposition reads as Resolved
everywhere, so send one whenever the close is not a plain fix. Reopening clears
the disposition along with the rest of the close record, and false-positive is
its own status rather than a disposition, so it never carries one.
To close a finding as a duplicate, send closedDisposition: "duplicate" with
duplicateOf naming the original, which must be another issue in the same
workspace:
duplicateOf is required with duplicate and refused with any other
disposition, and the issue must end up closed. The original cannot itself be
closed as a duplicate, so point at the original it names instead. An original
outside the workspace is a 404; the other cases are a 400. Reopening the
issue, or changing its disposition, clears the link.
Closing an issue that has duplicates of its own as a duplicate moves those duplicates onto the new original, so every link points at an issue that is not itself a duplicate. Deleting an original reopens its duplicates, since nothing would be left tracking the vulnerability.
other is not accepted — it is being retired from the vocabulary. Any value
outside the five is a 400, including null, so omit the field rather than
sending one back from a read:
Retest Issue
Queues an asynchronous retest of a security issue against its original target.
Response (201):
The retest runs asynchronously. Rather than polling for the verdict, subscribe to the retest.completed webhook — it carries the verdict and the full issue.
List Issue Retests
Returns the issue’s retest history, most recent first. An issue that has never been retested returns an empty array, not a 404.
Response:
status is derived from the retest’s timestamps, error, and result rather than stored, so it cannot drift out of sync with them: queued, in-progress, fixed, still-vulnerable, or error. confidence is high, medium, or low, and is the field to build an escalation rule on.
Fixes
List Fixes
Returns all available auto-remediation fixes for an issue.
Get Fix
Returns the full fix details including the code diff, explanation, and affected file path.
Agent Logs
List Agent Logs
Returns agent execution logs for a specific issue.
Search Agent Logs
Search agent logs by text pattern with configurable context.
Request Body:
Target Logs
A pentest’s execution logs are persisted against the individual targets it
tested (see List Pentest Targets), not against the
issues it discovered. These endpoints query those logs directly by target id,
so you can inspect a target’s full pentest activity even when it produced no
issue. The level, role, and contextLines filters behave identically to
the issue Agent Logs endpoints.
List Target Logs
Returns agent execution logs for a specific pentest target.
Search Target Logs
Search a target’s agent logs by text pattern with configurable context.
Attack Surface
These endpoints expose the discovered attack surface of the workspace — the applications (apps) Pensar tracks and the individual endpoints within them. They have no MCP equivalent.
List Apps
Returns the applications in the workspace.
Create App
Creates a new application in the workspace.
Get App
Returns full details of a specific application.
Update App
Updates an application. All body fields are optional.
Delete App
Deletes an application.
List Endpoints
Returns the endpoints discovered for a specific application.
Create Endpoint
Adds an endpoint to an application.
Get Endpoint
Returns full details of a specific endpoint.
Update Endpoint
Updates an endpoint. All body fields are optional.
Delete Endpoint
Deletes an endpoint.
Search Apps
Searches applications in the workspace.
Search Endpoints
Searches endpoints across all applications in the workspace.
Knowledge Base
The workspace knowledge base is the context Pensar agents use during pentests: technology notes, testing history, and false positive patterns. A false_positive_pattern entry suppresses matching findings.
Any API key with knowledge:read can list and read entries. Creating, updating, and archiving require a named user (a signed-in user credential): a workspace API key has no author to attribute the change to and receives 403. Every entry records the user who created it in createdBy.
An entry has this shape:
category is one of false_positive_pattern, technology_context, testing_history, user_note, or agent_learning. status is active, stale, or archived; only active entries are given to agents.
List Knowledge Entries
Returns a JSON array of entries, newest first.
Create Knowledge Entry
Requires a named user. Returns the created entry with 201; source is always "user".
Get Knowledge Entry
Update Knowledge Entry
Requires a named user. Send at least one body field; omitted fields are unchanged and tags replaces the existing list. Returns the updated entry.
Archive Knowledge Entry
Requires a named user. Archives the entry (sets status to "archived") rather than deleting it, and returns the archived entry.
An unknown category or status returns 400; an entry outside the workspace returns 404.
Webhooks
Register HTTPS endpoints that Pensar pushes signed findings to as they are created, source-mapped, retested, and closed. The payload contract, signature-verification snippets, retry semantics, and endpoint requirements are documented on the Webhooks page; this section is the route reference.
The path is /webhook-endpoints, not /webhooks. /webhooks/* is reserved for inbound provider receivers and returns 404.
Subscribable event names are issue.created, issue.updated, issue.status_changed, retest.completed, and pentest.completed.
List Webhook Endpoints
Returns the workspace’s webhook endpoints, newest first. The signing secret is never included.
Response:
The health block is updated on every delivery attempt, so this single call tells you whether a receiver is healthy. At 20 consecutive failures the endpoint is disabled automatically.
Create Webhook Endpoint
Registers an endpoint and mints its signing secret. The URL must be HTTPS, must use a hostname rather than an IP literal, and must resolve exclusively to publicly routable addresses — see Endpoint requirements. Duplicate entries in events are collapsed.
Response (201):
secret is returned by this route and by no other. Store it before you close the response — it cannot be read back, and rotating means deleting and re-registering the endpoint.
A URL Pensar would refuse to fetch is a 400 naming the reason; a URL already registered in the workspace is a 409.
Get Webhook Endpoint
Returns the endpoint plus its 20 most recent delivery attempts.
Response:
Update Webhook Endpoint
Updates an endpoint. All body fields are optional, but at least one must be supplied — an empty body is a 400. A changed url is re-validated. Setting enabled to true resets consecutiveFailures to zero, so a re-enabled endpoint does not trip the auto-disable threshold on its next failure.
Response:
Delete Webhook Endpoint
Deletes the endpoint and, by cascade, its delivery history.
Response:
Test Webhook Endpoint
Queues a real delivery so you can verify the wiring — URL reachability, signature verification, and your 2xx — without waiting for a real finding. It renders the workspace’s most recent finding as an issue.created event; if the workspace has no findings, it sends a clearly-marked synthetic one instead (label VULN-SAMPLE, nil UUID for every id). The event is always issue.created, whatever the endpoint subscribes to.
Response (202):
sample is true when the synthetic event was sent. The delivery is queued, not yet sent — read the delivery log for the outcome. Testing a disabled endpoint is a 409.
List Webhook Deliveries
Returns delivery attempts for an endpoint, newest first. One record per attempt, including retries.
Response:
status is pending, success, or failed. id is the value that travelled as the X-Pensar-Delivery header, and responseBody is truncated to 2 KB.
Auth
Validate API Key
Validates the API key and returns the associated workspace. Useful for CLI tools to resolve workspace context from a stored API key.
Response:
Error Handling
The API returns standard HTTP status codes. Error responses include a JSON body with an error field:
Other resource-specific variants include "Issue not found or not in this workspace" and "Fix not found or not in this workspace".
Endpoints taking an :issueId distinguish a malformed reference from an unknown
one. A value that is neither a UUID nor a VULN-… label is rejected before the
lookup runs:
A well-formed reference that matches no issue in the workspace returns 404.