Scanner
Start scans and read what they found: cookies, requests, findings, reports.
- PATCH/v1/cookies/{id}
- GET/v1/domains/{domainId}/cookies
- GET/v1/domains/{domainId}/declaration
- PUT/v1/domains/{domainId}/deep-scan
- GET/v1/domains/{domainId}/enforcement
- GET/v1/domains/{domainId}/enforcement.pdf
- GET/v1/domains/{domainId}/enforcement/history
- GET/v1/domains/{domainId}/scan-settings
- GET/v1/domains/{domainId}/scans
- POST/v1/domains/{domainId}/scans
- GET/v1/enforcement/summary
- GET/v1/scans/{id}
- GET/v1/scans/{id}/findings
/v1/domains/{domainId}/declarationCookie declaration (JSON by default, ?format=html)
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string | |
| format | query | "json" | "html" |
| Status | Body | When |
|---|---|---|
| 200 | DeclarationDto |
| Field | Type | What it is |
|---|---|---|
| generatedAt required | string | |
| totalCookies required | number | |
| categories required | DeclarationCategoryDto[] | |
| categories[].category required | "unknown" | "marketing" | "preferences" | "analytics" | "necessary" | |
| categories[].count required | number | |
| categories[].cookies required | DeclarationCookieDto[] |
/v1/domains/{domainId}/deep-scanNominate this domain for the monthly deep scan
Agency and above. The plan’s allowance is enforced here, not in the panel. Turning one off is always permitted, even while over the allowance — a plan downgrade can leave a workspace above its own limit.
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string |
| Field | Type | What it is |
|---|---|---|
| enabled required | boolean |
| Status | Body | When |
|---|---|---|
| 200 | DomainScanSettingsDto |
| Field | Type | What it is |
|---|---|---|
| domainId required | string | |
| hostname required | string | |
| cadence required | "weekly" | "monthly" | How often a routine scan runs for this plan. |
| routinePages required | number | Pages a routine run may visit. |
| deepPages required | number | Pages a deep run may visit. Zero on a plan with no deep scan. |
| routineEstimatedMs required | number | The longest a routine scan of this domain can take, in milliseconds, and the same for a deep scan where the plan has one. A ceiling, not a prediction. |
| deepEstimatedMs required | number | Zero where deepPages is zero. |
| deepDomainsAllowed required | number | How many domains this workspace may nominate. Zero below Agency. |
| deepDomainsUsed required | number | How many it has nominated, including this one. |
| deepScan required | boolean | Is this domain one of them? |
| nextRunAt required | string | null | Null until the domain is verified — an unverified domain is never crawled. |
| nextRunBlockedBy required | "unverified" | "in_flight" | null | Why there is no next run, when there is none. |
| nextRunDeep required | boolean | Whether the next scheduled run is the monthly deep scan. It takes the place of that period's routine run; between deep runs the routine schedule carries on. |
| lastRunAt required | string | null | |
| lastStatus required | "queued" | "failed" | "blocked" | "running" | "completed" | null | |
| lastVerdict required | "blocked" | "not_run" | "assessed" | "undetermined" | null | |
| lastBlockedReason required | string | null | The reason the site turned us away, verbatim, for the panel to explain — taken from the latest scan that reached the site, so a later failure on our side does not hide it. Null when that scan got through. |
| lastRegion required | string | null | |
| scannerUserAgent required | string | How the crawler identifies itself — what a WAF rule allows. |
| egressAddresses required | string[] | The addresses the scanner connects from, for a firewall rule. Empty when they have not been published. |
| scannerInfoUrl required | string | The public page that explains the crawler to a site owner. |
| routineAllowance required | ScanAllowanceDto | The routine allowance, which Scan now spends. |
| routineAllowance.total required | number | Pages the plan gives per period. |
| routineAllowance.used required | number | Pages spent in the current window. |
| routineAllowance.remaining required | number | What a scan started now may visit; zero when spent. |
| routineAllowance.nextAvailableAt required | string | null | When a scan becomes possible again, ISO-8601; null unless spent. |
| routineAllowance.windowDays required | number | The rolling window, in days: 30 for monthly, 7 for weekly. |
| deepAllowance required | ScanAllowanceDto | null | The deep-scan allowance, or null where the plan has no deep scans. |
| deepAllowance.total required | number | Pages the plan gives per period. |
| deepAllowance.used required | number | Pages spent in the current window. |
| deepAllowance.remaining required | number | What a scan started now may visit; zero when spent. |
| deepAllowance.nextAvailableAt required | string | null | When a scan becomes possible again, ISO-8601; null unless spent. |
| deepAllowance.windowDays required | number | The rolling window, in days: 30 for monthly, 7 for weekly. |
/v1/domains/{domainId}/enforcementThe enforcement report for a domain
The latest version, or a named one with ?version=. A report is a fixed snapshot of what one scan proved, plus the diff against the previous version: what appeared, what was fixed, and what came back after being fixed — which is a different conversation from what is merely new.
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string | |
| version | query | number |
| Status | Body | When |
|---|---|---|
| 200 | EnforcementReportDto |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| domainId required | string | |
| hostname required | string | |
| scanId required | string | |
| version required | number | Per domain, 1..n. What a customer cites. |
| verdict required | "blocked" | "not_run" | "assessed" | "undetermined" | |
| critical required | number | |
| warnings required | number | |
| passes required | number | Findings that are good news: correct gating, compliant denied pings. |
| pagesVisited required | number | |
| pageBudget required | number | null | Pages the plan allowed. Null when the scan predates budgets. |
| region required | string | null | |
| added required | string[] | Fingerprints new since the previous version — never seen before. |
| fixed required | string[] | In the previous version, gone now. |
| regressed required | string[] | Fixed once and back again, which is a different conversation from added. |
| scannedAt required | string | null | When the scan ran, as against when the report row was written. |
| createdAt required | string | |
| findings required | ScanFindingDto[] | |
| findings[].id required | string | |
| findings[].type required | "consent_state_mismatch" | "leak" | "failed_to_fire" | "fired_correctly" | "cookieless_ping" | |
| findings[].severity required | "info" | "warning" | "critical" | |
| findings[].vendorKey required | string | null | Catalogue key where the host matched a known vendor, else null. |
| findings[].vendorName required | string | |
| findings[].host required | string | |
| findings[].pageUrl required | string | |
| findings[].passKind required | "accept" | "reject" | "no_interaction" | Which pass proves it — before any choice, after reject, or after accept. |
| findings[].title required | string | |
| findings[].detail required | string | |
| findings[].fingerprint required | string | Stable across scans for the same problem on the same page. |
| findings[].evidence required | object[] | |
| findings[].explanation required | ScanFindingExplanationDto | null | An AI-written explanation of the same finding, or null. It is labelled as generated wherever it is shown, and nothing in the verdict, the counts or the evidence comes from it. |
/v1/domains/{domainId}/enforcement.pdfThe enforcement report as a PDF an agency can forward
Cover, summary, every finding with the request lines it rests on, and a methodology page — because the reader was not in the room and their first question is how we know. Contains no visitor data: the recorder stores cookie names, never values, and this quotes only those.
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string | |
| version | query | number |
| Status | Body | When |
|---|---|---|
| 200 | — |
/v1/domains/{domainId}/enforcement/historyEvery report version for a domain, newest first
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string |
| Status | Body | When |
|---|---|---|
| 200 | EnforcementHistoryEntryDto[] |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| version required | number | |
| scanId required | string | |
| verdict required | "blocked" | "not_run" | "assessed" | "undetermined" | |
| critical required | number | |
| warnings required | number | |
| added required | number | |
| fixed required | number | |
| regressed required | number | |
| createdAt required | string |
/v1/domains/{domainId}/scan-settingsWhen this domain is next scanned, how many pages it gets, and why not
Worked out from the plan and the schedule. nextRunAt is an estimate: due scans are started every hour, so the run happens at or after that time.
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string |
| Status | Body | When |
|---|---|---|
| 200 | DomainScanSettingsDto |
| Field | Type | What it is |
|---|---|---|
| domainId required | string | |
| hostname required | string | |
| cadence required | "weekly" | "monthly" | How often a routine scan runs for this plan. |
| routinePages required | number | Pages a routine run may visit. |
| deepPages required | number | Pages a deep run may visit. Zero on a plan with no deep scan. |
| routineEstimatedMs required | number | The longest a routine scan of this domain can take, in milliseconds, and the same for a deep scan where the plan has one. A ceiling, not a prediction. |
| deepEstimatedMs required | number | Zero where deepPages is zero. |
| deepDomainsAllowed required | number | How many domains this workspace may nominate. Zero below Agency. |
| deepDomainsUsed required | number | How many it has nominated, including this one. |
| deepScan required | boolean | Is this domain one of them? |
| nextRunAt required | string | null | Null until the domain is verified — an unverified domain is never crawled. |
| nextRunBlockedBy required | "unverified" | "in_flight" | null | Why there is no next run, when there is none. |
| nextRunDeep required | boolean | Whether the next scheduled run is the monthly deep scan. It takes the place of that period's routine run; between deep runs the routine schedule carries on. |
| lastRunAt required | string | null | |
| lastStatus required | "queued" | "failed" | "blocked" | "running" | "completed" | null | |
| lastVerdict required | "blocked" | "not_run" | "assessed" | "undetermined" | null | |
| lastBlockedReason required | string | null | The reason the site turned us away, verbatim, for the panel to explain — taken from the latest scan that reached the site, so a later failure on our side does not hide it. Null when that scan got through. |
| lastRegion required | string | null | |
| scannerUserAgent required | string | How the crawler identifies itself — what a WAF rule allows. |
| egressAddresses required | string[] | The addresses the scanner connects from, for a firewall rule. Empty when they have not been published. |
| scannerInfoUrl required | string | The public page that explains the crawler to a site owner. |
| routineAllowance required | ScanAllowanceDto | The routine allowance, which Scan now spends. |
| routineAllowance.total required | number | Pages the plan gives per period. |
| routineAllowance.used required | number | Pages spent in the current window. |
| routineAllowance.remaining required | number | What a scan started now may visit; zero when spent. |
| routineAllowance.nextAvailableAt required | string | null | When a scan becomes possible again, ISO-8601; null unless spent. |
| routineAllowance.windowDays required | number | The rolling window, in days: 30 for monthly, 7 for weekly. |
| deepAllowance required | ScanAllowanceDto | null | The deep-scan allowance, or null where the plan has no deep scans. |
| deepAllowance.total required | number | Pages the plan gives per period. |
| deepAllowance.used required | number | Pages spent in the current window. |
| deepAllowance.remaining required | number | What a scan started now may visit; zero when spent. |
| deepAllowance.nextAvailableAt required | string | null | When a scan becomes possible again, ISO-8601; null unless spent. |
| deepAllowance.windowDays required | number | The rolling window, in days: 30 for monthly, 7 for weekly. |
/v1/domains/{domainId}/scansA domain’s recent scans, with the pages each one visited
Newest first, capped at 50. Each scan carries its three passes per page, how many requests and storage entries each pass saw, and whether the pass could operate the site’s consent controls at all.
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string |
| Status | Body | When |
|---|---|---|
| 200 | ScanDetailDto[] |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| domainId required | string | |
| status required | "queued" | "failed" | "blocked" | "running" | "completed" | |
| pagesCrawled required | number | |
| pageBudget required | number | null | Pages this run was allowed to visit, from the plan as it was when the scan was queued. Sent so a report can say "25 of 25 pages" rather than leaving a reader to assume it saw the whole site — which is the assumption that makes a clean result on a partial crawl dangerous. |
| pagesPlanned required | number | null | Pages this run actually set out to visit — discovery's result, capped by the budget. The denominator for progress, and null on scans that predate it. Separate from pageBudget because the budget is what the plan allows: a five-page site on a fifty-page plan is at 100% after five, not 10%. |
| estimatedMs required | number | The longest this scan can take, in milliseconds. A ceiling, not a prediction: most scans finish in well under a third of it. |
| deep required | boolean | A deep scan: the monthly, far larger look at a nominated domain. |
| region required | string | null | The region the scan ran from. Null until the scan has started. |
| verdict required | "blocked" | "not_run" | "assessed" | "undetermined" | What the scan concluded. The findings endpoint has the detail. |
| error required | string | null | |
| startedAt required | string | null | |
| finishedAt required | string | null | |
| createdAt required | string | |
| passes required | ScanPassSummaryDto[] | |
| passes[].pageUrl required | string | |
| passes[].kind required | "accept" | "reject" | "no_interaction" | |
| passes[].reachedDecision required | boolean | Whether the scanner could operate the site's consent controls. Read this first: a pass that did not reach a decision proves nothing about the site. |
| passes[].reason required | string | null | Why not, when not: "no reject control found", "blocked by a bot challenge". |
| passes[].requests required | number | |
| passes[].storage required | number | |
| passes[].ms required | number | |
| pagesVisited required | number | Distinct pages visited, across the three passes. |
| pagesUndetermined required | number | Pages where at least one pass could not be operated. |
| criticalFindings required | number | |
| warningFindings required | number |
/v1/domains/{domainId}/scansQueue a cookie scan for a domain
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| domainId required | path | string |
| Status | Body | When |
|---|---|---|
| 201 | ScanDto |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| domainId required | string | |
| status required | "queued" | "failed" | "blocked" | "running" | "completed" | |
| pagesCrawled required | number | |
| pageBudget required | number | null | Pages this run was allowed to visit, from the plan as it was when the scan was queued. Sent so a report can say "25 of 25 pages" rather than leaving a reader to assume it saw the whole site — which is the assumption that makes a clean result on a partial crawl dangerous. |
| pagesPlanned required | number | null | Pages this run actually set out to visit — discovery's result, capped by the budget. The denominator for progress, and null on scans that predate it. Separate from pageBudget because the budget is what the plan allows: a five-page site on a fifty-page plan is at 100% after five, not 10%. |
| estimatedMs required | number | The longest this scan can take, in milliseconds. A ceiling, not a prediction: most scans finish in well under a third of it. |
| deep required | boolean | A deep scan: the monthly, far larger look at a nominated domain. |
| region required | string | null | The region the scan ran from. Null until the scan has started. |
| verdict required | "blocked" | "not_run" | "assessed" | "undetermined" | What the scan concluded. The findings endpoint has the detail. |
| error required | string | null | |
| startedAt required | string | null | |
| finishedAt required | string | null | |
| createdAt required | string |
/v1/enforcement/summaryWhat moved across the workspace this week
Sums the diffs of reports written in the window rather than counting findings open now: the question is what happened while you were not looking, and the compliance score already answers the other one.
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Status | Body | When |
|---|---|---|
| 200 | EnforcementWeekDto |
| Field | Type | What it is |
|---|---|---|
| windowDays required | number | How many days the window covers. |
| added required | number | Problems that appeared, across every domain. |
| fixed required | number | Problems that went away. |
| regressed required | number | Problems that were fixed and came back. |
| domainsWithCritical required | number | Domains with at least one critical finding in their latest report. |
| domainsReported required | number | Domains that have a report at all — the denominator for the above. |
| latest required | EnforcementLatestDto | null | The report to link to: the newest one in the window that found something new or returning, otherwise the newest report of all. Null when no domain has a report. |
| latest.domainId required | string | |
| latest.hostname required | string | |
| latest.version required | number |
/v1/scans/{id}Get a scan, with the pages it visited and what each pass did
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| id required | path | string |
| Status | Body | When |
|---|---|---|
| 200 | ScanDetailDto |
| Field | Type | What it is |
|---|---|---|
| id required | string | |
| domainId required | string | |
| status required | "queued" | "failed" | "blocked" | "running" | "completed" | |
| pagesCrawled required | number | |
| pageBudget required | number | null | Pages this run was allowed to visit, from the plan as it was when the scan was queued. Sent so a report can say "25 of 25 pages" rather than leaving a reader to assume it saw the whole site — which is the assumption that makes a clean result on a partial crawl dangerous. |
| pagesPlanned required | number | null | Pages this run actually set out to visit — discovery's result, capped by the budget. The denominator for progress, and null on scans that predate it. Separate from pageBudget because the budget is what the plan allows: a five-page site on a fifty-page plan is at 100% after five, not 10%. |
| estimatedMs required | number | The longest this scan can take, in milliseconds. A ceiling, not a prediction: most scans finish in well under a third of it. |
| deep required | boolean | A deep scan: the monthly, far larger look at a nominated domain. |
| region required | string | null | The region the scan ran from. Null until the scan has started. |
| verdict required | "blocked" | "not_run" | "assessed" | "undetermined" | What the scan concluded. The findings endpoint has the detail. |
| error required | string | null | |
| startedAt required | string | null | |
| finishedAt required | string | null | |
| createdAt required | string | |
| passes required | ScanPassSummaryDto[] | |
| passes[].pageUrl required | string | |
| passes[].kind required | "accept" | "reject" | "no_interaction" | |
| passes[].reachedDecision required | boolean | Whether the scanner could operate the site's consent controls. Read this first: a pass that did not reach a decision proves nothing about the site. |
| passes[].reason required | string | null | Why not, when not: "no reject control found", "blocked by a bot challenge". |
| passes[].requests required | number | |
| passes[].storage required | number | |
| passes[].ms required | number | |
| pagesVisited required | number | Distinct pages visited, across the three passes. |
| pagesUndetermined required | number | Pages where at least one pass could not be operated. |
| criticalFindings required | number | |
| warningFindings required | number |
/v1/scans/{id}/findingsWhat a scan proved about enforcement
The deterministic findings for a scan: leaks, consent-state mismatches, vendors that fired correctly and vendors that never fired. verdict is the part to read first — undetermined means the crawler reached the site but could not operate its consent controls, which is emphatically not "clean".
Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.
| Name | Where | Type | What it is |
|---|---|---|---|
| id required | path | string |
| Status | Body | When |
|---|---|---|
| 200 | ScanFindingsDto |
| Field | Type | What it is |
|---|---|---|
| scanId required | string | |
| verdict required | "blocked" | "not_run" | "assessed" | "undetermined" | |
| assessedAt required | string | null | When the assertions last ran, or null when they have not. |
| critical required | number | |
| warnings required | number | |
| passes required | number | Findings that are good news: correct gating and compliant denied pings. |
| findings required | ScanFindingDto[] | |
| findings[].id required | string | |
| findings[].type required | "consent_state_mismatch" | "leak" | "failed_to_fire" | "fired_correctly" | "cookieless_ping" | |
| findings[].severity required | "info" | "warning" | "critical" | |
| findings[].vendorKey required | string | null | Catalogue key where the host matched a known vendor, else null. |
| findings[].vendorName required | string | |
| findings[].host required | string | |
| findings[].pageUrl required | string | |
| findings[].passKind required | "accept" | "reject" | "no_interaction" | Which pass proves it — before any choice, after reject, or after accept. |
| findings[].title required | string | |
| findings[].detail required | string | |
| findings[].fingerprint required | string | Stable across scans for the same problem on the same page. |
| findings[].evidence required | object[] | |
| findings[].explanation required | ScanFindingExplanationDto | null | An AI-written explanation of the same finding, or null. It is labelled as generated wherever it is shown, and nothing in the verdict, the counts or the evidence comes from it. |
Next groupAnalytics