# Update User XP and Currency

Add to, subtract from or set one trader's XP, coin balance or both, and get back the new balances.

- Endpoint: `POST https://api.returning.ai/v1/users/update-xp-currency`
- Section: Users and data / Users
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `manageUser` (Shown in the dashboard as "Delete/Restore User")
- Retries: No Idempotency-Key; read back before retrying
- Guide: hand-written
- Verified: live, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/users/update-user-xp-and-currency

## When to use this

- Credit coins or XP for something your platform tracks, such as a funded account or a completed course.
- Correct a trader's balance after a support case.
- Set a balance to an exact figure with `overwrite`, for example when you migrate from another loyalty system.

**Instead:** Use [Bulk update users from CSV](https://docs.returning.ai/api-reference/bulk-operations/bulk-update-users-from-csv.md) instead to change balances for many traders in one job.

**Not repeat-safe:** `add` and `subtract` apply again on every call. After a timeout, read the balances before you send the same request.

## Authentication

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

This endpoint needs `manageUser`, the permission shown in the dashboard as Delete/Restore User. The key limits every change to traders in its own community. Keep it on your server.

## Behaviour

Each change is saved to the trader's XP or coin history, and the response returns the new balance. `overwrite` sets the balance to exactly `value`, and later changes count from there. `subtract` has no floor, so it can take a balance below zero. A coin earning cap set for your community does not limit changes made here.

When you send both `xp` and `currency`, XP is saved first and coins second. If the request fails in between, the XP change stays.

## Request

### Headers

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

### Body

Send `identifier` and at least one of `xp` or `currency`. The two can use different actions. Values are JSON numbers of 0 or more; `subtract` a positive number to lower a balance.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `object` | Yes | Selects the trader to change. |
| `identifier.type` | `string` | Yes | How `value` identifies the trader. `custom_user_identifier` uses your community's active identifier field, usually the broker customer ID. (`platform_user_id`, `username`, `email`, `custom_user_identifier`) |
| `identifier.value` | `string \| number` | Yes | The identifier value to match. Emails and usernames are not case-sensitive. (Non-empty string, or a number) |
| `xp` | `object` | No | The XP change. Send `xp`, `currency` or both. |
| `xp.action` | `string` | Yes | `add` and `subtract` change the balance by `value`. `overwrite` sets the balance to exactly `value`. (`add`, `subtract`, `overwrite`) |
| `xp.value` | `number` | Yes | The amount. Send a JSON number, not a string. (Number, 0 or more) |
| `currency` | `object` | No | The coin change. Send `xp`, `currency` or both. |
| `currency.action` | `string` | Yes | `add` and `subtract` change the balance by `value`. `overwrite` sets the balance to exactly `value`. (`add`, `subtract`, `overwrite`) |
| `currency.value` | `number` | Yes | The number of coins. Send a JSON number, not a string. (Number, 0 or more) |

### Example request

```bash
curl --request POST \
  --url https://api.returning.ai/v1/users/update-xp-currency \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "identifier": {
      "type": "platform_user_id",
      "value": "3247779"
    },
    "xp": {
      "action": "add",
      "value": 100
    },
    "currency": {
      "action": "add",
      "value": 500
    }
  }'
```

## Response

A `200` means every change you sent is saved. `data.xp` and `data.currency` are the balances after the change, and each is present only when you sent it. Branch on the HTTP status and `code`, never on `message`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `status` | `string` | always | Result of the request. (`success`) |
| `code` | `string` | always | Machine-readable result code. (`USER_XP_CURRENCY_UPDATED`) |
| `message` | `string` | always | Human-readable summary. Do not branch on it. |
| `data` | `object` | always | The trader's new balances. |
| `data.userId` | `integer` | always | Platform user ID. This endpoint returns it as a number; store it as a string. (Integer) |
| `data.xp` | `number` | when xp sent | The trader's XP balance after the change. |
| `data.currency` | `number` | when currency sent | The trader's coin balance after the change. Get User Data calls the same balance `coins`. |

### Example response (200)

```json
{
  "status": "success",
  "code": "USER_XP_CURRENCY_UPDATED",
  "message": "User XP and currency updated successfully",
  "data": {
    "userId": 3247779,
    "xp": 100,
    "currency": 500
  }
}
```

## Errors

Errors from the API key check, such as 401 and 403, carry the code in `meta.code`; other JSON errors carry it in `code`. A validation `400` has no `code`; its `detail` names each invalid field. Two malformed bodies return an HTML `500` instead of JSON, as listed below.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | A field is missing or invalid, such as an unknown `identifier.type`, an `action` other than `add`, `subtract` or `overwrite`, a negative value, or a number sent as a string. `detail` names the field. Nothing was written. |
| 500 | - | The body has neither `xp` nor `currency`, or has a top-level field this endpoint does not accept (such as `updates`). The response is an HTML error page, not JSON. Nothing was written; fix the body. |
| 401 | `AUTHENTICATION_REQUIRED` | The key is missing, invalid or expired. Send `Authorization: Bearer <API_KEY>` with a current key. |
| 403 | `API_KEY_PERMISSION_DENIED` | The key is valid but lacks `manageUser`. 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 matches the identifier. Check the type and value. |
| 400 | `ACTIVE_IDENTIFIER_NOT_CONFIGURED` | You sent `custom_user_identifier`, but your community has no single active identifier field. Use the platform user ID, email or username instead. |
| 404 | `COMMUNITY_NOT_FOUND` | The key's community no longer exists. Use a key from an active community. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 500 | `USER_XP_CURRENCY_UPDATE_FAILED` | The update failed unexpectedly, or a `custom_user_identifier` lookup was not ready or matched more than one trader. If you sent both balances, XP may already be saved. Read the balances with Get User Data before you retry. |
| 500 | `AUTHENTICATION_FAILED` | The key could not be checked. Retry the same request with backoff. |

**Retries:** This endpoint does not accept an `Idempotency-Key`, and `add` and `subtract` apply again on every call. After a timeout or a `500`, read the balances with Get User Data and send the change again only if it is missing. `overwrite` is safe to repeat. 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`. Read back the trader's XP and coins. The coin balance is called `coins` there.
