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):
| Limit | Scope | Tier |
|---|---|---|
| 20 | Requests per day | Anonymous (all endpoints) |
| 1 | Request per 5 seconds | Anonymous (all endpoints) |
| 500 | Requests per day | API key (all endpoints) |
| 1 | Request per 1 second | API key (all endpoints) |
| 30 | Submissions per day | API key (POST /api/v1/submit) |
| 1 | Submission per 10 seconds | API 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:
| Code | Meaning |
|---|---|
| 400 | Bad Request — missing/invalid parameters, generic User-Agent, invalid domain |
| 401 | Unauthorized — missing, invalid, or expired API token |
| 403 | Forbidden — submission does not belong to your API key |
| 404 | Not Found — resource does not exist |
| 429 | Too Many Requests — rate limit exceeded (daily cap or interval) |
| 500 | Internal Server Error — something broke |
Errors are always indicated by the presence of the error key in the response, regardless of HTTP status code.
Endpoints
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
}
}
Retrieve a certificate by its SHA-256 fingerprint. Returns an array (typically one entry, but the same cert may appear in multiple logs).
| sha256 | string (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": []
}
]
Search certificate Subject Alternative Names (SANs) by domain, IP, or keyword. Supports lite mode for faster lookups without cert metadata, and CSV export.
| q | string (query) | Required. Domain/IP/keyword. Max 253 chars. |
| include_wildcard | bool (query) | Optional. Include wildcard SANs (default false). |
| limit | int (query) | Optional. Max results (default 100, max 1000). |
| offset | int (query) | Optional. Pagination offset (default 0). |
| format | string (query) | Optional. json (default) or csv. |
| lite | bool (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.
Resolve which IP addresses a domain has historically resolved to, based on CT log entries.
| q | string (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
}
]
}
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/"
}
]
}
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
}
]
}
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
}
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",...]
}
Submit a domain for tracking. Requires API key (Bearer token). Domain is fetched, ingested, and resolved.
| domain | string (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".
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
}
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
}
]
}
Check if a domain was recently submitted (within the 10min dedup window). Requires API key.
| domain | string (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
| Route | Description |
|---|---|
/ | Home page with search interface |
/docs | API documentation (this page) |
/about | About crtlog |
/status | System status dashboard |
/coverage | CT log coverage map |
/stats | Aggregate statistics |
/legal/terms | Terms of service |
/legal/privacy | Privacy policy |
/legal/abuse | Abuse 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.