---
title: "Linked-account clusters"
method: GET
path: "/stats/risk/clusters"
tags: ["Stats"]
---

# Linked-account clusters

`GET /stats/risk/clusters`

## Request

- Base URL: `https://api.nippy.host`
- URL: `https://api.nippy.host/stats/risk/clusters`
- Auth: `bearer` (a scheme the document does not define)

Admin-only. Clusters of accounts joined by shared evidence (device, real mailbox, IP within 24 hours, private email domain, numbered address stem, same uploaded document), highest risk first, each with its evidence (and which members share each link), member rollups and verdict state. Filter by `level`, `status` or the `member` it holds.

## Query parameters

- `level` 'high' | 'medium' | 'low'
- `status` 'open' | 'contacted' | 'dismissed' | 'banned'
- `member` string, uuid
- `page` number
- `pageSize` number

## Response `200`

- APIPaginatedRiskClustersResponse
  - `data` APIRiskCluster[], required
    - `id` string, required: Earliest-signup member id; stable while that member stays.
    - `size` number, required
    - `maxScore` number, required
    - `level` 'high' | 'medium' | 'low', required
    - `firstSignupAt` string, required
    - `lastSignupAt` string, required
    - `status` 'open' | 'contacted' | 'dismissed' | 'banned', required: `banned` when every member is banned, else `contacted` while a member is in an open duplicate-account case (ADR 0057), else `dismissed` while a review covers every member, else `open`.
    - `duplicateCaseId` string, nullable, required: The open duplicate-account case that holds a member; null when none.
    - `review` APIRiskClusterReview, required
      - `id` string, required
      - `note` string, nullable, required
      - `reviewedAt` string, required
      - `reviewedByName` string, nullable, required
      - `newUserIds` string[], required: Members that were not part of the review. Empty while the review covers the cluster; otherwise the cluster is open again.
    - `evidence` APIRiskEvidence[], required: Widest link first. Each link names the members it joins.
      - `kind` 'device' | 'mailbox' | 'ip_burst' | 'email_domain' | 'numbered_stem' | 'same_file' | 'ip_prefix_day', required
      - `value` string, required: What is shared: a device, IP, domain, file…
      - `users` number, required: Accounts sharing it.
      - `userIds` string[], required: The cluster members that share it.
    - `members` APIRiskClusterMember[], required: Members who are not banned first, then by score.
      - `userId` string, required
      - `email` string, required
      - `signedUpAt` string, required
      - `emailVerified` boolean, required
      - `bannedAt` string, nullable, required
      - `score` number, required
      - `level` 'high' | 'medium' | 'low', required
      - `sites` number, required: Sites the user created.
      - `liveSites` number, required
      - `recentSites` APIRiskMemberSite[], required: The newest 5 sites the user created, newest first.
        - `id` string, required: Site id (UUID).
        - `subdomain` string, required
        - `url` string, required: Public URL of the site.
        - `status` 'pending' | 'awaiting_verification' | 'live' | 'inactive' | 'suspended', required
        - `file` string, nullable, required: First file name of the newest finished upload.
  - `page` number, required
  - `pageSize` number, required
  - `total` number, required
  - `totalPages` number, required

## Other responses

- `403`: Admin access required

---

[API](https://skmtc.dev/nippy/apis/nippy-backend-api.md) · [All operations](https://skmtc.dev/nippy/apis/nippy-backend-api/llms.txt) · [OpenAPI document](https://skmtc.dev/nippy/apis/nippy-backend-api/revisions/7ab23f040a55?raw)
