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. The API needs no key and no signup.

Every check is a full scan, and results are cached for 24 hours by host, port and protocol.

European by default. Scanning servers in Germany, Finland and the Netherlands, 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. Polling the URL, or following the SSE stream described below, returns the report once the scan is done:

{
  "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). Separate proto= and port= query parameters work as an alternative to the combined form.

The direct-TLS protocols are https, ftps, smtps, pop3s, imaps, ldaps, and the STARTTLS protocols are smtp, smtp-submission, imap, pop3, ftp, ldap, xmpp, nntp. Each protocol has a default port for requests without one.

Response

The finished report looks like this, with 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, because a test the grade depends on got no answer. The reason field names the test.
202processingNo fresh report exists yet, and the scan is queued or running. Poll the same URL, or follow the stream.
403opted_outThe host owner published the DNS opt-out record, so the host is never scanned and no report exists.
422invalid_targetThe host parameter is missing, or does not parse as a hostname or IP address with an optional port and a supported protocol.
429rate_limitedThe client sent too 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

The API needs no key, so limits apply per client address. Polling a scan already running and reading a cached report are free, and only starting a scan counts against the limit.

ScopeLimitWindow
One target6 scans1 minute
One client address60 scans10 minutes

Scan duration

In a sample of 50 hosts, half the scans finished within a minute of the first request, and the slowest took six minutes. 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, so scripts read them as well.

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 runs as an MCP tool (ssl_check) over Streamable HTTP at api.ssltest.com/mcp. The connection instructions are on mcp.ssltest.com.

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, and private, loopback and other 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?

A client polls 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 every poll loop has a defined end.

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?

The pipeline reads the grade from the report and compares the grade with the expected one. Certificate expiry works the same way through days_until_expiry, a field worth watching now certificates last 200 days, falling to 47 days from 2029.

Is a cached report a problem for monitoring?

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

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 preferring 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 with something other than TLS. The error field gives 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.