Certificate Transparency Log API

The Certificate Transparency Log API enables proactive monitoring and auditing of SSL/TLS certificates issued for domain names. It helps verify proper issuance, spot misuse, and fortify web security by safeguarding online interactions and maintaining the trustworthiness of digital environments. The Certificate Transparency (CT) Log is a public, append-only ledger of all SSL/TLS certificates issued by trusted Certificate Authorities (CAs). Developed by Google, its primary purpose is to enhance internet security by making the process of certificate issuance transparent and publicly auditable. This transparency allows domain owners and the broader community to monitor and verify certificates, quickly detect any misissued or malicious certificates, and prevent potential security threats like man-in-the-middle attacks.

Our Certificate Transparency Log Replica hosted at https://crt.edgewatch.net/ provides an independent and readily accessible copy of the CT logs. By offering this replica, we enable organizations, security researchers, and individuals to proactively monitor and audit SSL/TLS certificates related to their domains. This service helps verify proper certificate issuance and swiftly identify any misuse, thereby fortifying web security and maintaining the trustworthiness of digital environments. Our API facilitates efficient querying and retrieval of certificate information, empowering users to safeguard their online interactions.

Base URL:

https://crt.edgewatch.net

Interactive documentation

All examples below use the production base URL. Replace placeholders such as YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, and YOUR_JWT_TOKEN with your own values. Never put real secrets in shared documents or source control.


What you need before you start

Certificate query paths under /certificates/ require a JWT access token.

This API does not include a public signup or OAuth login form. You receive a Client ID and Client Secret from Edgewatch (your account or an administrator). Those credentials live in the Edgewatch client registry. You exchange them for a short-lived JWT, then send that JWT on certificate requests.

Get credentials and product access from https://edgewatch.com. For account or quota questions, contact Edgewatch Support or email [email protected].

There is no certificate lookup by SHA-1/SHA-256 fingerprint. Search and domain endpoints match DNS names. The /auth/client-info/ helper returns your client IP and TLS fingerprint (JA4), not a certificate hash.


Step-by-step: authenticate as an API client

Step 1. Obtain Client ID and Client Secret

  1. Request API access from Edgewatch (platform account or support).
  2. You will be given a Client ID and a Client Secret.
  3. Store the secret like a password. Do not embed it in front-end JavaScript or public repositories.

The token endpoint looks up the Client ID in the X-Client-Id header and the secret in X-Client-Secret.

Step 2. Exchange credentials for a JWT

Call GET /auth/token/ with both credential headers. This is a GET request. There is no JSON body.

curl -sS "https://crt.edgewatch.net/auth/token/" \
  -H "X-Client-Id: YOUR_CLIENT_ID" \
  -H "X-Client-Secret: YOUR_CLIENT_SECRET"

Successful response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Save both values. token is the access token you send on certificate queries. refreshToken is used only to mint a new pair when the access token expires.

Access tokens typically expire after 3600 seconds (1 hour) (JWT_EXPIRES_IN). Refresh tokens typically expire after 604800 seconds (7 days) (REFRESH_TOKEN_EXPIRES_IN). Exact lifetimes are configured on the server.

If credentials are missing:

{
  "detail": "Missing X-Client-Id or X-Client-Secret"
}

Status: 401 Unauthorized.

If credentials do not match a registered client:

{
  "detail": "Invalid X-Client-Id or X-Client-Secret"
}

Status: 401 Unauthorized.

If the authentication database is unavailable:

{
  "detail": "Authentication service unavailable"
}

Status: 503 Service Unavailable.

Step 3. Send the access token on certificate requests

Every path that starts with /certificates/ expects an Authorization header.

Preferred form:

Authorization: Bearer YOUR_JWT_TOKEN

The service also accepts the raw JWT with no Bearer prefix, but Bearer is the documented scheme and matches Swagger’s Bearer security definition.

Example:

curl -sS "https://crt.edgewatch.net/certificates/search/?dns_name=example.com&search_mode=exact&limit=5" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Do not send X-Client-Id / X-Client-Secret on certificate endpoints. Those headers are only for GET /auth/token/. Certificate routes validate the JWT in Authorization.

Step 4. Confirm the token (optional)

GET /auth/validate/ checks the access token and returns the registered client record.

curl -sS "https://crt.edgewatch.net/auth/validate/" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Successful response (200) includes fields such as:

{
  "uid": "YOUR_CLIENT_ID",
  "clientId": "crt-client-example",
  "clientGuid": "11111111-2222-3333-4444-555555555555",
  "isGeneric": false,
  "isInternal": false,
  "targetApis": ["crt"],
  "name": "Example Client",
  "createdAt": "2024-01-15T10:00:00.000Z",
  "expiresAt": "2027-01-15T10:00:00.000Z"
}

