Public API
The public API reads your HeaderHawk data from your own code: your sites, the issues browsers have reported on them and the reports behind each one, network errors and crashes, the deprecated APIs your pages use, the pages you monitor with their scans, the alerts HeaderHawk sent, your script inventory, and each site’s PCI DSS 4.0 readiness. It is read-only: nothing it does changes your account.
Every operation, with its parameters and responses, is in the reference. This page walks through a first session with it.
Create an API key
Section titled “Create an API key”API access is on the Professional plan and above. An owner or admin creates
keys under Settings › API keys. A key looks like hh_live_ followed by 49
more characters, and is shown once, when you create it: store it as a secret,
as you would a database password.
A key reads what a viewer reads, across every site in your account, and
works until it expires or someone revokes it. Revoking a key takes effect
within 60 seconds.
Make a first request
Section titled “Make a first request”Every request goes to https://api.us.headerhawk.com and carries the key as a
bearer token. GET /v1/me is the call that always works with a valid key, so
it is the one to try first:
curl https://api.us.headerhawk.com/v1/me \ -H "Authorization: Bearer $HEADERHAWK_API_KEY"{ "tenant": { "id": "…", "name": "Example Ltd", "plan": "professional", "retentionDays": 90 }, "key": { "id": "…", "name": "CI", "prefix": "a1b2c3d4", "createdAt": "2026-10-01T09:00:00.000Z", "expiresAt": "2027-10-01T09:00:00.000Z" }, "rateLimits": { "key": { "limit": 600, "windowSeconds": 60 }, "tenant": { "limit": 60, "windowSeconds": 60 } }}A 401 with the code invalid_api_key means the key is missing, malformed,
unknown, revoked or expired. The answer is the same for all five, so it
cannot be used to find out which keys exist.
List your issues
Section titled “List your issues”An issue groups the reports about the same thing, such as one CSP directive
blocking one host, whichever page sent them. GET /v1/issues lists them
across every site, most recently seen first:
curl "https://api.us.headerhawk.com/v1/issues?limit=20" \ -H "Authorization: Bearer $HEADERHAWK_API_KEY"{ "data": [ { "id": "…", "siteId": "…", "reportType": "csp-violation", "subject": "script-src-elem", "targetHost": "cdn.example.net", "reportCount": 412, "lastSeen": "2026-10-01T08:41:07.000Z" } ], "nextCursor": "eyJ2IjoxLCJvIjoyMH0"}The example leaves out most of each issue’s fields; the reference lists them all.
Operations that read reported data cover a window of time. Without from and
to, the issue lists cover the last 7 days, and you can ask for up to 31.
Each operation’s reference page gives its default and its limit, and a longer
window is refused with 400 rather than cut short. For a longer period, ask
for consecutive windows.
Page through a list
Section titled “Page through a list”A list takes limit (from 1 to 200, 50 by default) and answers with
nextCursor. Pass it back as cursor, with the same other parameters, for
the next page. On the last page it is null.
async function allIssues(key) { const issues = []; let cursor = null; do { const url = new URL("https://api.us.headerhawk.com/v1/issues"); url.searchParams.set("limit", "200"); if (cursor) url.searchParams.set("cursor", cursor); const response = await fetch(url, { headers: { Authorization: `Bearer ${key}` }, }); if (!response.ok) throw new Error(`HeaderHawk answered ${response.status}`); const page = await response.json(); issues.push(...page.data); cursor = page.nextCursor; } while (cursor); return issues;}A cursor is opaque and belongs to the query that produced it: sent with
different parameters, it is refused with 400.
Handle rate limits
Section titled “Handle rate limits”Each key may make 600 requests a minute. The operations that read reported
data (each one’s reference page says so) also share a budget of 60 requests
a minute across your whole account, whichever key spends it (an Enterprise
contract can set a higher one). GET /v1/me shows both limits.
A request that reaches a budget gets three headers back for whichever budget is nearest to running out:
| Header | Meaning |
|---|---|
RateLimit-Limit |
Requests allowed in the current window |
RateLimit-Remaining |
Requests left in it |
RateLimit-Reset |
Seconds until it starts again |
Over a budget, the answer is 429 with the code rate_limited and a
Retry-After header, in seconds. Wait that long and send the request again:
async function get(url, key) { for (let attempt = 0; attempt < 5; attempt++) { const response = await fetch(url, { headers: { Authorization: `Bearer ${key}` }, }); if (response.status !== 429 && response.status !== 503) return response; const seconds = Number(response.headers.get("Retry-After") ?? "5"); await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); } throw new Error("HeaderHawk is still busy; try again later");}A 503 with Retry-After means HeaderHawk could not answer just then, and
is never a sign that the request was wrong: retry it the same way.
Errors lists every code and what to do about each.
- Errors: the error shape and every code.
- Versioning and stability: what
v1promises and how a change is announced. - Reference: every operation.