# Get loyalty overview

Page through one loyalty summary row per trader in your community, with balances, roles, visits and join date.

- Endpoint: `GET https://api.returning.ai/v1/community-analytics/loyalty-overview`
- Section: Community / Analytics
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `customerSuccess` (Shown in the dashboard as "Customer Success")
- Retries: Read-only; exact retries are safe
- Guide: hand-written
- Verified: live, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/community-analytics/get-loyalty-overview

## When to use this

- Load every trader's XP and coin balances into your data warehouse on a daily schedule.
- Pull the traders who joined your community in a date range, with their loyalty totals.
- Give your CRM a trader's roles, visit count and lifetime coins spent in the store.

**Instead:** Use [Get User Data](https://docs.returning.ai/api-reference/users/get-user-data.md) instead to read one trader's full profile.

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `customerSuccess`

Use a Community API key with `customerSuccess`, shown in the dashboard as Customer Success, and keep it on your server. The key decides the community, so you only ever see your own traders. A key without the permission gets `401`, not `403`. Personal API keys are rejected with `401`.

## Behaviour

Each row is a trader who has an XP record in your community, so traders who have never earned XP may be missing. Rows are sorted by platform user ID. Read every page up to `totalPages`: a page can come back with fewer rows than `limit` before the last page, so a short page doesn't mean you have reached the end.

`from` and `to` filter on the join date in `first_loyalty_login`, and both are inclusive. When either is set, traders with no recorded join date are left out, so the filtered `total` can be lower than the unfiltered one even for a range that covers all time.

Balances are the values at the moment you read them. XP and coin balances are cut, not rounded, to two decimal places.

## Request

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | `integer` | No | Page number, starting at 1. (Whole number, 1 or more; default 1) |
| `limit` | `integer` | No | Rows per page. (Default 100; max 500) |
| `from` | `string` | No | Only traders who joined your community at or after this moment. Traders with no recorded join date are left out whenever `from` or `to` is set. (ISO 8601 date-time in UTC, ending in `Z`) |
| `to` | `string` | No | Only traders who joined your community at or before this moment. (ISO 8601 date-time in UTC, ending in `Z`) |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `customerSuccess`. (`Bearer <API_KEY>`) |

### Example request

```bash
curl --request GET \
  --url 'https://api.returning.ai/v1/community-analytics/loyalty-overview?page=1&limit=100&from=2026-09-01T00:00:00Z' \
  --header 'Authorization: Bearer <API_KEY>'
```

## Response

A `200` returns the page in `data.data` and the totals in `data.pagination`. A page past the last one returns an empty `data.data` with the same `total`. The response holds trader emails, so store it as personal data.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `status` | `string` | always | Result of the request. (`success`) |
| `message` | `string` | always | Human-readable summary. Do not branch on it. |
| `data` | `object` | always | This page of rows and the paging totals. |
| `data.data` | `object[]` | always | One row per trader on this page, ordered by platform user ID. Empty past the last page. |
| `data.data.client_id` | `string` | always | The trader's platform user ID, as a string. |
| `data.data.email` | `string` | always | The trader's email, or `null` when the account has none. Treat it as personal data. (Email format; nullable) |
| `data.data.current_xp_balance` | `number` | always | The trader's XP in your community now, cut to two decimal places. |
| `data.data.current_coin_balance` | `number` | always | The trader's coin balance in your community now, cut to two decimal places. |
| `data.data.lifetime_coins_redeemed` | `number` | always | Coins the trader has spent in your store over all time (price times quantity), not counting failed purchases. |
| `data.data.roles` | `string[]` | always | Titles of the trader's roles in your community. Empty when they hold none. |
| `data.data.login_count` | `integer` | always | Number of days the trader has a recorded visit to your community. (Min 0) |
| `data.data.first_loyalty_login` | `string` | always | When the trader joined your community. `null` when no join date is recorded. This is the date `from` and `to` filter on. (Date-time; nullable) |
| `data.data.last_loyalty_login` | `string` | always | The trader's most recent recorded login, or `null` when none is recorded. (Date-time; nullable) |
| `data.pagination` | `object` | always | Paging totals for every row that matches. |
| `data.pagination.total` | `integer` | always | Traders that match, across all pages. (Min 0) |
| `data.pagination.page` | `integer` | always | The page you asked for. (Min 1) |
| `data.pagination.limit` | `integer` | always | The page size used. (1-500) |
| `data.pagination.totalPages` | `integer` | always | Pages at this `limit`. `0` when nothing matches. (Min 0) |

### Example response (200)

```json
{
  "status": "success",
  "message": "Loyalty overview fetched successfully",
  "data": {
    "data": [
      {
        "client_id": "3247779",
        "email": "trader@example.com",
        "current_xp_balance": 1250,
        "current_coin_balance": 480.5,
        "lifetime_coins_redeemed": 25,
        "roles": ["Member"],
        "login_count": 14,
        "first_loyalty_login": "2026-09-01T08:30:00.000Z",
        "last_loyalty_login": "2026-09-26T09:15:00.000Z"
      }
    ],
    "pagination": {
      "total": 1,
      "page": 1,
      "limit": 100,
      "totalPages": 1
    }
  }
}
```

## Errors

Errors on this endpoint have no machine-readable code; branch on the HTTP status. A validation `400` names the parameter in `detail`. A `401` carries only `message`, or `status` and `message`.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | A query value is invalid: `page` below 1, `limit` outside 1-500, or `from` or `to` not an ISO 8601 date-time ending in `Z` (an offset such as `+08:00` is rejected). `detail` names the parameter. |
| 401 | - | The key is missing (`Invalid token`), unknown (`Invalid API key`) or expired, it's a personal key, or it lacks `customerSuccess` (`Your api key does not have permission to access this action`). Send `Authorization: Bearer <API_KEY>` with a Community API key, and add Customer Success in Settings > Integration > API Keys. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 429 | - | Your community has a [rate limit](https://docs.returning.ai/how-it-works.md#rate-limits) for these analytics endpoints, and this caller has used it up for the current window. Wait until the window resets, as shown in the `RateLimit` header, then retry. |
| 500 | - | The rows could not be read, or the key could not be checked (`Authentication failed`). Retry the same request with exponential backoff. |

**Retries:** This endpoint is read-only and doesn't accept an `Idempotency-Key`. Retrying the exact same request is safe after a network error or a `500`; use bounded exponential backoff. The request limit is off unless Returning.AI has set one for your community. When it is on, it counts requests per community and caller IP address over a fixed window, shared across the community analytics endpoints (including Get phone verification contacts), and responses carry `RateLimit` and `RateLimit-Policy` headers. A `429` means wait for the window to reset, then retry.

## Next step

- [Look up one trader from a row with Get User Data](https://docs.returning.ai/api-reference/users/get-user-data.md): `POST /v1/users/info`. Use `client_id` or `email` to read the trader's full profile and custom fields.
- [Get phone verification contacts](https://docs.returning.ai/api-reference/community-analytics/get-phone-verification-contacts.md): `GET /v1/community-analytics/phone-verification-contacts`. Pull the phone numbers your traders verified, with the same key and permission.
