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.

apisecurity headersmigrationci/cdguide

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

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:

  1. CI/CD gate on HTTP security headers - fail the build if CSP, HSTS or X-Frame-Options regress.
  2. Scheduled audits across an estate of sites - run a weekly scan on 50+ domains and dump results to a dashboard or Slack.
  3. Compliance evidence - JSON output filed as evidence for SOC 2, PCI or internal policy reviews.
  4. 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 wantSecurityHeaders.comGuardr
Overall grade (A–F)summary.gradegrade
Numeric scorenot availablescore (0–100)
Scanned sitesummary.sitedomain
Timestampsummary.timestampscanned_at (ISO 8601)
Header-by-header resultssummary.headers (traffic-light colors)issues[] with severity, title, description, remediation
Raw response headersrawHeaders[]not returned - use a separate HEAD request if needed
Missing headersinferred from rawHeadersfilter issues[] where category == "Security Headers"
Grade cap explanationsummary.gradeCapcategories 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.

PlanBurst limitScan quotaQuota window
Public (no key)20 req/day per IPRead only-
Free5 req/min1/domain7 days
Solo15 req/min1/domain24 hours
Starter30 req/min1/domain24 hours
Pro60 req/min1/domain6 hours
Agency120 req/min1/domain1 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.effort classification (quick-fix / moderate / requires-planning) for backlog prioritization
  • remediation.warning field flagging common pitfalls in the naive fix
  • TLS/SSL scanning with HSTS parsing (hstsMaxAge, hstsIncludesSubs, hstsPreload as 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=on parameter. 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:

  1. Sign up at guardr.io (free, no credit card).
  2. Go to Settings → API Access and generate a key.
  3. Swap api.securityheaders.com for api.guardr.io/v1/scan and update your auth header from x-api-key to X-API-Key.
  4. 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.

Check your website's security score

Free scan - no signup required.

Scan your site →