Scan API
POST /v1/scans starts a whole-repo scan and GET /v1/scans/:id reads it. Authentication with an aevr_ key from AEVRAL_API_KEY, request fields, responses, the polling contract, idempotency, and the error codes with the action a coding agent should take.
A key starts and reads scans for its own organization only. It cannot authorize an overage charge, buy a plan or raise a spend cap: those stay in the console with an owner or admin. Scans take 10 to 40 minutes, so start one, then poll.
Authentication
Authorization: Bearer aevr_...- Keys start with
aevr_and are minted in the console Developer API page (see Console and API keys). - Coding agents look for the key in the
AEVRAL_API_KEYenvironment variable. Never ask the human to paste a key into chat, and never put a key in a URL. If the variable is not set, tell the human to export it in their shell and stop. - The organization comes from the key alone. A key never reads or starts scans in another organization.
Start a scan
POST https://aevral-worker-prod.fly.dev/v1/scans
Authorization: Bearer $AEVRAL_API_KEY
Content-Type: application/json
{
"installation_repo_id": 123456789,
"idempotency_key": "6f1c2e0a-3b7d-4c55-9a0e-2d4b8f7a1c90"
}curl -X POST https://aevral-worker-prod.fly.dev/v1/scans \
-H "Authorization: Bearer $AEVRAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"installation_repo_id": 123456789, "idempotency_key": "'"$(uuidgen)"'"}'Fields
| Field | Type | Required | Meaning |
|---|---|---|---|
installation_repo_id | integer | yes | GitHub's numeric repository id, not the name. Get it with gh api repos/OWNER/REPO --jq .id. The repository must be authorized for your organization in the console. |
idempotency_key | string | yes | Any unique string you choose, such as a UUID. Sending the same key again returns the scan it already started instead of a new one. |
sha | string | no | The commit to scan. Omit it (or send fewer than 7 characters) to scan the head of the default branch, resolved by the server. A value of 7 or more characters must be a 7 to 40 character hex commit id, else 400 invalid_sha. A branch name is not a commit. |
max_overage_cents | integer | no | Console session only. With a key, any value above 0 returns 400 overage_requires_console_session. |
Response
202 Accepted for a new scan, 200 OK for a duplicate. In the normal case (scan_id is a UUID) both carry the poll URL in the body (status_url) and in the Location header. If status_url is absent, check scan_id, do not build a URL yourself, and report the malformed response to the human.
{
"ok": true,
"scan_id": "5ca11ab1-0000-4000-8000-000000000001",
"sha": "0123456789abcdef0123456789abcdef01234567",
"billing_mode": "included",
"charge_cents": 0,
"status_url": "https://aevral-worker-prod.fly.dev/v1/scans/5ca11ab1-0000-4000-8000-000000000001"
}| Field | Meaning |
|---|---|
scan_id | The scan's UUID. |
sha | The full commit that will be scanned (resolved when you omitted it). |
billing_mode | How the scan counts: included, overage, demo (a private free-trial scan; unpaid public-repository scans are included) or idempotent (a duplicate of a scan already run this period). |
charge_cents | What this scan costs beyond the plan, in cents. 0 for an included scan. |
remaining_included | Included scans left this period, when the plan has a counter. |
duplicate | true on a 200: the same idempotency_key, or the same repository and commit already scanned this period. No second charge. |
status_url | Poll this URL. Present whenever scan_id is a UUID. |
Read a scan
GET https://aevral-worker-prod.fly.dev/v1/scans/{scan_id}
Authorization: Bearer $AEVRAL_API_KEY200 OK returns:
| Field | Meaning |
|---|---|
scan_id, sha, created_at | Which scan, which commit, when. |
status | queued, running, or one of the terminal statuses below. |
phase, progress | Engine progress while running (phase name, batches and files done). Informational only. |
billing_mode, charge_cents | As on admission. |
summary, coverage, report | The result, filled when the scan is terminal. The same report the console shows. |
Polling contract
- Poll
status_urlevery 30 seconds. A scan takes 10 to 40 minutes. If you stop waiting before a terminal status, give the human the last status you read and thescan_id: the scan can be read later. - Stop when
statusis terminal:
Terminal status | What to tell the human |
|---|---|
findings | Findings exist. Read report and point to the console report. |
no_confirmed_findings | No confirmed findings over the covered tree. Say what coverage covered; it is not a guarantee. |
scan_incomplete | An incomplete result: part of the tree was not covered, or analysis or output failed partway. It may still carry findings. Not a clean bill of health. |
unsupported_repo | The repository could not be scanned. |
failed | The scan failed. The human can start a new one. |
queuedandrunningare not terminal: keep polling.- A
502 scan_lookup_failedmeans the status is unknown right now: waitretry_afterseconds and poll again. Do not report the scan as running, finished or failed from this response.
Idempotency
idempotency_key is required. Generate one key per intended scan and keep it for retries: if a request times out, send the same body again. A duplicate returns 200 with duplicate: true and the same scan_id, never a second charge. Starting the same repository and commit again in the same billing period also returns the existing scan as a duplicate. A scan already queued or running for that commit returns 409 in_flight with its scan_id.
Errors
Error bodies are JSON with an error field. Some add message, retry_after, scan_id, charge_cents or usage_url.
POST /v1/scans
| Status | error | What the agent should do |
|---|---|---|
| 400 | invalid json | Send a JSON object body with Content-Type: application/json. |
| 400 | installation_repo_id and idempotency_key are required | Add both. installation_repo_id is the numeric id from gh api repos/OWNER/REPO --jq .id. |
| 400 | sha is required (server-side resolution unavailable) | This host cannot resolve the default branch head. Send an explicit 7 to 40 character hex sha (for example from git rev-parse HEAD on the default branch). |
| 400 | invalid_sha | Send a 7 to 40 character hex commit id, or omit sha to scan the default branch head. Do not send a branch name. |
| 400 | overage_requires_console_session | Remove max_overage_cents. An overage scan is confirmed by a human in the console. |
| 401 | invalid_api_key_format | The key is quoted, truncated or mistyped. Ask the human to check AEVRAL_API_KEY (it starts with aevr_). Do not ask for the key itself. |
| 401 | invalid API key | The key was not recognized. Ask the human to verify that AEVRAL_API_KEY holds the key they meant to use, and to recover the existing key from wherever they stored it. Mint a new one in the console Developer API page only if the key is confirmed revoked or lost. Never ask for the key in chat. |
| 401 | missing or malformed credentials ... | Send Authorization: Bearer $AEVRAL_API_KEY. |
| 402 | quota_exhausted | No scans left this period. Stop. Tell the human. For the plan state, follow usage_url when present; otherwise call GET /v1/usage. Do not retry. |
| 402 | overage_confirmation_required | Included scans are used up; the next one costs charge_cents. Stop. Only a human can confirm it, from the console. Never retry with max_overage_cents. Follow usage_url when present; otherwise call GET /v1/usage. |
| 402 | spend_cap_reached | The organization's monthly spend cap blocks the charge (billed_cents, cap_cents). Stop. An owner or admin can raise the cap in the console. Follow usage_url when present; otherwise call GET /v1/usage. |
| 403 | scans_disabled | Scanning is not enabled for this organization. Stop and tell the human. |
| 404 | unknown_repo | The repository is not authorized for this organization. Check the numeric id, then ask the human to authorize the repository or press Sync from GitHub in the console. |
| 404 | unknown_org | The key's organization no longer exists. Stop. |
| 409 | in_flight | A scan for this commit is already running. Poll /v1/scans/{scan_id} from the body instead of starting another. |
| 429 | rate_limited | Too many scan starts. Wait retry_after seconds (also in Retry-After), then retry with the same idempotency_key. |
| 502 | sha_resolution_failed | The default branch head could not be read from GitHub. Retry later, or send an explicit sha. |
| 502 | rpc_failed | Admission failed on our side. Retry later with the same idempotency_key. |
| 503 | scan_service_unavailable | No scan can run right now. Nothing was admitted or charged. Retry later. |
GET /v1/scans/{scan_id}
| Status | error | What the agent should do |
|---|---|---|
| 401 | (as above) | Fix the key as above. |
| 404 | not_found | No scan with that id in this organization, or the id is not a UUID. Stop polling. Use the status_url (or the UUID scan_id) from the admission response. |
| 502 | scan_lookup_failed | Temporary lookup outage: the status is unknown. Wait retry_after seconds (5, also in Retry-After) and poll again. Do not say the scan is running, finished or failed. |
For coding agents
- Read the key from
AEVRAL_API_KEY. Never ask the human to paste it into chat, never print it, never put it in a URL. - Get
installation_repo_idfromgh api repos/OWNER/REPO --jq .id. Omitshaunless the human named a commit. - Generate one
idempotency_keyper scan and reuse the same key when retrying after a timeout or a 429. - Poll
status_urlevery 30 seconds until a terminal status. Expect 10 to 40 minutes. Ifstatus_urlis missing, checkscan_id, build no URL yourself, and report the malformed response. - On any 402, stop and tell the human. Never say you bought, confirmed or upgraded anything.
- Report the terminal status honestly:
scan_incompleteandno_confirmed_findingsare not guarantees. - Plan and usage: Usage API. Prices: only from
GET https://aevral-worker-prod.fly.dev/v1/billing/plans, never from memory.
See also What a scan looks like, Console and API keys and For AI agents.