Cactus
JSON API
Run the same URL and email analyses programmatically. Public, rate-limited, no API key required.
Endpoints
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) orfr-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.
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; checkverdict,confidenceScore, andisInconclusive.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). - 5 / minute for
/api/check-email. - 5 / minute each for
/api/check-tlsand/api/check-email-auth(they open outbound connections per request).
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).