Retrieve every occurrence (source + file) for a specific leak hash

GET
/leaks/details/occurrences

Retrieve every distinct detection of a deduplicated credential — one row per (telegram_channel, file_name) pair — read directly from the tenant-routed leaks_matched_* table (no separate occurrence store, no JOIN). The row count EQUALS the occurrences figure shown in the /leaks/details list (same grain), across ALL types (Combo and Stealer).

Use this after /leaks/details to drill into a credential and see each detection. The proprietary telegram_channel and file_name are NEVER returned: the source is replaced by a stable opaque id (opaque_source_id, "Source #" + md5(channel)[:6]) and, when the detection carried a published file, an opaque lot id (opaque_lot_id, md5(file_name)[:6]). Backfilled / historical detections have no file, hence no lot. computer_name is never selected nor returned.

date is max(upload_stealed) for that detection, i.e. when Stealed ingested it — the same clock as the list's first_seen / last_seen, so the two views can never contradict each other. It is deliberately NOT the source's own publication timestamp (upload_date, the Telegram message date), which runs hours to days ahead of ingestion and is unset on some rows.

Parameters:

  • hash (required): The leak hash from the /leaks/details response
  • tenant_id_override (optional): workspace (MSSP) only — inspect a child tenant's detail; validated against the caller's portfolio (403/404 otherwise)

Tenant isolation: the query is scoped WHERE tenant_id = <server-derived> AND hash = <param>, so a hash belonging to another tenant returns nothing.

Response:

{
  "total_occurrences": 2,
  "occurrences": [
    {"date": "2026-06-01T10:30:00", "source": "Source #a1b2c3", "lot": "d4e5f6",
     "type": "Stealer", "country": "FR", "stealer_name": "RedLine",
     "software": "Chrome", "ip_address": "1.2.3.4", "machine_user": "admin",
     "machine_id": "…", "protocol": "https"},
    {"date": "2026-05-20T08:00:00", "source": "Source #99aa11", "lot": null,
     "type": "Combo", "country": null, "stealer_name": null, "software": null,
     "ip_address": null, "machine_user": null, "machine_id": null,
     "protocol": null}
  ]
}

Example:

GET /leaks/details/occurrences?hash=5d41402abc4b2a76b9719d911017c592

Authorization

ApiKeyAuth
X-API-Key<token>

API key for authentication

In: header

Query Parameters

hash*Hash

The leak hash to look up

Response Body

application/json

application/json

curl -X GET "https://api.stealed.io/leaks/details/occurrences?hash=string"
{
  "total_occurrences": 0,
  "occurrences": [
    {
      "date": "2019-08-24T14:15:22Z",
      "source": "string",
      "lot": "string",
      "type": "string",
      "country": "string",
      "stealer_name": "string",
      "software": "string",
      "ip_address": "string",
      "machine_user": "string",
      "machine_id": "string",
      "protocol": "string"
    }
  ]
}
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

Reveal one credential's password, and record that it was read GET

The password behind a hash, in clear, for a credential belonging to the calling organisation. Every other endpoint returns it masked. This one does not, which is why it is the one act in the product that writes to the access journal as `sensitive`: a client asking a year later who read his collaborators' passwords gets an answer with a name, a time and an IP. Scoped like every other read: a hash belonging to another organisation returns 404, never someone else's secret.

Paginated, count-sorted distinct-value breakdown for a KPI tile drill-down GET

Drill into a KPI tile (Total Users / Total Domains / Total Sources) with a server-paginated, count-sorted list of distinct values. Reuses the exact same tenant scoping as `GET /leaks/details`: results come from the tenant-routed `leaks_matched_*` table, restricted to the org's active domains, plus all standard `DynamicFilters` (dates, type, domain, not_domain, country, etc.). **Parameters:** | Parameter | Default | Description | |-----------|---------|-------------| | `field` | *required* | `username`, `domain`, or `source` | | `identifier_column` | *required* | `root_domain`, `email_domain`, `username`, or `all` | | `page` | 1 | Page number | | `page_size` | 50 | Items per page (max 200) | | `search` | - | Substring filter on the returned `value` | | `leak_mode` | `new` | `new` (first-seen in period) or `all` (any occurrence in period) -- must match the `/leaks/stats` KPI tile being drilled into | `domain` only counts round-trippable hostnames (drops base64 "email-blob" parsing garbage), matching the Insights domain pie. `source` emits an opaque label (`Source-<md5 prefix>`) -- the real channel name never leaves the API. **Response:** ```json { "data": [{"value": "admin@example.com", "count": 42}], "total": 118, "page": 1, "page_size": 50, "total_pages": 3 } ``` `total` is the number of distinct `field` values matching the filters -- the same figure as the corresponding `/leaks/stats` KPI (`unique_usernames` / `unique_domains` / `unique_sources`). **Example:** ```bash GET /leaks/breakdown?field=domain&identifier_column=root_domain&page=1&page_size=50 ```