# Creates a new administrative user account in the system

`POST /api/users`

- **Products:** Operator (`FOH`)
- **Features:** Front of House Generic Access (`FOHGENERIC`)

HTML version: [Creates a new administrative user account in the system](https://support.gladstonesoftware.cloud/reference/users/creates-a-new-administrative-user-account-in-the-system)

## Overview

Creates a staff (back-office) user account for the tenant. These are the people who operate the leisure centre — not members or customers — and the account created here is what they sign in with. It returns `201 Created` with no response body; read the new account back with [Retrieves detailed information for a specific administrative user](retrieves-detailed-information-for-a-specific-administrative-user.md) using the `username` you sent.

Two things have to exist before you call this. The **site group** — a named collection of sites that configuration is applied across — must already be set up, and the site you name in `preferredSiteId` must be one of the sites inside it. List the available groups and their member sites with [Retrieves all site groups with their configuration details](../configuration/retrieves-all-site-groups-with-their-configuration-details.md).

The username is effectively permanent. Deleting a user (see [Permanently deletes a user account from the system](permanently-deletes-a-user-account-from-the-system.md)) keeps the deleted account on record, and deleted accounts still count when this endpoint checks for duplicates — so a username that has ever been used cannot be used again, and re-creating a deleted user returns `409`.

Password rules are per-tenant configuration, not a fixed policy. The password is checked against a regular expression drawn from the tenant's `PasswordComplexity` setting (`low`, `medium` or `high`) before anything else happens, and a failure returns `400` with the tenant's own wording, so do not hard-code a complexity rule in your client.

## Endpoint

`POST {base_url}/api/users`

`{base_url}` is the environment URL issued to your integration. See [Authenticating with Gladstone APIs](../../guides/authenticating-with-gladstone-apis.md).

## Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `username` | string | Yes | The sign-in name for the new account, and the identifier every other endpoint on this group takes in its path. Rejected with `400` if blank or longer than 8 characters (`Username cannot be more than 8 characters`); existing usernames can be up to 15 characters, but this endpoint accepts no more than 8. Must be unique across the tenant, including against previously deleted accounts, or the call fails with `409` and `The user: {username} already exists`. |
| `password` | string | Yes | The initial sign-in password, sent in plain text over TLS and stored hashed. Rejected with `400` if blank or longer than 128 characters. It is validated against the tenant's configured complexity regular expression before any other check; when it fails, the `400` body is a JSON object `{"errorMessage": "…", "isPasswordError": true}` carrying the tenant's own message, rather than a plain string. Also recorded in the user's password history, so it cannot be reused later through [Updates the password for a specific user account](updates-the-password-for-a-specific-user-account.md). |
| `firstName` | string | Yes | The user's given name, used to build `displayName` when you do not supply one. Rejected with `400` if blank or longer than 25 characters. |
| `lastName` | string | Yes | The user's family name, used to build `displayName` when you do not supply one. Rejected with `400` if blank or longer than 25 characters. |
| `displayName` | string | No | The name shown to other staff in the back office. Rejected with `400` above 50 characters. Omitted or blank, it defaults to `"{firstName} {lastName}"` — so leave it out unless you need a different form, such as a shortened or preferred name. |
| `email` | string | No | Contact address for the account. Rejected with `400` above 255 characters, and — when non-empty — validated against an RFC-style address pattern, failing with `The given email address is invalid`. Omit it or send an empty string to create the account with no address; the format check is skipped in that case. |
| `cardId` | string | No | The identifier printed on or encoded in the physical staff card this user presents at a reader or till. Rejected with `400` above 50 characters. Omit it if the user has no card. |
| `siteGroupId` | string | Yes | The site group this user belongs to, taken from `id` in [Retrieves all site groups with their configuration details](../configuration/retrieves-all-site-groups-with-their-configuration-details.md). It must resolve to an existing group or the call fails with `400` and `The given site group doesn't exist.`, and it also constrains `preferredSiteId`. Rejected with `400` if blank. |
| `preferredSiteId` | string | Yes | The site this user works at by default. It must be one of the sites listed inside `siteGroupId`, compared case-insensitively; anything else fails with `400` and `The preferred site ID must be contained in the given site group.` Rejected with `400` if blank. It is saved as a user preference named `PreferredSiteId` rather than as a field of the account, and comes back in that form through [Retrieves detailed information for a specific administrative user](retrieves-detailed-information-for-a-specific-administrative-user.md). |
| `avatarId` | string | No | The profile image the back office shows for this user. Rejected with `400` above 25 characters. On ObjectId-role tenants (see `roleId`), omitting it or sending an empty string saves the literal value `default`. On numeric-role tenants no default is applied and the value is not saved at all — the user-retrieval endpoints return `avatarId: null` for such accounts. |
| `roleId` | string | No | The permission role to grant, as a 24-character hexadecimal ObjectId string. Honoured only on ObjectId-role tenants — those whose users' `roleId` values are 24-character hexadecimal strings in [Retrieves all administrative users configured in the system](retrieves-all-administrative-users-configured-in-the-system.md) — where the role must already exist or the call fails with `400` and `The given role ID does not exist`. On numeric-role tenants it is ignored entirely and the account is placed in the tenant's default user group instead. Omit it to create the account with no role claim. Note that a non-hexadecimal or wrong-length value is not validated and returns `500`, not `400`. |
| `sharedSiteGroupId` | integer (int32) | No | Ignored on input. Whatever you send is replaced with the numeric identifier of the site group resolved from `siteGroupId`, so there is no need to populate it. |

## Sample request

```bash title="cURL"
curl -X POST '{base_url}/api/users' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "username": "string",
  "password": "string",
  "firstName": "string",
  "lastName": "string",
  "displayName": "string",
  "email": "member@example.com",
  "cardId": "string",
  "siteGroupId": "string",
  "preferredSiteId": "string",
  "avatarId": "string",
  "roleId": "string",
  "sharedSiteGroupId": 12345
}'
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | The password does not match the tenant's configured complexity pattern. The body is a JSON object `{"errorMessage": "…", "isPasswordError": true}` carrying the tenant's own message, not a plain string. |
| `400` | Bad request | A required field is blank, or a field exceeds its maximum length — for example `Username cannot be more than 8 characters` or `DisplayName cannot be more than 50 characters`. |
| `400` | Bad request | `email` is non-empty but not a valid address: `The given email address is invalid`. |
| `400` | Bad request | `siteGroupId` does not resolve to a site group: `The given site group doesn't exist.` |
| `400` | Bad request | `preferredSiteId` is not one of the sites in `siteGroupId`: `The preferred site ID must be contained in the given site group.` |
| `400` | Bad request | On ObjectId-role tenants only, `roleId` is a well-formed ObjectId that matches no role: `The given role ID does not exist`. |
| `409` | Conflict | The username is already taken: `The user: {username} already exists`. Deleted accounts still occupy their username, so this also fires when you re-create a user you previously deleted. |
| `409` | Conflict | The new account clashes with an existing record on a value that must be unique: `This entry already exists in the database`. |
| `500` | Internal server error | The tenant's password security configuration is missing or names a complexity level with no matching regular expression — this endpoint does not translate that fault into a `422`. Also returned when `roleId` is not a valid 24-character hexadecimal ObjectId, and for other unexpected server-side failures, such as the account failing to save completely. |
