Run scans
A scan audits one commit of one repository. You choose the scan type and a credit budget. zkao reserves the budget, runs the analysis, and records findings on the project.
The TypeScript examples use a client created as shown in the SDK reference.
The curl examples assume ZKAO_API_TOKEN and ZKAO_PROJECT_ID are set.
The flow
Section titled “The flow”- Find the repository and check that it is ready.
- Pick a scan type (a preset).
- Launch the scan with a budget.
- Wait for it to finish.
- Read its findings. See Triage findings.
Find a repository
Section titled “Find a repository”List the repositories in the project. A token restricted to some repositories only sees those.
zkao reposconst repositories = await zkao.listRepositories();curl -H "Authorization: Bearer $ZKAO_API_TOKEN" \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/repositoriesEach repository has a readiness field.
A repository added moments ago is analyzing.
zkao makes a one-time pass over it first, and that pass costs no credits.
A launch during that pass fails with 409 repository_initializing.
Wait for ready instead of retrying the launch.
zkao repos:wait <repoId>await zkao.waitForRepositoryReady(repoId);# Poll until the repository reports "readiness": "ready".curl -H "Authorization: Bearer $ZKAO_API_TOKEN" \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/repositoriesThe SDK waits up to 30 minutes by default.
It backs off between polls and honors Retry-After.
Pick a scan type
Section titled “Pick a scan type”A preset is a scan type. Each one runs a fixed set of analyses. Broader types run more analysis and need a larger budget.
zkao presetsconst presets = await zkao.listScanPresets();curl -H "Authorization: Bearer $ZKAO_API_TOKEN" \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/scan-presetsEach preset has a ref, a name, a description, and a minCredits floor.
Pass the ref when you launch.
Without one, the launch uses the first active preset.
Choose a budget
Section titled “Choose a budget”The budget is the most a scan may spend, in credits. zkao reserves it at launch and never charges more. Credits the scan does not use go back to the balance.
You can leave the budget out. zkao then picks the budget it recommends for this scan type and scope on this repository. The recommendation follows what past scans of the repository spent. The first scan of a repository starts at the scan type’s minimum. The launch response tells you which budget was used.
A scan that runs short of budget stops early, so treat its coverage as partial. See Credits and usage for how reservations affect the balance.
Launch a scan
Section titled “Launch a scan”zkao scans launch --repo <repoId> --preset <ref> --budget 500const { scanId, creditBudget } = await zkao.launchScan({ repositoryId: repoId, presetRef: ref, creditBudget: 500,});curl -X POST -H "Authorization: Bearer $ZKAO_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"repositoryId":"<repoId>","presetRef":"<ref>","creditBudget":500}' \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/scansThe API answers 202 with scanId, queued, and creditBudget.
Launching needs the scans:launch scope.
Choose the commit
Section titled “Choose the commit”By default a scan runs on the head of the repository’s default branch.
branchscans the head of that branch, resolved when the scan launches.commitHashpins one commit (7 to 40 hex characters). It wins overbranch.commitMessageis optional metadata shown with the scan.
The CLI flags are --branch, --commit, and --message.
The commit must be pushed to GitHub, because zkao reads it from there.
Scope a scan to audit areas
Section titled “Scope a scan to audit areas”An audit area is a named subsystem of the repository. zkao discovers areas when it maps the code. You can add your own. Scoping a scan to a few areas keeps a large repository affordable.
zkao areas list <repoId>zkao scans launch --repo <repoId> --area <key> --area <otherKey>const { areas } = await zkao.listAuditAreas(repoId);await zkao.launchScan({ repositoryId: repoId, auditAreaKeys: [areas[0].key],});curl -H "Authorization: Bearer $ZKAO_API_TOKEN" \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/repositories/<repoId>/audit-areasEach area reports its size in files and lines from one branch’s map.
Pass --branch (or branch) to read another branch’s sizes.
Leave the area list out to scan the whole repository. A key the current map no longer names fails the launch. zkao never drops it silently, so a budget sized for one area never runs against everything.
An area is a name and a description, never a file list. Each scan resolves it against the code at its own commit.
zkao areas add <repoId> "Proof verifier" --description "On-chain verifier and its key loading"zkao areas rm <repoId> <areaKey>const area = await zkao.createAuditArea( repoId, "Proof verifier", "On-chain verifier and its key loading",);await zkao.deleteAuditArea(repoId, area.key);curl -X POST -H "Authorization: Bearer $ZKAO_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Proof verifier","description":"On-chain verifier and its key loading"}' \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/repositories/<repoId>/audit-areasAdding and deleting areas needs the guidance:write scope.
Only custom areas can be deleted.
Deleting a discovered area returns 409.
Guide one scan
Section titled “Guide one scan”Guidance tells a scan what matters: scope, threat model, and accepted risks.
A launch can carry guidance for that scan only.
It replaces the repository’s stored guidance for this run.
A zkao.md committed in the repository still applies underneath.
zkao scans launch --repo <repoId> --guidance focus.mdcat focus.md | zkao scans launch --repo <repoId> --guidance -await zkao.launchScan({ repositoryId: repoId, guidance: "Focus on the proof verifier. The prover is trusted.",});curl -X POST -H "Authorization: Bearer $ZKAO_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"repositoryId":"<repoId>","guidance":"Focus on the proof verifier."}' \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/scansIn the API, the guidance field has three states:
- Left out: the scan uses the repository’s stored guidance.
- A string: that text replaces it for this scan.
nullor an empty string: the scan runs with no stored guidance layer.
Guidance is limited to 100,000 characters. See Repository guidance for the standing layer.
Follow a scan
Section titled “Follow a scan”A scan moves through these statuses:
| Status | Meaning |
|---|---|
QUEUED |
Accepted and waiting to start. |
PROCESSING |
Running. |
COMPLETED |
Finished. Findings are ready. |
FAILED |
Stopped on an error. |
CANCELLED |
Cancelled before it finished. |
The last three are terminal.
While a scan runs, its progress field reports phasesCompleted, phasesTotal, and a weighted percent.
There is no time estimate.
How long a scan takes depends on the repository and its guidance.
A finished scan carries a findingsSummary with counts by severity.
The easiest way to follow a scan is to wait on it.
zkao scans wait <scanId>zkao scans wait <scanId> --timeout 7200const scan = await zkao.waitForScan(scanId, { onPoll: (s) => console.log(s.status, s.progress?.percent),});curl -i -H "Authorization: Bearer $ZKAO_API_TOKEN" \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/scans/<scanId>zkao scans wait prints each poll on stderr and the final scan as JSON on stdout.
Its --timeout is in seconds.
The SDK’s waitForScan gives up after one hour by default.
Set timeoutMs for longer scans, and pass a signal to abort.
Poll politely
Section titled “Poll politely”If you poll by hand, read the Retry-After header on the scan response.
It says how many seconds to wait before the next poll.
It is present only while the scan is not terminal.
A token that polls too fast gets 429 rate_limited, also with Retry-After.
Wait that long before the next request.
The CLI and SDK do this for you.
List scans
Section titled “List scans”Scans are listed newest first.
The list is paginated with page and limit (at most 100, default 50).
zkao scans list --limit 20zkao scans get <scanId>const { items, total } = await zkao.listScans({ limit: 20 });const detail = await zkao.getScan(items[0].id);curl -H "Authorization: Bearer $ZKAO_API_TOKEN" \ "https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/scans?limit=20"Cancel a scan
Section titled “Cancel a scan”You can cancel a queued or running scan.
zkao stops its work, marks it CANCELLED, and releases its reserved credits.
Work already in flight settles what it actually spent as it winds down.
zkao scans cancel <scanId>await zkao.cancelScan(scanId);curl -X POST -H "Authorization: Bearer $ZKAO_API_TOKEN" \ https://zkao.io/api/v1/projects/$ZKAO_PROJECT_ID/scans/<scanId>/cancelCancelling needs the scans:launch scope.
A scan that already reached COMPLETED or FAILED cannot be cancelled and returns 400.
Launch errors
Section titled “Launch errors”| Status | Code | What to do |
|---|---|---|
400 |
bad_request |
Fix the request. The message names the problem. |
400 |
diff_base_* |
See Diff scans. |
402 |
insufficient_credits |
Add credits, or launch with a smaller budget. |
403 |
forbidden |
The token lacks scans:launch. |
404 |
not_found |
The repository is outside the token’s project or repository allowlist. |
409 |
repository_initializing |
Wait for the repository to be ready. |
422 |
diff_empty |
The diff scan has nothing to audit. |
429 |
rate_limited |
The token’s spend limit is reached, or it sent too many requests. Wait for Retry-After. |
The full contract is in the API reference for launchScan and getScan.

