
## Create employee

`POST /v1/users`

Creates a new employee in the Bitrix24 account. The minimum required field is `email`. Returns the ID and the full record of the just-created employee.

> For integrations with user input, [`POST /v1/users/invite`](/docs/entities/users/invite) is more convenient: it validates email on the Vibecode side (`EMAIL_REQUIRED`, `EMAIL_INVALID`) and automatically sets `departmentId: [1]` for intranet employees. This endpoint is the low-level form; errors are propagated from Bitrix24 as-is.

## Request fields (body)

| Parameter | Type | Req. | Description |
|----------|-----|:-----:|---------|
| `email` | string | yes | Email — must be unique across all employees in the Bitrix24 account |
| `name` | string | no | First name |
| `lastName` | string | no | Last name |
| `secondName` | string | no | Middle name |
| `workPosition` | string | no | Position |
| `workPhone` | string | no | Work phone |
| `personalPhone` | string | no | Personal phone |
| `personalMobile` | string | no | Mobile phone |
| `personalBirthday` | string | no | Date of birth (ISO 8601) |
| `personalGender` | string | no | Gender: `M` — male, `F` — female |
| `personalCity` | string | no | City |
| `departmentId` | number[] | conditional | Array of department IDs. For intranet employees it is **required** — without it Bitrix24 returns `wrong_email`. Not used for extranet (`EXTRANET: "Y"`) |
| `xmlId` | string | no | External identifier |
| `EXTRANET` | string | no | `"Y"` for an extranet user. Then instead of `departmentId` you need `SONET_GROUP_ID` (array of workgroup IDs) |
| `SONET_GROUP_ID` | number[] | conditional | Array of workgroup IDs — required with `EXTRANET: "Y"`. List: [`GET /v1/workgroups`](/docs/entities/workgroups) |

Full list — [Employee fields](/docs/entities/users/fields). User fields (`UF_*`) are also accepted.

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/users" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John",
    "lastName": "Brown",
    "email": "john.brown@example.com",
    "workPosition": "Manager",
    "departmentId": [1]
  }'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/users" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John",
    "lastName": "Brown",
    "email": "john.brown@example.com",
    "workPosition": "Manager",
    "departmentId": [1]
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/users', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John',
    lastName: 'Brown',
    email: 'john.brown@example.com',
    workPosition: 'Manager',
    departmentId: [1],
  }),
})

const { success, data } = await res.json()
console.log('User ID:', data.id)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/users', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John',
    lastName: 'Brown',
    email: 'john.brown@example.com',
    workPosition: 'Manager',
    departmentId: [1],
  }),
})

const { success, data } = await res.json()
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | Always `true` on success |
| `data.id` | number | ID of the created employee |

The response contains the full record with all employee fields — the same format as [`GET /v1/users/:id`](/docs/entities/users/get).

The employee card URL in Bitrix24 is built from `id`:

```
https://<portal>.bitrix24.com/company/personal/user/<id>/
```

`<portal>` — the Bitrix24 account domain. Access is restricted by the employee's rights in Bitrix24.

## Response example

```json
{
  "success": true,
  "data": {
    "id": 1331,
    "xmlId": "74668002",
    "active": true,
    "name": "John",
    "lastName": "Brown",
    "email": "john.brown@example.com",
    "lastLogin": null,
    "dateRegister": "2026-05-06T00:00:00.000Z",
    "isOnline": false,
    "timestampX": {},
    "personalGender": null,
    "personalBirthday": null,
    "departmentId": [1],
    "workPosition": "Manager",
    "userType": "employee"
  }
}
```

## Error response example

400 — email is not passed or duplicates an existing employee:

```json
{
  "success": false,
  "error": {
    "code": "BITRIX_ERROR",
    "message": "wrong_email",
    "hint": "Bitrix24 `user.add` returns `wrong_email` for several distinct cases, not only malformed email: (a) the email is already used by ANOTHER Bitrix24 user globally — check with GET /v1/users?filter[EMAIL]=the-email first; (b) the email domain is not allowed by portal policy (some portals restrict external domains); (c) missing UF_DEPARTMENT for intranet users — it is required for intranet invites (try `UF_DEPARTMENT: [1]` for the root department); (d) for external users pass `EXTRANET: \"Y\"` + `SONET_GROUP_ID: [groupId]` instead of UF_DEPARTMENT. For a convenience wrapper, try POST /v1/users/invite which sets sensible defaults. If the email is valid and unused, ask the portal admin to check user-invite settings."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `BITRIX_ERROR` (`wrong_email`) | Email is not passed, duplicates an existing employee, failed the portal domain policy, or `departmentId` is not specified for an intranet employee |
| 401 | `TOKEN_MISSING` | The API key has no configured tokens |
| 403 | `SCOPE_DENIED` | The API key lacks the `user` scope |

Full list of common API errors — [Errors](/docs/errors).

## Known specifics

**Email must be globally unique.** Bitrix24 checks uniqueness across all employees in the Bitrix24 account including previously deactivated ones. Before creation — search via `GET /v1/users?filter[EMAIL]=john.brown@example.com`. If a record is found and deactivated, bring it back with `PATCH /v1/users/:id { active: true }` — this preserves the employee's action history and relations, unlike creating a duplicate.

**Ambiguity of the `wrong_email` error.** Bitrix24 returns `wrong_email` for several different situations: email already taken, domain forbidden by Bitrix24 account policy, `departmentId` not specified for an intranet employee, `SONET_GROUP_ID` for an external one. The message does not clarify which one. To separate these cases on the Vibecode side, use [`POST /v1/users/invite`](/docs/entities/users/invite) — the wrapper returns explicit codes (`EMAIL_REQUIRED`, `EMAIL_INVALID`, `SONET_GROUP_ID_REQUIRED`) before the request to Bitrix24.

## See also

- [Invite employee](/docs/entities/users/invite) — a wrapper with validation and defaults
- [Employee fields](/docs/entities/users/fields) — full field list
- [List employees](/docs/entities/users/list) — find existing ones
- [Departments](/docs/entities/departments) — source of `departmentId`
- [Limits and optimization](/docs/optimization)
