Cactus

JSON API

Run the same URL and email analyses programmatically. Public, rate-limited, no API key required.

Endpoints

A machine-readable OpenAPI 3 description of every endpoint below is available at /openapi.json — import it into Postman, Insomnia, or a client generator.

GET /api/check-url

Analyze a single URL. Use this for quick lookups, bookmarks, and command-line use.

Query parameters:

  • u — the URL to analyze (max 2048 chars). Required.
  • lang — response language: en-CA (default) or fr-CA. Optional.

Example:

curl "https://your-safecheck-host/api/check-url?u=https://example.com"
curl "https://your-safecheck-host/api/check-url?u=https://example.com&lang=fr-CA"

POST /api/check-url

Same analysis, but accepts a JSON body. Use this when the URL might contain characters awkward in a query string.

curl -X POST "https://your-safecheck-host/api/check-url?lang=fr-CA" \
     -H "Content-Type: application/json" \
     -d '{"url":"https://example.com"}'

POST /api/check-email

Analyze an email body, with optional raw headers.

JSON body:

  • emailBody — the email body text (max 20000 chars). Required.
  • rawHeaders — raw email headers (max 20000 chars). Optional.
curl -X POST https://your-safecheck-host/api/check-email \
     -H "Content-Type: application/json" \
     -d '{
       "emailBody": "Dear customer, your account has been suspended...",
       "rawHeaders": "From: PayPal \nReply-To: hacker@evil.example"
     }'

GET /api/check-tls

Scan a host's TLS/SSL configuration: negotiated protocol and cipher, certificate details and chain, HSTS, revocation, and an overall A+–F grade. Only public hosts on port 443 are scanned.

Query parameters:

  • host — the domain or host to scan (max 255 chars). Required.
curl "https://your-safecheck-host/api/check-tls?host=example.com"

GET /api/check-email-auth

Check a domain's email authentication posture — SPF, DKIM, and DMARC — with an overall A–F grade. Accepts a domain or an email address.

Query parameters:

  • domain — the domain or email address to check (max 255 chars). Required.
curl "https://your-safecheck-host/api/check-email-auth?domain=example.com"

The grade field (e.g. "A+", "F") is the headline result. The /api/check-tls and /api/check-email-auth responses return localization keys (gradeNoteKeys, noteKeys, errorKey) rather than localized prose; resolve them client-side if you need display text.

POST /api/check-message

Analyze a text message (SMS or chat) for scam signals using local heuristics. The message is never stored.

JSON body:

  • message — the message text (max 20000 chars). Required.
curl -X POST https://your-safecheck-host/api/check-message \
     -H "Content-Type: application/json" \
     -d '{"message":"URGENT: your parcel is held, pay at http://bit.ly/x"}'

GET /api/check-headers

Grade a site's HTTP security headers (HSTS, CSP, X-Content-Type-Options, frame options, Referrer-Policy, Permissions-Policy) with an A+–F grade.

Query parameters:

  • url — the site URL to scan (max 2048 chars). Required.
curl "https://your-safecheck-host/api/check-headers?url=https://example.com"

GET /api/check-typosquat

Generate look-alike / typosquat variants of a domain and report which ones are actually registered (resolve in DNS).

Query parameters:

  • domain — the domain to check (max 255 chars). Required.
curl "https://your-safecheck-host/api/check-typosquat?domain=example.com"

GET /api/check-certs

List Certificate Transparency records for a domain — unique subdomains and a newest-first sample of certificates. Sourced from crt.sh, with Cert Spotter as an automatic fallback. The source field says which one answered.

Query parameters:

  • domain — the domain to look up (max 255 chars). Required.
curl "https://your-safecheck-host/api/check-certs?domain=example.com"

GET /api/check-ip

Reputation and identity for an IP address, from public DNS only: reverse DNS, owner/ASN (Team Cymru), and a sample of DNS blocklists. It never connects to the IP itself.

Query parameters:

  • ip — the IPv4 or IPv6 address (max 255 chars). Required.
curl "https://your-safecheck-host/api/check-ip?ip=8.8.8.8"

GET /api/check-dns

The common DNS records for a domain — A, AAAA, CNAME, MX, TXT, NS, CAA, and SOA — grouped by type. Public DNS only; it never connects to the host.

Query parameters:

  • domain — the domain to look up (max 255 chars). Required.
curl "https://your-safecheck-host/api/check-dns?domain=example.com"

GET /api/check-whois

