SecurityHeaders.com API Shutdown: How to Migrate Your Scans
Keep CI/CD HTTP header checks running after the SecurityHeaders.com API shutdown in April 2026. A step-by-step migration guide with a drop-in replacement.
TL;DR - The SecurityHeaders.com API is shut down as of April 2026. If you were using it for CI/CD header checks, dated grade records or scheduled audits, you now need a replacement. The Guardr REST API is a drop-in alternative: same shape of GET request, structured JSON response and a free tier for automated use. Every request and response in this post follows what the live API returns today - not pseudocode. Migrations typically take under five minutes per pipeline.
What is covered
- What happened
- What you actually need to replace
- The Guardr REST API - what it is
- Side-by-side: the old call vs. the new one
- What a response looks like
- What a clean-site response looks like
- Response field mapping
- Rate limits and the free tier
- Error handling
- Five-minute CI/CD migration (GitHub Actions)
- What is actually different, honestly
- What about other alternatives?
- Frequently asked questions
What happened
If you have been watching the HTTP header tooling space, you already know: the SecurityHeaders.com API has been discontinued.
The short version of how we got here:
- January 2023 - SecurityHeaders.com launched its paid API, letting developers automate what had been a manual website check.
- April 2026 - The API shut down. The API page at securityheaders.com/api now reads: “This service has been discontinued. We are no longer providing new subscriptions or renewing existing subscriptions.” The free web UI at securityheaders.com remains live.
In between, Probely acquired SecurityHeaders.com from its original creator, Scott Helme. Snyk later acquired Probely.
Credit where it is due: SecurityHeaders.com, the tool Scott Helme built, has done more to raise the baseline for HTTP security headers than almost anything else on the web. This post is not a criticism of any business decision.
But if you are one of the many developers who built CI/CD pipelines, scheduled audits, compliance reports or monitoring systems on top of that API, you still need somewhere to go. That is what this guide is for.
What you actually need to replace
Before picking a replacement, it is worth being clear about what the SecurityHeaders.com API actually did. Across issues, forum threads and support email, the four most common use cases were:
- CI/CD gate on HTTP security headers - fail the build if CSP, HSTS or X-Frame-Options regress.
- Scheduled audits across an estate of sites - run a weekly scan on 50+ domains and dump results to a dashboard or Slack.
- Grade records - JSON output kept as a dated record of each site’s grade.
- Client reporting for agencies - automated scans feeding into monthly client reports.
All four share a common shape: one HTTP GET per domain, JSON in, structured response out, repeat on a schedule. That is the shape we need to replicate.
The Guardr REST API - what it is
Guardr is a website health and uptime monitoring service built on Cloudflare. It checks headers, TLS, DNS, cookies and exposure paths for misconfigurations, scans JavaScript bundles for leaked keys and gives each site a health grade from A+ to F with a health score. It also checks uptime every minute and repeats a failed check from three regions before it counts the site as down.
The Guardr API v1 launched specifically to give SecurityHeaders.com API users a place to land. It covers the same core job - programmatic scans with a structured JSON response - plus a few things the old API did not have.
The full endpoint list is short:
GET /v1/scan/:domain- read a domain’s latest scan. Without a key it allows 5 requests per day per IP and returns the grade, the score and the five category scores, scanning the domain first when nothing is cached. With a key it returns the full document, for a domain that is a site on your account or was scanned through the API in the last hour.POST /v1/scan- trigger a fresh on-demand scan. Requires an API key, available on every plan including Free.GET /v1/account- check your plan, quota config and active keys.
Base URL: https://api.guardr.io
Full documentation with response samples is at guardr.io/docs/api/.
Side-by-side: the old call vs. the new one
Here is the exact migration for the most common pattern - a scheduled GET against a single domain.
Before (SecurityHeaders.com):
curl -H "x-api-key: YOUR_OLD_KEY" \
"https://api.securityheaders.com/?q=example.com&hide=on&followRedirects=on"
After (Guardr):
curl -H "X-API-Key: YOUR_NEW_KEY" \
"https://api.guardr.io/v1/scan/example.com"
Same style of auth header, same shape of GET request, domain passed as a URL path segment instead of a query parameter. The hide and followRedirects parameters from the old API have no equivalent in Guardr. Redirects are always followed as part of the scan.
Want to try it without signing up? The GET endpoint works without a key. It allows 5 requests per day per IP and returns the grade, the score and the five category scores:
curl https://api.guardr.io/v1/scan/example.com
That is useful for quick health checks from a shell or a read-only monitoring script, but for CI/CD and anything with quotas or full results, you will want a key.
With a key, the GET endpoint never runs a scan. It returns a result that any API scan of that domain cached in the last hour. Failing that, it returns the latest stored scan of a site on your account. To force a fresh scan, use the POST variant - available on every plan:
curl -X POST "https://api.guardr.io/v1/scan" \
-H "X-API-Key: YOUR_NEW_KEY" \
-H "Content-Type: application/json" \
-d '{"domain":"example.com"}'
POST scans run inline and typically return in 5 to 15 seconds depending on the target’s response times and redirect chain.
What a response looks like
This is the response from GET /v1/scan/example.com with an API key, trimmed for readability. A Free key and an All sites key return the same document. Every field shown is named exactly as the API returns it:
{
"domain": "example.com",
"scanned_at": "2026-04-18T12:46:48Z",
"grade": "C-",
"score": 58,
"categories": {
"tls": 55,
"headers": 0,
"cookies": 100,
"dns": 90,
"exposure": 100
},
"issues": [
{
"title": "HSTS not enabled",
"severity": "high",
"category": "TLS",
"description": "Strict-Transport-Security header is missing. Browsers won't enforce HTTPS for future visits.",
"remediation": {
"summary": "Add the Strict-Transport-Security header to tell browsers to always use HTTPS. This prevents protocol downgrade attacks.",
"effort": "moderate",
"warning": "Before enabling HSTS, make sure your entire site works over HTTPS - including all subdomains if using includeSubDomains. Once browsers cache the HSTS policy, HTTP access is blocked until max-age expires.",
"snippets": [
{
"platform": "Cloudflare (_headers)",
"code": "/*\n Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"
},
{
"platform": "Nginx",
"code": "add_header Strict-Transport-Security \"max-age=31536000; includeSubDomains; preload\" always;"
},
{
"platform": "Apache",
"code": "Header always set Strict-Transport-Security \"max-age=31536000; includeSubDomains; preload\""
}
],
"learnMore": "/blog/security-headers-guide"
}
}
],
"issues_truncated": false,
"total_issues": 8,
"secrets_found": [],
"tls": { "hstsPresent": false, "httpToHttps": false, "score": 55, "...": "..." },
"dns": { "dnssecEnabled": true, "caaPresent": false, "score": 90, "...": "..." },
"cookies": { "present": false, "cookiesCount": 0, "score": 100 },
"exposure_paths": {
"/.env": { "status": 404, "exposed": false },
"/.git/HEAD": { "status": 404, "exposed": false },
"/phpinfo.php": { "status": 404, "exposed": false }
}
}
Three things worth highlighting:
remediation.effort is a field you will not find on the old API. Every issue comes back tagged quick-fix, moderate or requires-planning. That is useful when you are prioritizing a backlog - you can filter for quick wins first and batch the larger CSP rollouts for a planned sprint.
remediation.warning flags issues where the naive fix will break something. HSTS has one about subdomain rollout. CSP has one about third-party integrations breaking. X-Frame-Options has one about intentional iframe embedding. These are the kinds of notes that usually live in a senior engineer’s head - having them inline in the API response means a junior dev can run the fix without breaking production.
remediation.snippets is an array of { platform, code } objects. Platforms include Cloudflare (_headers), Nginx, Apache and occasionally multi-step flows like Step 1: Audit with report-only mode / Step 2: Enforce after testing for CSP deployments.
What a clean-site response looks like
For contrast, here is GET /v1/scan/guardr.io on the same key, trimmed the same way. This is the shape you will see when a site is mostly configured correctly - one low-severity informational note, everything else clean:
{
"domain": "guardr.io",
"scanned_at": "2026-04-18T14:15:30Z",
"grade": "A+",
"score": 99,
"categories": {
"tls": 100,
"headers": 95,
"cookies": 100,
"dns": 100,
"exposure": 100
},
"issues": [
{
"title": "CSP note: content-security-policy",
"severity": "low",
"category": "Security Headers",
"description": "CSP present but: uses 'unsafe-inline' in script-src without hashes/nonces.",
"remediation": { "effort": "requires-planning", "...": "..." }
}
],
"issues_truncated": false,
"total_issues": 1,
"tls": {
"httpsServed": true,
"httpToHttps": true,
"hstsPresent": true,
"hstsMaxAge": 31536000,
"hstsIncludesSubs": true,
"hstsPreload": true,
"score": 100
},
"dns": {
"dnssecEnabled": true,
"caaPresent": true,
"score": 100
}
}
For CI/CD gating, the three fields that matter most are at the top level: grade, score and categories. Everything else is detail you only drill into when something regresses.
Response field mapping
If you are porting existing code, here is the practical mapping between the two APIs:
| What you want | SecurityHeaders.com | Guardr |
|---|---|---|
| Overall grade (A–F) | summary.grade | grade |
| Numeric score | not available | score (0–100) |
| Scanned site | summary.site | domain |
| Timestamp | summary.timestamp | scanned_at (ISO 8601) |
| Header-by-header results | summary.headers (traffic-light colors) | issues[] with severity, title, description, remediation |
| Raw response headers | rawHeaders[] | not returned - use a separate HEAD request if needed |
| Missing headers | inferred from rawHeaders | filter issues[] where category == "Security Headers" |
| Grade cap explanation | summary.gradeCap | categories object shows which area lost points |
The headline difference: the old API was header-centric (the whole response was organized around the raw HTTP headers). Guardr is issue-centric (the response is organized around “what is wrong and how to fix it”). That is a better fit for CI/CD gating, worse if what you wanted was a dump of every HTTP header on the response.
Rate limits and the free tier
Keyed requests have two independent limits: a per-minute burst limit on every scan request and a per-domain scan quota for POST scans. Requests without a key have a daily cap instead.
| Plan | Request limit | Scan quota | Quota window |
|---|---|---|---|
| Public (no key) | 5 requests/day per IP | GET only, scans on a cache miss | 1 day, reset at 00:00 UTC |
| Free | 60 req/min | 1/domain | 6 hours |
| All sites | 60 req/min | 1/domain | 6 hours |
Scan quota is per-domain, so scanning 10 different domains uses 10 independent slots - not 10× a single slot. With a key, GET /v1/scan/:domain never runs a scan and does not count against your scan quota at all, only against the burst limit. This matters for large sweeps: if you are auditing 50 client sites nightly, trigger one POST per domain and keep the result it returns. A keyed GET can read it again within the hour. After that, GET answers 404 for a domain that is not a site on your account unless another API call has scanned it since.
Keyed scan responses include rate-limit headers. Here is a 429 from a burst overflow:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 18
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1776550081
{
"error": "rate_limit_exceeded",
"message": "Burst limit reached (60 req/min).",
"retry_after": 18
}
There is also a distinct quota_exceeded error (also 429) when you have used your per-domain scan slot for the current window. Its body sets retry_after to the full window of 21600 seconds. The Retry-After header on that response counts only the burst window, so wait the retry_after seconds in the body before the next POST. For the burst 429, respect Retry-After - do not hammer.
One note on response shape: an authenticated key returns the same document on either plan. A scan response carries the grade, the score, the category breakdown and the full issues array with remediation text, alongside secrets_found, tls, dns, cookies and exposure_paths. issues_truncated is always false and total_issues matches the length of issues. The only trimmed surface is the no-key public endpoint, which returns the grade, the score and the five category scores with no issues array.
Error handling
Four error shapes you should handle:
401 Unauthorized - invalid key (a POST with no key gets a 401 with the error missing_key instead):
{
"error": "invalid_key",
"message": "API key not recognized. Check your key at guardr.io/dashboard/settings."
}
429 Rate Limited - shown above. For a burst 429, respect Retry-After. For quota_exceeded, wait the retry_after seconds in the body. Do not hammer.
404 Not Found - a keyed GET found no result cached in the last hour and no stored scan of a site on your account at that domain. Fall back to POST /v1/scan, which runs the scan and returns the result in the same response. A GET without a key never answers 404: it scans the domain instead.
5xx - transient. Retry with exponential backoff.
Five-minute CI/CD migration (GitHub Actions)
A common setup among SecurityHeaders.com API users: a nightly GitHub Actions workflow that hits the API, parses the grade and fails the job if it regresses. Here is that workflow, migrated:
# .github/workflows/security-scan.yml
name: Security Header Scan
on:
schedule:
- cron: '0 4 * * *' # daily at 04:00 UTC
workflow_dispatch:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Scan site with Guardr
env:
GUARDR_API_KEY: ${{ secrets.GUARDR_API_KEY }}
run: |
response=$(curl -sf \
-H "X-API-Key: $GUARDR_API_KEY" \
"https://api.guardr.io/v1/scan/example.com")
grade=$(echo "$response" | jq -r '.grade')
score=$(echo "$response" | jq -r '.score')
headers_score=$(echo "$response" | jq -r '.categories.headers')
echo "Grade: $grade | Score: $score | Headers: $headers_score"
# Fail if overall grade drops below B, OR headers category below 70
if [[ "$grade" == "D" || "$grade" == "E" || "$grade" == "F" \
|| "$grade" == "C" || "$grade" == "C-" ]]; then
echo "::error::Security grade regressed to $grade"
echo "$response" | jq '.issues[] | select(.severity == "critical" or .severity == "high")'
exit 1
fi
if (( headers_score < 70 )); then
echo "::error::Headers score dropped to $headers_score"
exit 1
fi
Add GUARDR_API_KEY to your repo secrets and generate the key in the Guardr dashboard under Settings → API Access. You are done.
This workflow reads with a keyed GET, which finds a result when the domain is a site on your account. For any other domain, swap the curl to a POST against /v1/scan: it runs a fresh scan and returns the result in the same response, within your quota of one scan per domain per 6 hours.
What is actually different, honestly
Better to migrate with clear eyes than to find out after launch that something surprised you.
Things Guardr adds that SecurityHeaders.com did not have:
- Platform-specific remediation snippets (Cloudflare, Nginx, Apache) inline in the response
remediation.effortclassification (quick-fix/moderate/requires-planning) for backlog prioritizationremediation.warningfield flagging common pitfalls in the naive fix- TLS/SSL scanning with HSTS parsing (
hstsMaxAge,hstsIncludesSubs,hstsPreloadas structured fields) - DNS checks (DNSSEC + CAA records)
- Exposure path checks (
/.git/HEAD,/.env,/phpinfo.php) - JavaScript bundle secret scanning (finds leaked OpenAI, Anthropic, Stripe, AWS, Supabase service-role keys in shipped JS)
- Cookie attribute audits (Secure, HttpOnly, SameSite)
- Rate-limit headers on keyed scan responses
- Numeric scoring per category, not just a letter grade
Things the old API did that Guardr’s v1 API does not:
- Raw HTTP header dump (
rawHeaders[]with color-coded explanatory text). Guardr returns structured findings instead of raw headers. If you specifically need a raw dump, a separate HEAD request is a better tool for the job. - The
hide=onparameter. Guardr has no equivalent setting.
Things deliberately left out of v1 until users ask:
- History endpoint (
GET /v1/scan/:domain/history) - Before/after comparison endpoint (
GET /v1/scan/:domain/compare) - PDF-via-API (
GET /v1/scan/:domain/pdf)
All three exist in the Guardr dashboard today; exposing them as endpoints is a matter of when enough users ask. If any of these is blocking your migration, contact us directly.
What about other alternatives?
Being honest: Guardr is not the only option. The best tool depends on what you are trying to accomplish.
- Mozilla Observatory - free scanner, excellent for one-off manual checks. Current version does not offer a public API.
- Qualys SSL Labs - gold standard for deep TLS analysis. Does not cover HTTP security headers.
- A handful of new replacement endpoints launched or expanded around the shutdown. Worth shopping around if Guardr’s scope is not a fit.
- Roll your own - if all you need is “does this site send HSTS”, a 30-line Python script and a cron job will do it. You lose the grading, remediation text, historical tracking and alerting, but it is free.
In short: if you want a grade-based API with remediation and scheduled monitoring, Guardr is the closest drop-in. If you want deep TLS analysis specifically, use SSL Labs. If you want manual one-off scans, Mozilla Observatory is free and excellent. Pick the tool that matches the job.
If HTTP security headers are still a little fuzzy for you beyond CSP and HSTS, we have published a complete guide to adding security headers across platforms and a primer on what a website health grade actually means that cover the fundamentals.
Frequently asked questions
Is the SecurityHeaders.com website also shutting down?
No. As of April 2026, only the paid API is discontinued. The free web-based scanner at securityheaders.com is still operational. Snyk has not announced any plan to retire it. This guide is specifically about replacing the programmatic API.
Do I need to pay to use the Guardr API?
No. The Guardr free tier includes two API keys. They return exactly what a paid key returns - the grade, the score, the category breakdown and the full issues list with TLS, DNS, cookie and exposure data. Free keys also carry POST scan access, so you can trigger fresh scans in CI, one scan per domain per 6-hour window. The scan window and the burst limit are the same on both plans. There is also a no-key public endpoint (GET /v1/scan/:domain, 5 requests per day per IP) that returns the grade, the score and the five category scores - useful for quick health checks from anywhere.
How fast is a Guardr scan?
A fresh POST scan typically completes in 5 to 15 seconds depending on the target site’s response times and redirect chain. If you are running a large sweep, read results with a keyed GET /v1/scan/:domain - it never runs a scan and does not count against your per-domain scan quota.
Will my grade be the same as it was on SecurityHeaders.com?
Often similar, sometimes different. Guardr scores across five categories - TLS, headers, cookies, DNS, exposure paths - with weights documented on the methodology page. SecurityHeaders.com graded primarily on HTTP response headers. A site with weak TLS will score lower on Guardr than it did on SecurityHeaders.com; a site with missing headers but perfect TLS might score similarly. The categories object in every response lets you see exactly where points are coming from.
How do I check my current usage?
Hit GET /v1/account with your key:
curl -H "X-API-Key: YOUR_KEY" \
"https://api.guardr.io/v1/account"
It returns your plan, burst limit, scan window and a redacted list of your active keys:
{
"plan": "paid",
"quota": {
"scan_window": 21600,
"burst_per_minute": 60
},
"keys": [
{
"key_id": "...",
"display": "grd_live_••••••••••••••••f630",
"label": "CI pipeline",
"created_at": "18.04.2026",
"last_used_at": "18.04.2026",
"revoked": false
}
]
}
Can I keep a record of each site’s grade?
Yes. PDF reports include the full scan result, timestamp and per-issue findings. Each report is a dated record of a site’s grade. Branding is on both plans, so you can white-label reports with your own logo for client deliverables.
Do you support webhook notifications?
Yes, on both plans. Guardr posts a JSON payload to a URL you choose on three events: a grade drop, a site going down and a site recovering. Configure it in dashboard settings after signing in.
What if I need an endpoint the API does not have?
Contact us directly. API v1 is deliberately minimal. History, compare and PDF-via-API all exist in the dashboard today; exposing them as API endpoints is a matter of when enough users ask, not whether.
Ready to migrate?
The shortest path:
- Sign up at guardr.io (free, no credit card).
- Go to Settings → API Access and generate a key.
- Swap
api.securityheaders.comforapi.guardr.io/v1/scanand update your auth header fromx-api-keytoX-API-Key. - Re-run your CI job.
Full documentation, response samples and curl examples are at guardr.io/docs/api/.
If anything is unclear or an endpoint you relied on in the old API has no Guardr equivalent yet, tell us. This API exists because the old one was discontinued - it should actually serve the use cases the old API served.