Errors (401):

  • Missing header: {"detail": "Missing or invalid Authorization header"}
  • Expired JWT: {"detail": "Token expired"}
  • Bad JWT or unknown client: {"detail": "Invalid token"}

Step 5. Refresh the access token

When token expires, call GET /auth/refresh/ with the refresh token in the X-Refresh-Token header (not Authorization).

curl -sS "https://crt.edgewatch.net/auth/refresh/" \
  -H "X-Refresh-Token: YOUR_REFRESH_TOKEN"

Successful response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Replace both stored values. The API issues a new refresh token as well as a new access token.

Refresh errors:

  • 400 — {"detail": "X-Refresh-Token header is required"} (header missing)
  • 403 — {"detail": "Invalid refresh token"} (token does not match a client)
  • 403 — {"detail": "Expired refresh token"} (refresh token past expiry)
  • 403 — {"detail": "Invalid or expired refresh token"} (JWT decode failed)
  • 503 — {"detail": "Authentication service unavailable"} (auth store down)

If the refresh token is expired, go back to Step 2 and call /auth/token/ with your Client ID and Client Secret.

Step 6. Revocation

There is no revoke endpoint in this API. To stop using a leaked access token, wait for it to expire (typically one hour) and request new tokens. If a Client Secret is leaked, contact Edgewatch support to rotate the client record.


Auth errors on /certificates/ routes

These responses come from the service auth and credit middleware (JSON field is message, not detail).

401 — missing token

{
  "message": "Missing Authorization header."
}

401 — expired access token

{
  "message": "Token expired"
}

Refresh with X-Refresh-Token, or request a new pair from /auth/token/.

401 — invalid access token

{
  "message": "Invalid token"
}

The JWT is malformed, signed with the wrong key, or otherwise not accepted.

400 — client not attached to the token

{
  "message": "client_id not found in request."
}

The JWT decoded, but the client record needed for credit checks was not found. Contact support.

403 — no remaining credits

{
  "message": "Insufficient credits."
}

Authenticated clients are checked against a credit wallet. Successful queries may include a response header:

X-Remaining-Credits: 12.5

When credits reach zero, certificate queries return 403 until credits are replenished.

429 — explorer / unauthenticated browser limits

If you call /certificates/ without Authorization, but with all of x-client-type, x-client-ip, and x-client-fingerprint (the website explorer path), Redis rate limits apply:

{
  "message": "Daily rate limit exceeded"
}

or

{
  "message": "Hourly request limit exceeded"
}

Integrators should use JWT auth (Steps 1–5) instead of the explorer headers.


Headers and response format

Certificate and auth endpoints are GET requests. You do not need a JSON request body or Content-Type for the examples in this guide.

Recommended headers:

  • Authorization: Bearer YOUR_JWT_TOKEN on /certificates/ and /auth/validate/
  • Accept: application/json (the API produces JSON)
  • Accept-Encoding: gzip is optional; the server can gzip responses

Responses are JSON. Many certificate endpoints also return performance (or cached / query_time_seconds) so you can see whether Redis served a cached result (typical TTL: 15 minutes). Use skip_cache=true only on endpoints that document that parameter.


Result limits (not page-based pagination)

List endpoints cap how many rows they return with a limit query parameter. They do not expose page / page_size on the public certificate routes.

Typical limit rules:

  • Search, wildcards, recently updated, expired, expiring soon, long validity, oldest active, multiple-certs domains: default 50 or 100, maximum 1000
  • Subdomain enumeration: default 1000, maximum 10000
  • Frequent chain changes: default 50, maximum 100

Domain monitor does not take limit. Each status bucket (active, expired, expiring soon) returns up to 50 sample certificates. Use count_only=true for counts without samples.

Identical queries within the cache window return the same capped result set.


Query examples

Unless noted, these paths require Authorization: Bearer YOUR_JWT_TOKEN.

1. Search certificates by DNS name

Method / path: GET /certificates/search/

Auth: Bearer JWT required

Query parameters:

  • dns_name (required) — domain or fragment to search
  • search_mode (optional) — exact, prefix, suffix, or contains (default contains)
  • from_date / to_date (optional) — YYYY-MM-DD; default last 30 days; maximum range 90 days
  • limit (optional) — default 100, max 1000

Use exact when you know the full hostname. prefix and suffix are faster than contains.

curl -sS "https://crt.edgewatch.net/certificates/search/?dns_name=example.com&search_mode=exact&limit=5" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Sample 200 response (fields shortened):

