Retrieve keyword-based leak statistics
Retrieve all leak statistics for the current organization's active keywords.
Returns the same format as /leaks/stats (StatsResponse), computed from
keyword search results on hash_registry (full ingestion history, folded per
hash). The rows matching active keywords (excluding the org's own monitored
root domains) are fed through the same calculate_all_statistics() pipeline.
If the organization has no active keywords, returns an empty stats response.
Parameters:
| Parameter | Default | Description |
|---|---|---|
granularity | day | Time granularity (unused in keyword stats, kept for API consistency) |
start_date / end_date | all-time | Date range filter; an explicit value narrows the window |
Plus all standard DynamicFilters.
Accepts ?tenant_id_override=<child_tenant_id> for an MSSP workspace: the
active keywords, excluded roots and paid-plan check then resolve from the
CHILD tenant instead of the caller's own org (tenant_id_override=all is
rejected with 400, a keyword search has no defined meaning across a whole
portfolio).
Example:
GET /leaks/keyword/stats?start_date=2026-03-01&end_date=2026-03-28Authorization
ApiKeyAuth API key for authentication
In: header
Query Parameters
Time granularity (kept for API consistency)
"day""hour" | "day"When true, include all-time aggregates (all_time_total_leaks, all_time_unique_users, all_time_unique_hosts, all_time_unique_passwords) computed without date filtering. Used by the cockpit landing page.
falseUsername of the leaked user (can be phone, email, ID, ...)
Type of leak (combo, stealer)
Uniq identifier for the concatenation of : username, password and domain
Upload date on stealed, ISO 8601, pattern YYYY-MM-DD
Upload date on the plateform the credential was found, ISO 8601, pattern YYYY-mm-dd
Log date of the device at compromized moment (if applicable, stealer only)
Start date to search from leaks, format: YYYY-mm-dd (default: today - 14days at 0:00am)
End date to search leaks from, format YYYY-mm-dd (default: today)
URL of the leaked data
FQDN of the leaked data
Local part of the username section (if applicable, email only)
Protocol identified (if applicable)
Email domain to filter on (if multiple email domains declared)
Root domain to filter on (if multiple root domains declared)
Machine ID (if applicable, stealer only)
Computer name (if applicable, stealer only)
Hardware ID (if applicable, stealer only)
Machine user (if applicable, stealer only)
IP address (if applicable, stealer only)
Country (if applicable, stealer only)
Software (if applicable, stealer only)
Stealer name (if applicable)
Opaque source label(s) to filter on, of the form Source-<6 hex> (as returned by the sources breakdown). The raw telegram_channel is never exposed: the label is matched back against the same MD5-prefix expression server-side.
Keyword to filter on (only active keywords for tenant)
Match type for leaks_matched table (root_domain or email_domain)
Root domains to exclude (NOT IN filter)
Email domains to exclude (NOT IN filter)
Domains to exclude (NOT IN filter)
Types to exclude
Software to exclude
Stealer names to exclude
Protocols to exclude
Countries to exclude
Excludes the credentials whose machine ID is one of these values (NOT IN filter, stealer only)
Excludes the credentials whose computer name is one of these values (NOT IN filter, stealer only)
Excludes the credentials whose hardware ID is one of these values (NOT IN filter, stealer only)
Excludes the credentials whose machine user is one of these values (NOT IN filter, stealer only)
Excludes the credentials whose IP address is one of these values (NOT IN filter, stealer only)
Show only hashes whose first appearance (min upload_stealed) falls on this exact date. Format: YYYY-MM-DD.
Show only hashes whose first appearance (min upload_stealed) is on or after this date. Format: YYYY-MM-DD.
Limit result length
Minimum number of sources (source_count >= N)
Response Body
application/json
application/json
curl -X GET "https://api.stealed.io/leaks/keyword/stats"null{
"detail": [
{
"loc": [
"string"
],
"msg": "string",
"type": "string"
}
]
}Retrieve every occurrence (source + file) for a specific leak hash GET
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 returned (`channel`, `file_name`, `source_type`) only to a USER SESSION of an organisation holding the `source_reveal` feature (on by default for a workspace, off for a client), never to an API key. Otherwise 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:** ```json { "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:** ```bash GET /leaks/details/occurrences?hash=5d41402abc4b2a76b9719d911017c592 ```
Paginated keyword search results GET
Retrieve paginated leak details matching the organization's active keywords. Returns the same paginated envelope as `/leaks/details`. The row shape differs slightly: keyword rows are folded per hash from `hash_registry`, so they carry `local_part` (derived) and no per-credential value sets (`softwares`, `ip_addresses`, ...). This endpoint has an additional `matched_keyword` field indicating which keyword matched each row. Searches `hash_registry` (full ingestion history) for rows where the keyword's tokens appear in `domain`/`host` (same predicate as the `idx_kw_text` index), for any active keyword, excluding the org's own monitored root domains. If the organization has no active keywords, returns an empty paginated response. **Parameters:** | Parameter | Default | Description | |-----------|---------|-------------| | `page` | 1 | Page number | | `page_size` | 50 | Items per page (max 200) | | `search` | - | Full-text search across username, host, domain, root_domain | | `sort_by` | `last_seen` | Sort order: `last_seen` (most recent first) | | `start_date` / `end_date` | all-time | Date range filter; an explicit value narrows the window | Plus all standard DynamicFilters. Accepts `?tenant_id_override=<child_tenant_id>` for an MSSP workspace: the active keywords, excluded roots and paid-plan check then resolve from the CHILD tenant instead of the caller's own org (`tenant_id_override=all` is rejected with 400). A Référencé child's rows render with `username` masked (`x***@domain`, same redaction as `/leaks/details`); an Activé child's `username` renders in clear, `password` stays masked either way. **Response Format:** ```json { "data": [ { "username": "user@example.com", "password": "a91f3c8b2d47", "password_masked": true, "type": "Stealer", "first_seen": "2026-01-20", "last_seen": "2026-03-15", "host": "acme-corp-recrute.talent-soft.com", "domain": "acme-corp-recrute.talent-soft.com", "local_part": "user", "protocol": "https", "email_domain": "example.com", "root_domain": "talent-soft.com", "country": "FR", "software": "chrome, profile: 0", "stealer_name": "RedLine", "hash": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "machine_id": "DESKTOP-ABC1234", "computer_name": "LAPTOP-XYZ", "hardware_id": "hwid-1234-5678", "machine_user": "john.doe", "ip_address": "192.168.1.10", "source_count": 3, "matched_keyword": "acme-corp" } ], "total": 12345, "page": 1, "page_size": 50, "total_pages": 247 } ``` **Examples:** ```bash # Basic paginated request GET /leaks/keyword/search?page=1&page_size=50 # With search filter GET /leaks/keyword/search?search=admin&page=1&page_size=50 # With date range GET /leaks/keyword/search?start_date=2026-03-01&end_date=2026-03-28 ```