Check security headers
A site can have a hardened server and a leaky front door if its response headers never tell the browser to lock anything down. This security headers checker API fetches a URL, reads the protective headers actually present, and reports what is set correctly, what is weak, and what is missing entirely, so hardening becomes a checklist instead of a guess.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
The gap between having HTTPS and being hardened
Plenty of teams assume that once a certificate is in place the security story is finished, but the browser still needs instructions: whether to allow the page in an iframe, which script sources to trust, whether to force HTTPS on every future visit. Those instructions live entirely in a handful of response headers, and it is entirely possible to run a perfectly encrypted site that still leaves every one of them unset.
What the check inspects
Call POST /web/security-headers with a URL and the task examines the response for the headers that matter for hardening — Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and their close relatives — and returns, for each one, whether it is present, what it is set to, and whether that value is meaningfully protective or just present as a formality.
Why these specific headers exist
Most of them were added to the web platform in response to real, well-documented attack classes: X-Frame-Options exists because clickjacking tricks users into clicking something invisible layered over a legitimate page, and Content-Security-Policy exists because script injection kept slipping past input sanitization alone. They are not decorative; each one closes a door that was, at some point, propped open in the wild before browsers agreed on a header to close it.
Fitting it into a security workflow
Because results arrive by signed webhook, the check drops cleanly into a CI pipeline as a release gate, into a periodic compliance scan across a portfolio of domains, or into a one-off audit before a client engagement — anywhere a human would otherwise open dev tools and check headers by memory, which inevitably means missing one.
Reading a result honestly
A missing header is not automatically a critical finding — a static marketing page with no user input has a very different risk profile from a login form — so treat the report as a starting point for a judgment call specific to what the site actually does, not a pass or fail stamp to apply blindly. Prioritize the pages that actually collect or display user data over the ones that never touch it.
What you can do with it
Pre-launch hardening review
Run a check before a site goes live to confirm the headers a security review would flag are already in place.
Client and portfolio audits
Scan a list of client domains on a schedule to catch a header that got dropped in a server migration or CDN change.
CI release gates
Fail a deploy pipeline automatically if a required header like Strict-Transport-Security disappears from a build.
Vendor and third-party due diligence
Check a partner's public-facing site headers as part of a lightweight security assessment before integrating with them.
FAQ
What security headers does the checker look at?
It examines the headers most relevant to browser-side hardening, including Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options and Referrer-Policy, reporting presence and value for each.
Does a missing header mean the site is insecure?
Not automatically. Impact depends on what the site does; a missing header on a static page carries less risk than the same gap on a page handling user input or authentication.
Is there a free plan?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
How is this different from reading headers manually in a browser?
It automates the check, runs it asynchronously at scale across any number of URLs, and delivers a structured, signed result instead of a manual read of raw text in dev tools.
How do results get delivered?
Via a signed webhook when the task completes, recommended for automation, or a signed link valid for 24 hours.
Is a failed check billed?
No. Failed tasks retry automatically up to three times at no charge; only successfully completed checks are billed.
What does a request cost?
$0.002 per request, one flat price regardless of how many headers are evaluated.
Can I audit many domains in bulk?
Yes, submit one task per URL and track them by task_id; the async model is built for scanning entire domain portfolios on a schedule.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/web/security-headers \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://ejemplo.com"}'const res = await fetch("https://api.kit.forhosting.com/web/security-headers", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"url": "https://ejemplo.com"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/security-headers",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"url": "https://ejemplo.com"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/security-headers", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"url":"https://ejemplo.com"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"url":"https://ejemplo.com"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/security-headers", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"url": "https://ejemplo.com"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.security_headers",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
timeout_sec | 30 |
max_crawl_pages | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |