API REFERENCE · GET · STABLE

SSL/TLS Report API — /api/v1/security/ssl-info

Live TLS handshake + full DER parse + per-version probe + HSTS/CAA inspection + Qualys-style grade + AI diagnosis, in one call. Returns the leaf certificate, full chain (typed), negotiated protocol/cipher/PFS, protocol matrix, HSTS state, CAA records, computed A+ to F grade with per-factor breakdown, 15 pass/warn/fail checks, and (optionally cached) AI verdict + copy-paste fix snippets.

01 · OVERVIEW

What this endpoint returns

One aggregated report per call. No follow-ups needed — the chain, TLS version probe, HSTS, CAA, grade, and AI meta pointer all come back in the same payload.

  • Method GET
  • Path /api/v1/security/ssl-info
  • Stability STABLE
  • Version v2026-07-18
  • Summary Full SSL/TLS posture report for a hostname on port 443 — cert, chain, protocols, HSTS, CAA, grade, AI diagnosis.
  • AI header Response includes `X-Analysis-Status: complete | pending`. When pending, poll the AI response via the id in `AiAnalysisMeta`.
  • Auth Cookie-based session (browser) or `Bearer ds_*` API key (Phase 2). Sends `Origin`/`Referer` from the frontend domain.
02 · PARAMETERS

Request parameters

Pass as query-string parameters. Only `domain` is required.

  • domain (required) — string Hostname or URL (scheme/path stripped server-side). Underscore-prefixed labels not supported here — this endpoint is for public HTTPS hosts on port 443.
  • raw — boolean, default false Include PEM-encoded certificates on the leaf and every chain link. Adds ~4 KB per cert to the payload; only turn on when you need to save/pin the cert bytes.
  • refresh — boolean, default false Force a fresh AI diagnosis instead of reusing the 7-day cache. The live TLS handshake always runs live; this flag only affects the AI layer.
03 · RESPONSE

Response schema

Wrapped in the standard `{ success: true, data: {...} }` envelope. The `data` object has nine top-level groups.

  • data.found — boolean True when the handshake succeeded and a certificate was returned. When false, only `domain`, `error`, and `fetchedAt` are populated.
  • data.domain — string Normalised hostname (scheme/path stripped).
  • data.fetchedAt — ISO8601 When the report was generated.
  • data.connection — object `{remoteAddress, port, servername, handshakeMs, authorized, authorizationError}` — actual TCP/TLS connection metadata.
  • data.certificate — object The end-entity leaf. See breakdown below.
  • data.chain — array<object> Full chain, position 0 = leaf, tagged with `type: EndEntity | Intermediate | Root`.
  • data.chain_issues — array<object> Detected chain problems with `severity: critical | warning | info` and a human-readable `message`.
  • data.handshake — object `{protocol, cipher, ephemeral_key, pfs, alpn}` — what was actually negotiated on the wire.
  • data.protocols — object Per-version probe results keyed by `TLS 1.3 / TLS 1.2 / TLS 1.1 / TLS 1.0` — each with `enabled`, `cipher`, `error`, `released`, `deprecated`.
  • data.hsts — object `{present, header, max_age, include_subdomains, preload, preloadable, preload_listed, status}` — from the HTTPS root response.
  • data.caa — object `{present, records[], note}` — from a `dns.resolveCaa` on the registered domain.
  • data.grade — object `{letter, score, factors, caps, reasons}` — Qualys-style A+ to F with per-factor breakdown.
  • data.tests — array<object> 15 pass/warn/fail checks — `{id, name, category, status, severity, message}`.
  • data.compliance — object `{pci_dss, hipaa_ready, modern_browsers, warnings[]}` — derived from protocol matrix + PFS.
  • data.AiAnalysis — object Present when the AI diagnosis is cached: `{summary, quick_recommendations, security_assessment[], fixes[]}`. Absent while pending — poll via `AiAnalysisMeta.id`.
  • data.AiAnalysisMeta — object Present when AI is pending: `{id, analysisCode, status, avgMs, etaMs, elapsedMs}` — pass `id` to the AI poll endpoint.
  • certificate.subject — object `{common_name, organization, organizational_unit, country, state, locality, email}` — decoded X.500 DN.
  • certificate.issuer — object Same shape as `subject`. The CA that signed the leaf.
  • certificate.validity — object `{from, to, days, days_remaining, is_expired, age_days}` — dates are ISO8601, days are integers.
  • certificate.serial_number — string Uppercase hex, no separators.
  • certificate.fingerprint — string SHA-1 colon-separated. Legacy — do not use for new pinning.
  • certificate.fingerprint256 — string SHA-256 colon-separated. Current standard for pinning + audit.
  • certificate.public_key — object `{type, bits, curve, strength, label}` — `type` is RSA/EC/ED25519/ED448; `bits` is derived from curve for EC; `strength` is weak/strong/very-strong; `label` is display-ready (e.g. `ECDSA P-256`).
  • certificate.signature_algorithm — object `{oid, name, hash, weak}` — MD5 and SHA-1 based algorithms are flagged `weak: true`.
  • certificate.subject_alt_names — array<string> Every DNS name the cert covers. Includes wildcards. `san_count` gives length. `is_wildcard: true` if any entry starts with `*.`.
  • certificate.key_usage — array<string> Extended Key Usage OIDs from the certificate extensions.
  • certificate.ocsp — object `{urls[], must_staple}` — OCSP responder URLs from AIA + Must-Staple flag from the TLS Feature extension (RFC 7633).
  • certificate.ca_issuer_urls — array<string> AIA CA Issuers URLs (where to fetch missing intermediates).
  • certificate.cert_transparency — object `{embedded, sct_count}` — SCT count parsed directly from the CT extension DER (OID 1.3.6.1.4.1.11129.2.4.2).
  • certificate.validation_level — string `DV | OV | EV | IV` — detected from CA/Browser Forum policy OIDs, falling back to known CA-specific EV OIDs, then to a subject-field heuristic.
  • certificate.policy_oids — array<string> Certificate Policies extension OIDs — useful for CA-specific policy lookups.
  • certificate.is_wildcard, is_ca, is_self_signed — booleans Structural flags on the cert.
  • certificate.pem — string (when raw=true) PEM-encoded certificate body.
