> ## Documentation Index
> Fetch the complete documentation index at: https://wiki.darknetsearch.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate and download assessment reports

> Find the report an assessment produces, generate more reports from a completed assessment, follow them until they finish and download them as a PDF bundle or JSON.

An Attack Surface assessment produces a report when it completes, without a call from you. A full assessment produces the Full Attack Surface Report. A targeted assessment produces the one report you chose when you started it.

From a completed assessment you can generate more reports: the same report in another language, format or branding, another risk report the assessment's modules support, an activity report for a calendar window, or a comparison with an earlier assessment of the same domain. Generating a report never uses an assessment allowance.

The flow is asynchronous: generate a report (or wait for the automatic one), poll the report list until its `status` is `completed`, then download it or [read its data](https://wiki.darknetsearch.com/api/guides/attack-surface-report-data.md).

> [!NOTE]
>
> Paths and fields call an assessment a "scan": `scan_uid` in a report is the `uid` of the assessment, as returned by [List assessments](https://client-api.leak.center/scalar-docs/#tag/attack-surface/GET/api/hackerview/scans/). To start one, see [Start an assessment](https://wiki.darknetsearch.com/api/guides/attack-surface-assessments.md).

### Availability and cost

**Availability:** Attack Surface Reports or Targeted Attack Surface Reports, included in every edition. Either feature opens every endpoint on this page.

**Cost:** no metered allowance. Generating, regenerating, listing and downloading reports use none of your Attack Surface Reports or Targeted Attack Surface Reports allowance. Only [starting an assessment](https://wiki.darknetsearch.com/api/guides/attack-surface-assessments.md) does. With an API key, each call that returns `2xx` counts once toward your monthly API Calls allowance, polls included. Any other status is refunded. See [Rate limits](https://wiki.darknetsearch.com/api/guides/rate-limits.md).

Without either feature, every call on this page returns `403`:

```json theme={"dark"}
{
  "detail": "This feature is not included in your plan."
}
```

A lapsed license returns the same message on these endpoints, not `"Your license is not active."`. If a call that used to work starts returning it, check your license first. See [Plans and quotas](https://wiki.darknetsearch.com/api/guides/plans-and-quotas).

These endpoints take no allowance unit and hold no capacity slot, so they never return the allowance `429` or a capacity `409`. A `429` with `"message": "Rate limit exceeded"` is the [rate limit](https://wiki.darknetsearch.com/api/guides/rate-limits.md).

### Reports produced automatically

When an assessment reaches `Completed`, its report is queued with `status: "pending"` and starts building about 3 minutes later. You don't need to call anything.

| Assessment | Report produced | Format |
| - | - | - |
| Full | Full Attack Surface Report (`asm_full`) | PDF |
| Targeted | The report named by `report_type` at start | PDF |

The automatic report uses the report options you sent when you started the assessment: `report_template_uid`, `report_language`, `report_baseline_scan_uid`, `report_privacy_gate` and `breach_primary_domain_only`. See [Report options](https://wiki.darknetsearch.com/api/guides/attack-surface-assessments.md#report-options).

* **Automatic reports are always PDF.** For JSON, [generate a report](#generate-a-report) with `"format": "json"`.
* **An unknown template stops the automatic report.** The template ID is not checked when the assessment starts. If `report_template_uid` does not match one of your organization's [templates](https://wiki.darknetsearch.com/api/guides/attack-surface-branding.md) when the report is queued, no report is created. Generate it yourself with a valid `template_uid`, or without one.
* **A cancelled assessment gets no report.** Reports need an assessment in `Completed`.
* **No data, no automatic report.** If the assessment lacks the data its report needs, no report is created. Examples: a targeted VIP Executive assessment that found no executives or employee email addresses, or a Breach Risk assessment whose credential exposure check did not complete. [Generate the report](#generate-a-report) yourself to get the reason in the error.

To find the automatic report, [list reports](#follow-a-report) newest first and match `scan_uid` to the assessment's `uid`.

### Report types

`report_type` picks the report. There are three kinds:

* **Snapshot reports** describe what one assessment found. A full assessment produces `asm_full`. A targeted assessment produces one of the other seven.
* **Activity reports** describe what became known during a calendar window you choose, in UTC.
* **Change Comparison** describes what changed between two assessments of the same domain.

| Report | `report_type` | Kind | Module flags it draws on |
| - | - | - | - |
| Full Attack Surface Report | `asm_full` | Snapshot | Every module that ran |
| Domain Spoofing Risk Report | `domain_spoofing_risk` | Snapshot | `dns_typosquatting_enabled` |
| VIP Executive Risk Report | `vip_executive_risk` | Snapshot | `vip_enumeration`, `email_enumeration` |
| Social Media Risk Report | `social_media_risk` | Snapshot | `social_media_enabled` |
| Application Security Report | `application_security` | Snapshot | `active_vuln_scan` |
| Breach Risk Report | `breach_risk` | Snapshot | `email_enumeration` |
| Infrastructure Risk Report | `infrastructure_risk` | Snapshot | None required. `perform_port_scan`, `active_dns_enumeration` and `dns_posture_enabled` add to it. |
| Exposed Assets Report | `exposed_assets_risk` | Snapshot | `code_exposure_enabled` and `tg_discord_paste_search` |
| Breach Activity Report | `breach_activity` | Activity | None |
| Phishing Activity Report | `phishing_activity` | Activity | None |
| Exposed Assets Activity Report | `exposed_assets_activity` | Activity | `tg_discord_paste_search` |
| Change Comparison | `delta_comparison` | Comparison | Depends on `comparison_scope`. See [Comparison reports](#comparison-reports). |

The flags are the module flags you set when you [start an assessment](https://wiki.darknetsearch.com/api/guides/attack-surface-assessments.md). A targeted assessment switches its report's flags on for you. A report you generate is built from what the assessment collected. When the data it needs is missing, the call returns `400` or `409` with an `error` that says what is missing.

From a full assessment started with the default flags, you can generate every snapshot report except Social Media and Exposed Assets, because `social_media_enabled` and `code_exposure_enabled` are off by default, and all three activity reports. Its Application Security Report has no verified vulnerability findings, because `active_vuln_scan` is off by default.

### Generate a report

[`POST /api/hackerview/reports/`](https://client-api.leak.center/scalar-docs/#tag/attack-surface/POST/api/hackerview/reports/) generates a report from a completed assessment.

```bash theme={"dark"}
curl https://client-api.leak.center/api/hackerview/reports/ \
  -X POST \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scan_uid": "5b0e7a3c-1d2f-4e6a-9b8c-7d3e2f1a0b9c",
    "format": "json",
    "language": "de",
    "template_uid": "2c4e6a8b-0d1f-4a3c-8e5b-7f9a1c3e5d7b"
  }'
```

| Field | Value | Default | What it does |
| - | - | - | - |
| `scan_uid` | UUID | Required | The completed assessment to report on |
| `report_type` | See [Report types](#report-types) | `asm_full` | Which report to build |
| `format` | `pdf` or `json` | `pdf` | `pdf` gives a ZIP with the PDF and its CSV evidence. `json` gives the report as one JSON file. See [Download a report](#download-a-report). |
| `language` | `en`, `de`, `fr`, `it` or `es` | `en` | The report language. It is not taken from the assessment, so send it every time you want a language other than English. |
| `template_uid` | UUID | None | A [report template](https://wiki.darknetsearch.com/api/guides/attack-surface-branding.md). Omit it for the built-in DarknetSearch branding. |
| `baseline_scan_uid` | UUID or `"auto"` | None | On a snapshot report, adds a comparison with another completed assessment of the same domain. `"auto"` picks the most recent other completed assessment of that domain, and skips the comparison without an error when there is none. Required for `delta_comparison`. Refused on activity reports. |
| `breach_primary_domain_only` | `true` or `false` | The assessment's own setting | `true` limits the credential and employee-email chapters to the assessed domain. It applies to `asm_full` and `vip_executive_risk`. `breach_risk` is always limited to the assessed domain. |
| `period_month`, `period_start`, `period_end`, `allow_partial_period` | See [Activity reports](#activity-reports) | None | The reporting window. Activity and comparison reports only. |
| `comparison_scope` | See [Comparison reports](#comparison-reports) | None | What a comparison covers. `delta_comparison` only. |

> [!WARNING]
>
> **Omit the fields a report type does not take.** A period field or `comparison_scope` on a report type that does not use it returns `400`, even when you send it as `null`, an empty string or `false`. The API refuses the field rather than ignore it, so a report never appears to cover a window or scope it does not.

The call returns `201` with the new report:

```json theme={"dark"}
{
  "uid": "8d1f5c2a-3b7e-4c9d-a1f0-6e2b4c8d9a10",
  "scan_uid": "5b0e7a3c-1d2f-4e6a-9b8c-7d3e2f1a0b9c",
  "scan_domain": "acme.com",
  "template_uid": "2c4e6a8b-0d1f-4a3c-8e5b-7f9a1c3e5d7b",
  "baseline_scan_uid": null,
  "format": "json",
  "report_type": "asm_full",
  "input_target": null,
  "language": "de",
  "status": "pending",
  "period_start": null,
  "period_end": null,
  "allow_partial_period": false,
  "comparison_scope": null,
  "breach_primary_domain_only": false,
  "privacy_gate": false,
  "regeneration_required": false,
  "file_size_bytes": null,
  "page_count": null,
  "sections_included": [],
  "error_message": null,
  "download_url": null,
  "created_at": "2026-10-08T09:30:12.418203Z",
  "completed_at": null
}
```

Keep the `uid`. You need it to download the report and read its data.

**An identical request returns the report already in progress.** If a report with the same assessment, type, format, language, template, baseline, period, scope and `breach_primary_domain_only` is still `pending` or `generating`, the call returns that report with `201` instead of starting a second one. Once it has finished, the same request builds a new report.

**Language.** In German, French, Italian or Spanish, the narrative, explanations and table headings are translated while the report builds. Host names, IP addresses, URLs, CVE IDs and other identifiers stay as they are. A passage that cannot be translated stays in English.

### Activity reports

An activity report covers a window you choose. Send the window in one of two forms, never both:

* **`period_month`**: one calendar month, as `YYYY-MM`.
* **`period_start` and `period_end`**: both dates, as `YYYY-MM-DD`. Both days are included.

Windows are in UTC, run up to 366 days and cannot start in the future.

An activity report reads what an assessment collected, so the assessment must cover the window:

* **The assessment must complete after the window starts.** An assessment that completed before the window began is refused with `409` and `code: "scan_predates_period"`.
* **An assessment that completed inside the window covers part of it.** It is refused with `409` and `code: "period_partially_covered"`. Send `"allow_partial_period": true` to accept it. The report then states the range it covers.
* **No baseline.** An activity report already compares the window with the time outside it, so `baseline_scan_uid` returns `400`.

For a monthly report, run an assessment after the month has ended, then ask for that month:

```bash theme={"dark"}
curl https://client-api.leak.center/api/hackerview/reports/ \
  -X POST \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scan_uid": "5b0e7a3c-1d2f-4e6a-9b8c-7d3e2f1a0b9c",
    "report_type": "breach_activity",
    "period_month": "2026-09"
  }'
```

The Breach Activity Report dates each record by when it became known, not by when the original compromise happened.

### Comparison reports

A Change Comparison (`delta_comparison`) lists what is new, changed and gone between two assessments of the same domain, and what could not be compared. It needs two more fields:

* **`comparison_scope`**: what to compare. One of `breach_risk`, `domain_spoofing_risk` or `exposed_assets_risk`. Any other value returns `400` with the supported values.
* **`baseline_scan_uid`**: the earlier assessment, or `"auto"` to pick the most recent eligible one.

Both assessments must be of the same domain and in `Completed`, and the baseline must have completed first. Both must also have run the modules the scope needs:

| `comparison_scope` | Both assessments must have run |
| - | - |
| `breach_risk` | Nothing extra |
| `domain_spoofing_risk` | `dns_typosquatting_enabled` |
| `exposed_assets_risk` | `code_exposure_enabled` and `tg_discord_paste_search` |

```bash theme={"dark"}
curl https://client-api.leak.center/api/hackerview/reports/ \
  -X POST \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scan_uid": "5b0e7a3c-1d2f-4e6a-9b8c-7d3e2f1a0b9c",
    "report_type": "delta_comparison",
    "comparison_scope": "breach_risk",
    "baseline_scan_uid": "auto"
  }'
```

* **`"auto"` with no eligible assessment** returns `409` with `code: "no_comparable_baseline"`.
* **A period is optional.** `period_month`, or `period_start` and `period_end`, only titles the report and makes `"auto"` pick a baseline that completed on or before the period start. It does not filter what is compared. `allow_partial_period` returns `400`.

### Follow a report

[`GET /api/hackerview/reports/`](https://client-api.leak.center/scalar-docs/#tag/attack-surface/GET/api/hackerview/reports/) lists your organization's reports, automatic and generated. There is no endpoint for a single report, so poll this list and find your report by `uid`.

```bash theme={"dark"}
curl -G https://client-api.leak.center/api/hackerview/reports/ \
  -H "X-API-Key: YOUR_API_KEY" \
  --data-urlencode "report_type=asm_full" \
  --data-urlencode "page_size=20"
```

| Parameter | Values | Default |
| - | - | - |
| `status` | `pending`, `generating`, `completed` or `failed`. Any other value is ignored. | All |
| `report_type` | Any `report_type`, matched exactly | All |
| `sort_by` | `recent` (creation time), `domain` (assessed domain) or `topic` (report type). Any other value falls back to `recent`. | `recent` |
| `order` | `asc` or `desc` | `desc` for `recent`, `asc` for `domain` and `topic` |
| `page`, `page_size` | `page_size` up to `100` | `1`, `10` |

The list has no filter for one assessment. List newest first and match `scan_uid`. Page by raising `page` until `next` is `null`. The `next` and `previous` URLs point at an internal address, so don't call them.

Poll every 30 seconds or slower. A report moves through these statuses:

| `status` | Meaning |
| - | - |
| `pending` | Queued. Automatic reports wait about 3 minutes here. |
| `generating` | Being built |
| `completed` | Ready to download and read |
| `failed` | Final. `error_message` says why. [Generate it again](#regenerate-a-report) to retry. |

Each report in the list has the same fields as the response to [Generate a report](#generate-a-report):

* **`uid`**: the report ID, for the download and data calls.
* **`scan_uid`**, **`scan_domain`**: the assessment and its domain.
* **`report_type`**, **`format`**, **`language`**, **`template_uid`**, **`baseline_scan_uid`**: what you asked for.
* **`period_start`**, **`period_end`**, **`allow_partial_period`**, **`comparison_scope`**: the window of an activity or comparison report, as dates in UTC, and the scope of a comparison.
* **`breach_primary_domain_only`**: the scope the report was actually built with, after the assessment's own setting is applied.
* **`status`**, **`error_message`**, **`created_at`**, **`completed_at`**: progress.
* **`file_size_bytes`**: set once the report is complete.
* **`sections_included`**: the report's chapters, set once it is complete. It stays `[]` on a JSON-format full report and on the JSON format of the risk reports built on it.
* **`page_count`**: always `null`.
* **`privacy_gate`**, **`regeneration_required`**: whether the report was built under the privacy gate, and whether it must be [regenerated](#regenerate-a-report) before you can read it.
* **`download_url`**: an internal address. Don't call it. Use [Download a report](#download-a-report).

### Download a report

[`GET /api/hackerview/reports/{report_uid}/download/`](https://client-api.leak.center/scalar-docs/#tag/attack-surface/GET/api/hackerview/reports/\{report_uid}/download/) streams the file of a completed report.

```bash theme={"dark"}
curl --fail -OJ https://client-api.leak.center/api/hackerview/reports/8d1f5c2a-3b7e-4c9d-a1f0-6e2b4c8d9a10/download/ \
  -H "X-API-Key: YOUR_API_KEY"
```

`-OJ` saves the file under the name in the `Content-Disposition` header. `--fail` keeps curl from saving an error body as the file.

| `format` | You receive | `Content-Type` | File name |
| - | - | - | - |
| `pdf` | A ZIP with the PDF report and its CSV evidence files. Activity and comparison reports add the report as a JSON file. | `application/zip` | `hackerview_report_<domain>_<scan_uid>.zip` |
| `json` | The whole report as JSON, the same data the [report data endpoints](https://wiki.darknetsearch.com/api/guides/attack-surface-report-data.md) read | `application/json` | `hackerview_report_<domain>_<scan_uid>.json` |

* **CSV files use `;` as the separator** and are UTF-8 with a byte order mark, so spreadsheet programs open them with accents intact.
* **The file name comes from the assessment, not the report.** Every report of one assessment downloads under the same name. When you download several, save each under your own name with `-o`.
* **Errors come back as JSON**, never as a file. See [Errors](#errors).

### Regenerate a report

A finished report does not change. Generate it again when:

* You changed [exclusion rules](https://wiki.darknetsearch.com/api/guides/attack-surface-exclusions.md). Rules are applied when a report is built, so a new report picks up your changes without a new assessment.
* You changed the [template](https://wiki.darknetsearch.com/api/guides/attack-surface-branding.md) it uses.
* It ended `failed`.
* Reading or downloading it returns `409` with `code: "report_regeneration_required"`, or `404` with `code: "report_data_missing"` or `"report_file_missing"`.

Send [Generate a report](#generate-a-report) again with the same `scan_uid` and options. It uses no assessment allowance. The earlier report stays in the list. Include rules are the exception: they add hosts while an assessment runs, so they need a new assessment.

**Privacy gate.** An assessment started with `"report_privacy_gate": true` builds its automatic reports under the privacy gate: each report is checked for leftover secret material before it is stored, and fails if a check trips. Reports you generate with this endpoint use the platform default, which is off. `privacy_gate` in the report tells you which applied. A gated report can later return `409` `report_regeneration_required` when it no longer meets the current check. Generate it again.

### Errors

Errors from report generation pass through from the assessment service with their own keys: `error`, and often a machine-readable `code`. Branch on `code` when it is present. Field errors use the field name as the key.

| Status | When | Body |
| - | - | - |
| `400` | A field is missing or invalid, such as an unknown `report_type`, a non-UUID `scan_uid`, or a field the report type does not take | Field errors, such as `{"report_type": ["Unknown report_type \"x\". ..."]}` |
| `400` | The window is malformed, reversed, longer than 366 days or starts in the future | `{"code": "...", "error": "..."}` with `code` `period_invalid_format`, `period_reversed`, `period_too_long` or `period_future` |
| `400` | A report was asked for before the assessment completed. Activity, comparison and Domain Spoofing reports answer `409` instead, with `code: "scan_not_completed"` on activity and comparison reports. A Domain Spoofing Risk Report can already be generated while the assessment is `Finalizing`. | `{"error": "Scan must be Completed to generate a report (current: Running)."}` |
| `400` | A comparison baseline is the same assessment, another domain, or not older | `{"code": "...", "error": "..."}` with `code` `baseline_same_scan`, `baseline_domain_mismatch` or `baseline_not_older` |
| `400` | You download a report that is not `completed` | `{"error": "Report not ready for download."}` |
| `400` | The assessment collected no data for the report, such as a VIP Executive Risk Report without executive or employee-email results | `{"error": "..."}` saying what is missing |
| `403` | Your plan has neither Attack Surface feature, or your license is not active | See [Availability and cost](#availability-and-cost) |
| `404` | No assessment, template or baseline with that ID in your organization | `{"error": "Scan <uid> not found for this organisation."}`, `{"error": "Template <uid> not found."}` or `{"error": "Baseline scan <uid> not found or not comparable."}` |
| `404` | No report with that `uid` | `{"error": "Report not found."}` |
| `404` | The report's stored file or data is missing | `{"code": "...", "error": "..."}` with `code` `report_file_missing` or `report_data_missing`. Regenerate the report. |
| `409` | The assessment is still finalizing, or a slower module is still running | `{"error": "..."}`, such as `"Vulnerability verification is still running."`. Retry when it completes. |
| `409` | The assessment did not run a module the report needs, or that module did not complete | `{"error": "..."}` saying what is missing, often naming the module flag |
| `409` | `breach_primary_domain_only` is in effect on a snapshot report, such as `asm_full` or `vip_executive_risk`, and the assessed domain's scope could not be established | `{"error": "Primary-domain scope could not be established; report creation was refused.", "code": "primary_domain_scope_failed"}`. Send `"breach_primary_domain_only": false`. A Breach Risk Report always needs the scope: it refuses the same case with a `409` that has no `code`. |
| `409` | An activity or comparison report cannot be built from this assessment | `{"code": "...", "error": "...", ...}`. See the codes below. |
| `409` | The report must be regenerated before you can download it | `{"code": "report_regeneration_required", "error": "...", "regeneration_required": true}` |
| `429` | The per-second rate limit or the API Calls allowance | See [Rate limits](https://wiki.darknetsearch.com/api/guides/rate-limits.md) |
| `502`, `504` | The assessment service failed or timed out | See [Errors](https://wiki.darknetsearch.com/api/guides/errors.md#500-502-503-and-504) |

The `409` codes for activity and comparison reports:

| `code` | Meaning | What to do |
| - | - | - |
| `scan_not_completed` | An assessment is not in `Completed` | Wait for it to complete |
| `source_flags_not_enabled` | An assessment did not run a module the report needs. The body lists `required_flags`. | Start an assessment with those flags on |
| `scan_predates_period` | The assessment completed before the window started | Use an assessment that completed after the window started |
| `period_partially_covered` | The assessment completed inside the window | Send `"allow_partial_period": true`, or use a later assessment |
| `period_no_covered_interval` | The collected data does not reach into the window | Use a later assessment |
| `modules_not_terminal`, `modules_not_successful`, `coverage_truncated`, `coverage_inconsistent`, `partial_period_freshness_unproven` | The assessment's data cannot prove it covers the window completely | Run a new assessment |
| `scan_not_eligible`, `adapter_version_mismatch`, `scan_completion_time_missing` | The assessment predates what this report needs | Run a new assessment |
| `no_comparable_baseline` | `"auto"` found no earlier eligible assessment | Run another assessment later, then compare |

### Endpoints

| Action | Endpoint |
| - | - |
| Generate a report | [`POST /api/hackerview/reports/`](https://client-api.leak.center/scalar-docs/#tag/attack-surface/POST/api/hackerview/reports/) |
| List reports and follow their status | [`GET /api/hackerview/reports/`](https://client-api.leak.center/scalar-docs/#tag/attack-surface/GET/api/hackerview/reports/) |
| Download a report | [`GET /api/hackerview/reports/{report_uid}/download/`](https://client-api.leak.center/scalar-docs/#tag/attack-surface/GET/api/hackerview/reports/\{report_uid}/download/) |
| List assessments, to find `scan_uid` | [`GET /api/hackerview/scans/`](https://client-api.leak.center/scalar-docs/#tag/attack-surface/GET/api/hackerview/scans/) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.