BCDRPulse

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.

Get a free API key

600 scans an hour instead of 60, and webhooks for alerts. We email you a link that shows the key once.

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=all and api=/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 carries share: null and stored: 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 it

Alerts 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.