Reference
ssl.sh JSON API: certificate check, chain, CT, CAA, HSTS
The ssl.sh API is a set of read-only JSON endpoints under https://ssl.sh/api/v1/. Send a GET request with the host name as a query parameter; there is no sign-up for normal use.
Authentication and limits
- Anonymous: 30 checks per minute per IP address (token bucket). Results are cached for 5 minutes (failed checks for 1 minute) and cached answers do not count against the limit.
- API key: send
Authorization: Bearer <key>(or?key=<key>). The limit follows your plan. An invalid key returns401 invalid_key. - Over the limit:
429 rate_limitedwith aRetry-Afterheader in seconds. fresh=1on check endpoints skips the cache (and counts against the limit).
Errors
Every error response has the same shape:
{"error": "blocked_address", "message": "10.0.0.1 resolves to 10.0.0.1: private or reserved range 10.0.0.0/8"}
| Status | error codes |
|---|---|
| 400 | missing_host, invalid_host, invalid_port, port_not_allowed, invalid_starttls, private_key_rejected, parse_failed |
| 401 | invalid_key |
| 403 | blocked_address (private, loopback, link-local, CGNAT or reserved IP) |
| 404 | not_found |
| 413 | too_large |
| 429 | rate_limited |
| 502 | dns_failed, connect_refused, connect_failed, timeout, tls_handshake_failed, protocol_mismatch, starttls_failed, ct_unavailable, fetch_failed |
For /api/v1/check, a 502 still carries the full result object (with error, error_detail and message), so you can tell a DNS failure from a refused connection.
GET /api/v1/check
Checks one host. Parameters:
| Parameter | Default | Description |
|---|---|---|
host |
required | Host name, host:port or a URL. |
port |
443 | Allowed: 443, 8443, 4443, 9443, 10443, 465, 587, 25, 993, 995, 143, 110, 21, 990, 636, 853, 5222, 5432, 6443, 2083, 2087, 8883. |
starttls |
auto | smtp, imap, pop3, ftp, xmpp, postgres. Automatic on 25, 587, 143, 110, 21, 5222, 5432. |
full |
off | full=1 also tests each TLS version, HSTS, CAA and revocation. |
fresh |
off | fresh=1 bypasses the cache. |
curl -s 'https://ssl.sh/api/v1/check?host=example.com&full=1'
Response (shortened):
{
"host": "example.com",
"port": 443,
"starttls": null,
"ip": "203.0.113.10",
"checked_at": "2026-11-10T08:00:00Z",
"ok": true,
"error": null,
"tls_version": "TLSv1.3",
"cipher": "TLS_AES_256_GCM_SHA384",
"key_exchange_group": null,
"alpn": "h2",
"protocols": {"TLSv1.0": false, "TLSv1.1": false, "TLSv1.2": true, "TLSv1.3": true},
"chain": [
{
"subject": "CN=example.com",
"subject_cn": "example.com",
"sans": ["example.com", "www.example.com"],
"issuer_cn": "R12",
"issuer_org": "Let's Encrypt",
"not_before": "2026-10-15T07:12:00Z",
"not_after": "2027-01-13T07:11:59Z",
"days_left": 64,
"validity_days": 90,
"key_type": "RSA 2048",
"sig_alg": "sha256WithRSAEncryption",
"validation_level": "DV",
"sct_count": 2,
"eku": ["serverAuth"],
"pem": "-----BEGIN CERTIFICATE-----\n…"
}
],
"chain_complete": true,
"trusted": true,
"hostname_match": true,
"hsts": "max-age=31536000; includeSubDomains",
"caa": ["0 issue \"letsencrypt.org\""],
"revocation": "good",
"findings": [
{"level": "ok", "code": "valid", "message": "Certificate is valid and trusted, expires in 64 days.", "message_zh": "证书有效且受信任,64 天后过期。", "link": null}
],
"grade": "A+",
"days_left": 64,
"root_subject": "CN=ISRG Root X1,O=Internet Security Research Group,C=US",
"fullchain_pem": "-----BEGIN CERTIFICATE-----\n…",
"report_url": "https://ssl.sh/example.com"
}
Field notes:
chainis in the order the server sent it;aia_fetchedlists intermediates the server did not send and ssl.sh downloaded via AIA. When it is non-empty,chain_completeisfalse.findings[].codeis machine-readable:expired,not_yet_valid,expires_soon,hostname_mismatch,self_signed,untrusted_root,incomplete_chain,revoked,weak_key,sha1,tls10_enabled,tls11_enabled,weak_cipher,legacy_only,no_tls13,no_hsts,hsts_short,caa_missing,client_auth_eku.linkpoints to the ssl.sh page that explains the fix.gradeisA+,A,B,C,ForT(untrusted); it isnullwhen no TLS handshake was possible.protocols,hsts,caaandrevocationare only filled withfull=1.key_exchange_groupisnullwhen it cannot be determined.
GET /api/v1/chain
Returns the completed chain: {host, port, trusted, chain_complete, root, pem_fullchain, certs: [...]}. pem_fullchain is the leaf followed by intermediates (no root), in path order. Each item in certs has the same fields as chain above plus source: server or aia.
curl -s 'https://ssl.sh/api/v1/chain?host=example.com' | jq -r .pem_fullchain > fullchain.pem
GET /api/v1/ct
Searches Certificate Transparency logs through crt.sh, falling back to Cert Spotter. Results are cached for 6 hours.
| Parameter | Default | Description |
|---|---|---|
domain |
required | Domain name. |
include_subdomains |
1 | 0 to search the exact name only. |
include_expired |
0 | 1 to include expired certificates. |
{
"domain": "example.com",
"include_subdomains": true,
"include_expired": false,
"count": 1,
"certificates": [
{"id": 1234567890, "issuer": "C=US, O=Let's Encrypt, CN=R12", "not_before": "2026-10-15T07:12:00Z",
"not_after": "2027-01-13T07:11:59Z", "dns_names": ["example.com", "www.example.com"],
"serial": "04a1…", "source": "crt.sh"}
]
}
GET /api/v1/caa
?domain=example.com returns the CAA records in effect, found by climbing from the name to its parents as RFC 8659 requires: {domain, found_at, records, issue, issuewild, iodef, any_ca_allowed}.
GET /api/v1/hsts
?host=example.com fetches https://example.com/ and returns {host, port, http_status, header, max_age, max_age_days, include_subdomains, preload_directive, preload_list}. preload_list is the domain's status from the hstspreload.org API (preloaded, pending, unknown …) or null if that service did not answer.
GET /api/v1/mixed-content
?url=https://example.com/page (or ?host=) downloads the page (up to 2 MB, redirects followed) and lists sub-resources loaded over http://: {url, final_url, status, page_is_https, count, active, passive, insecure_forms, items: [{tag, attr, url, type}]}. active items (scripts, stylesheets, iframes) are blocked by browsers; passive items (images, media) trigger warnings. See mixed content warnings.
POST /api/v1/decode
Send a certificate or CSR in the body as PEM or base64 DER (raw text, JSON {"pem": "…"} or form field pem). Up to 256 KB. Any input containing PRIVATE KEY is rejected with 400 private_key_rejected and is not processed.
curl -s https://ssl.sh/api/v1/decode --data-binary @cert.pem
Certificates return {"type": "certificate", "count": n, "certificates": [...]} with the same fields as chain; CSRs return {"type": "csr", "csr": {subject, subject_cn, sans, key_type, sig_alg, signature_valid}}.
FAQ
Is there an official client library?
No library is needed: every endpoint is a plain GET (or one POST for decoding) that returns JSON. Use curl, requests, fetch or any HTTP client.
Can I call the API from JavaScript in the browser?
Not from another origin: responses carry no Access-Control-Allow-Origin header. Call it from your backend, a serverless function or a script.