Skip to content

SSL/TLS Checker API

This API runs a full SSL/TLS scan for a host and returns a JSON report: certificate chain, protocol and cipher support, known vulnerabilities, ALPN, the HTTP redirect chain, HSTS, CAA, revocation status, and a letter grade. No API key, no signup.

Every check is a full scan (no quick/extended toggle) and results are cached for 24 hours, keyed by host, port, and protocol.

European by default. Scanning servers in Germany and Finland, reports stored in an EU database. How the data is handled.

Quick start

curl "https://api.ssltest.com/v1/check?host=example.com"

A cold check has nothing cached yet, so the first call returns 202 with a poll URL. Poll it (or use the SSE stream below) until the report is ready:

{
  "status": "processing",
  "report_id": "a1b2c3d4e5f60718293a",
  "poll": "/v1/check?host=example.com"
}

Endpoints

EndpointDescription
GET /v1/check?host=Cache-or-scan report (JSON)
GET /v1/check/stream?host=Live scan progress (SSE)

Target format

host accepts a bare host (example.com), a host with an explicit port (example.com:8443), or protocol/host[:port] for a non-HTTPS check (smtp/mail.example.com, imap/mail.example.com:993). A protocol and port can also be passed as separate proto=/port= query params instead of the combined form.

Direct-TLS protocols: https, ftps, smtps, pop3s, imaps, ldaps. STARTTLS protocols: smtp, smtp-submission, imap, pop3, ftp, ldap, xmpp, nntp. Each has a default port used when none is given.

Response

Returned once the report is ready (fields trimmed for brevity):

{
  "host": "example.com",
  "port": 443,
  "protocol": "https",
  "scanned_at": 1753272000,
  "cached": true,
  "primary_ip": "93.184.216.34",
  "consistent": true,
  "days_until_expiry": 207,
  "primary_result": {
    "hostname": "example.com",
    "port": 443,
    "success": true,
    "chain_valid": true,
    "hostname_match": true,
    "connection": {
      "protocol": "TLSv1.3",
      "cipher": "TLS_AES_128_GCM_SHA256"
    },
    "grade": {
      "grade": "A",
      "cap": null,
      "cap_reason": null,
      "trust_only": false
    },
    "alpn": {
      "checked": true,
      "supported": true,
      "protocols": [
        "h2"
      ]
    },
    "http": {
      "checked": true,
      "status_code": 200,
      "downgrades_to_http": false
    },
    "alt_chains": {
      "checked": true,
      "chains": [],
      "problem": false
    },
    "certificates": [
      {
        "subject": {
          "common_name": "example.com"
        },
        "public_key": {
          "algorithm": "id-ecPublicKey",
          "size": 256
        },
        "key_strength": 3072,
        "validation_type": "DV",
        "pin_sha256": "Cq2gW1DVwWX5gG0Rf1Fq3zVe8i4mQe5xO6Yb0Y8pQfE="
      }
    ]
  },
  "ips": {
    "93-184-216-34": {
      "ip": "93.184.216.34",
      "grade": {
        "grade": "A"
      }
    }
  }
}

Status codes

CodeMeaningDetail
200The report, or a failureA finished report, or status: "failed" for a scan that ran and could not produce one, or status: "no_connection" for a host that resolved but completed no handshake. All three are terminal. A report with status: "not_graded" is complete but has no grade: a test the grade depends on got no answer, and reason says which.
202processingNo fresh report yet. The scan is queued or running: poll the same URL, or stream it.
403opted_outThe host owner published the DNS opt-out record, so the host is never scanned and no report exists.
422invalid_targethost is missing, or does not parse as a hostname/IP, an optional port, and a supported protocol.
429rate_limitedToo many requests. Retry-After carries the wait in seconds.

A failure is reported for several minutes before another scan of the same target is attempted, so a poll loop against an unreachable host settles instead of queueing work.

Response headers

HeaderWhenValue
X-RateLimit-LimitEvery JSON responseRequests allowed in the window of whichever limit is closest to refusing
X-RateLimit-RemainingEvery JSON responseRequests left before that limit refuses
X-RateLimit-ResetEvery JSON responseUnix time at which that window resets
Retry-After403 and 429 responsesSeconds to wait

Rate limits

No key is needed, so limits are per client address. Polling a scan already running and reading a cached report are free: only starting a scan counts.

ScopeLimitWindow
One target6 scans1 minute
One client address60 scans10 minutes

Scan duration

Measured over a 50-host sample: half the scans finished within a minute of the first request, and the slowest took six. A host resolving to many addresses is the slow case, because every address gets its own full handshake and its own vulnerability probes. The 202 response carries queue_position and an indicative eta_seconds for choosing a polling interval.

Cross-origin requests

Every endpoint here answers with Access-Control-Allow-Origin: *, so a browser script reaches the JSON, the stream and the spec directly. The Retry-After and X-RateLimit-* headers are listed in Access-Control-Expose-Headers, which is what lets a script read them.

Streaming events

GET /v1/check/stream?host=... enqueues a scan the same way /v1/check does, then streams named Server-Sent Events until the report is ready:

EventPayload
queued{ahead} while the scan waits for a worker, resent whenever the number changes
progress{progress, status} while the scan runs
resultthe finished report, same shape as /v1/check
error{error} for a scan that failed or timed out
curl -N "https://api.ssltest.com/v1/check/stream?host=example.com"

MCP

The same check is also exposed as an MCP tool (ssl_check) over Streamable HTTP at api.ssltest.com/mcp. See mcp.ssltest.com for connection instructions.

Limitations

One scan runs per host, port and protocol at a time, and a report is cached for 24 hours. Scans reach public addresses only: private, loopback and otherwise non-routable targets are refused. A host whose owner published the opt-out record is never scanned.

Frequently asked

How long does a client keep polling?

Until the response is a report or a failure. Both arrive with status 200, and both are terminal. A scan reaching no conclusion answers with status "failed" and a retry_after, so a poll loop always has somewhere to stop.

What happens when two clients scan the same host at once?

The second request joins the first scan instead of starting another. One scan runs per host, port and protocol at a time, and every caller waiting on it receives the same report.

How does a pipeline fail a build when the grade drops?

Read the grade out of the report and compare. Certificate expiry works the same way through days_until_expiry, which is worth watching now certificates last 200 days and will last 47 from 2029.

Is a cached report a problem for monitoring?

A report is served from cache for 24 hours, which suits a daily expiry check. A scan after a configuration change needs the Re-scan button on the report page.

Does the API scan a host without permission?

A scan is a normal TLS handshake against a public port, the same connection a browser or mail client opens. Host owners who prefer no scans publish a DNS record and every request for the host answers 403 from then on.

Are these endpoints callable from a browser?

Yes. The JSON endpoint, the stream, the spec and the MCP endpoint all send permissive CORS headers, and the rate-limit headers are exposed to scripts.

What does a failed scan mean about the host?

Usually the port is closed, filtered, or answering something other than TLS. The error field carries the reason in plain words. A host answering slowly is retried, so a timeout reflects the host rather than one unlucky connection.

Terms and policies

Use of the API is subject to the Terms of Service and Privacy Policy. To report abuse, or to stop a host being scanned, see Opt-out.