04 · GRADE FACTORS

How the letter grade is computed

Five factors, each scored 0-100, combined with a weighted average. A single low factor drags the total down (worst-in-class principle).

  • certificate (weight 25) Key strength (RSA < 2048 = weak, ECDSA P-256+ = strong), signature algorithm strength (SHA-1 = weak = capped at 40), days remaining (< 14d = capped at 60).
  • key_exchange (weight 25) PFS check. ECDHE / DHE ephemeral = 100. No ephemeral key = 60.
  • cipher_strength (weight 20) AEAD (GCM, ChaCha20) = 100. CBC-mode non-AEAD = 80. RC4/DES/EXPORT/NULL = 20.
  • protocols (weight 20) TLS 1.3 = 100. 1.2 only = 90 max. 1.1 enabled = 70 max. 1.0 enabled = 60 max. No modern TLS = 20.
  • features (weight 10) +40 HSTS, +15 preloadable, +15 CAA, +20 CT SCTs, +10 Must-Staple (max 100).
  • A+ criteria A grade AND HSTS preloadable AND TLS 1.3 supported AND TLS 1.0/1.1 disabled AND PFS AND CT SCTs embedded.
  • Hard caps Expired cert → F outright. Trust chain error (self-signed, hostname mismatch, unknown CA) → capped at 50.
05 · EXAMPLE

Example response

Abridged shape for `?domain=github.com`. Fields omitted for brevity marked with `…`.

