Domain health
TLS signature-health grade for a domain
https://ecdsa.com/api/v1/health/{domain}Connects to the domain on port 443, examines the served certificate chain, key and signature algorithms, protocol versions and lifetimes, and returns a letter grade with findings, plus the leaf certificate's expiry and key algorithm as structured fields. Results are cached for 1 hour. Only public DNS hostnames are accepted — IP addresses, single-label names and private/reserved addresses are refused. Extra rate limit: 10 requests/minute per IP. A fresh scan can take several seconds.
- Chave de API
- Opcional; a chave precisa do escopo scan
- Custo em cota com chave
- 5 unidades por chamada
- Limite de taxa
- 60 requisições/min por IP, mais 10/min para varreduras
- Cota de varreduras
- 6 varreduras reais de um mesmo host a cada 10 min e 300 por minuto no total, compartilhadas por todos; respostas em cache não consomem nada
Requisição
Parâmetros de caminho
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| domain | string | sim | Public DNS hostname, e.g. "github.com". |
Respostas
200Health grade, findings and the served leaf certificate.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| domain | string | sim | |
| grade | object | sim | |
| grade.letter | string | sim |
|
| grade.score | integer | sim |
|
| findings | HealthFinding[] | sim | |
| scannedAt | string | sim |
|
| cached | boolean | sim | Best-effort: true when the result came from the 1-hour cache. |
| cacheTtlSeconds | integer | sim |
|
| reportUrl | string | sim | Human-readable report for the same domain.
|
| certificate | HealthCertificate | null | sim | null only when the report carries no certificate. |
Cabeçalhos: X-Request-Id · X-RateLimit-Limit · X-RateLimit-Remaining · X-RateLimit-Reset · X-Quota-Limit · X-Quota-Remaining · X-Quota-Reset
| Status | Significado | Códigos |
|---|---|---|
| 400 | Bad request. Input limits: a request body of at most 100 KB (a larger one is answered with 413) and arrays and objects nested at most 64 levels deep, JSON documents inside string fields included. | |
| 401 | Unauthorized. | |
| 403 | Forbidden. | |
| 404 | Not found. | |
| 405 | Method not allowed. | |
| 429 | Too many requests. | |
| 500 | Internal server error. | |
| 502 | Bad gateway. | |
| 503 | Service unavailable. | |
| 504 | Gateway timeout. |
Toda resposta de erro traz o envelope JSON; a tabela de erros explica o que cada código significa e o que fazer.
Exemplo
A public domain
curl -sS 'https://ecdsa.com/api/v1/health/github.com'const response = await fetch("https://ecdsa.com/api/v1/health/github.com");
console.log(response.status, await response.json());import httpx
response = httpx.get("https://ecdsa.com/api/v1/health/github.com", timeout=30.0)
print(response.status_code, response.json()){
"domain": "github.com",
"grade": {
"letter": "A+",
"score": 100
},
"findings": [
{
"id": "pqc-migration-ahead",
"severity": "info",
"title": "Classical algorithm — post-quantum migration ahead",
"detail": "ECDSA P-256 is a sound choice today. It is, however, quantum-vulnerable: NIST IR 8547 (draft) plans to disallow classical signature algorithms — ECDSA, EdDSA and RSA — after 2035; at this key size there is no earlier 2030 milestone — that step covers only 112-bit parameter sets such as RSA-2048. No penalty and no emergency — but certificate rotation is the easy part; knowing everywhere else your systems sign is the real migration work."
},
{
"id": "key-modern",
"severity": "ok",
"title": "Modern signature key (ECDSA P-256)",
"detail": "ECDSA P-256 gives compact keys and signatures at full strength — the current best practice for TLS server certificates."
},
{
"id": "cert-valid",
"severity": "ok",
"title": "Within validity period",
"detail": "Valid until 2026-11-29 — 41 days remaining."
},
{
"id": "cert-short-lifetime",
"severity": "ok",
"title": "Short certificate lifetime (90 days)",
"detail": "Lifetimes of 90 days or less are the signature of automated issuance and rotation (ACME et al.) — exactly the posture the shrinking CA/Browser Forum limits are pushing everyone toward. Well done."
},
{
"id": "legacy-tls-refused",
"severity": "ok",
"title": "Legacy TLS refused",
"detail": "Handshakes capped at TLS 1.1 were rejected — the deprecated protocol versions are switched off."
},
{
"id": "tls13",
"severity": "ok",
"title": "TLS 1.3 negotiated",
"detail": "The handshake settled on TLS 1.3 with TLS_AES_128_GCM_SHA256 — the current protocol generation, with a cleaner handshake and only AEAD ciphers."
},
{
"id": "chain-complete",
"severity": "ok",
"title": "Certificate chain complete (as served)",
"detail": "Every issuer → subject link matches and the chain ends at a self-signed root (chain as served)."
}
],
"scannedAt": "2026-10-20T07:14:02.839Z",
"cached": true,
"cacheTtlSeconds": 3600,
"reportUrl": "https://ecdsa.com/health/github.com",
"certificate": {
"notAfter": "2026-11-29T23:59:59.000Z",
"daysRemaining": 41,
"keyAlgorithm": "ECDSA",
"curve": "P-256"
}
}A resposta da API a este exemplo, gerada para esta página em 20 de outubro de 2026; tempos relativos e contagens de dias nela se referem a esse dia. A varredura de github.com é a que foi registrada em produção nesse dia; uma chamada ao vivo faz uma nova varredura ou responde a partir do cache de uma hora.
Códigos de erro
| Código | Status | Significado |
|---|---|---|
| invalid_json | 400 | The body is not valid JSON or not a JSON object; or the input nests arrays and objects more than 64 levels deep (JSON documents inside string fields count too), or is too complex to inspect for private keys. |
| private_key_rejected | 400 | The request contains private-key material (a PEM PRIVATE KEY block, an OpenSSH, SSH2 or PuTTY private key, or a JWK with "d"). Never send private keys anywhere. |
| invalid_domain | 400 | Not a public DNS hostname (IP addresses, single-label names and paths are refused). |
| scan_dns_error | 400 | The domain does not resolve in public DNS. |
| scan_private_address | 400 | The domain resolves (at least partly) to a private or reserved address. |
| unauthorized | 401 | The Authorization or X-API-Key header is malformed, the API key is not valid (wrong checksum, unknown, revoked or expired), or the operation needs a key. |
| forbidden_scope | 403 | The API key is valid but lacks a scope this operation requires; the message names it. |
| not_found | 404 | No operation exists at this path. |
| method_not_allowed | 405 | The path exists but does not accept this HTTP method; see the Allow header. |
| rate_limited | 429 | Too many requests — see Retry-After. |
| quota_exceeded | 429 | A daily or monthly quota of the API key's account is used up. X-Quota-Reset (and Retry-After) say when it starts over; retrying earlier cannot succeed. |
| internal_error | 500 | Unexpected server error. |
| scan_unreachable | 502 | The domain refused or dropped the connection on port 443. |
| scan_tls_error | 502 | The TLS handshake failed or presented no usable certificate. |
| service_unavailable | 503 | API keys cannot be verified right now (the key store is unreachable); retry shortly. |
| scan_timeout | 504 | The scan did not finish in time. |
Schemas
HealthFinding
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| id | string | sim | Stable identifier, e.g. "cert-expired" or "tls13". |
| severity | string | sim |
|
| title | string | sim | |
| detail | string | sim |
HealthCertificate
The served leaf certificate, as structured fields.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| notAfter | string | sim | End of the leaf certificate's validity.
|
| daysRemaining | integer | sim | Whole days from scannedAt until notAfter, rounded up; zero or negative once expired. |
| keyAlgorithm | string | sim | Public-key algorithm of the leaf certificate: ECDSA, RSA, Ed25519, Ed448, an ML-DSA or SLH-DSA parameter set, or the scanner's label for anything else. |
| curve | string | null | sim | Named curve of an ECDSA key ("P-256", "P-384", …); null otherwise. |
Error
The error envelope of every non-2xx response.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| error | object | sim | |
| error.code | string | sim | Machine-readable error code. Each response lists the codes it can carry; new codes may be added within v1, so treat an unknown code by its HTTP status. |
| error.message | string | sim | Human-readable explanation (English). |
| error.requestId | string | não | The X-Request-Id of this request — quote it when reporting a problem. Sent since v1.1. |
| error.detail | object | não | Structured context for the error, such as the offending field; omitted when there is none.
|
| error.docs | string | não | Where the error codes are explained. Sent since v1.1.
|
Todas as operações estão listadas na referência da API.