---
title: "List Duplicate Pairs"
method: GET
path: "/api/v1/report-ai/customer-profiles/duplicates"
tags: ["report-ai", "customer-profiles"]
---

# List Duplicate Pairs

`GET /api/v1/report-ai/customer-profiles/duplicates`

Every open duplicate pair in the tenant, strongest evidence first.

Merge used to be reachable only from a contact page you already knew to
open, one candidate at a time. Against 243,256 profiles that is not a
workflow — this is the screen where reconciling actually happens.

## Query parameters

- `cursor` string, nullable

## Response `200`

Successful Response

- DuplicatePairListResponse — A keyset page of open pairs. Keyset, not offset: at 243k profiles offset pagination degrades, and a pair merged from page 1 shifts every later page under the operator. ``next_cursor`` is opaque — pass it back verbatim.
  - `items` DuplicatePairRead[], required
    - `id` string, uuid, required
    - `profile_a` DuplicatePairProfileRead, required — One side of a candidate pair, with just enough to judge it (PR 20).
      - `id` string, uuid, required
      - `customer_name` string, nullable, required
      - `customer_email` string, nullable, required
      - `phone_number` string, nullable, required
      - `identity_kind` string, required
      - `total_investigations` integer, required
      - `linked_bookings` integer, required
      - `first_seen_at` string, date-time, required
      - `last_seen_at` string, date-time, required
    - `profile_b` DuplicatePairProfileRead, required — One side of a candidate pair, with just enough to judge it (PR 20).
      - `id` string, uuid, required
      - `customer_name` string, nullable, required
      - `customer_email` string, nullable, required
      - `phone_number` string, nullable, required
      - `identity_kind` string, required
      - `total_investigations` integer, required
      - `linked_bookings` integer, required
      - `first_seen_at` string, date-time, required
      - `last_seen_at` string, date-time, required
    - `matched_on` string, required
    - `confidence_tier` string, required
    - `signal_details` object, required
    - `last_detected_at` string, date-time, required
    - `survivor_profile_id` string, uuid, required
    - `survivor_reason` string, required
    - `preview` MergePreviewRead, required — What the merge would fold and what it would leave conflicted. Computed from the merge service's own strategy registries, never a second copy of the fold rules — a preview that disagrees with the merge is worse than no preview, because the operator acted on it.
      - `filled` MergeFieldFoldRead[], required
        - `field_name` string, required
        - `survivor_value` unknown
        - `loser_value` unknown
      - `conflicts` MergeFieldFoldRead[], required
        - `field_name` string, required
        - `survivor_value` unknown
        - `loser_value` unknown
      - `stat_deltas` object, required
      - `freed_identifiers` string[], required
  - `next_cursor` string, nullable, required
  - `open_count` integer, required
  - `last_detected_at` string, date-time, nullable, required

## Other responses

- `422` — Validation Error

## Changes

- **2026-09-03** `d65b8fefae34` — 2 info
  - added the optional property `detail/items/ctx` to the response with the `422` status
  - added the optional property `detail/items/input` to the response with the `422` status

[Change history](https://skmtc.dev/getanana/apis/cleon-api/changes/api/v1/report-ai/customer-profiles/duplicates/get.md)

---

[API](https://skmtc.dev/getanana/apis/cleon-api.md) · [All operations](https://skmtc.dev/getanana/apis/cleon-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/getanana/cleon-api/revisions/38166468bfc5/schema)
