API documentation
Query every formally-filed breach disclosure over a REST/JSON API. Read-scoped keys are free for public-interest use.
A near-real-time, machine-readable feed of formally-filed breach disclosures worldwide — SEC Item 1.05, US state-AG notifications, HHS OCR, EU DPA decisions, and ransomware leak claims. REST, JSON, cursor-paginated. Every response carries meta.ai_assisted: true (EU AI Act Art. 50).
Base URL & authentication
All endpoints are under a single base. Send your key as a Bearer token on every request (health endpoints are the only ones that work without one).
https://api.disclosurelens.com/v1
Authorization: Bearer df_your_key_hereKeys are read-scoped, hashed at rest, and shown to you only once at creation. Create and manage them under Settings → API keys.
Quick start
curl -s "https://api.disclosurelens.com/v1/disclosures?limit=3&jurisdiction=us-ca" \
-H "Authorization: Bearer $DL_API_KEY"Response envelope — a data payload plus a meta block:
{
"data": [ /* … */ ],
"meta": {
"cursor": { "next": "<opaque>", "prev": null },
"ai_assisted": true
}
}Pagination
List endpoints return up to limit rows (1–200, default 50). Disclosures and incidents use keyset cursors — pass the value from meta.cursor.next (disclosures) or meta.next_cursor (incidents) back as ?cursor= for the next page; a null value means the last page under the default filed_desc sort. The change-feed sorts updated_asc and created_asc are cursor-paginated too; the ranking sorts (severity, ttd, confidence) and filed_asc are single-page and always return a null cursor. Threat actors use ?limit/?offset instead.
Change feed — keeping a copy in sync
To mirror the corpus, poll on updated_after — not filed_after. Filing dates belong to the regulator, and most of this corpus arrives through archive backfill, so a record's filing date is typically far older than the moment it reached us: the median gap between the two is over a year for state-AG notifications and over four years for HHS OCR. A poller keyed on filed_after would see leak-site claims and almost nothing else, and would miss backdated records permanently — they are born already behind a window that has moved past them.
updated_after covers new records and revisions (re-extraction, entity resolution, incident linkage). Each row carries record.updated_at and record.created_at; store the last value you durably wrote and pass it back on the next poll. Use created_after instead when you want a reproducible snapshot, where later revisions must not move a record into or out of your set.
# page forward through everything changed since your last sync
curl -H "Authorization: Bearer $DL_KEY" \
"https://api.disclosurelens.com/v1/disclosures?updated_after=2026-07-30T00:00:00Z&sort=updated_asc&limit=200"
# then follow meta.cursor.next until it is null, and persist the last
# record.updated_at you saw as the watermark for the next run.Records are delivered at least once: a row revised while you are paging reappears later in the walk under its new timestamp. Deduplicate on id.
Rate limits
The free research tier allows 60 requests per minute per key. Over the limit returns 429; the X-RateLimit-Remaining header tracks your budget. Higher throughput and signed PDF deliverables are part of the paid tier (bulk exports and webhooks are planned).
Endpoints
| GET | /disclosures | List/filter filings (paginated). |
| GET | /disclosures/{id} | One disclosure with full extraction. |
| GET | /disclosures/facets | Facet counts for the current filter set. |
| GET | /disclosures/{id}/related | Cross-source siblings of the same breach. |
| GET | /disclosures/{id}/extraction | Extraction confidence + provenance. |
| GET | /export/disclosures.ndjson | Streaming bulk export (see Bulk export). |
| GET | /openapi.json | Machine-readable OpenAPI spec (requires a key). |
| GET | /incidents | List incidents (one per breach, across sources). |
| GET | /incidents/{id} | One incident with its member filings + timeline. |
| GET | /entities | Search/list resolved organizations. |
| GET | /entities/{id} | One entity. |
| GET | /entities/{id}/disclosures | An entity's filing history. |
| GET | /entities/{id}/scorecard | Compliance scorecard (?window_days=365). |
| GET | /threat-actors | List named actors / ransomware groups (offset paging). |
| GET | /threat-actors/{slug} | One actor with attributed incidents. |
| GET | /stats/daily | Filings per day (?days=90). |
| GET | /stats/jurisdictions | Filings by jurisdiction. |
| GET | /stats/compliance_distribution | On-time vs. late notification mix. |
| GET | /stats/late_disclosure_leaderboard | Slowest notifiers. |
| GET | /stats/time_to_disclose_percentiles | Discovery→notification percentiles. |
| GET | /me/api-keys | List your keys. |
| POST | /me/api-keys | Create a key (secret returned once). |
| DELETE | /me/api-keys/{id} | Revoke a key. |
| GET | /me/subscriptions | Your saved-filter email alerts. |
| GET | /health/sources | Per-source ingestion health. |
| GET | /health/extract | Extraction pipeline health. |
| GET | /health/dedup | Incident-dedup health. |
Filtering /disclosures
Combine any of these query params (all optional):
| jurisdiction | e.g. us-ca, us-federal, global (canonical lowercase) |
| source_type | sec_8k · state_ag · ocr · leak_site · press · gdpr_dpa |
| source_category | claim · report · filing · enforcement |
| severity | low · medium · high · critical |
| naics_sector | 2-digit NAICS, e.g. 62 (health care) |
| vertical | repeatable: ?vertical=healthcare&vertical=manufacturing |
| compliance | e.g. ca_60day_late (repeatable) |
| cve | exact CVE-YYYY-NNNN |
| actor_named | ransomware group / actor name |
| search | full-text over victim + summary |
| filed_after / filed_before | ISO-8601. Bounds the REGULATOR's filing date — see Change feed |
| updated_after / updated_before | ISO-8601. Bounds when we created or last revised the record |
| created_after / created_before | ISO-8601. Bounds when we first ingested the record |
| sort | filed_desc (default) · filed_asc · severity · ttd · confidence · updated_asc · created_asc |
| limit | 1–200 (default 50) |
| cursor | opaque keyset cursor from meta.cursor.next |
Errors
Every error — auth, rate limit, not-found, validation, paid-tier — returns the same envelope. Branch on error.code, which is stable, rather than on the human-readable message.
{ "error": { "code": "unauthorized", "message": "missing bearer token" } }401unauthorized— missing / invalid / revoked key403paid_tier_required— endpoint needs a paid plan404not_found— no such resource422validation_error— invalid parameter; adds anerror.fields[]array of{ field, message, type }429rate_limited— seeRetry-After
Bulk export
Paging the whole corpus through /disclosures takes hundreds of sequential requests. To take a copy, stream it instead — one JSON object per line, in exactly the same record shape the list endpoint returns:
curl -H "Authorization: Bearer $DL_KEY" \
"https://api.disclosurelens.com/v1/export/disclosures.ndjson?updated_after=2026-01-01T00:00:00Z" \
> corpus.ndjsonThe last line is a trailer, not a record — an object under the reserved key _export carrying count, truncated, and resume_after. Always check truncated before treating a file as complete: a stream that hit the row cap, or that died mid-transfer, is otherwise indistinguishable from a finished one, because the response has already committed to 200 OK before the first row is written. To continue, pass resume_after back as updated_after.
Accepts updated_after/updated_before, source_type, source_category, jurisdiction, include_leak_site, and max_rows (default 100,000). Records are delivered at least once — deduplicate on id.
There is no CSV endpoint on purpose: the record is deeply nested, and every flattening is a lossy editorial choice we would rather leave to you. Project the columns you want, e.g. jq -r 'select(._export==null)|[.id,.victim_entity.raw_name]|@csv'.
Use & attribution
Public-interest use is free; attribution is requested when you republish. Disclosures are reported as filed, with provenance — the platform draws no legal conclusions. See the methodology and terms.
Ready? Create your API key.