SecurityHeaders.com API Shutdown: How to Migrate Your Scans
SecurityHeaders.com API is discontinued in April 2026. Here is a step-by-step guide for CI/CD security header checks, with examples and 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, compliance scans 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 is a real example from the live API - 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 real 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 web security space this spring, you already know: the SecurityHeaders.com API has been discontinued. Visit securityheaders.com/api today and you will see the notice - no new subscriptions, no renewals, service retired.
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.
- June 2023 - Probely acquired SecurityHeaders.com from its original creator, Scott Helme.
- June 2025 - Snyk acquired Probely.
- April 2025 - Probely announced the API would be discontinued in April 2026, giving users a year of notice.
- April 2026 - The API is now shut down. The free web UI at securityheaders.com remains live, but the programmatic endpoint is gone.
Credit where it is due: Snyk and Probely gave a full year of warning. SecurityHeaders.com itself - a tool that has done more to raise the baseline for HTTP security headers than almost anything else on the web - continues to exist. Scott Helme’s original work remains a huge contribution to the field. 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. From what I have seen in issues, forums and my own inbox, 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.
- Compliance evidence - JSON output filed as evidence for SOC 2, PCI or internal policy reviews.
- 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
I am Anatoli, founder of Guardr - a Cloudflare-native tool that scans websites for security misconfigurations (headers, TLS, DNS, cookies, exposure paths and JS bundle secrets) and monitors uptime around the clock, confirming an outage from three global regions before recording it.
The Guardr API v1 launched this month specifically to give SecurityHeaders.com API users a place to land. It covers the same core job - programmatic security 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 the most recent cached scan for a domain. Works with no key (20 req/day per IP, grade + score only) or with a key for richer results.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 - scan results are private to your account by default. Redirects are always followed as part of the scan.
Want to try it without signing up? The GET endpoint works without a key, rate-limited to 20 req/day per IP, returning grade + score only:
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.
If you want to force a fresh scan (the Guardr GET endpoint returns a cached result, up to an hour old), 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–15 seconds depending on the target’s response times and redirect chain.
What a real response looks like
This is the actual response from GET /v1/scan/example.com on a Solo-plan key, trimmed for readability but with every field name preserved exactly as the API returns it:
{
"domain": "example.com",
"scanned_at": "2026-04-18T12:46:48.373Z",
"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.",
"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 },
"/wp-login.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. 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:30.429Z",
"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
Two independent limits apply to every request: a per-minute burst limit and a per-domain scan quota for POST scans.
| Plan | Burst limit | Scan quota | Quota window |
|---|---|---|---|
| Public (no key) | 20 req/day per IP | Read only | - |
| Free | 5 req/min | 1/domain | 7 days |
| Solo | 15 req/min | 1/domain | 24 hours |
| Starter | 30 req/min | 1/domain | 24 hours |
| Pro | 60 req/min | 1/domain | 6 hours |
| Agency | 120 req/min | 1/domain | 1 hour |
Scan quota is per-domain, so scanning 10 different domains uses 10 independent slots - not 10× a single slot. The GET /v1/scan/:domain endpoint reads from cache 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, then use GET for all subsequent reads during the day.
Every response includes standards-compliant rate-limit headers. Here is a real 429 from a burst overflow on the free tier:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 18
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1776550081
{
"error": "rate_limit_exceeded",
"message": "Burst limit reached (5 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. Handle both in CI by respecting the Retry-After header - do not hammer.
One note on free-tier responses: on the Free plan, scan responses include the grade, score, category breakdown and the top three critical/high severity issues with full remediation text. The response carries issues_truncated: true, total_issues and upgrade_url so your code can detect when it is hitting the ceiling. The secrets_found, tls, dns, cookies and exposure_paths objects are omitted until you are on a paid plan. Plan your integration accordingly - if you need full TLS or DNS data in CI, start with Solo at $7/month.
Error handling
Four error shapes you should handle:
401 Unauthorized (missing or invalid key):
{
"error": "invalid_key",
"message": "API key not recognized. Check your key at guardr.io/dashboard/settings."
}
429 Rate Limited - shown above. Respect Retry-After; do not hammer.
404 Not Found - domain was never scanned and the GET path cannot find a cached result. Either fall back to POST /v1/scan or try again after the first scan completes.
5xx - transient. Retry with exponential backoff.
Five-minute CI/CD migration (GitHub Actions)
The most common setup I saw with SecurityHeaders.com 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.
If you would rather trigger a fresh scan per run instead of reading from cache, swap the curl to a POST against /v1/scan - just be mindful of your per-domain scan quota.
What is actually different, honestly
I would rather you migrate with clear eyes than find out post-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 security checks (DNSSEC + CAA records)
- Exposure path checks (
/.git/HEAD,/.env,/phpinfo.php,/wp-login.php) - JavaScript bundle secret scanning (finds leaked OpenAI, Anthropic, Stripe, AWS, Supabase service-role keys in shipped JS)
- Cookie attribute audits (Secure, HttpOnly, SameSite)
- Standards-compliant rate-limit headers on every response
- 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. Scan results are private to your account by default - nothing to opt out of.
Things I am deliberately leaving 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, email me 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 announcement. 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.
My honest take: 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 website security scores actually mean 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 one API key that returns the grade, score, category breakdown and top three critical/high severity findings per scan - and it also includes POST scan access, so you can trigger fresh scans in CI (one scan per domain per 7-day window on Free). You need a paid plan if you want full TLS/DNS/cookie/exposure data in the response, tighter scan windows or higher burst limits. There is also a no-key public endpoint (GET /v1/scan/:domain, 20 req/day per IP) that returns grade + score only - useful for read-only health checks from anywhere.
How fast is a Guardr scan?
A cached GET returns in under 200ms. A fresh POST scan typically completes in 5–15 seconds depending on the target site’s response times and redirect chain. If you are running a large sweep, prefer the GET /v1/scan/:domain endpoint - it reads from cache 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": "starter",
"quota": {
"scan_window": 86400,
"burst_per_minute": 30
},
"keys": [
{
"key_id": "...",
"display": "••••••••••••••••f630",
"label": "CI pipeline",
"created_at": "18.04.2026",
"last_used_at": "18.04.2026",
"revoked": false
}
]
}
Can I use Guardr for compliance evidence?
Yes. PDF reports (available from the Starter plan and above) include the full scan result, timestamp and per-issue findings - which is what most SOC 2 and PCI auditors are looking for. Agency-plan branding lets you white-label reports with your own logo for client deliverables.
Do you support webhook notifications?
Yes, on Pro and above. Slack, Microsoft Teams and Discord webhooks fire on grade drops, SSL expiry warnings and downtime events. Configure them in dashboard settings after signing in.
What if I need an endpoint the API does not have?
Email me 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’s unclear or if there is an endpoint from the old API you relied on that does not have a Guardr equivalent yet, I want to hear about it. This API exists because the old one is gone - it should actually serve the use cases it was built to replace.
- Anatoli, founder of Guardr. Tel Aviv, Israel.