Crtlog API

A privacy-respecting Certificate Transparency log recon service.

Base URL: https://crtlog.com

Tor: A .onion (Tor) endpoint is also available — see below.

All API responses are JSON unless otherwise noted. Timestamps are Unix seconds (UTC). POST /api/v1/submit (requires API key) is the only write endpoint — all other endpoints are read-only.


Table of Contents


Rate Limiting

Rate limits apply per source IP (anonymous) or per API key (authenticated):

LimitScopeTier
20Requests per dayAnonymous (all endpoints)
1Request per 5 secondsAnonymous (all endpoints)
500Requests per dayAPI key (all endpoints)
1Request per 1 secondAPI key (all endpoints)
30Submissions per dayAPI key (POST /api/v1/submit)
1Submission per 10 secondsAPI key (POST /api/v1/submit)

The following endpoints are exempt from rate limits: /health, /api/v1/db/stats, /api/v1/storage, /api/v1/system_status, /api/v1/log/stats. Exceeded limits return 429 Too Many Requests. Request an API key via to raise your daily limit to 500.

Note: Use a descriptive User-Agent header for anonymous requests. Generic UAs like curl/, python-requests, etc. are blocked. Authenticated requests bypass this restriction.

Error Format

All errors return a consistent JSON envelope:

{
  "error": {
    "code": "short_error_code",
    "message": "Human-readable description"
  }
}

HTTP status codes reflect the nature of the error:

CodeMeaning
400Bad Request — missing/invalid parameters, generic User-Agent, invalid domain
401Unauthorized — missing, invalid, or expired API token
403Forbidden — submission does not belong to your API key
404Not Found — resource does not exist
429Too Many Requests — rate limit exceeded (daily cap or interval)
500Internal Server Error — something broke

Errors are always indicated by the presence of the error key in the response, regardless of HTTP status code.

Endpoints

GET/health

Liveness probe — lightweight health check with no DB dependency. Returns disk usage and overall status.

HTTP 200
{
  "status": "ok",
  "disk": {
    "path": "/data",
    "total_bytes": 211157901312,
    "free_bytes": 170003652608,
    "used_bytes": 41154248704,
    "usage_percent": 19.0
  }
}
GET/api/v1/cert/{sha256}

Retrieve a certificate by its SHA-256 fingerprint. Returns an array (typically one entry, but the same cert may appear in multiple logs).

sha256string (path)Required. Hex SHA-256 fingerprint (64 chars).
HTTP 200
[
  {
    "sha256": "3bb6b2d1711f0de6...",
    "parent_domain": "example.com",
    "is_wildcard": false,
    "issuer_dn": "/2.5.4.6=US/2.5.4.10=Let's Encrypt/2.5.4.3=YE2",
    "not_before": 1783442931,
    "not_after": 1791218930,
    "key_algorithm": "ecdsa",
    "key_bits": 256,
    "is_precert": true,
    "seen_at": 1783620208,
    "log_id": "letsencrypt_sycamore_2026h2",
    "sans": [
      "10b73a7c.example.com",
      "www-898232048.test.example.com"
    ],
    "ips": []
  }
]
GET/api/v1/query

Search certificate Subject Alternative Names (SANs) by domain, IP, or keyword. Supports lite mode for faster lookups without cert metadata, and CSV export.

qstring (query)Required. Domain/IP/keyword. Max 253 chars.
include_wildcardbool (query)Optional. Include wildcard SANs (default false).
limitint (query)Optional. Max results (default 100, max 1000).
offsetint (query)Optional. Pagination offset (default 0).
formatstring (query)Optional. json (default) or csv.
litebool (query)Optional. Lite mode — returns (SAN, [IPs]) pairs, no cert metadata (default false).
HTTP 200
{
  "query": "example.com",
  "count": 42,
  "limit": 100,
  "offset": 0,
  "has_more": false,
  "lite": false,
  "results": [
    {
      "sha256": "3bb6b2d1711f0de6...",
      "parent_domain": "example.com",
      "is_wildcard": false,
      "issuer_dn": "/2.5.4.6=US/2.5.4.10=Let's Encrypt/2.5.4.3=YE2",
      "not_before": 1783442931,
      "not_after": 1791218930,
      "key_algorithm": "ecdsa",
      "key_bits": 256,
      "is_precert": true,
      "seen_at": 1783620208,
      "log_id": "letsencrypt_sycamore_2026h2",
      "sans": [
        "10b73a7c.example.com",
        "www-898232048.test.example.com"
      ],
      "ips": []
    }
  ]
}

Lite mode JSON response (?lite=true):

