---
title: "List certificates expiring soon for a domain"
method: GET
path: "/mcp-api/get_expiring_certs/{domain}"
tags: ["mcp"]
---

# List certificates expiring soon for a domain

`GET /mcp-api/get_expiring_certs/{domain}`

## Request

- Base URL: `https://api.ctindex.io`
- URL: `https://api.ctindex.io/mcp-api/get_expiring_certs/{domain}`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Returns certificates for the domain that expire within `days` days from now. Useful for proactive renewal monitoring. Default window is 30 days.

**Precert twin collapsing (GH #262):** a CT precertificate and its final leaf are distinct rows (different SHA-256) describing the same logical certificate. By default this tool collapses each twin pair (same subject_cn, SAN set, not_before, not_after, issuer) to the final leaf so renewals aren't double-counted; the suppressed precert's fingerprint is exposed as `precert_sha256` on the kept row, and `total` counts distinct certificates after collapsing. Pass `collapse_precert_twins=false` for the raw twin-per-row listing, or `is_precert` ("true"/"false"/empty, matching the submit_global_sweep contract) to filter to precerts or final leaves only.

Out-of-range `days` values are clamped into [1, 365] (0/negative → 1, >365 → 365); when that happens the response carries `days_clamped: true` plus `days_requested` (GH #189, mirrors the `limit_clamped` convention).

**Self-healing for cold domains (task #113 / GH #266):** if CertIndex has not indexed this domain yet, the response contains zero results plus a `backfill_status` sentinel. When `backfill_status == 'in_progress'`, wait `retry_after_seconds` seconds and call this tool again. Use `eta_basis` for the stopping rule: `'live'` = measured ETA; `'jobs_coarse'` = coarse estimate from job-completion rate; `'unknown'` = no projection — `terminal_at_latest` (ISO-8601 UTC) and `seconds_until_terminal` give the concrete stop point. `in_progress_over_budget` (no `retry_after_seconds`) means the projection exceeds the interactive budget — do NOT poll in a loop; retry much later (see `hint`). Any other status means the result will not change on retry.

**Null crypto fields:** on partially-indexed domains `is_weak_crypto` and `key_type` can be `null` — crypto metadata has not been extracted yet for those certificates. Treat `null` as UNKNOWN, never as 'not weak': all-null rows are not evidence that the domain has no weak-crypto certificates. To resolve a specific certificate, call `get_certificate` with its `sha256_fingerprint` and `include_raw=true` — that path re-derives key metadata from the raw certificate.

## Path parameters

- `domain` string, required

## Query parameters

- `days` integer: Lookahead window in days (1–365; out-of-range values are clamped and flagged via days_clamped / days_requested)
- `is_precert` string: "true" = precerts only; "false" = final leaf certs only; empty = all (matches the submit_global_sweep contract).
- `collapse_precert_twins` boolean: GH #262: collapse precert/final-leaf twin pairs (same subject_cn, SAN set, not_before, not_after, issuer) to the final leaf so certificates aren't double-counted; the suppressed precert's fingerprint is exposed as `precert_sha256` on the kept row. Set false for the raw twin-per-row listing.
- `wait_for_backfill` boolean: When false, cold domains return a 'not_indexed' sentinel immediately without triggering a background backfill. Default true preserves the existing self-healing behaviour.

## Cookies

- `ctix_session` string, nullable

## Response `200`

Successful Response

- MCPExpiringCertsResponse
  - `backfill_status` string, nullable
  - `retry_after_seconds` integer, nullable
  - `hint` string, nullable
  - `progress` object, nullable
  - `typical_eta_seconds` integer, nullable
  - `eta_seconds` integer, nullable
  - `eta_basis` string, nullable
  - `index_freshness` object, nullable
  - `domain` string, required
  - `days` integer, required
  - `total` integer, required
  - `results` CertificateSummary[], required
    - `sha256` string, required
    - `subject_cn` string, nullable
    - `subject_o` string, nullable
    - `sans` string[]
    - `issuer_cn` string, nullable
    - `issuer_o` string, nullable
    - `not_before` string, date-time, nullable
    - `not_after` string, date-time, nullable
    - `is_wildcard` boolean
    - `is_precert` boolean: True when this row is a CT precertificate (poison-extension log entry), False for a final leaf certificate. Precert and final-leaf twins have different SHA-256 hashes and are returned as separate rows; present on every list and detail row. Exception (GH #262): get_expiring_certs collapses twin pairs to the final leaf by default.
    - `precert_sha256` string, nullable: SHA-256 fingerprint of the CT precertificate twin that was collapsed into this final-leaf row (get_expiring_certs default twin collapsing, GH #262). Omitted when no twin was collapsed.
    - `key_type` string, nullable
    - `key_size` integer, nullable
    - `signature_algorithm` string, nullable
    - `is_weak_crypto` boolean, nullable
    - `key_fields_source` string, nullable
    - `first_seen` string, date-time, nullable: When CertIndex wrote this certificate to its database (batch-write timestamp). Multiple unrelated certificates often share an identical first_seen within the same ingestion batch. It is not the CT-log append time and is never substituted for a missing logged_at. Compare with logged_at to measure detection latency.
    - `logged_at` string, date-time, nullable: Timestamp from the CT log entry itself (MerkleTreeLeaf TimestampedEntry.timestamp, RFC 6962 §3.4) — when the log committed this certificate, NOT when CertIndex ingested it. Null for rows indexed before this field shipped, for crt.sh-sourced rows (reconciled_from_crtsh=true), and for any path that could not supply it. A null value means the CT-log timestamp is unavailable for that row; it does not mean the certificate was observed at first_seen, and first_seen is never synthesized into logged_at. Compare with first_seen (CertIndex write time) to measure detection latency. See logged_at_coverage disclosure block.
    - `reconciled_from_crtsh` boolean, nullable
    - `last_seen` string, date-time, nullable
    - `match_class` string, nullable: Attribution class when match_class_filter was supplied on search: first_party, lookalike_candidate, third_party_integration, substring_collision, or shared_hosting. Null on unfiltered search.
    - `match_reason` string, nullable: Semicolon-separated rule=value audit string for match_class. Null on unfiltered search.
    - `from_cold_tier` boolean, nullable: True when this certificate was served exclusively from the Tier 1 cold ClickHouse index (historical data not present in the hot Neon index). Absent/null for certificates served from the primary index. Cold-sourced rows carry key_fields_source='unavailable' and no raw_pem.
  - `truncated` boolean, nullable
  - `days_clamped` boolean, nullable
  - `days_requested` integer, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/ctindex/apis/certindex-api.md) · [All operations](https://skmtc.dev/ctindex/apis/certindex-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ctindex/apis/certindex-api/revisions/d5bc0ec85bc9?raw)
