# Update a user field value

Set, increase or decrease one trader's value for one user field, and record the change in that field's history.

- Endpoint: `POST https://api.returning.ai/v1/communities/{communityId}/users/{userId}/user-fields/{fieldIdOrName}/histories`
- Section: Users and data / Field history
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `userFields` (Shown in the dashboard as "User Fields")
- Retries: Send an Idempotency-Key; reuse it to retry
- Guide: hand-written
- Verified: live, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/user-fields-user-field-history/update-a-user-field-value

## When to use this

- A trader's status changes on your platform, such as passing KYC, and your rewards community should know.
- You sync a running metric, such as deposits or challenges purchased, by adding to it after each event.
- You set the broker identifier for a trader who doesn't have one yet.

**Instead:** Use [Update User Data](https://docs.returning.ai/api-reference/users/update-user-data.md) instead to replace a trader's broker identifier once it is set.

## Authentication

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

Use a Community API key with the `userFields` permission, and keep it on your server. `communityId` must be the ID of the community that owns the key.

## Behaviour

Each successful write adds one entry to the field's history and updates the trader's current value, which Get User Data returns when you list the key in `customFields`. Milestones, referral conditions and data workflows that use the field react after the response.

- `increase` and `decrease` start from the trader's latest total for the field, or from `0` if they have none; the field's default is not used. Totals can go below zero.
- Numerical writes to the same trader and custom field run one at a time. A write that waits more than 5 seconds returns `409 USER_FIELD_MUTATION_IN_PROGRESS` and changes nothing.
- The built-in `total_xp` and `total_coins` fields change the trader's XP or coin balance: `overwrite` sets it, `increase` and `decrease` adjust it.
- If the field is your broker identifier field, each value can belong to one trader only, and once a trader has one it can only be replaced with Update User Data.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `communityId` | `string` | Yes | Your community's ID. It must be the community that owns your API key. (24 hex characters) |
| `userId` | `string` | Yes | The trader. Use the platform user ID, or their internal record ID, email or username. URL-encode an email. (Platform user ID recommended) |
| `fieldIdOrName` | `string` | Yes | The field key, or the definition's `_id`. Not the display name. (Field key or 24-character ID) |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `userFields`. (`Bearer <API_KEY>`) |
| `Idempotency-Key` | `string` | No | Optional, and strongly recommended. One value per logical change, such as your event ID plus the field key. Reuse it only to retry that exact request. Kept for 24 hours. (1-200 chars; `^[!-~]{1,200}$`) |
| `Content-Type` | `string` | Yes | Request body format. (`application/json`) |

### Body

`value` must suit the field's type. Values are not converted between types except as listed:

- `single-line-text`: a string of 1-255 characters with no line breaks.
- `multi-line-text`: any string, including an empty one.
- `numerical`: a number, or a string holding one, such as `"12.5"`. The only type that takes `increase` and `decrease`.
- `boolean`: `true`, `false`, `"true"`, `"false"`, `1` or `0`.
- `date`: `YYYY-MM-DD`.
- `time`: `HH:mm:ss`, 24-hour.
- `date-time`: `YYYY-MM-DDTHH:mm:ss`, optionally ending in `Z` or an offset such as `+08:00`. Milliseconds are rejected, so send `2026-09-26T08:30:00Z`, not `2026-09-26T08:30:00.000Z`.
- `single-select-dropdown` and `multi-select-dropdown`: not supported yet.

`false`, `0` and an empty multi-line string are real values and are kept.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `value` | `any` | Yes | The new value, or for `increase` and `decrease` the amount. Must suit the field's type; see Request body. (Matches the field type) |
| `action` | `string` | No | `overwrite` sets the value. `increase` and `decrease` add or subtract, on numerical fields only. (`overwrite`, `increase`, `decrease`; default `overwrite`) |

### Example request

```bash
curl --request POST \
  --url https://api.returning.ai/v1/communities/66f000000000000000000010/users/3247779/user-fields/kycstatus/histories \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: write-field-<unique-id>' \
  --data '{
    "value": "verified",
    "action": "overwrite"
  }'
```

## Response

A `201` means the value is saved. Branch on the HTTP status and `meta.code`, never on `message`. `storedValue` is the trader's value now; for numerical fields it is the new total, also in `meta.sum`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `meta` | `object` | always | - |
| `meta.status` | `string` | always | `success` |
| `meta.statusCode` | `integer` | always | `201` |
| `meta.code` | `string` | always | Machine-readable result code. (`USER_FIELD_VALUE_UPDATED`) |
| `meta.updated` | `boolean` | always | Always `true`. |
| `meta.idempotencyPersistence` | `string` | when degraded | `degraded` when the write was saved but a retry with the same key may not replay it. Treat the write as done; read it back before any other change. |
| `meta.idempotentReplay` | `boolean` | on replay | `true` when this repeats the first response for your `Idempotency-Key`. Nothing new was written. |
| `meta.sum` | `number` | numerical fields | The trader's new total. Numerical fields only. |
| `message` | `string` | always | Human-readable summary. Do not branch on it. |
| `data` | `object` | always | The history entry this write created. |
| `data._id` | `string` | always | ID of the new history entry. (`^[0-9a-fA-F]{24}$`) |
| `data.communityID` | `string` | always | Your community ID. (`^[0-9a-fA-F]{24}$`) |
| `data.userID` | `string` | always | The trader's internal record ID. (`^[0-9a-fA-F]{24}$`) |
| `data.userNumericID` | `integer` | always | The trader's platform user ID, as a number here. Store it as a string. |
| `data.fieldID` | `string` | always | The definition's `_id`. (`^[0-9a-fA-F]{24}$`) |
| `data.fieldName` | `string` | always | The field key. |
| `data.fieldType` | `string` | always | The field type. (`single-line-text`, `multi-line-text`, `numerical`, `date`, `time`, `date-time`, `boolean`, `single-select-dropdown`, `multi-select-dropdown`) |
| `data.value` | `any` | always | The value or amount you sent. |
| `data.storedValue` | `any` | always | The trader's value after this write. For numerical fields, the new total. |
| `data.updatedValue` | `any` | always | Same as `storedValue`. |
| `data.action` | `string` | always | The action applied. (`overwrite`, `increase`, `decrease`) |
| `data.createdAt` | `string` | always | When the change was recorded. (Date-time) |
| `data.updatedAt` | `string` | always | When the entry last changed. (Date-time) |
| `data.updated` | `boolean` | always | Always `true`. |

### Example response (201)

```json
{
  "meta": {
    "status": "success",
    "statusCode": 201,
    "code": "USER_FIELD_VALUE_UPDATED",
    "updated": true
  },
  "message": "Update user field value api success.",
  "data": {
    "_id": "66f000000000000000000511",
    "communityID": "66f000000000000000000010",
    "userID": "<userObjectId>",
    "userNumericID": 3247779,
    "fieldID": "66f000000000000000000510",
    "fieldName": "kycstatus",
    "fieldType": "single-line-text",
    "value": "verified",
    "storedValue": "verified",
    "updatedValue": "verified",
    "action": "overwrite",
    "createdAt": "2026-09-26T08:30:00.000Z",
    "updatedAt": "2026-09-26T08:30:00.000Z",
    "updated": true
  }
}
```

## Errors

Every JSON error carries its code in `meta.code`. The exception is a request with no `Authorization` header and a JSON body: it returns `400` with a non-JSON body instead of `401`. On a `500`, `meta.mutationState` says what happened: `rolled-back` means nothing was kept, `ambiguous` means the write may have been kept.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | No `Authorization` header was sent. With a JSON body this returns `400` with a non-JSON body (`Request body must encrypted`), not `401`. Send `Authorization: Bearer <API_KEY>`. |
| 400 | `INVALID_FIELD_VALUE` | `value` is missing or doesn't suit the field's type, or the field is a select-dropdown, which can't be written yet. Nothing was written. `detail` gives the reason. |
| 400 | `INVALID_FIELD_ACTION` | `action` isn't `overwrite`, `increase` or `decrease`, or you sent `increase` or `decrease` to a field that isn't numerical. |
| 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` must be 1-200 visible ASCII characters with no spaces. |
| 401 | `AUTHENTICATION_REQUIRED` | The key is invalid or expired, or no key was sent with an empty body. Send `Authorization: Bearer <API_KEY>` with a current Community API key. |
| 403 | `API_KEY_PERMISSION_DENIED` | The key lacks `userFields`. Add the permission in Settings > Integration > API Keys. |
| 403 | `API_KEY_COMMUNITY_MISMATCH` | `communityId` is not the community that owns your key. Use your own community's ID. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `USER_NOT_FOUND` | No active trader matches `userId`. Check it, or create the trader first. A 12-character username or email can't be matched; use the platform user ID. |
| 404 | `USER_FIELD_NOT_FOUND` | No field in your community has this key or ID. Check the key with List user field definitions. |
| 403 | `USER_NOT_IN_COMMUNITY` | The trader exists but isn't a member of your community. Check the ID, or add them to the community first. |
| 400 | `USER_IDENTIFIER_UPDATE_REJECTED` | The field is your broker identifier field and this write broke its rules: only `overwrite` is allowed, the value can't be empty, and a trader's identifier can't be changed here once set (use Update User Data). Rarely it is a `500` after a failed save; nothing was kept, so send it again. |
| 409 | `USER_IDENTIFIER_CONFLICT` | Another trader already has this identifier value. Nothing was written. Reconcile the two accounts first. |
| 409 | `IDEMPOTENCY_KEY_CONFLICT` | This `Idempotency-Key` was used with a different trader, field, value or action. Use a new key for a new change. |
| 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 |
| --- | --- | --- |
| 409 | `USER_FIELD_MUTATION_IN_PROGRESS` | Another numerical write for this trader and field didn't finish within 5 seconds. Nothing was written. Retry the same request, with the same key, after a short wait. |
| 500 | `USER_FIELD_VALUE_PROJECTION_FAILED` | The write failed and was undone (`meta.mutationState` is `rolled-back`). Retry the same request, with the same key. |
| 503 | `IDEMPOTENCY_STORE_UNAVAILABLE` | Nothing was written. Retry the same request, with the same key, after a short wait. |
| 503 | `USER_FIELD_MUTATION_LOCK_UNAVAILABLE` | Nothing was written. Retry the same request, with the same key, after a short wait. |

### Do not retry

| Status | Code | What to do |
| --- | --- | --- |
| 409 | `IDEMPOTENCY_REQUEST_IN_PROGRESS` | The first request with this key is still running, or its outcome is unclear. Read [the field's history](https://docs.returning.ai/api-reference/user-fields-user-field-history/get-a-traders-field-history.md) for this trader before doing anything else, and never switch to a new key for the same change. |
| 500 | `USER_FIELD_HISTORY_RECONCILIATION_REQUIRED` | The write may or may not have been kept (`meta.mutationState` is `ambiguous`). Read [the field's history](https://docs.returning.ai/api-reference/user-fields-user-field-history/get-a-traders-field-history.md) for this trader before any other change. |
| 500 | `USER_FIELD_HISTORY_INTERNAL_ERROR` | The write failed unexpectedly and may be partly done. Read [the field's history](https://docs.returning.ai/api-reference/user-fields-user-field-history/get-a-traders-field-history.md), and for XP or coins the trader's balance, before you try again. |

**Retries:** Send an `Idempotency-Key` with every write. If a request times out, send it again with the same key, the same `userId` and field in the path, and the same body: within 24 hours you get the original `201` with `meta.idempotentReplay: true`, and nothing is written twice. The same trader under a different identifier, such as email instead of platform user ID, counts as a different request. Without a key, never retry `increase` or `decrease` blindly, because each retry adds again; read the history first. 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 a trader's field history](https://docs.returning.ai/api-reference/user-fields-user-field-history/get-a-traders-field-history.md): `GET /v1/communities/{communityId}/users/{userId}/user-fields/{fieldIdOrName}/histories`. Read this trader's history for the field to confirm the write.
- [Read current values with Get User Data and `customFields`](https://docs.returning.ai/api-reference/users/get-user-data.md): `POST /v1/users/info`.