Sample response
{
  "success": true,
  "data": {
    "found": true,
    "domain": "github.com",
    "fetchedAt": "2026-07-18T…",
    "connection": {
      "remoteAddress": "20.207.73.82",
      "port": 443,
      "servername": "github.com",
      "handshakeMs": 129,
      "authorized": true,
      "authorizationError": null
    },
    "certificate": {
      "subject":  { "common_name": "github.com" },
      "issuer":   { "common_name": "Sectigo Public Server Authentication CA DV E36", "organization": "Sectigo Limited", "country": "GB" },
      "validity": { "from": "2026-07-03T…", "to": "2026-10-01T…", "days": 90, "days_remaining": 75, "is_expired": false, "age_days": 15 },
      "serial_number":  "72010E03F4A067FE4E796266430718F6",
      "fingerprint":    "A5:47:1F:…",
      "fingerprint256": "17:F8:FD:2E:…",
      "public_key":          { "type": "EC", "bits": 256, "curve": "prime256v1", "strength": "strong", "label": "ECDSA P-256" },
      "signature_algorithm": { "oid": "1.2.840.113549.1.1.11", "name": "SHA256-RSA", "hash": "SHA256", "weak": false },
      "subject_alt_names": ["github.com", "www.github.com"],
      "san_count": 2,
      "is_wildcard": false,
      "validation_level": "DV",
      "cert_transparency": { "embedded": true, "sct_count": 3 },
      "ocsp":  { "urls": ["http://ocsp.sectigo.com"], "must_staple": false },
      "ca_issuer_urls": ["http://…"]
    },
    "chain": [
      { "position": 0, "type": "EndEntity",    "subject": {…}, "issuer": {…}, "…": "…" },
      { "position": 1, "type": "Intermediate", "subject": {…}, "issuer": {…}, "…": "…" },
      { "position": 2, "type": "Intermediate", "subject": {…}, "issuer": {…}, "…": "…" },
      { "position": 3, "type": "Root",         "subject": {…}, "issuer": {…}, "…": "…" }
    ],
    "chain_issues": [],
    "handshake": {
      "protocol": "TLSv1.3",
      "cipher":   { "name": "TLS_AES_128_GCM_SHA256", "version": "TLSv1.3" },
      "ephemeral_key": { "type": "ECDH", "name": "X25519", "size": 253 },
      "pfs": true,
      "alpn": "h2"
    },
    "protocols": {
      "TLS 1.3": { "enabled": true,  "cipher": "TLS_AES_128_GCM_SHA256", "released": 2018, "deprecated": false },
      "TLS 1.2": { "enabled": true,  "cipher": "…",                        "released": 2008, "deprecated": false },
      "TLS 1.1": { "enabled": false, "error":  "ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION", "released": 2006, "deprecated": true },
      "TLS 1.0": { "enabled": false, "error":  "ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION", "released": 1999, "deprecated": true }
    },
    "hsts": {
      "present": true,
      "header":  "max-age=31536000; includeSubdomains; preload",
      "max_age": 31536000,
      "include_subdomains": true,
      "preload": true,
      "preloadable": true,
      "preload_listed": true,
      "status": 200
    },
    "caa": {
      "present": true,
      "records": [ { "critical": false, "tag": "issue", "value": "digicert.com" } ]
    },
    "grade": {
      "letter": "A+",
      "score":  99,
      "factors": {
        "certificate":     { "score": 100, "weight": 25, "reasons": ["Strong key: ECDSA P-256"] },
        "key_exchange":    { "score": 100, "weight": 25, "reasons": ["PFS via X25519 253 bits"] },
        "cipher_strength": { "score": 100, "weight": 20, "reasons": ["Modern AEAD cipher"] },
        "protocols":       { "score": 100, "weight": 20, "reasons": ["TLS 1.3 supported"] },
        "features":        { "score": 90,  "weight": 10, "reasons": ["HSTS enabled", "HSTS preloadable", "CAA record set", "3 SCTs embedded (CT)"] }
      },
      "caps": [],
      "reasons": ["…"]
    },
    "tests":     [ { "id": "cert_valid", "category": "certificate", "status": "pass", "severity": "critical", "message": "Valid for 75 more days." } ],
    "compliance": { "pci_dss": true, "hipaa_ready": true, "modern_browsers": true, "warnings": [] },
    "AiAnalysisMeta": { "id": "…", "analysisCode": "SSL_INFO@OVERALL_ANALYSIS", "status": "pending", "avgMs": 7500, "etaMs": 7500 }
  }
}
06 · CURL EXAMPLES

One-line invocations

Copy-paste ready. Replace `github.com` with your target.

Basic
curl -s 'https://api.domainscan.in/v1/security/ssl-info?domain=github.com' \
  -H 'Origin: https://domainscan.in' | jq '.data.grade.letter'
Force fresh AI + include raw PEM
curl -s 'https://api.domainscan.in/v1/security/ssl-info?domain=github.com&raw=true&refresh=1' \
  -H 'Origin: https://domainscan.in' | jq '.data.certificate.pem'
