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

# Get scan

> Fetch a scan's full result, including the triage report.

Fetch the full record for a scan, including the triage report once the
scan has completed.

```http theme={null}
GET /api/v2/scans/{scan_id}
```

## Path parameters

<ParamField path="scan_id" type="string" required>
  The `scan_id` returned by
  [`POST /api/v2/scan/deep`](/api/scans/create) (or `/scan/quick`) or
  one of the per-profile ids from a batch.
</ParamField>

## Response

The response is the scan record. Fields you should rely on:

<ResponseField name="profile_id" type="string" required>
  Same value as `scan_id`. Tumban tracks scans and profile records
  under the same identifier.
</ResponseField>

<ResponseField name="batch_id" type="string" required>
  The batch id. Single scans (submitted via
  [`POST /api/v2/scan/deep`](/api/scans/create) or `/scan/quick`) get a
  synthetic `single_<scan_id>` batch id for internal bookkeeping; match
  on the `single_` prefix to distinguish single-scan submissions from
  batched ones.
</ResponseField>

<ResponseField name="url" type="string" required>
  The submitted profile URL.
</ResponseField>

<ResponseField name="status" type="string" required>
  See [Status values](/reference/status).
</ResponseField>

<ResponseField name="scan_mode" type="string">
  Which mode the scan ran in: `deep` or `quick`. A quick scan skips
  link traversal, social-media scraping, and external context search;
  the [`coverage`](/concepts/coverage) object on the triage report
  reflects the skipped steps. May be absent on legacy scans submitted
  before scan modes existed (treat absent as `deep`).
</ResponseField>

<ResponseField name="created_at" type="string" required>
  ISO 8601 UTC timestamp of when the scan was submitted.
</ResponseField>

<ResponseField name="processing_started_at" type="string">
  When pipeline processing actually began. `null` while waiting for a
  concurrency slot.
</ResponseField>

<ResponseField name="processing_completed_at" type="string">
  When pipeline processing finished. `null` while still processing.
</ResponseField>

<ResponseField name="is_banned" type="boolean">
  Optional. **Absent** until the ban-checker has evaluated this profile
  — the key is omitted (not `null`) on fresh scans — then `true` if the
  creator has been banned by the upstream platform (e.g. 404 / 410 /
  redirect on the profile URL) or `false` if the profile is still live.
  Test for it with `"is_banned" in response`, not
  `response["is_banned"] is None`. Useful for skipping enforcement on
  already-banned creators.
</ResponseField>

<ResponseField name="callback_url" type="string">
  The callback URL the scan was submitted with (or the org default if
  not provided), as a string. Empty when no callback was configured.
</ResponseField>

<ResponseField name="metadata" type="object">
  The metadata you submitted with the scan, echoed back unchanged.
</ResponseField>

<ResponseField name="error" type="string">
  Present when `status` is `failed`. Brief description of what went
  wrong.
</ResponseField>

<ResponseField name="webhook_delivered_at" type="string">
  ISO 8601 UTC timestamp of successful webhook delivery. Absent when
  no callback was configured or delivery has not (yet) succeeded.
</ResponseField>

<ResponseField name="triage_report" type="object">
  Present once `status` reaches a terminal value with a usable result
  (`completed`). Shape below.
</ResponseField>

### `triage_report`

<ResponseField name="recommendation" type="string" required>
  One of `no_flags`, `review_low`, `review_medium`, `review_high`. See
  [Recommendation values](/reference/recommendation).
</ResponseField>

<ResponseField name="risk_score" type="integer" required>
  0–100. See [Recommendations](/concepts/recommendations).
</ResponseField>

<ResponseField name="confidence" type="string" required>
  `low`, `medium`, or `high`. See [Confidence](/concepts/confidence).
</ResponseField>

<ResponseField name="reason_codes" type="string[]" required>
  Machine-readable codes explaining the decision. See
  [Reason codes](/reference/reason-codes).
</ResponseField>

<ResponseField name="reason_summary" type="string" required>
  Human-readable summary of what was found.
</ResponseField>

<ResponseField name="review_targets" type="string[]" required>
  URLs your reviewers should look at first.
</ResponseField>

<ResponseField name="link_chain" type="string" required>
  Short label describing the path Tumban followed (e.g.
  `Profile -> External site`).
</ResponseField>

<ResponseField name="evidence_index" type="object[]">
  Per-URL evidence Tumban cited in support of the decision. See
  [Evidence index](/concepts/evidence-index).
</ResponseField>

## Coverage

Poll responses include the [`coverage`](/concepts/coverage) object too
— it lives at `triage_report.coverage` on `GET /api/v2/scans/{scan_id}`.
On the [webhook payload](/webhooks/payload) the same object is delivered
as a top-level `coverage` key. Same fields either way; only the
placement differs. The scan's `status` is `completed` even when
individual steps were skipped — read `coverage` to see what ran.

## Example

```bash theme={null}
curl https://api-v2.tumban.com/api/v2/scans/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_xxx"
```

```json theme={null}
{
  "profile_id": "550e8400-e29b-41d4-a716-446655440000",
  "batch_id": "single_550e8400-e29b-41d4-a716-446655440000",
  "url": "https://creator.example/username",
  "status": "completed",
  "scan_mode": "deep",
  "created_at": "2026-04-29T12:00:00.123456+00:00",
  "processing_started_at": "2026-04-29T12:00:01.234567+00:00",
  "processing_completed_at": "2026-04-29T12:01:38.987654+00:00",
  "is_banned": false,
  "callback_url": "https://your-app.example/webhooks/tumban",
  "metadata": {"reviewer_id": "rv_42"},
  "webhook_delivered_at": "2026-04-29T12:01:39.345678+00:00",
  "triage_report": {
    "recommendation": "review_high",
    "risk_score": 85,
    "confidence": "high",
    "reason_codes": ["PROHIBITED_DOMAIN", "ADULT_KEYWORDS"],
    "reason_summary": "Direct link to a prohibited platform combined with adult keywords in bio.",
    "review_targets": ["https://prohibited-platform.example/username"],
    "link_chain": "Profile -> External site",
    "evidence_index": [
      {
        "ref": "link_1",
        "url": "https://prohibited-platform.example/username",
        "type": "traversed_link",
        "domain": "prohibited-platform.example"
      }
    ]
  }
}
```

<Note>
  The response also includes internal fields not listed above. These
  reflect the underlying scan record and are subject to change. Do not
  rely on their names, types, or presence; treat them as opaque.
</Note>

## Errors

| Status | Detail                                                                  |
| ------ | ----------------------------------------------------------------------- |
| 404    | `Scan not found` — the scan id does not exist within your organization. |

## Using the dashboard

Click any non-processing scan row from the **Home** page or the
**Scan** page to open the scan detail. The page renders these
read-only sections:

* **Summary** — profile URL, scan id, status, submitted/completed
  timestamps, webhook delivery timestamp, error.
* **Triage** — recommendation badge, risk score, confidence, reason
  codes, reason summary.
* **Coverage** — which analysis steps ran, login-blocked URLs,
  referrer match counts.
* **Raw JSON** — collapsible *"Show full document"* panel that
  exposes the entire scan record.
