---
title: "Scan a target and return a verdict bound to its content hash."
method: POST
path: "/v1/scan"
---

# Scan a target and return a verdict bound to its content hash.

`POST /v1/scan`

## Request

- Base URL: `https://lazaretto.dev`
- URL: `https://lazaretto.dev/v1/scan`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP x402

Auth: X-API-Key, OR x402 payment (when enabled). Without valid auth the paid path returns 402 with payment requirements. An `error` verdict returns HTTP 422 and is never billed (§9.5).

## Request body

- ScanRequest
  - `target` object, required
    - `type` 'inline' | 'raw_url' | 'npm_package' | 'pypi_package' | 'github_repo' | 'clawhub_skill' | 'mcp_server' | 'mcp_tools', required
    - `ref` string: Identifier for non-inline targets.
    - `content` string: Literal content for type=inline.
    - `name` string: Filename for type=inline, for example SKILL.md or index.js. Which rules run depends on the kind of file, so send it when you have it; without it the kind is inferred from the content.
  - `depth` 'lookup' | 'full': lookup = hash + IOC match only; full = + behavioral rules.

## Response `200`

A verdict (clear/flagged/malicious). x402 only: the signed `attestation` comes back, and is published, only when the payment settled. When the payment facilitator did not answer clearly, the result still comes back, with no `attestation` and a `payment` block {outcome:"unknown", detail, contact, time}. Whether that payment settled is unknown (settlement_outcome_unknown). Do not pay again for the scan; if you were charged, nothing more is owed. Email contact@lazaretto.dev with the paying wallet address and the `time` if in doubt.

- ScanReport
  - `scan_id` string
  - `target_hash` string: Hash of exactly what was analyzed. EMPTY STRING when no bytes were read: an npm package that was taken down from the registry is still reported malicious on package IDENTITY, and there is no content to bind that claim to. Check for the empty string before using this in a /report/ or badge URL.
  - `verdict` 'clear' | 'flagged' | 'malicious' | 'error'
  - `confidence` 'high' | 'medium' | 'low'
  - `risk` 'critical' | 'high' | 'medium' | 'low' | 'none': How serious the concern is, and the field to gate on. `verdict` only says whether anything fired, so a credential stealer and a bundler that uses Function() are both `flagged`. `risk` separates them: reading secrets AND being able to ship them is critical; constructing code at run time is medium.
  - `risk_summary` string: One plain sentence naming the concern.
  - `known_bad` KnownBad
    - `matched` boolean, nullable: true=known-bad; false=checked clean; null=could not check (fail-closed).
    - `match_type` 'exact_hash' | 'fuzzy_hash' | 'embedded_ioc' | 'known_publisher' | 'malicious_package': How the known-bad match was made. `malicious_package` means the package identity (name + version) is listed as malware in the OSV/OpenSSF corpus, which is scoped to the affected versions: a project compromised in one release is not condemned in its later clean ones.
    - `sources` string[]
  - `findings` Finding[]
    - `rule_id` string
    - `category` string
    - `severity` 'high' | 'medium' | 'low' | 'info'
    - `description` string
    - `evidence` object
      - `file` string
      - `line` integer
      - `snippet` string
  - `rules_version` string
  - `scanned_at` string, date-time
  - `disclaimer` string
  - `attestation` string: Compact JWS (EdDSA) over the verdict core, present on non-error verdicts. Verify it against /.well-known/jwks.json (offline) or POST /v1/verify. Its `sub` claim binds it to this artifact, so an agent can share the verdict and any recipient can trust it without re-scanning. Left out when the payment outcome is unknown (see `payment`).
  - `payment` object: x402 only, present only when the payment facilitator did not answer clearly, so whether the payment settled is unknown. Do not pay again for this scan.
    - `outcome` 'unknown'
    - `detail` string
    - `contact` string
    - `time` string, date-time: Quote this with the paying wallet address when writing in.

## Other responses

- `401`: Missing or invalid API key.
- `402`: Payment required (x402, DUAL-STACK): the PAYMENT-REQUIRED response header carries the x402 v2 challenge (network eip155:8453, `amount`), the JSON body carries the v1 challenge (network base, `maxAmountRequired`) plus a human-readable `hint` with prices and the free tier. Pay via PAYMENT-SIGNATURE (v2) or X-PAYMENT (v1) headers.
- `409`: payment_in_use: this x402 payment is already in use by another request, or was used for one within the last few minutes. One payment pays for one request, so nothing was run or settled for this one. Sign a new payment.
- `422`: An `error` verdict: analysis could not complete. Never billed.
- `429`: Rate limited.

---

[API](https://skmtc.dev/lazaretto/apis/lazaretto-skill-tool-verification-api.md) · [All operations](https://skmtc.dev/lazaretto/apis/lazaretto-skill-tool-verification-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lazaretto/apis/lazaretto-skill-tool-verification-api/revisions/2bd4e34d0067?raw)
