---
title: "List Customer Profiles"
method: GET
path: "/api/v1/report-ai/customer-profiles"
tags: ["report-ai", "customer-profiles"]
---

# List Customer Profiles

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

List customer profiles with pagination, search, and filters.

## Query parameters

- `search` string, nullable
- `loyalty_tier` string, nullable
- `requires_attention` boolean, nullable
- `source` string, nullable — Filter to contacts asserted by this source key.
- `sort_by` 'last_seen_at' | 'customer_name' | 'created_at' | 'total_investigations'
- `sort_order` 'asc' | 'desc'
- `page` integer
- `per_page` integer

## Response `200`

Successful Response

- CustomerProfileListResponse — Paginated customer profile list response.
  - `items` CustomerProfileRead[], required
    - `id` string, uuid, required
    - `tenant_id` string, uuid, required
    - `customer_email` string, nullable, required
    - `customer_name` string, nullable, required
    - `phone_number` string, nullable, required
    - `total_investigations` integer, required
    - `complaints_count` integer, required
    - `last_sentiment` string, nullable, required
    - `total_revenue_cents` integer, nullable, required
    - `average_booking_value_cents` integer, nullable, required
    - `loyalty_tier` string, nullable, required
    - `country` string, nullable
    - `identity_kind` 'person' | 'shared_inbox' | 'unclassified' | 'stay_guest' — Whether a customer profile stands for one person. ``SHARED_INBOX`` marks an address that books on behalf of many different guests (tour operator, bed bank, travel agent). Such a profile is a real counterparty but NOT a person: it must never receive guest-directed messaging and must not appear in the contacts list as a customer. Classified by ``app.bookings.private.identity_durability``; see ``app.bookings.management.classify_shared_inboxes``. ``STAY_GUEST`` marks the guest of one reservation, known by their stay rather than by their identity — used when a reservation carries no durable identifier and a person cannot honestly be inferred from a name. Keyed on ``('stay', 'episode_id', <uuid>)``, and holds NULL email and NULL phone **always**. Like ``SHARED_INBOX`` it is a real record and not a contact, and is held out of the roster, campaigns, segments and duplicate detection by the same mechanism.
    - `requires_attention` boolean
    - `attention_reason` string, nullable
    - `business_id` string, uuid, nullable
    - `first_seen_at` string, date-time, required
    - `last_seen_at` string, date-time, required
    - `sources` SourceBadgeRead[]
      - `source_key` string, required
      - `label` string, required
      - `badge_hue` string, required
      - `external_id` string, nullable
      - `first_seen_at` string, date-time, required
      - `last_seen_at` string, date-time, required
    - `attention` AttentionFlag[]
      - `kind` 'open_duplicate' | 'field_conflict' | 'relay_identity' | 'no_source', required
      - `detail` string, required
  - `total` integer, required
  - `page` integer, required
  - `per_page` integer, required

## Other responses

- `422` — Validation Error

---

[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/3ad158624c53/schema)
