
## Invite employee

`POST /v1/users/invite`

Creates a new employee or invites an external user to the Bitrix24 account. Accepts fields in `camelCase` and `UPPER_SNAKE_CASE`, sets sensible defaults, and validates email before the Bitrix24 call — returning clear error codes (`EMAIL_REQUIRED`, `EMAIL_INVALID`, `SONET_GROUP_ID_REQUIRED`) instead of the generic `wrong_email`.

## Request fields (body)

| Parameter | Type | Req. | Description |
|----------|-----|:-----:|---------|
| `email` | string | yes | Email — must be unique across all employees in the Bitrix24 account. A basic syntax check happens on the Vibecode side |
| `name` | string | no | First name |
| `lastName` | string | no | Last name |
| `secondName` | string | no | Middle name |
| `workPosition` | string | no | Position |
| `workPhone` | string | no | Work phone |
| `workCompany` | string | no | Company name |
| `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[] | no | Array of department IDs. Defaults to `[1]` (root department) for intranet employees. List: `GET /v1/departments` |
| `extranet` | string | no | `"Y"` to invite an external user. In this case `departmentId` is ignored and `sonetGroupId` is required |
| `sonetGroupId` | number[] | conditional | Array of workgroup IDs — required with `extranet: "Y"`. List: [`GET /v1/workgroups`](/docs/entities/workgroups) |
| `active` | boolean | no | Activity flag (defaults to `true`) |

## Examples

### curl — personal key

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

### curl — OAuth application

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

### JavaScript — personal key

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

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

### JavaScript — OAuth application

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

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 |
| `data.user` | object | Full record of the just-created employee — the same fields as [`GET /v1/users/:id`](/docs/entities/users/get) |

## Response example

```json
{
  "success": true,
  "data": {
    "id": 1331,
    "user": {
      "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,
      "departmentId": [1],
      "workPosition": "Manager",
      "userType": "employee"
    }
  }
}
```

The HTTP response status is `201 Created`.

## Error response example

400 — email not passed:

```json
{
  "success": false,
  "error": {
    "code": "EMAIL_REQUIRED",
    "message": "EMAIL (or `email`) is required to invite a user."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `EMAIL_REQUIRED` | The request body has no `email` (or `EMAIL`) |
| 400 | `EMAIL_INVALID` | Email failed the Vibecode syntax check |
| 400 | `SONET_GROUP_ID_REQUIRED` | `sonetGroupId` is not passed when `extranet: "Y"` |
| 400 | `BITRIX_ERROR` (`wrong_email`) | Bitrix24 rejected the creation: email already taken, domain forbidden by Bitrix24 portal policy, or another email error |
| 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

**Why the `departmentId: [1]` default.** Without `UF_DEPARTMENT` Bitrix24 returns a generic `wrong_email` without stating the reason — a missing department for an intranet employee is one of the most frequent pitfalls. Substituting the root department `[1]` eliminates it for the typical "inviting an employee, the specific department is unknown" case. If the department structure matters — pass `departmentId` explicitly.

**Email uniqueness in the Bitrix24 account.** Email is checked across all employees including previously deactivated ones — on a duplicate Bitrix24 returns `BITRIX_ERROR: wrong_email`. Before inviting — search via `GET /v1/users?filter[EMAIL]=john.brown@example.com`. If a deactivated one is found, bring it back with [`PATCH /v1/users/:id { active: true }`](/docs/entities/users/update) — this preserves history and relations, unlike creating a duplicate.

**Best-effort re-read.** After a successful creation the wrapper re-reads the record and puts it into `data.user`. If the helper call fails, `data.id` is still returned — the employee is already created, and a repeated request [`GET /v1/users/:id`](/docs/entities/users/get) will load the record.

## See also

- [Create employee](/docs/entities/users/create) — the low-level form without defaults
- [List employees](/docs/entities/users/list) — search for a duplicate email before inviting
- [Update employee](/docs/entities/users/update) — reactivation via `active: true`
- [Departments](/docs/entities/departments) — source of `departmentId`
- [Limits and optimization](/docs/optimization)
