> For the complete documentation index, see [llms.txt](https://docs.nullify.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nullify.ai/api-reference/api-reference/bug-hunts.md).

# Bug Hunts API

Bug Hunts assess an authorized external attack surface. Read the [scope and authorization guide](/capabilities/bug-hunts.md) before starting a scan. All endpoints live under `/dast/bughunt` on `https://api.<TENANT>.nullify.ai`.

A tenant has one Bug Hunt configuration (scope, suites, intensity) and any number of scans. Scans run asynchronously: starting one returns a `scanId`, and you poll the status operation until `terminal` is `true`. Only one Bug Hunt scan can be active for a tenant at a time; starting another while one holds the lock returns 409.

## Configure scope

### Get the configuration

Returns the tenant's Bug Hunt configuration. If none exists yet, a default is created and returned: enabled, empty scope, low intensity, and the `dns_enumeration`, `port_scan`, `web_fingerprint` and `nuclei_scan` suites on a daily cadence. The default has an empty `scope.include`, which the validate operation rejects, so add scope before saving. Requires configuration read permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/config" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Update the configuration

Replaces the tenant's configuration with the `config` object in the body (`enabled`, `scope`, `vulnClasses`, `suites`, `credentials` and `intensity` are required; `cadence`, `rulesOfEngagement`, `scopeProvenance` and `ownershipProof` are optional). The server validates it with the same rules as the validate operation and returns 400 with the first error if it fails. The scope version increments only when the include or exclude selectors actually change, and `roEAcknowledged: true` stamps the acknowledgement time. Requires configuration write permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/config" method="put" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Validate a configuration

Checks a proposed `config` without saving it and returns a list of typed errors with a `path` and `message` each; an empty list means it passed. It reports an empty `scope.include`, unknown intensity, suite, vulnerability class or selector kinds, duplicate suite or vulnerability-class rules, include and exclude selectors that overlap, forbidden targets such as loopback, private (RFC 1918), link-local or cloud metadata addresses in `scope.include`, and enabled authenticated suites with no credential binding. It also checks the top-level cadence value and suite overrides, but not rules of engagement or ownership proof.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/config/validate" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Preview a bug-bounty program import

Fetches a public HackerOne or Bugcrowd program page and returns the parsed `scope`, `rulesOfEngagement` and `scopeProvenance` for you to review. Send `programType` (`hackerone` or `bugcrowd`) and `programUrl`. This is preview only and saves nothing: inspect the domains, exclusions and program rules, then save what you accept with the update-configuration operation. A public program's existence is not blanket permission to test every related host. An unsupported type, empty URL or a program that cannot be fetched or parsed returns 400. Requires configuration write permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/program/import" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

## Run scans

### Start a scan

Starts a scan and returns its `scanId`. Send `intensity` (`low`, `medium` or `high`) and a `scope` with `include` and `exclude` selectors. If you send an empty scope, the stored configuration's scope is used; if you send a non-empty scope, it is used for this scan and also written to the tenant's configuration (keeping the stored suites, vulnerability classes and credential bindings). The call returns 409 if the tenant already has an active Bug Hunt scan, and 500 if the scan could not be started, in which case the scan is recorded as failed. Starting a scan is asynchronous: poll the status operation. Requires finding write permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"intensity": "low", "scope": {"include": [], "exclude": []}}' \
  'https://api.<TENANT>.nullify.ai/dast/bughunt/scans?githubOwnerId=1234'
