---
title: "Versioned scan: returns PublicReportV1 (stable allowlist, JSON Schema at /api/v1/schema)"
method: POST
path: "/api/v1/scan"
---

# Versioned scan: returns PublicReportV1 (stable allowlist, JSON Schema at /api/v1/schema)

`POST /api/v1/scan`

## Request

- Base URL: `https://crawlcheck.io`
- URL: `https://crawlcheck.io/api/v1/scan`
- Auth: one of:
  - API key in header `x-crawlcheck-key`
  - HTTP bearer

## Request body

- object
  - `domain` string, required

## Response `200`

PublicReportV1

- PublicReportV1
  - `schema` string, required
  - `version` '1.0', required
  - `id` string, nullable, required
  - `domain` string, nullable, required
  - `path` string, nullable
  - `scanned_host` string, nullable
  - `scanned_at` string, nullable, required
  - `score_version` number, nullable
  - `grade` string, nullable, required
  - `overall` number, nullable, required
  - `grade_detail` object
    - `capped` boolean
    - `cap` string, nullable
    - `cap_reason` string, nullable
  - `sections` object[], required
    - `id` string, nullable
    - `title` string, nullable
    - `score` number, nullable: null means not measured, never zero
  - `findings` object[], required
    - `code` string, nullable
    - `path` string, nullable
    - `severity` number, nullable
    - `title` string, nullable
    - `detail` string, nullable
    - `meaning` string, nullable
    - `evidence` string, nullable
    - `fid` string, nullable
    - `decision_id` string, nullable
    - `graph` string, nullable
    - `locked` boolean: true on an unlicensed read for every finding but the top one: title, severity and path are present, the rest is withheld
    - `fingerprint` string, nullable: code|path; stable across scans of the same domain; null on a locked finding
    - `state` 'new' | 'persisted' | 'worsened' | 'improved' | 'regressed' | 'resolved' | 'rule-changed' | 'null', nullable: lifecycle on this scan; null when the domain has no stored history
    - `first_seen` string, nullable
    - `scans_seen` number, nullable
    - `recurrences` number, nullable
    - `valid_time` object, nullable: bitemporal: when the condition held on the site (valid_from / valid_to as after-by intervals between observations; null ends are unknown or still open) and when CrawlCheck recorded it (recorded_at, superseded_at)
  - `findings_withheld` string, nullable: Set on an unlicensed read: every finding is listed by title, severity and path; detail, evidence and code beyond the top one are withheld
  - `findings_summary` object, nullable
    - `total` number, nullable
    - `serious` number, nullable
    - `by_severity` object, nullable
  - `provenance` object, nullable: Observer build, vantage, rule version, observed and stored times, sealed manifest
  - `root_causes` unknown
  - `fix_impact` unknown
  - `score_ledger` object, nullable: combined {all_rows_fixed, all_findings_cleared, both} and next_letter {target, reachable, ...} are recomputed, never summed. What each decision moved. rows[]: every failing scored row with points = the score recomputed with that row passing minus the score now (same formula, Reach + 15 ceiling included), points_section_average, and why a row moves nothing; withheld (rows_withheld) where the fix list is. Findings move no points: each finding's score_effect states the letter cap it sets, whether it binds, and the letter without it.
  - `confidence` object, nullable: Counts and ratios behind the reading: sections_scored, identities_answered, fetches_kept, stage_failures, non_observations, vantages. No composite number, because the parts do not combine into one honestly
  - `bitemporal` object, nullable: Each stated fact with two times: when CrawlCheck observed it (observed_from, observed_last, removed_at) and when the site says it became true (valid time, null when the site does not say)
  - `non_observations` object[], nullable: What was not measured on this scan and why; an unmeasured subject is never scored as a pass or a fail
    - `subject` string, nullable
    - `state` string, nullable
    - `reason` string, nullable
    - `attempted` boolean, nullable
  - `fact_lineage` object, nullable: Each value carries locators[] (one per source): bytes [start, end) in the page this scan received, line, col, scope (meta[og:title], script[ld+json]#0, title, page text) and match (value | property | normalised | partial), or {found:false, why}; check them with /api/locate. Each business fact (name, phone, address, email, founder) with every value the site stated and where it stated it, and whether the statements agree
  - `checks` object[]
    - `path` string, nullable
    - `status` number, nullable
    - `bytes` number, nullable
  - `agentview` object[]
    - `label` string, nullable
    - `status` number, nullable
    - `words` number, nullable
  - `nap` object, nullable
  - `headroom` object, nullable
    - `gain` number, nullable
    - `failing` number, nullable
  - `history` object[]
    - `at` string, nullable
    - `overall` number, nullable
    - `score_version` number, nullable
  - `proof` object, required
    - `digest` string, nullable
    - `algorithm` string, nullable
    - `anchored` string, nullable
    - `verify` string, nullable
  - `links` object

## Other responses

- `400`: ErrorV1: bad_request
- `429`: ErrorV1: rate_limited

---

[API](https://skmtc.dev/crawlcheck/apis/crawlcheck-api.md) · [All operations](https://skmtc.dev/crawlcheck/apis/crawlcheck-api/llms.txt) · [OpenAPI document](https://skmtc.dev/crawlcheck/apis/crawlcheck-api/revisions/57acbd6d4489?raw)