{
  "count": 2,
  "query_time_seconds": 0.184,
  "cached": false,
  "search": {
    "dns_name": "example.com",
    "search_mode": "exact",
    "from_date": "2026-08-22",
    "to_date": "2026-09-21",
    "limit": 5,
    "days_range": 30
  },
  "certificates": [
    {
      "url": "https://ct.googleapis.com/logs/us1/argon2026h2",
      "cert_index": "123456789",
      "url_plus_index": "https://ct.googleapis.com/logs/us1/argon2026h2/123456789",
      "not_before": "2026-09-01 12:00:00",
      "not_after": "2027-09-02 12:00:00",
      "all_dns_names": "example.com www.example.com",
      "dns_name_count": 2
    }
  ]
}

Missing dns_name returns 400 with "error": "dns_name parameter is required". An invalid search_mode returns 400 and lists valid_modes.

2. Monitor a domain (lookup by domain)

Method / path: GET /certificates/domain-monitor/{domain}/

Auth: Bearer JWT required

Path: domain — for example example.com

Query parameters:

  • from_date / to_date (optional) — YYYY-MM-DD. Defaults depend on match type (exact is typically about 7 days; prefix about 3; contains about 7)
  • exact (optional) — true for an exact hostname match (faster)
  • match_mode (optional) — prefix (default) or contains (contains max range 90 days)
  • count_only (optional) — true to return counts only
  • skip_cache (optional) — true to bypass Redis
curl -sS "https://crt.edgewatch.net/certificates/domain-monitor/example.com/?exact=true&count_only=false" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Sample 200 response:

{
  "domain": "example.com",
  "total_certificates": 12,
  "summary": {
    "active_count": 8,
    "expired_count": 3,
    "expiring_soon_count": 1
  },
  "active_certificates": [
    {
      "url_plus_index": "https://ct.googleapis.com/logs/us1/argon2026h2/123456789",
      "not_before": "2026-09-01 12:00:00",
      "not_after": "2027-09-02 12:00:00",
      "all_dns_names": "example.com",
      "days_until_expiry": 346
    }
  ],
  "expired_certificates": [],
  "expiring_soon_certificates": [],
  "filters_applied": {
    "from_date": "2026-09-14",
    "to_date": "2026-09-21",
    "domain": "example.com",
    "exact_match": true,
    "match_mode": "prefix",
    "using_defaults": true
  },
  "performance": {
    "cached": false,
    "execution_time": 0.42,
    "query_method": "dns_index_exact",
    "dns_index_available": true,
    "exact_match": true
  },
  "note": "Results limited to certificates between 2026-09-14 and 2026-09-21. Each category shows up to 50 certificates."
}

With count_only=true, the certificate arrays are omitted and summary still contains the counts.

3. Enumerate subdomains

Method / path: GET /certificates/subdomains/{domain}/

Auth: Bearer JWT required

Query parameters:

  • from_date / to_date (optional) — default last 30 days
  • limit (optional) — default 1000, max 10000
  • skip_cache (optional)
curl -sS "https://crt.edgewatch.net/certificates/subdomains/example.com/?limit=100" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Sample 200 response:

{
  "domain": "example.com",
  "subdomain_count": 4,
  "subdomains": [
    "*.example.com",
    "api.example.com",
    "example.com",
    "www.example.com"
  ],
  "filters_applied": {
    "from_date": "2026-08-22",
    "to_date": "2026-09-21",
    "limit": 100,
    "using_defaults": true
  },
  "performance": {
    "cached": false,
    "execution_time": 0.91,
    "dns_index_available": true
  },
  "note": "Results show unique subdomains found between 2026-08-22 and 2026-09-21. Limited to 100 results."
}

4. Certificates expiring soon

Method / path: GET /certificates/expiring-soon/

Auth: Bearer JWT required

Query parameters:

  • days_ahead (optional) — default 30, max 365
  • domain_name (optional) — partial domain filter
  • limit (optional) — default 50, max 1000
  • skip_cache (optional)
curl -sS "https://crt.edgewatch.net/certificates/expiring-soon/?days_ahead=7&domain_name=example.com&limit=20" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Sample 200 response:

{
  "count": 1,
  "expiring_soon_certificates": [
    {
      "url": "https://ct.googleapis.com/logs/us1/argon2026h2",
      "cert_index": "123456789",
      "url_plus_index": "https://ct.googleapis.com/logs/us1/argon2026h2/123456789",
      "not_before": "2025-10-01 00:00:00",
      "not_after": "2026-09-25 00:00:00",
      "all_dns_names": "example.com",
      "dns_name_count": 1
    }
  ],
  "filters_applied": {
    "days_ahead": 7,
    "expiration_window": "Now to 2026-09-28",
    "domain_name": "example.com",
    "limit": 20
  },
  "performance": {
    "cached": false,
    "execution_time": 1.02,
    "dns_index_available": true
  },
  "note": "Results show certificates expiring within the next 7 days. Limited to 20 results. Sorted by expiration date (soonest first)."
}

5. Expired certificates

Method / path: GET /certificates/expired/

Auth: Bearer JWT required

Query parameters:

  • from_date / to_date (optional) — filter on expiration (not_after); default last 1 day
  • domain_name (optional) — strongly recommended
  • limit (optional) — default 50, max 1000
  • skip_cache (optional)

Keep the date window short (1–3 days) and add domain_name when you can. Wide unfiltered windows can be slow.

curl -sS "https://crt.edgewatch.net/certificates/expired/?from_date=2026-09-20&to_date=2026-09-21&domain_name=example.com&limit=20" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Sample 200 response:

{
  "count": 1,
  "expired_certificates": [
    {
      "url": "https://ct.googleapis.com/logs/us1/argon2025h2",
      "cert_index": "987654321",
      "url_plus_index": "https://ct.googleapis.com/logs/us1/argon2025h2/987654321",
      "not_before": "2025-09-01 00:00:00",
      "not_after": "2026-09-20 00:00:00",
      "all_dns_names": "old.example.com",
      "dns_name_count": 1
    }
  ],
  "filters_applied": {
    "from_date": "2026-09-20",
    "to_date": "2026-09-21",
    "domain_name": "example.com",
    "limit": 20,
    "using_defaults": false
  },
  "performance": {
    "cached": false,
    "execution_time": 8.44,
    "dns_index_available": true
  },
  "note": "Results show certificates that expired between 2026-09-20 and 2026-09-21."
}

6. Wildcard certificates

Method / path: GET /certificates/wildcards/

Auth: Bearer JWT required

Query parameters:

  • from_date / to_date (optional) — default last 30 days, max 90 days
  • domain (optional) — partial match such as example.com
  • limit (optional) — default 100, max 1000
curl -sS "https://crt.edgewatch.net/certificates/wildcards/?domain=example.com&limit=10" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Sample 200 response:

{
  "count": 1,
  "query_time_seconds": 0.31,
  "cached": false,
  "filters": {
    "from_date": "2026-08-22",
    "to_date": "2026-09-21",
    "domain": "example.com",
    "limit": 10,
    "days_range": 30
  },
  "certificates": [
    {
      "url": "https://ct.googleapis.com/logs/us1/argon2026h2",
      "cert_index": "555555555",
      "url_plus_index": "https://ct.googleapis.com/logs/us1/argon2026h2/555555555",
      "not_before": "2026-08-30 00:00:00",
      "not_after": "2027-08-31 00:00:00",
      "all_dns_names": "*.example.com example.com",
      "dns_name_count": 2,
      "wildcard_count": 1
    }
  ],
  "wildcard_info": "Wildcard certificates cover a domain and all its subdomains (e.g., *.example.com)"
}

7. Recently updated certificates

Method / path: GET /certificates/recently-updated/

Auth: Bearer JWT required

Query parameters:

  • from_date / to_date (optional) — default last 7 days, max 90 days
  • domain (optional)
  • limit (optional) — default 50, max 1000
curl -sS "https://crt.edgewatch.net/certificates/recently-updated/?domain=example.com&limit=10" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Each certificate object includes url, cert_index, url_plus_index, not_before, not_after, updated_at, all_dns_names, and dns_name_count.

8. Statistics

These summary endpoints live under /certificates/ and therefore require Bearer JWT.

Last 45 days (chart rows): GET /certificates/stats/

curl -sS "https://crt.edgewatch.net/certificates/stats/" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Accept: application/json"

Sample 200 response:

{
  "data": [
    ["Date", "Total Certificates", "New/Updated", "Expired", "Issued per Day"],
    ["2026-08-07", 7800000000, 50000, 12000, 48000]
  ]
}

Today’s stats: GET /certificates/stats/today/

{
  "date": "2026-09-21",
  "total_certificates": 7800000000,
  "updated_certificates": 150000,
  "new_certificates": 72000,
  "expired_certificates": 18000,
  "issued_certificates": 72000
}

If nothing was updated today, the API returns 404 with "error": "No statistics found for today".

Active count: GET /certificates/active-count/