Alert if expiring within 30 days
days=$(curl -s 'https://api.domainscan.in/v1/security/ssl-info?domain=github.com' \
  -H 'Origin: https://domainscan.in' | jq '.data.certificate.validity.days_remaining')
if [ "$days" -lt 30 ]; then
  echo "⚠️  Cert expires in $days days — renew now"
fi
07 · ERRORS

Error codes

Errors return HTTP status codes paired with stable machine-readable codes. Match on the code field rather than the human-readable message. On soft failures (host unreachable, TLS timeout) the response is still 200 with `data.found: false` and `data.error` populated.

  • 200 + data.found=false TLS handshake failed (host down, port 443 closed, TCP timeout, TLS alert). `data.error` holds the underlying reason (e.g. `ENOTFOUND`, `ECONNREFUSED`, `TLS timeout after 6000ms`).
  • 400 VALIDATION_FAILED `domain` missing or malformed. Response has `error.details` listing the offending fields.
  • 401 AUTH_REQUIRED No valid session cookie or API key on this request. Log in via `/api/v1/auth/session` and retry.
  • 403 FORBIDDEN Origin/Referer not allowlisted, or User-Agent flagged as bot/headless. Send from a real browser or supply a valid `Origin` matching your account.
  • 500 INTERNAL Unexpected server-side error — log the `x-txn-id` response header and report to support.
08 · AI POLL PATTERN

Waiting on the AI diagnosis

AI never blocks the main SSL response. First request usually returns `AiAnalysisMeta` with `status: pending`; subsequent requests within the 7-day cache return `AiAnalysis` inline.

  • Ready `X-Analysis-Status: complete` + `data.AiAnalysis: {summary, quick_recommendations, security_assessment, fixes}`.
  • Pending `X-Analysis-Status: pending` + `data.AiAnalysisMeta: {id, status, avgMs, etaMs, elapsedMs}`. Poll `/api/v1/ai/analysis/:id` at `etaMs` intervals until `status: complete`.
  • Force refresh Add `refresh=1` to invalidate the cache and re-run the AI analysis. Cert data still comes back live regardless.
  • Cache TTL AI responses are cached for 7 days per (domain, tool). Force refresh with `refresh=1` when you need a fresh diagnosis.
09 · BEST PRACTICES

Integration tips

  • Alert on `certificate.validity.days_remaining < 30`. Cheapest insurance against a renewal-automation failure. Alert on `< 14` for critical infra.
  • Fail deploys on `grade.letter` regression. Store the previous grade, compare after each deploy, fail if it dropped.
  • Ship the full chain. If `chain.length === 1`, your server isn't sending intermediates — browsers work via AIA, Java + curl + older Android don't. Fix at the server config level.
  • Watch `handshake.pfs === false`. No forward secrecy = past traffic exposed if the private key ever leaks. Almost always fixable via nginx `ssl_ciphers` prioritising ECDHE/DHE suites.
  • Use `refresh=1` sparingly. AI generation is the expensive path (~7s). Cached responses are effectively free — force refresh only after a real configuration change.
  • For chain-only inspection use `/security/ssl-chain`. Same underlying data, minimal payload — just the chain + issues.
10 · FAQ

Frequently asked questions

  • Is OCSP revocation checked? The OCSP responder URL is extracted from the certificate's AIA extension and returned in `certificate.ocsp.urls[0]`. Live OCSP validation against the CA responder isn't performed here — most clients defer to short-lived certs (Let's Encrypt 90-day) or OCSP Must-Staple instead. If Must-Staple is set on the cert, `certificate.ocsp.must_staple: true`.
  • Why is my TLS 1.3 detected as disabled when the server actually supports it? The old shell-based openssl detection had this bug — the new endpoint uses Node's native `tls.connect` with `minVersion`/`maxVersion` pinning, which is authoritative. If the endpoint says `TLS 1.3 disabled`, your server genuinely rejected the pinned-version handshake.
  • Why does the grade differ from Qualys SSL Labs? Same 5-factor philosophy, different weights + criteria. Our A+ requires HSTS preloadable + CT SCTs + PFS + TLS 1.3 — Labs weighs each factor slightly differently and also runs cipher-suite compatibility checks against historical browsers, which we don't.
  • Is the AI cached? Yes, 7-day cache per (domain, analysis code). Force refresh with `refresh=1`.