The public registration record (WHOIS) for a domain, read over RDAP via rdap.org — registrar, creation/expiry/updated dates, EPP status codes, DNSSEC, nameservers, and any non-redacted registrant. It never connects to the domain itself.

Query parameters:

  • domain — the domain to look up (max 255 chars). Required.
curl "https://your-safecheck-host/api/check-whois?domain=example.com"

GET /api/check-hash

VirusTotal's multi-engine reputation for a file, looked up by hash (MD5, SHA-1, or SHA-256). Only the hash is sent — no file is uploaded or scanned. found: false means the file is unknown to VirusTotal, which is not the same as safe.

Query parameters:

  • hash — an MD5, SHA-1, or SHA-256 hex hash. Required.
curl "https://your-safecheck-host/api/check-hash?hash=275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"

Several of these endpoints return localization keys (e.g. titleKey, errorKey, signal keys) rather than localized prose; resolve them client-side if you need display text.

Try it

Enter any URL to call GET /api/check-url live and see the full JSON response.

Response shape

The response is the same analysis object the web UI renders. Enums (severity levels, etc.) are returned as strings, not ints. Example shape for /api/check-url:

{
  "input": "https://example.com",
  "normalizedUrl": "https://example.com",
  "analyzedHost": "example.com",
  "confidenceScore": 95,
  "verdict": "Likely low risk",
  "verdictKey": "verdict.lowRisk",
  "summary": "This link does not show obvious warning signs from the available checks.",
  "summaryKey": "summary.url.lowRisk",
  "isInconclusive": false,
  "recommendedAction": "This link does not show obvious warning signs, but stay cautious...",
  "findings": [
    {
      "title": "Uses HTTPS",
      "titleKey": "finding.usesHttps.title",
      "description": "The link uses HTTPS. This is good, but HTTPS alone does not prove a site is safe.",
      "descriptionKey": "finding.usesHttps.description",
      "severity": "Info",
      "scoreImpact": 0,
      "parameters": null,
      "sourceHintKey": null
    }
  ],
  "redirects": {
    "wasAttempted": true,
    "wasSuccessful": true,
    "wasBlockedByPolicy": false,
    "hops": [],
    "finalUrl": "https://example.com",
    "finalHost": "example.com",
    "hitMaxHops": false,
    "unresolvable": false,
    "containsHttpsToHttpDowngrade": false,
    "errorMessage": null,
    "skipReason": null
  },
  "domainAge": {
    "wasAttempted": true,
    "wasSuccessful": true,
    "lookedUpDomain": "example.com",
    "registeredAt": "1995-08-14T04:00:00+00:00",
    "ageInDays": 10878,
    "ageBand": "established",
    "humanReadableAge": "Registered about 29 years ago",
    "errorMessage": null,
    "skipReason": null
  }
}

Status codes

  • 200 — analysis returned. The response body always contains a result; check verdict, confidenceScore, and isInconclusive.
  • 400 — missing required field or input exceeded length cap. Response body is { "error": "..." }.
  • 429 — rate limit exceeded. Response body is plain text.

Rate limits

Per client IP, shared with the HTML form:

  • 10 / minute for /api/check-url (GET and POST share the same partition) and /api/check-message (local analysis).
  • 5 / minute each for /api/check-email, /api/check-tls, /api/check-email-auth, /api/check-headers, /api/check-certs, /api/check-whois, /api/check-dns, /api/check-hash, and /api/check-ip (outbound work per request).
  • 3 / minute for /api/check-typosquat (it fans out to dozens of DNS lookups).

Language / localization

The URL and email endpoints support bilingual responses (English and French Canadian). Two ways to request French:

  • Query parameter: append ?lang=fr-CA (or just ?lang=fr) to the request URL.
  • Header: send Accept-Language: fr-CA. The query parameter takes priority if both are present.

When a language is requested, the string fields in the response (verdict, summary, recommendedAction, and each finding's title and description) are returned in that language. The *Key fields (e.g. verdictKey, titleKey) are always returned and can be used to look up or re-render translations on the client side.

Notes

  • No authentication or API key for now. Plan accordingly if you embed the host URL anywhere public.
  • No CORS headers are emitted, so the API is intended for server-to-server use, scripts, or browser-extension manifests — not direct calls from a SPA in a different origin.
  • The same caching as the HTML pages applies (results memoized in-process for up to 6 hours), so identical requests cost nothing after the first. Caching is language-neutral — the localization step runs at response time without hitting the cache again.
  • See Privacy for what each analysis sends to third parties (Google Web Risk, rdap.org).