Passive scans
Start a passive scan, wait for it, and read its failing checks.
A passive scan runs Barrion's 35+ checks against a site without attacking it: TLS, security headers, cookies, CORS, email records and more. Scans are free and take a few minutes, which makes them a good gate on every deploy. They follow your plan's scan limits, the same as in the dashboard.
Types
type ScanStatus =
| "STARTED" | "COMPLETED" | "FAILED"
| "SCHEDULED"; // waiting to (re)start, e.g. a monitoring scan or one picked up again after an interruption; keep polling
type Scan = {
id: string;
domain: string;
status: ScanStatus;
securityScore: number | null; // null until COMPLETED
riskLevel: string | null; // null until COMPLETED
findings: { critical: number; high: number; medium: number; low: number } | null;
startedAt: string; // ISO 8601
completedAt: string | null;
};
type ScanFinding = {
checkKey: string;
name: string;
category: string;
severity: "critical" | "high" | "medium" | "low";
detectedValue: string | null;
affectedUrlCount: number;
};Start a scan
POST /v1/scans · permission scans:write · requires an Idempotency-Key header
Prop
Type
curl -X POST https://api.barrion.io/v1/scans \
-H "Authorization: Bearer $BARRION_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: deploy-$CI_RUN_ID-$CI_RUN_ATTEMPT" \
-d '{"url": "https://staging.example.com"}'const res = await fetch("https://api.barrion.io/v1/scans", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BARRION_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `deploy-${runId}-${attempt}`,
},
body: JSON.stringify({ url: "https://staging.example.com" }),
});
const { id: scanId } = await res.json();res = requests.post(
"https://api.barrion.io/v1/scans",
headers={
"Authorization": f"Bearer {os.environ['BARRION_API_KEY']}",
"Idempotency-Key": f"deploy-{run_id}-{attempt}",
},
json={"url": "https://staging.example.com"},
)
scan_id = res.json()["id"]Response 202
The scan has been queued.
type StartScanResponse = { id: string; domain: string; status: "STARTED" };{ "id": "5b1c7e2a-9d3f-4a1b-8c2e-7f6a5b4c3d2e", "domain": "staging.example.com", "status": "STARTED" }Errors
| Status | Code | Why |
|---|---|---|
| 400 | VALIDATION_ERROR | url is missing or not a valid URL |
| 403 | PLAN_LIMIT_EXCEEDED | Your plan's scan limit is reached, or the same site was scanned too recently |
| 403 | TARGET_NOT_ASSIGNED | In an organization, this domain is not one of the domains assigned to you. Ask an organization admin for access |
| 409 | IDEMPOTENCY_IN_PROGRESS | The first request with this key is still running |
| 422 | IDEMPOTENCY_KEY_REUSED | This key was already used with a different body |
| 429 | RATE_LIMITED | Too many starts in a minute |
The message on a limit error says which limit was hit.
Get a scan
GET /v1/scans/{id} · permission scans:read
Poll every 15 to 30 seconds until status is COMPLETED or FAILED.
curl https://api.barrion.io/v1/scans/5b1c7e2a-9d3f-4a1b-8c2e-7f6a5b4c3d2e \
-H "Authorization: Bearer $BARRION_API_KEY"const res = await fetch(`https://api.barrion.io/v1/scans/${scanId}`, {
headers: { Authorization: `Bearer ${process.env.BARRION_API_KEY}` },
});
const scan = (await res.json()) as Scan;scan = requests.get(
f"https://api.barrion.io/v1/scans/{scan_id}",
headers={"Authorization": f"Bearer {os.environ['BARRION_API_KEY']}"},
).json()Response 200: Scan
{
"id": "5b1c7e2a-9d3f-4a1b-8c2e-7f6a5b4c3d2e",
"domain": "staging.example.com",
"status": "COMPLETED",
"securityScore": 82,
"riskLevel": "Medium",
"findings": { "critical": 0, "high": 1, "medium": 3, "low": 5 },
"startedAt": "2026-09-28T10:02:11.000Z",
"completedAt": "2026-09-28T10:05:47.000Z"
}Findings you have ignored in the dashboard are left out of the counts. An unknown id is 404 NOT_FOUND.
Read the failing checks
GET /v1/scans/{id}/findings · permission scans:read
Prop
Type
curl "https://api.barrion.io/v1/scans/5b1c7e2a-9d3f-4a1b-8c2e-7f6a5b4c3d2e/findings?severity=critical,high" \
-H "Authorization: Bearer $BARRION_API_KEY"Response 200
type ScanFindingsResponse = {
id: string;
domain: string;
status: ScanStatus;
total: number | null; // null until the scan has completed
returned: number;
hasMore: boolean;
more: string | null; // e.g. "4 further findings. Call again with offset 10."
findings: ScanFinding[]; // worst first
};{
"id": "5b1c7e2a-9d3f-4a1b-8c2e-7f6a5b4c3d2e",
"domain": "staging.example.com",
"status": "COMPLETED",
"total": 1,
"returned": 1,
"hasMore": false,
"more": null,
"findings": [
{
"checkKey": "hsts",
"name": "Strict-Transport-Security header",
"category": "Headers",
"severity": "high",
"detectedValue": null,
"affectedUrlCount": 1
}
]
}