AevralDocs

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_KEY environment 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

FieldTypeRequiredMeaning
installation_repo_idintegeryesGitHub'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_keystringyesAny unique string you choose, such as a UUID. Sending the same key again returns the scan it already started instead of a new one.
shastringnoThe 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_centsintegernoConsole 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"
}
FieldMeaning
scan_idThe scan's UUID.
shaThe full commit that will be scanned (resolved when you omitted it).
billing_modeHow 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_centsWhat this scan costs beyond the plan, in cents. 0 for an included scan.
remaining_includedIncluded scans left this period, when the plan has a counter.
duplicatetrue on a 200: the same idempotency_key, or the same repository and commit already scanned this period. No second charge.
status_urlPoll 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_KEY

200 OK returns:

FieldMeaning
scan_id, sha, created_atWhich scan, which commit, when.
statusqueued, running, or one of the terminal statuses below.
phase, progressEngine progress while running (phase name, batches and files done). Informational only.
billing_mode, charge_centsAs on admission.
summary, coverage, reportThe result, filled when the scan is terminal. The same report the console shows.

Polling contract

  • Poll status_url every 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 the scan_id: the scan can be read later.
  • Stop when status is terminal:
Terminal statusWhat to tell the human
findingsFindings exist. Read report and point to the console report.
no_confirmed_findingsNo confirmed findings over the covered tree. Say what coverage covered; it is not a guarantee.
scan_incompleteAn 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_repoThe repository could not be scanned.
failedThe scan failed. The human can start a new one.
  • queued and running are not terminal: keep polling.
  • A 502 scan_lookup_failed means the status is unknown right now: wait retry_after seconds 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

StatuserrorWhat the agent should do
400invalid jsonSend a JSON object body with Content-Type: application/json.
400installation_repo_id and idempotency_key are requiredAdd both. installation_repo_id is the numeric id from gh api repos/OWNER/REPO --jq .id.
400sha 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).
400invalid_shaSend a 7 to 40 character hex commit id, or omit sha to scan the default branch head. Do not send a branch name.
400overage_requires_console_sessionRemove max_overage_cents. An overage scan is confirmed by a human in the console.
401invalid_api_key_formatThe 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.
401invalid API keyThe 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.
401missing or malformed credentials ...Send Authorization: Bearer $AEVRAL_API_KEY.
402quota_exhaustedNo 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.
402overage_confirmation_requiredIncluded 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.
402spend_cap_reachedThe 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.
403scans_disabledScanning is not enabled for this organization. Stop and tell the human.
404unknown_repoThe 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.
404unknown_orgThe key's organization no longer exists. Stop.
409in_flightA scan for this commit is already running. Poll /v1/scans/{scan_id} from the body instead of starting another.
429rate_limitedToo many scan starts. Wait retry_after seconds (also in Retry-After), then retry with the same idempotency_key.
502sha_resolution_failedThe default branch head could not be read from GitHub. Retry later, or send an explicit sha.
502rpc_failedAdmission failed on our side. Retry later with the same idempotency_key.
503scan_service_unavailableNo scan can run right now. Nothing was admitted or charged. Retry later.

GET /v1/scans/{scan_id}

StatuserrorWhat the agent should do
401(as above)Fix the key as above.
404not_foundNo 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.
502scan_lookup_failedTemporary 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_id from gh api repos/OWNER/REPO --jq .id. Omit sha unless the human named a commit.
  • Generate one idempotency_key per scan and reuse the same key when retrying after a timeout or a 429.
  • Poll status_url every 30 seconds until a terminal status. Expect 10 to 40 minutes. If status_url is missing, check scan_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_incomplete and no_confirmed_findings are 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.

On this page