HTTP 200
{
  "query": "example.com",
  "count": 1,
  "limit": 100,
  "offset": 0,
  "has_more": false,
  "lite": true,
  "results": [
    {
      "san": "example.com",
      "ips": [
        {"ip": "93.184.216.34", "record_type": "A"}
      ]
    }
  ]
}

Lite mode (?lite=true) returns san + ips[] where ips[] objects contain ip (string) and record_type ("A" or "AAAA"). No resolved_at in lite mode. Faster, no certs table join, survives cert pruning. IPs per SAN capped at 100. CSV with ?lite=true&format=csv returns san,ip,record_type rows.

GET/api/v1/domain-ips

Resolve which IP addresses a domain has historically resolved to, based on CT log entries.

qstring (query)Required. Domain to look up.
HTTP 200
{
  "query": "example.com",
  "count": 2,
  "results": [
    {
      "ip": "104.20.23.154",
      "record_type": "A",
      "resolved_at": 1783997277
    },
    {
      "ip": "172.66.147.243",
      "record_type": "A",
      "resolved_at": 1783997277
    }
  ]
}
GET/api/v1/log/stats

List all known Certificate Transparency logs currently being ingested (8 total).

HTTP 200
{
  "logs": [
    {
      "id": "google_argon_2027h1",
      "name": "Google Argon 2027 H1",
      "operator": "Google",
      "sth_url": "https://ct.googleapis.com/logs/us1/argon2027h1/ct/v1/get-sth"
    },
    {
      "id": "cloudflare_nimbus_2027",
      "name": "Cloudflare Nimbus 2027",
      "operator": "Cloudflare",
      "sth_url": "https://ct.cloudflare.com/logs/nimbus2027/ct/v1/get-sth"
    },
    {
      "id": "letsencrypt_sycamore_2027h1",
      "name": "Let's Encrypt Sycamore 2027 H1",
      "operator": "Let's Encrypt",
      "sth_url": "https://mon.sycamore.ct.letsencrypt.org/2027h1/"
    }
  ]
}
GET/api/v1/db/stats

Database row counts and per-log ingestion progress.

HTTP 200
{
  "certs": 3621469,
  "sans": 12700271,
  "ips": 141632,
  "logs": 10,
  "progress": [
    {
      "log_id": "cloudflare_nimbus_2026",
      "ingested": 2492106
    },
    {
      "log_id": "google_argon_2026h2",
      "ingested": 879568
    },
    {
      "log_id": "letsencrypt_sycamore_2027h1",
      "ingested": 62464
    }
  ]
}
GET/api/v1/storage

Disk usage and backup health. backup_status: ok (last backup < 26h) or down (stale or missing).

HTTP 200
{
  "database_bytes": 7699148800,
  "database_total_bytes": 8786429072,
  "backup_status": "ok",
  "backup_last_sync_seconds_ago": 6,
  "app_heartbeat_seconds_ago": 35,
  "backup_bytes": 2367537655,
  "backup_bytes_gb": 2.37,
  "backup_bytes_gib": 2.20,
  "total_s3_bytes": 4938150912,
  "total_s3_bytes_gb": 4.94,
  "total_s3_bytes_gib": 4.60
}
GET/api/v1/system_status

Comprehensive system health: ingestion, DNS resolution, and coverage metrics.

HTTP 200
{
  "generated_at": 1784049953,
  "last_ingest_at": 1784048918,
  "last_ingest_duration_s": 518,
  "last_ingest_logs_ok": 4,
  "last_ingest_logs_errored": 0,
  "last_ingest_log_id": "cloudflare_nimbus_2026",
  "ticks_last_24h": 29,
  "ticks_failed_last_24h": 0,
  "last_resolve_at": 1784049028,
  "last_resolve_duration_s": 73,
  "last_resolve_errors": 0,
  "last_resolve_names_checked": 142,
  "last_resolve_ips_inserted": 7,
  "resolve_passes_last_24h": 81,
  "resolve_passes_last_hour": 4,
  "warmup_passes_last_24h": 24,
  "warmup_passes_last_hour": 1,
  "tracked_sans_count": 4651,
  "resolved_sans_count": 4545,
  "unresolved_sans_count": 106,
  "ip_addresses_count": 1594,
  "resolver_coverage_pct": 97.7,
  "tombstoned_sans_count": 120000,
  "active_tombstone_sans_count": 24000,
  "grid_7x24": ["ok","ok","ok",...],
  "resolve_grid_7x24": ["ok","ok","ok",...]
}
POST/api/v1/submit

Submit a domain for tracking. Requires API key (Bearer token). Domain is fetched, ingested, and resolved.

