Reference

ssl.sh JSON API: certificate check, chain, CT, CAA, HSTS

By · Last verified

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 returns 401 invalid_key.
  • Over the limit: 429 rate_limited with a Retry-After header in seconds.
  • fresh=1 on 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:

  • chain is in the order the server sent it; aia_fetched lists intermediates the server did not send and ssl.sh downloaded via AIA. When it is non-empty, chain_complete is false.
  • findings[].code is 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. link points to the ssl.sh page that explains the fix.
  • grade is A+, A, B, C, F or T (untrusted); it is null when no TLS handshake was possible.
  • protocols, hsts, caa and revocation are only filled with full=1. key_exchange_group is null when 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.

Sources

  1. RFC 6962: Certificate Transparency
  2. crt.sh
  3. Cert Spotter API v1
  4. HSTS preload list
  5. RFC 8659: DNS CAA resource record