# Get User Milestones

Check which of the milestones you name a trader has cleared, stage by stage, using their email.

- Endpoint: `POST https://api.returning.ai/v1/users/milestones/`
- Section: Users and data / Users
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `getUserData` (Shown in the dashboard as "Get User Data")
- Retries: Read-only; exact retries are safe
- Guide: hand-written
- Verified: code, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/users/get-user-milestones

## When to use this

- Show a trader's milestone progress in your own platform, such as which deposit tiers they've reached.
- Decide whether a trader qualifies for an offer that depends on a milestone you run in Returning.AI.
- Support asks whether a trader has cleared a stage.

**Instead:** Use [Get User Data](https://docs.returning.ai/api-reference/users/get-user-data.md) instead to read a trader's XP, coins, roles and custom field values.

## Authentication

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

This endpoint needs `getUserData`, shown in the dashboard as Get User Data. Use a Community API key and keep it on your server. The key decides the community: the trader and the milestones are looked up in that community only.

## Behaviour

The trader is looked up first, then the milestones, so an unknown email returns `404` even when the IDs are also wrong. One unknown ID fails the whole request with `400`: there's no partial result. A disabled milestone counts as unknown. A trader you removed with Manage User Account is still found by their email.

`cleared` on a milestone and on each stage reflects the trader's current awards; an award that was taken back doesn't count. Disabled stages are left out of `stages`.

There's no endpoint that lists milestone IDs. Ask your Returning.AI contact for the IDs of the milestones you want to check.

The published path ends in `/`, and `/v1/users/milestones` without it works the same way.

## Request

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `getUserData`. (`Bearer <API_KEY>`) |
| `Content-Type` | `string` | Yes | Request body format. (`application/json`) |

### Body

Send JSON with `email` and `milestones`. `milestones` is one string, such as `"<milestoneId1>, <milestoneId2>"`, not an array.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | `string` | Yes | The trader's email. Not case-sensitive. A platform user ID or username isn't accepted. (Valid email) |
| `milestones` | `string` | Yes | The IDs of the milestones to check, in one string, separated by commas. Spaces around each ID are ignored. (Non-empty string, not an array) |

### Example request

```bash
curl --request POST \
  --url https://api.returning.ai/v1/users/milestones/ \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "email": "trader@example.com",
    "milestones": "<milestoneId1>, <milestoneId2>"
  }'
```

## Response

A `200` returns one entry per milestone ID, in the order you sent them. Branch on the HTTP status, never on `message`. The body has no `code`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `status` | `string` | always | Result of the request. (`success`) |
| `data` | `object` | always | The trader's progress. |
| `data.milestones` | `object[]` | always | One entry per ID you sent, in the order you sent them. |
| `data.milestones.id` | `string` | always | The milestone ID, as you sent it. |
| `data.milestones.name` | `string` | always | The milestone's name. |
| `data.milestones.cleared` | `boolean` | always | `true` when the trader holds the award for the whole milestone. |
| `data.milestones.stages` | `object[]` | always | The milestone's enabled stages, in their set order. Empty when it has none. |
| `data.milestones.stages.name` | `string` | always | The stage's name. |
| `data.milestones.stages.cleared` | `boolean` | always | `true` when the trader holds the award for this stage. |

### Example response (200)

```json
{
  "status": "success",
  "data": {
    "milestones": [
      {
        "id": "<milestoneId1>",
        "name": "Balance boosters",
        "cleared": false,
        "stages": [
          {
            "name": "USD $0 to $2,000",
            "cleared": true
          },
          {
            "name": "USD $2,000 to $5,000",
            "cleared": false
          }
        ]
      },
      {
        "id": "<milestoneId2>",
        "name": "First trade",
        "cleared": true,
        "stages": [
          {
            "name": "Open a first trade",
            "cleared": true
          }
        ]
      }
    ]
  }
}
```

## Errors

The 401, 403 and 404 `COMMUNITY_NOT_FOUND` errors, and `500 AUTHENTICATION_FAILED`, come from the API key check and carry the code in `meta.code`. Other errors from this endpoint have no `code`, so branch on the HTTP status and read `message`.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | There is no `code`. `message` `Milestone not found: <ids>` means no enabled milestone in your community has those IDs; it lists every ID that didn't match, so check them and send the request without them. A validation error means `email` isn't a valid email, or `milestones` is missing, empty, only commas, or an array; `detail` names the field. |
| 401 | `AUTHENTICATION_REQUIRED` | The key is missing, invalid or expired. Send `Authorization: Bearer <API_KEY>` with a current Community API key. |
| 403 | `API_KEY_PERMISSION_DENIED` | The key lacks `getUserData`, or it is a personal key. Use a Community API key and add the permission in Settings > Integration > API Keys. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | - | `User not found`: no trader in your community has this email. There is no `code`. Check the email, or look the trader up with Get User Data. |
| 404 | `COMMUNITY_NOT_FOUND` | The community this key belongs to no longer exists. Contact Returning.AI support. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 500 | - | The progress could not be read. There is no `code`; `detail` holds the reason. Retry the same request with exponential backoff. |
| 500 | `AUTHENTICATION_FAILED` | The key could not be checked. Retry with backoff; nothing was read. |

**Retries:** This endpoint only reads, so retrying the exact same request is safe after a network error or a `500`. Use bounded exponential backoff. Don't retry a `400` or `404` without changing the body. Over the [rate limit](https://docs.returning.ai/how-it-works.md#rate-limits), requests return `429`; wait for the window to reset, then retry.

## Next step

- [Get User Data](https://docs.returning.ai/api-reference/users/get-user-data.md): `POST /v1/users/info`. Look the trader up first if you only have their platform user ID or broker customer ID; this endpoint needs their email.
- [See what else changed for the trader with Get user field histories for a specific user](https://docs.returning.ai/api-reference/user-fields-user-field-history/get-user-field-histories-for-a-specific-user.md): `GET /v1/communities/{communityId}/users/{userId}/user-field-histories`.
