API documentation
Everything the dashboard does, your pipeline can do. Add an asset, launch a scan, pull findings, download the report — with a scoped key that cannot do more than you granted.
The specification is generated from the running service, so it is never out of date with the API it describes. Point any OpenAPI client generator at that URL.
Authentication
Every request carries a bearer token. An API key looks like secqon_<prefix>_<secret> and is shown once, when you create it — we store only a hash, so a lost key is replaced rather than recovered.
curl https://api.secqon.com/assets \
-H "Authorization: Bearer secqon_a1b2c3_your_secret_here"Keys are scoped, and a key is refused on any path no scope covers. There is deliberately no scope for team administration, billing, plan changes, legal acceptance, or minting another key — those are things a person does with an account behind them, not things a build server should do at 3am with a leaked variable.
| Scope | Grants |
|---|---|
assets:read | List assets and their verification status. |
assets:write | Add an asset and start or check ownership verification. |
scans:read | List scans, read status and progress. |
scans:run | Launch a scan and cancel one. |
findings:read | Read findings and their evidence. |
findings:write | Change a finding's triage status. |
reports:read | List reports and download a PDF. |
reports:write | Generate a report for a completed scan. |
Quickstart: scan an asset and read the findings
An asset must be verified before it can be scanned — that rule has no exception and no override. Verification is a DNS TXT record, a hosted file, or a meta tag.
1. Add the asset
curl -X POST https://api.secqon.com/assets \
-H "Authorization: Bearer $SECQON_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"domain","value":"example.com","attest_authorized":true}'2. Start verification, then check it
curl -X POST https://api.secqon.com/assets/$ASSET_ID/verify/start \
-H "Authorization: Bearer $SECQON_KEY" \
-H "Content-Type: application/json" -d '{"method":"dns_txt"}'
# publish the token it returns, then:
curl -X POST https://api.secqon.com/assets/$ASSET_ID/verify/check \
-H "Authorization: Bearer $SECQON_KEY"3. Launch a scan
curl -X POST https://api.secqon.com/scans \
-H "Authorization: Bearer $SECQON_KEY" \
-H "Content-Type: application/json" \
-d '{
"asset_ids": ["'$ASSET_ID'"],
"profile": "standard",
"attest_authorized": true
}'profile is light, standard or deep. A Deep scan also needs "deep_ack": true — it is intrusive, and the acknowledgement is recorded in your audit log. The full penetration test adds "pentest_ack": true and is available on Business.
4. Poll for completion, then read findings
curl https://api.secqon.com/scans/$SCAN_ID -H "Authorization: Bearer $SECQON_KEY"
# {"status":"completed","progress":100, ...}
curl https://api.secqon.com/scans/$SCAN_ID/findings -H "Authorization: Bearer $SECQON_KEY"5. Generate and download the report
curl -X POST https://api.secqon.com/reports \
-H "Authorization: Bearer $SECQON_KEY" \
-H "Content-Type: application/json" -d '{"scan_id":"'$SCAN_ID'"}'
curl -L https://api.secqon.com/reports/$REPORT_ID/pdf \
-H "Authorization: Bearer $SECQON_KEY" -o report.pdfUsing it in CI
A common pattern: scan on every release, and fail the build when a new high-severity finding appears. Findings carry a stable identity across scans, so “new” means new rather than re-reported.
- name: SecQon scan
run: |
SCAN=$(curl -sS -X POST $SECQON_API/scans \
-H "Authorization: Bearer ${{ secrets.SECQON_KEY }}" \
-H "Content-Type: application/json" \
-d '{"asset_ids":["'$ASSET'"],"profile":"standard","attest_authorized":true}' \
| jq -r .id)
until [ "$(curl -sS $SECQON_API/scans/$SCAN \
-H "Authorization: Bearer ${{ secrets.SECQON_KEY }}" | jq -r .status)" = "completed" ]; do
sleep 20
done
HIGH=$(curl -sS $SECQON_API/scans/$SCAN/findings \
-H "Authorization: Bearer ${{ secrets.SECQON_KEY }}" \
| jq '[.[] | select(.severity=="high" or .severity=="critical")] | length')
[ "$HIGH" -eq 0 ] || { echo "::error::$HIGH high-severity findings"; exit 1; }Rate limits and errors
- 402 — your plan does not include what you asked for. The body names the tier that does, in
required_plan. - 403 — the asset is not verified, or the key's scopes do not cover this path.
- 409 — you have not accepted the current version of an agreement; the body lists which.
- 429 — too many requests, or no scanner slot is free. Retry after the interval the response names.
Something missing from these docs? Tell us — the API is meant to be driven by people who never open the dashboard.