Guides Create account 🇬🇧 🇩🇪

Developer API

The security header API

One endpoint, one dimension: the security result of a scan as JSON, without walking the full report schema. Built for CI pipelines, dashboards and agency tooling that check response headers on a schedule.

Get an API token Jump to the migration

01 Authentication

A bearer token per account

Every request carries an API token as a bearer token in the Authorization header. Tokens are created and revoked on the API tokens page of your account, and a token only ever sees the scans of the account it belongs to. API access is part of the Agency plan; whether a lighter, registration-free tier follows is an open product question and not decided here.

02 The endpoint

Read the security result of a scan

The endpoint returns the security dimension of a finished scan and nothing else. It reads what the scan stored and measures nothing of its own, so the same scan token answers the same way from the full report endpoint.

GET /api/v1/scans/{token}/security JSON

curl -H "Authorization: Bearer $SCAN_API_TOKEN" \ "https://scan.erseni.com/api/v1/scans/8f14e45fceea167a5a36dedd4bea2543/security?locale=en-gb"
{ "schemaVersion": 6, "token": "8f14e45fceea167a5a36dedd4bea2543", "url": "https://example.com/", "status": "finished", "resultAvailable": true, "scanErrorMessage": null, "progressStage": null, "progressPercent": 100, "security": { "dimension": "security", "measured": true, "score": 62, "band": "warn", "rank": "needs_work", "checkSummary": { "passed": 21, "violated": 9, "notChecked": 18, "ran": 30, "total": 48 }, "checks": [ { "key": "strict-transport-security", "state": "passed", "label": "Strict-Transport-Security", "reason": "Enforces HTTPS for every later request." } ], "findings": [ { "code": "security.missing-header.content-security-policy", "severity": "high", "riskLevel": "high", "effort": "medium", "title": "Content-Security-Policy header missing", "detail": "...", "riskStatement": "...", "remediation": "..." } ] } }

03 Starting a scan

Two calls, not one

A scan runs a real browser against the page, so it is asynchronous. The first call queues it and hands back a token, the second reads the result once the status is finished. Poll the security endpoint: while the scan is still running it answers with the status and progress and a null security block.

POST /api/v1/scans

curl -X POST \ -H "Authorization: Bearer $SCAN_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/"}' \ "https://scan.erseni.com/api/v1/scans"
{ "token": "8f14e45fceea167a5a36dedd4bea2543", "id": 4711, "status": "queued", "statusUrl": "/api/v1/scans/8f14e45fceea167a5a36dedd4bea2543" }

04 Reading the answer

What each field means

Nothing in the answer is a legal statement, and nothing is filled in for the sake of a tidy shape. A dimension that was never measured comes back as measured false with a null score rather than a zero.

score

The security score of the scan from 0 to 100, or null when the dimension was not measured. band is good, warn, poor or unknown; rank is the finer seven-step scale used in the report.

measured

False when the scan carries no security score. Everything else in the block may still be present, but no score stands behind it.

checkSummary

How many of the declared security checks passed, were violated and could not be run. ran is passed plus violated, total counts all three. Null for a scan taken before the check ledger existed.

checks

One entry per check with its key, its state, a label and the reason it is checked. The list is either complete for the dimension or null: a partial list would present the checks that happen to be named as the full scope of what was tested.

findings

The public security findings of the scan. code is a stable fingerprint you can key on; the four texts are rendered in the requested locale.

locale

Optional query parameter, en-gb by default, de-de also supported. It selects the language of the labels, reasons and finding texts. Keys, codes and states never change with the locale.

05 Errors

What the endpoint answers when it cannot answer

Every error carries the HTTP status and a JSON body with errorMessage. Branch on the status code, not on the text: the wording may change, the codes do not.

Status codes 400 Unsupported locale. The locale query parameter carries a value we do not serve; supported are en-gb and de-de. Omitting the parameter is always valid and selects en-gb. 401 Invalid or missing API token. The Authorization header is absent, malformed, or the bearer token was revoked. 403 API access requires an Agency plan. The token authenticated, but its account is not on the plan that includes API access. 404 Scan not found. The scan token does not exist, or it belongs to another account. Both cases answer alike on purpose, so that a token cannot be used to probe which scans exist elsewhere. 429 Rate limit exceeded, counted per account and calling IP address, per minute. The answer usually carries a Retry-After header with the seconds to wait; wait that long rather than retrying at once, and fall back to a minute when the header is absent.

06 Migration

Coming from the securityheaders.com API

Snyk is retiring the securityheaders.com API in April 2026. The mapping below is deliberately literal: where we mean something different, it says so instead of bending our answer into a shape that would read as compatible and quietly report something else.

Field mapping grade No equivalent. We return score from 0 to 100 with band and rank. We do not emit a letter grade, because ours would be computed from a different set of checks and an A would not mean their A. missing headers checks entries whose state is violated, plus a findings entry with a stable code. A header we could not read at all is notChecked, not violated. raw headers Not returned. A public scan result describes risk and never hands out the response evidence of a foreign site. warnings The checks information-disclosure, x-xss-protection and cors-configuration, each with its own finding. q=<url> POST /api/v1/scans with a JSON body {"url": "https://example.com/"}. The response carries the scan token you then read. hide=on No equivalent. Every finished scan appears on the public /scans list with its URL, its creation time and its token. followRedirects Always followed. Note that a browser upgrades HTTP to HTTPS internally on an HSTS-preloaded domain, so an empty redirect chain is not proof that a site does not redirect. synchronous answer Asynchronous. POST queues the scan and returns a token, GET reads the result once the status is finished.

07 Beyond the headers

What we check in addition

The security dimension is wider than a header list. These checks come with the same call and appear in checks and findings alongside the header ones.

  • TLS certificate and transport: validity, hostname match, trust chain, key strength and signature algorithm, plus which protocol versions the server actually offers, including legacy TLS 1.0 and 1.1.
  • HSTS beyond presence: whether the policy is effective and whether it qualifies for the preload list, rather than only whether the header exists.
  • CSP delivery and strength: the enforcing header, the report-only header and a CSP delivered as a meta element, and how strong the directives actually are.
  • Cookie attributes: Secure, HttpOnly and SameSite, including the insecure SameSite=None case.
  • DNS and mail security: SPF, DMARC, DNSSEC, CAA, MTA-STS and TLS-RPT.
  • security.txt, subresource integrity, mixed content and the HTTPS redirect.

08 Limits

What we deliberately do not do

The honest half of the comparison. None of these is an oversight, and each one is a decision we would rather state than have you discover in production.

  • No letter grade, for the reason given in the mapping above.
  • No raw header values and no evidence payload on the public result.
  • No active probing of foreign infrastructure: no port scans, no subdomain enumeration, no admin panel or backup discovery.
  • OCSP stapling is not checked. PHP does not hand out the stapled response, so the check would be a claim rather than a measurement.
  • A header value longer than 512 characters is stored truncated and is reported as notChecked. A long, correct Content-Security-Policy must not come back as a weak one.
  • Rate limits apply per account and calling IP address, counted per minute, and a scan is queued rather than run on the spot.

Ready to wire it up?

Create a token in your account, run one scan by hand to see the shape of the answer, then point your pipeline at it.

Manage API tokens See the plans
Recent scans Guides Pricing API Data protection Imprint © 2026 Erseni