domainstring (JSON body)Required. Valid DNS hostname (max 253 chars).
HTTP 200
{
  "tracking_id": "aBcDeFgHiJkLmNoP",
  "status": "submitted",
  "domain": "example.com"
}

Rate-limited: 30 submissions/24h per API key, 1 per 10s. Duplicate submissions within 10min return status: "already_submitted".

GET/api/v1/submission/{tracking_id}

Check the status of a domain submission. Requires API key.

HTTP 200
{
  "tracking_id": "aBcDeFgHiJkLmNoP",
  "domain": "example.com",
  "status": "resolved",
  "sha256": "abcdef0123456789...",
  "error": null,
  "submitted_at": 1717200000,
  "completed_at": 1717200100
}
GET/api/v1/submissions

List your recent submissions (last 30). Requires API key.

HTTP 200
{
  "count": 3,
  "results": [
    {
      "tracking_id": "aBcDeFgHiJkLmNoP",
      "domain": "example.com",
      "status": "resolved",
      "sha256": "abcdef0123456789...",
      "error": null,
      "submitted_at": 1717200000,
      "completed_at": 1717200100
    }
  ]
}
GET/api/v1/submissions/recent

Check if a domain was recently submitted (within the 10min dedup window). Requires API key.

domainstring (query)Required. Domain to check.
HTTP 200
{
  "found": true,
  "tracking_id": "aBcDeFgHiJkLmNoP",
  "domain": "example.com",
  "status": "resolved",
  "sha256": "abcdef0123456789...",
  "error": null,
  "submitted_at": 1717200000,
  "window_secs": 600
}

Static Routes

RouteDescription
/Home page with search interface
/docsAPI documentation (this page)
/aboutAbout crtlog
/statusSystem status dashboard
/coverageCT log coverage map
/statsAggregate statistics
/legal/termsTerms of service
/legal/privacyPrivacy policy
/legal/abuseAbuse reporting

cURL Examples at a Glance

# Health check
curl -H "User-Agent: MyTool/1.0" https://crtlog.com/health

# Fetch certificate by SHA-256
curl -H "User-Agent: MyTool/1.0" https://crtlog.com/api/v1/cert/abc123...

# Search SANs
curl -H "User-Agent: MyTool/1.0" "https://crtlog.com/api/v1/query?q=example.com"

# Lite mode (fast, no cert metadata)
curl -H "User-Agent: MyTool/1.0" "https://crtlog.com/api/v1/query?q=example.com&lite=true"

# CSV export
curl -H "User-Agent: MyTool/1.0" "https://crtlog.com/api/v1/query?q=example.com&format=csv"

# Include wildcard SANs
curl -H "User-Agent: MyTool/1.0" "https://crtlog.com/api/v1/query?q=example.com&include_wildcard=true"

# Search by IP address
curl -H "User-Agent: MyTool/1.0" "https://crtlog.com/api/v1/query?q=93.184.216.34"

# Domain IP history
curl -H "User-Agent: MyTool/1.0" "https://crtlog.com/api/v1/domain-ips?q=example.com"

# CT log list
curl -H "User-Agent: MyTool/1.0" https://crtlog.com/api/v1/log/stats

# Database stats
curl -H "User-Agent: MyTool/1.0" https://crtlog.com/api/v1/db/stats

# Disk & backup health
curl -H "User-Agent: MyTool/1.0" https://crtlog.com/api/v1/storage

# System status
curl -H "User-Agent: MyTool/1.0" https://crtlog.com/api/v1/system_status

# Submit a domain (requires API key)
curl -X POST -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}' \
  https://crtlog.com/api/v1/submit

Tor Access

The API is accessible via a .onion (Tor) hidden service for additional privacy at the network layer. Contact us to request the address — an API key is required for all Tor requests.

# Query over Tor
curl --socks5-hostname localhost:9050 \
  -H "Authorization: Bearer <your-api-key>" \
  http://<onion-address>.onion/api/v1/query?q=example

# Submit a domain over Tor
curl --socks5-hostname localhost:9050 -X POST \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}' \
  http://<onion-address>.onion/api/v1/submit

Rate limits on the Tor endpoint are the same as the authenticated clearnet tier: 500 requests / 24h, 1 request / 1s.


Usage with Open-Source API Clients

HTTPie

http https://crtlog.com/api/v1/query q==example limit==5 User-Agent:MyTool/1.0

Other Clients

# Bruno — import via plain-text markdown collections
# https://docs.usebruno.com

# Hoppscotch — web interface, set User-Agent header to MyTool/1.0
# Insomnia — new request, set User-Agent: MyTool/1.0

All anonymous requests require a custom User-Agent header. Authenticated requests (Bearer token) bypass this check. All endpoints accept GET with query parameters.

↑ Back to top