{
  "active_certificates_count": 6200000000,
  "timestamp": 1758470000.0
}

Landing-page helpers without JWT (not under /certificates/):

  • GET /api/home-stats/ — badges and chart series
  • GET /api/certificate-counts/ — active_certificates, expired_certificates (and related count fields)
  • GET /ping/ — {"status": "ok", "message": "pong"}
  • GET /health/ — service connectivity
  • GET /auth/client-info/ — your ip and TLS fingerprint (JA4)
curl -sS "https://crt.edgewatch.net/ping/"
curl -sS "https://crt.edgewatch.net/api/certificate-counts/"

9. Other certificate filters

These all require Bearer JWT.

Oldest still-valid certificates — GET /certificates/oldest-active/

  • max_age_years (default 10, max 30)
  • domain_name, limit (default 50, max 1000), skip_cache
curl -sS "https://crt.edgewatch.net/certificates/oldest-active/?domain_name=example.com&max_age_years=5&limit=20" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Long validity periods — GET /certificates/long-validity/

  • min_validity_days (default 730, max 3650)
  • from_date / to_date (default last 180 days)
  • domain_name, limit, skip_cache

Modern public TLS certificates are capped well below two years, so most hits are historical.

Multiple certificates per domain — GET /certificates/multiple-certs-domains/

  • domain (required) — prefix match, for example example
  • min_cert_count (default 2, max 1000)
  • limit (default 100, max 1000)
curl -sS "https://crt.edgewatch.net/certificates/multiple-certs-domains/?domain=example&min_cert_count=2&limit=20" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Response domains entries look like:

{
  "domain_name": "example.com",
  "certificate_count": 42,
  "certificate_ids": [
    "https://ct.googleapis.com/logs/us1/argon2026h2/123"
  ]
}

Frequent chain changes — GET /certificates/frequent-chain-changes/

This query is slow (often tens of seconds). Use a short date range (1–7 days, default last 7) and a domain_name filter. limit max is 100. min_chain_changes default 2, max 10.


import requests

BASE = "https://crt.edgewatch.net"
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"

session = requests.Session()
session.headers.update({"Accept": "application/json"})

token_resp = session.get(
    f"{BASE}/auth/token/",
    headers={
        "X-Client-Id": CLIENT_ID,
        "X-Client-Secret": CLIENT_SECRET,
    },
    timeout=30,
)
token_resp.raise_for_status()
access_token = token_resp.json()["token"]

search_resp = session.get(
    f"{BASE}/certificates/search/",
    headers={"Authorization": f"Bearer {access_token}"},
    params={
        "dns_name": "example.com",
        "search_mode": "exact",
        "limit": 10,
    },
    timeout=60,
)
search_resp.raise_for_status()
print(search_resp.json()["count"])

To refresh later:

refresh = session.get(
    f"{BASE}/auth/refresh/",
    headers={"X-Refresh-Token": token_resp.json()["refreshToken"]},
    timeout=30,
)
access_token = refresh.json()["token"]

Practical tips

  1. Prefer search_mode=exact or exact=true on domain monitor when you know the hostname.
  2. Keep from_date / to_date as short as your use case allows. Several endpoints reject ranges over 90 days.
  3. Always add a domain filter on expired, chain-change, and similar wide scans.
  4. Reuse cached results: identical query strings within about 15 minutes are served from Redis.
  5. Watch X-Remaining-Credits and handle 403 with "Insufficient credits.".
  6. Retry 503 (auth store or DNS index unavailable) after a short wait.

Troubleshooting and next steps

401 on /auth/token/ — Confirm X-Client-Id and X-Client-Secret are present and match the values Edgewatch issued. Header names are case-insensitive in HTTP, but the spelling must be X-Client-Id and X-Client-Secret.

401 Token expired on /certificates/ — Call GET /auth/refresh/ with X-Refresh-Token. If that returns 403, call /auth/token/ again.

403 Insufficient credits. — Your client wallet is empty. Contact support to top up or review usage.

400 Invalid date format. Use YYYY-MM-DD — Dates must be calendar dates such as 2026-09-21, not timestamps.

400 Limit must be between 1 and 1000 (or 1 and 100 / 1 and 10000) — Lower limit to the documented maximum for that path.

429 — You are on the unauthenticated explorer path. Switch to JWT auth for integration work.

Timeouts / 500 with a suggestion field — Narrow the date range, add a domain filter, or switch from contains to exact / prefix.

Try any operation interactively in Swagger UI (Authorize with Bearer YOUR_JWT_TOKEN). Full schema browsing is also available in ReDoc.

Was this article helpful?

Related Articles