# Create User

Register one trader in the community that owns your API key, and store their broker customer ID in the same call.

- Endpoint: `POST https://api.returning.ai/v1/users`
- Section: Users and data / Users
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `createUser` (Shown in the dashboard as "Create 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/create-user

## When to use this

- A trader signs up on your platform and needs a loyalty account straight away.
- You sign traders in with your own login (OAuth or single sign-on), so they never get a separate password.
- You want the broker customer ID attached from the start, so activity data matches the right trader.

**Instead:** Use [Update User Data](https://docs.returning.ai/api-reference/users/update-user-data.md) instead to change a trader who already exists.

**No personal data needed:** `firstname`, `lastname` and `username` are required, but they can be placeholders, such as your customer ID. `email` is optional. The program runs on your customer IDs.

## Authentication

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

The API key decides which community the trader joins, so never send a `communityId`. Keep the key on your server, and never place an API key, broker credential or login token in `customFields`.

## Request

### Headers

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

### Body

Required fields are `firstname`, `lastname`, `username` and `accessLevel`. For traders who sign in through your platform, omit `password` and `confirmPassword` and set `joinServer` to `true`.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `firstname` | `string` | Yes | The trader's first name, or a placeholder such as `Trader`. (1-64 chars) |
| `lastname` | `string` | Yes | The trader's last name, or a placeholder such as your customer ID. (1-64 chars) |
| `username` | `string` | Yes | Unique username. Letters, numbers, underscores and periods; cannot start or end with `_` or `.`; no `@`. (2-32 chars; `^[a-zA-Z0-9][a-zA-Z0-9._]*[a-zA-Z0-9]$`) |
| `displayname` | `string` | No | Public display name. Defaults to `username`. (Max 50 chars) |
| `email` | `string` | No | The trader's email. Optional; leave it out to run without personal data. (Email format) |
| `accessLevel` | `integer` | Yes | Access level. Most broker-created traders use `1`. (0-99999) |
| `joinServer` | `boolean` | No | Make the trader a full community member immediately. Use `true` for traders who sign in through your platform. (Default `false`) |
| `password` | `string` | No | Local-password accounts only. Omit for traders who sign in through your platform. Never returned. (8-20 chars, one number, one special character, no spaces, not equal to `username`) |
| `confirmPassword` | `string` | No | Required with `password` and must match it. (Must equal `password`) |
| `sendEmail` | `boolean` | No | Send the create-account email. Use `false` for traders who sign in through your platform. (Default `false`) |
| `emailPassword` | `boolean` | No | Include the password in that email. Use `false` for traders who sign in through your platform. (Default `false`) |
| `emailTemplate` | `string` | No | Email template to use when `sendEmail` is `true`. |
| `phoneNumber` | `string` | No | Phone number, digits only. (`^\d+$`) |
| `country` | `string` | No | Country name from the supported country list. (Supported country name) |
| `customFields` | `object[]` | No | Custom field values to write at creation. Use it for the broker customer ID when `externalId` is not configured. |
| `customFields.fieldIdorName` | `string` | Yes | Field key or ID. Preferred spelling on this endpoint. Required, as this or its alias `fieldIdOrName`. (Send this or `fieldIdOrName`) |
| `customFields.fieldIdOrName` | `string` | No | Accepted alias for `fieldIdorName`. (Alias; send one of the two) |
| `customFields.value` | `string \| number \| boolean` | Yes | The value. `false`, `0` and an empty multi-line string are kept. (Matches the field's type) |
| `externalId` | `string` | No | Shortcut for your community's active identifier field, usually the broker customer ID. (Needs an active identifier field) |
| `referral` | `string` | No | Referrer's referral value, matched against your referral mapping. |

### Example request

```bash
curl --request POST \
  --url https://api.returning.ai/v1/users \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "firstname": "Sample",
    "lastname": "Trader",
    "username": "sample_trader",
    "displayname": "Sample Trader",
    "email": "trader@example.com",
    "accessLevel": 1,
    "joinServer": true,
    "sendEmail": false,
    "emailPassword": false,
    "externalId": "<brokerCustomerId>"
  }'
```

## Response

A `200` means the trader and their community membership are saved. Save `data.userId`, then confirm the trader with Get User Data. Email, referral tracking and analytics follow after the response and never turn a saved trader into an error.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `status` | `string` | always | Result of the request. (`success`) |
| `code` | `string` | always | Machine-readable result code. (`USER_CREATED`) |
| `message` | `string` | always | Human-readable summary. Do not branch on it. |
| `data` | `object` | always | The created trader. |
| `data.userId` | `string` | always | Platform user ID. Use it for follow-up reads; never do arithmetic on it. (Decimal digits) |
| `data.username` | `string` | always | The stored username. |
| `data.email` | `string` | always | The stored email. (Email format) |
| `data.communityMemberId` | `string` | always | ID of the community membership record created for this trader. |
| `data.identifierKey` | `string` | when configured | The active identifier field key used for this request. |
| `data.externalId` | `string` | when configured | The stored broker identifier. |
| `data.created` | `boolean` | always | `true` when a new trader was saved. |

### Example response (200)

```json
{
  "status": "success",
  "code": "USER_CREATED",
  "message": "User created successfully",
  "data": {
    "userId": "3247779",
    "username": "sample_trader",
    "email": "trader@example.com",
    "communityMemberId": "66f000000000000000000001",
    "identifierKey": "customerid",
    "externalId": "<brokerCustomerId>",
    "created": true
  }
}
```

## Errors

401 and 403 responses carry the code in `meta.code`. A body that fails validation returns `400` with a `detail` object naming each field, and no `code` or `data`. Every other error carries its code in `code`, and for coded 400 and 409 errors `data.created` is `false`. No `400` or `409` saves a trader.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | A field is missing or breaks its rule, such as a missing `accessLevel` or a username with a character other than letters, numbers, `_` and `.`. There is no `code` or `data`; `detail` names each field. Nothing was saved. |
| 400 | `PASSWORD_MISMATCH` | `password` and `confirmPassword` differ. Send matching values. |
| 400 | `PASSWORD_MATCHES_USERNAME` | The password equals the username. Choose a different password. |
| 400 | `EXTERNAL_IDENTIFIER_REQUIRED` | Your community expects an identifier. Send `externalId` or the identifier in `customFields`. |
| 400 | `EXTERNAL_IDENTIFIER_CONFLICT` | `externalId` and the identifier in `customFields` disagree. Send one value. |
| 400 | `INVALID_CUSTOM_FIELDS` | A custom field name or value failed validation. Check `errorFields`, and keep valid `false`, `0` and empty values. |
| 401 | `AUTHENTICATION_REQUIRED` | The key is missing or invalid. Send `Authorization: Bearer <API_KEY>` with a current key. |
| 403 | `API_KEY_PERMISSION_DENIED` | The key lacks `createUser`. Add the permission in Settings > Integration > API Keys. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `DUPLICATE_EMAIL` | The email already belongs to a trader. Look them up with Get User Data instead of creating again. |
| 400 | `DUPLICATE_USERNAME` | The username is taken. Choose another username, or look up the existing trader. |
| 409 | `DUPLICATE_EXTERNAL_ID` | Another trader already has this identifier. Look up that trader instead of creating another. |
| 400 | `EXTERNAL_IDENTIFIER_NOT_CONFIGURED` | Your community has no active identifier field. Send the value in `customFields`, or set up the field first. |

### Retry with backoff

| Status | Code | What to do |
| --- | --- | --- |
| 500 | `USER_CREATE_FAILED` | Creation failed unexpectedly. Check with Get User Data before you try again. |

### Do not retry

| Status | Code | What to do |
| --- | --- | --- |
| 500 | `USER_CREATION_RECONCILIATION_REQUIRED` | The outcome is unclear. Stop automatic retries and reconcile using `data.userId` and your identifier. |

**Retries:** Create User does not accept an `Idempotency-Key`. After a timeout, call Get User Data with the broker identifier, email or platform user ID first, and create again only when no trader exists. 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

- [Update User Data](https://docs.returning.ai/api-reference/users/update-user-data.md): `POST /v1/users/update`. Change a trader's profile or roles, or replace their identifier. Needs `manageUser`, plus `bulkUpdateUser` for role changes.
- [Confirm the new trader with Get User Data](https://docs.returning.ai/api-reference/users/get-user-data.md): `POST /v1/users/info`.
