Home / Free tools API
Free tools API
The JSON API behind the four checkers. Without a key or an account: 3 scans a day per tool (the Down Checker: 60 an hour per address). A free key gives 60 an hour with a webmail address and 600 with a work email, plus webhooks.
Scans
GET https://bcdrpulse.com/api/tools/v1/<tool>?url=example.com tool: security-headers | ssl | response-time | down Authorization: Bearer bcdr_... (the key's own budget: 60 or 600 an hour)
ua=mobile: scan as a mobile browser.minGrade=B(security-headers, ssl): answers 422 with the whole scan when the grade is lower, so a CI step fails on a regression.surfaces=allandapi=/path(security-headers): the Coverage Map across the www host, robots.txt, a 404 page, a static file and an API route (counts as three scans).subdomains=1: the subdomains HSTS includeSubDomains would cover, from Certificate Transparency.origin=203.0.113.10(response-time): time the server behind a CDN directly.stream=1(response-time): NDJSON, one line per sample as it is measured, then the result.format=text: the report as plain text with a SHA-256 of its evidence, for scripts and AI assistants.store=0: the scan is answered once and not stored. The answer carriesshare: nullandstored: false; there is no share link and nothing is added to a history. It still counts against the limit.
The answer is { id, tool, createdAt, cached, report, capture, share }. The same URL and options asked again within a minute return the same scan. Errors: 400 bad address or option, 401 bad key, 403 an address we never scan, 422 below minGrade, 429 over the limit (with Retry-After), 503 scanner busy.
Other endpoints
GET /api/tools/v1/result/<id> a stored scan (30 days); ?format=text
GET /api/tools/v1/badge/<tool>?url= an SVG badge of the newest result (never scans)
POST /api/tools/v1/security-headers/paste grade pasted headers (text, or JSON { text, url })
GET /api/tools/v1/hsts-preload?domain= the HSTS preload list's status and checklist
POST /api/tools/v1/watches { kind, url, email | webhook, thresholdMs? }
GET /api/tools/v1/watches/<id>?code= an alert and its history; /evidence.csv for the log
DELETE /api/tools/v1/watches/<id>?code= stop it
POST /api/tools/v1/keys { email, label }: a key, confirmed by email
GET|DELETE /api/tools/v1/keys/me (Bearer) the key's limits, or revoke itAlerts and webhooks
An alert re-runs one tool for one address and tells you what changed: headers (security headers and the page's scripts, every 6 hours), ssl (30, 14, 7 and 1 days before expiry, replacement, revocation; twice a day), response-time (p95 over 24 hours above thresholdMs; hourly) and down-back (once, when a site answers again; every 5 minutes for 24 hours). With an email address the alert starts after the confirmation link; with a key it posts to your https webhook at once, signed with the key's secret.
POST <your webhook>
X-BCDRPulse-Event: headers.changed | scripts.changed | grade.changed | certificate.expiring
| certificate.changed | certificate.revoked | certificate.expired
| response-time.slow | response-time.recovered | site.back
X-BCDRPulse-Signature: t=1794500000,v1=<hex HMAC-SHA256 of "t.body" with your webhook secret>
{ "event", "watch": { "id", "kind", "url" }, "title", "details": [...], "severity",
"grade", "verdict", "scanId", "share", "at" }// Node: verify X-BCDRPulse-Signature (t=<unix seconds>,v1=<hex>)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, header, rawBody, toleranceS = 300) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceS) return false;
const want = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest();
return timingSafeEqual(want, Buffer.from(m[2], "hex"));
}Fair use
Scan sites you own or may test. The scanner identifies itself as BCDRPulseHeaders (about the scanner), never connects to private or internal addresses, and skips domains whose owners asked us not to check them.