```

### Run a single suite now

Starts an ad-hoc scan that runs only the suite named in the path (for example `port_scan` or `nuclei_scan`) against the stored configuration's scope, and returns the new `scanId`. An unknown suite returns 400. The optional `intensity` body field overrides the stored intensity, which itself defaults to `medium` if unset. Like a normal scan it is exclusive: it returns 409 while another Bug Hunt scan is active. Poll the returned scan with the status operation. Requires finding write permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/suites/{suite}/run-now" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### List scans

Lists the tenant's scans. With no query parameters it returns every scan. With `limit` (default 50, maximum 1000), `offset`, `nextToken` or `search` (text match over scan id, status and targets) it returns a page with `totalCount`; pass the returned `nextToken` for the next page (see [pagination](/api-reference/api-reference/pagination.md)). An invalid `nextToken` returns 400.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Get a scan

Returns one scan by `scanId`, which comes from the list or start operations. It returns 404 if the id does not exist for your tenant.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans/{scanId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Poll scan status

Returns a lightweight progress view: `status`, `stage`, `progress`, `currentActivity`, `findingsCount`, `lastActivityAt`, token cost and a short per-phase list. It reads cheap database columns rather than the full scan record, so it is the call to poll on a short interval. `terminal` becomes `true` when the scan reaches a final state (`completed`, `cancelled`, `failed`, `aborted_config_error` or `skipped`). Returns 404 for an unknown scan.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans/{scanId}/status" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Get scan activity

Returns the scan's activity log for live progress displays. `limit` defaults to 50 and is clamped to 200. When a page is full the response carries `nextCursor`; pass it back as `since_id` to fetch only newer entries. An empty list means nothing has been logged yet.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans/{scanId}/activity" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Stop a scan

Requests cancellation of a scan. A scan that is in progress moves to `stopping` and then `cancelled`; a scan already in a final state is left unchanged and the call still succeeds, so check the status operation rather than the `success` flag to confirm the outcome. Returns 404 for an unknown scan. Requires finding write permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans/{scanId}/stop" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Get a scan's network log

Returns a short-lived presigned `downloadURL` for the scan's sub-agent network log. Download it promptly because the URL expires. It returns 404 if the scan does not exist or no log has been written yet.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans/{scanId}/logs" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### List a scan's findings

Lists the findings recorded for one scan. With no parameters it returns all of them; with `limit` (default 50, maximum 1000) or `offset` it returns a page and `totalCount`.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans/{scanId}/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Compare two scans

Compares the scan in the path with an older scan given in the required `against` query parameter and returns four lists: `new`, `reopened`, `unchanged` and `resolved`. Both scans must belong to your tenant and have status `completed`; otherwise the call returns 400 (or 404 for an unknown id). Each scan is limited to 5,000 findings, beyond which the call returns a resource-exhausted error. Comparing a scan with itself returns empty lists.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/scans/{scanId}/diff" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### List suite runs

Intended to return per-suite last-run state (`suite`, `lastRunAt`, `lastRunId`). In the current implementation the handler always returns an empty `runs` list, so do not rely on it to learn when a suite last ran. Use the scan list and scan detail operations instead.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/suite-runs" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

## Findings

### List findings

Lists every Bug Hunt finding for the tenant, across scans, in one response with no pagination. For a single scan use the scan findings operation.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Get a finding

Returns one finding by `findingId`. It returns 404 when the id does not exist for your tenant.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/findings/{findingId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Get a finding's triage

Returns how Nullify triaged the finding, along with the vulnerability-management framework mappings derived from it. It returns 404 when the finding does not exist for your tenant.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/findings/{findingId}/triage" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Get a finding's events

Returns the finding's event history (`id`, `time`, `type`, `data`), such as state changes.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/findings/{findingId}/events" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Allowlist or unallowlist a finding

Sets whether the finding is allowlisted. Send `allow` (`true` to allowlist, `false` to restore) and a `reason`; both are required. Unlike the POST allowlist routes in the Pentests API, Bug Hunt uses PATCH with a single toggle. Allowlisting closes any linked ticket, and restoring a finding that is otherwise still open reopens or recreates its ticket; ticket-sync failures are logged and do not fail the call. Requires finding write permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/findings/{findingId}/allowlist" method="patch" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X PATCH \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"allow": true, "reason": "Accepted risk on staging host"}' \
  'https://api.<TENANT>.nullify.ai/dast/bughunt/findings/01J6EEXK3NKYKWW9XTPQYAF41N/allowlist?githubOwnerId=1234'
```

### Create a ticket

Creates a ticket for the finding in the tenant's configured ticket integration. The optional body takes `message`, `assignees` and `project`. It can return 404 for an unknown finding and failed-precondition or unimplemented errors when no usable integration is configured.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/findings/{findingId}/ticket" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

### Delete a ticket

Closes the finding's ticket on the integration and clears Nullify's link to it. It is idempotent: a finding with no ticket still returns success. Requires finding write permission.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/bughunt/findings/{findingId}/ticket" method="delete" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-a96d8165240befab9d431c87b16a7387b6914215%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

## Published operation index

This index comes from the [published specification](/api-reference/api-reference/openapi-spec.md). Check its request and response schemas before calling an operation; deployment versions and feature availability can differ.

| Method   | Path                                           | Operation                         |
| -------- | ---------------------------------------------- | --------------------------------- |
| `GET`    | `/dast/bughunt/config`                         | Get BugHunt Config                |
| `PUT`    | `/dast/bughunt/config`                         | Put BugHunt Config                |
| `POST`   | `/dast/bughunt/config/validate`                | Validate BugHunt Config           |
| `GET`    | `/dast/bughunt/findings`                       | Get BugHunt Findings              |
| `GET`    | `/dast/bughunt/findings/{findingId}`           | Get BugHunt Finding               |
| `PATCH`  | `/dast/bughunt/findings/{findingId}/allowlist` | Allowlist BugHunt Finding         |
| `GET`    | `/dast/bughunt/findings/{findingId}/events`    | Get BugHunt Finding Events        |
| `DELETE` | `/dast/bughunt/findings/{findingId}/ticket`    | Delete Ticket for BugHunt Finding |
| `POST`   | `/dast/bughunt/findings/{findingId}/ticket`    | Create Ticket for BugHunt Finding |
| `GET`    | `/dast/bughunt/findings/{findingId}/triage`    | Create Interactor \[]             |
| `POST`   | `/dast/bughunt/program/import`                 | Preview BugHunt Program Import    |
| `GET`    | `/dast/bughunt/scans`                          | Get BugHunt Scans                 |
| `POST`   | `/dast/bughunt/scans`                          | Start BugHunt Scan                |
| `GET`    | `/dast/bughunt/scans/{scanId}`                 | Get BugHunt Scan                  |
| `GET`    | `/dast/bughunt/scans/{scanId}/activity`        | Get BugHunt Scan Activity         |
| `GET`    | `/dast/bughunt/scans/{scanId}/diff`            | Get BugHunt Scan Diff             |
| `GET`    | `/dast/bughunt/scans/{scanId}/findings`        | Get BugHunt Scan Findings         |
| `GET`    | `/dast/bughunt/scans/{scanId}/logs`            | Get BugHunt Sub-Agent Network Log |
| `GET`    | `/dast/bughunt/scans/{scanId}/status`          | Get BugHunt Scan Status           |
| `POST`   | `/dast/bughunt/scans/{scanId}/stop`            | Stop BugHunt Scan                 |
| `GET`    | `/dast/bughunt/suite-runs`                     | Get BugHunt Suite Runs            |
| `POST`   | `/dast/bughunt/suites/{suite}/run-now`         | Run BugHunt Suite Now             |
