Start a scan, follow it, read its problems, and stop it. Paths are under https://www.valiqai.com/api/v1; see the API overview for keys and scopes.
POST /scans
Starts a scan. Scope scan.
$ curl -X POST https://www.valiqai.com/api/v1/scans \ -H "Authorization: Bearer $VALIQAI_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: deploy-4817-attempt-1" \ -d '{"url": "https://staging.example.com", "type": "e2e"}'| Field | Type | What it is |
|---|---|---|
url | string | The address to scan. Its domain must be verified (for a team key, by anyone on the team). |
type | e2e or security | The scan: End-to-End, or Security, which also tests the site's chatbot. See Choose a scan. |
chatbotConsent | boolean | With security: true says you're allowed to send the site's chatbot attack prompts. Without it, the chatbot is found but left alone. |
organizationId | string | The workspace to bill. A personal key in more than one workspace needs it. A team key can only name its own team. |
source | object | Where the scan was started from, shown on its page: provider (required, like github), repository, branch, commit, pullRequest, runUrl, label. The CLI fills it in from your CI. |
label | string | A note shown with the scan, up to 120 characters. |
webhook | string | An address to POST the scan's result to when it ends. See Per-scan callbacks. |
mode, contractId | string | mode: "custom" with a contractId runs one of your contracts instead of the URL. |
Send an Idempotency-Key header so a retried request returns the scan it already started.
Response 200:
{
"id": "cmh2x8k1p0",
"status": "pending",
"url": "https://staging.example.com",
"mode": "generic",
"type": "e2e",
"credits": { "reserved": 2, "free": false },
"message": "Scan started",
"pollUrl": "/api/v1/scans/cmh2x8k1p0",
"badgeUrl": "/api/v1/scans/cmh2x8k1p0/badge",
"runUrl": "https://www.valiqai.com/runs/cmh2x8k1p0"
}credits.reservedis what the scan holds; what it doesn't use comes back when it ends.credits.freeistruewhen your free End-to-End scan paid for it.noteappears when asecurityrequest runs as End-to-End because chatbot scans aren't available yet.
Before anything is held, a scan can be refused: the domain isn't verified (403 DOMAIN_NOT_VERIFIED), the site is behind protection (422 SITE_PROTECTED), the key's monthly limit is reached (402 KEY_CREDIT_LIMIT), there aren't enough credits (402 INSUFFICIENT_CREDITS), or another scan is running in the workspace (429 ORG_IN_FLIGHT_CAP, with Retry-After). All are on Errors.
GET /scans
Lists scans, newest first. Scope read. A team key lists its team's scans; a personal key, the ones you started.
| Query | What it does |
|---|---|
site | Only scans of this domain and its subdomains, like example.com. |
status | Only these, comma-separated: pending, running, passed, failed, warning, error, partial, cancelled. |
source | Only scans started from this provider, like github. |
limit | How many, 1 to 100. Default 20. |
cursor | The next page: nextCursor from the last response. |
organizationId | Only scans in this workspace. |
{
"scans": [
{
"id": "cmh2x8k1p0",
"url": "https://staging.example.com",
"status": "failed",
"type": "e2e",
"score": 64,
"problems": { "open": 3, "blocker": 1, "major": 2, "minor": 0, "dismissed": 0 },
"source": { "provider": "github", "branch": "main", "commit": "a1b2c3d" },
"startedAt": "2026-10-20T09:12:04.000Z",
"completedAt": "2026-10-20T09:15:16.000Z",
"duration": 192000,
"runUrl": "https://www.valiqai.com/runs/cmh2x8k1p0"
}
],
"total": 57,
"nextCursor": "bzoyMA"
}score is null while a scan runs and for a stopped one. problems counts open problems the way the Scans page does: major is High and minor is Medium.
GET /scans/{id}
A scan's status and, once it ends, its results. Scope read. A scan the key can't reach answers 404 Scan not found.
While it runs (pending or running), it has progress:
{ "progress": { "tests": { "total": 18, "running": 2, "done": 12, "failed": 1 }, "problemsSoFar": 2, "elapsedMs": 180000 } }tests is null until the scan lists them. Once it ends, the scan has:
| Field | What it is |
|---|---|
status | passed, failed, warning, error, partial or cancelled. |
score, scoreBreakdown | The score out of 100, and its parts. |
problems | One per distinct problem, worst first: key, severity (blocker, major, minor, info), category, title, count, urls, status (open or dismissed), dismissReason. |
openCounts, dismissedCount | Open problems by severity, and how many were dismissed for the site. Problems dismissed in ValiqAI don't count as open. |
tests | Each test the scan ran: id, section, name, state, result (passed, failed, inconclusive), reason, durationMs, findings. |
findings, findingsCount | The raw findings behind the problems, each with dismissed. |
type, source, riskSummary, coverage | The scan type, where it was started from, a risk summary, and what it covered. |
runUrl, startedAt, completedAt, duration, error | Its page, times, and what went wrong if it didn't finish. |
Polling every 10 seconds is plenty; that's what the CLI does.
DELETE /scans/{id}
Stops a scan that's still running, and releases its unused credits. Scope scan.
{ "id": "cmh2x8k1p0", "status": "cancelled", "runUrl": "https://www.valiqai.com/runs/cmh2x8k1p0" }A scan that already ended answers 409 This scan already finished.
GET /scans/{id}/problems
A scan's problems, filtered, worst first. Scope read.
| Query | What it does |
|---|---|
severity | Comma-separated: blocker, high (or major), medium (or minor), info (or low). |
category | Comma-separated, like security,accessibility. |
status | open (the default), dismissed or all. |
{ "id": "cmh2x8k1p0", "url": "https://staging.example.com", "status": "failed", "problems": [ … ], "openCounts": { "blocker": 1, "major": 2, "minor": 0, "info": 0 }, "dismissedCount": 0 }GET /scans/quote
What a scan would reserve, and what the workspace has, before starting it. Scope read.
| Query | What it does |
|---|---|
type | e2e (the default) or security. |
organizationId | The workspace, as for POST /scans. |
{
"type": "e2e",
"requestedType": "e2e",
"sku": "quick",
"label": "End-to-End scan",
"hold": 2,
"typicalMin": 1,
"typicalMax": 1,
"available": 140,
"freeEligible": false,
"workspace": { "id": "org_8f2…", "name": "Northstar" }
}type is what it would actually run as, and note says so when that differs from requestedType.
GET /scans/{id}/badge
A status badge for a README, as SVG: the score once the scan ends, or its status. ?style=risk shows the risk level instead. It needs no key, so anyone with the scan's id can see it.
