# Importing CRM records

Move existing data into the CRM: up to 100 records per request, keeping the author and the dates from the external system.

**Import does not run automation.** Automation rules, triggers and business processes configured to fire on record creation do not run on imported records — that is a property of the Bitrix24 operation itself, not a parameter of ours. If you want automation to run, create the records the usual way with `POST /v1/{entity}`.

`POST /v1/{entity}/import`

**Scope:** `crm` | **Base URL:** `https://vibecode.bitrix24.com/v1` | **Authorization:** `X-Api-Key` (APP key)

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|----------|
| `entity` (path) | string | yes | The CRM object to import into. The accepted paths are in the table below. For smart process items, take `entityTypeId` from [`GET /v1/smart-processes`](/docs/entities/smart-processes/list) |

| Path | Object |
|------|--------|
| `POST /v1/leads/import` | Leads |
| `POST /v1/deals/import` | Deals |
| `POST /v1/contacts/import` | Contacts |
| `POST /v1/companies/import` | Companies |
| `POST /v1/quotes/import` | Quotes |
| `POST /v1/invoices/import` | Invoices |
| `POST /v1/items/{entityTypeId}/import` | Smart process items |

No other entity has this route — Bitrix24 can only import CRM objects.

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:---:|---------|
| `items` | array | yes | Array of records, 1 to 100. Each record takes the same fields as `POST /v1/{entity}`, plus the service fields below. Record fields are flat: when the schema does not declare `fields`, an object under that key rejects the whole import with `400 IMPORT_ITEM_VALIDATION`, the record index and the `NESTED_FIELDS_ENVELOPE` reason. |
| `items[].createdBy` | number | no | Who created the record. Employee list: [`GET /v1/users`](/docs/entities/users/list) |
| `items[].createdTime` | datetime | no | When the record was created |
| `items[].updatedBy` | number | no | Who changed the record. Employee list: [`GET /v1/users`](/docs/entities/users/list) |
| `items[].updatedTime` | datetime | no | When the record was changed |
| `items[].movedBy` | number | no | Who moved the record to its current stage. Employee list: [`GET /v1/users`](/docs/entities/users/list) |
| `items[].movedTime` | datetime | no | When the stage changed |

The six service fields are accepted on import only — on an ordinary create Bitrix24 fills them in itself.

Only a **Bitrix24 account administrator** may set them. For any other user, Bitrix24 rejects that record with `CRM_FIELD_ERROR_VALUE_NOT_VALID` — the remaining records of the batch are still created.

Pass the ID of an existing employee. Bitrix24 does not check whether the identifier exists and will store any number, leaving a phantom author on the card. You can still work with the record as usual.

The set of available fields varies by object: deals and invoices have all six, leads have the author plus the creation and update dates. The exact list for a given entity comes from `GET /v1/{entity}/fields` — service fields are marked there with `"importable": true` next to `"readonly": true`. The same flag is available without a session from `GET /v1/guide`, in `data.entities[].fieldsDetailed`.

## Examples

### curl — personal key

```bash
curl -X POST 'https://vibecode.bitrix24.com/v1/leads/import' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      {
        "title": "Website request",
        "name": "Jane",
        "lastName": "Doe",
        "phone": "+15551234567",
        "email": "jane@example.com",
        "assignedById": 31,
        "createdBy": 11
      }
    ]
  }'
```

### curl — OAuth application

```bash
curl -X POST 'https://vibecode.bitrix24.com/v1/leads/import' \
  -H 'X-Api-Key: YOUR_APP_KEY' \
  -H 'Authorization: Bearer USER_SESSION_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      {
        "title": "Website request",
        "name": "Jane",
        "lastName": "Doe",
        "phone": "+15551234567",
        "email": "jane@example.com",
        "assignedById": 31,
        "createdBy": 11
      }
    ]
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/leads/import', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      {
        title: 'Website request',
        name: 'Jane',
        lastName: 'Doe',
        phone: '+15551234567',
        email: 'jane@example.com',
        assignedById: 31,
        createdBy: 11,
      },
    ],
  }),
})

const { data } = await res.json()
console.log('Not created:', data.summary.failed)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/leads/import', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      {
        title: 'Website request',
        name: 'Jane',
        lastName: 'Doe',
        phone: '+15551234567',
        email: 'jane@example.com',
        assignedById: 31,
        createdBy: 11,
      },
    ],
  }),
})

const { data } = await res.json()
console.log('Not created:', data.summary.failed)
```

## Response fields

The response comes back with status `200` even when some records failed — import is not transactional, and the outcome has to be read per record.

| Field | Type | Description |
|------|-----|-------------|
| `success` | boolean | `true` when the request was processed — even if some records were not created |
| `data.results` | array | The outcome for each record. `results` is always the same length as `items`. |
| `data.results[].index` | number | Position of the record in the `items` array you sent. |
| `data.results[].success` | boolean | Whether the record was created. |
| `data.results[].id` | number | Identifier of the created record. Present when `success: true` — and on the `STAGE_NOT_APPLIED` / `AMOUNT_NOT_APPLIED` refusals, because the record has already been created by then. |
| `data.results[].error` | string | Bitrix24 error code — or a platform verdict code: [`STAGE_NOT_APPLIED`](/docs/errors) when the stage or pipeline that was sent did not apply, for example a nonexistent value, and the record was created on the default stage, and [`AMOUNT_NOT_APPLIED`](/docs/errors) when an explicitly requested manual amount mode on a smart-process item did not stick — on a type without product rows. |
| `data.results[].message` | string | Explanation of the error. For the platform verdict codes it names the requested and the actual value. |
| `data.results[].details` | object | Platform verdict codes only: `unappliedFields` — what did not apply, `currentValues` — what the record holds. |
| `data.summary` | object | Totals: `total`, `succeeded`, `failed`. |

## Response example

The first record is created, the second one is rejected by Bitrix24 — its creation date is in the future:

```json
{
  "success": true,
  "data": {
    "results": [
      { "index": 0, "success": true, "id": 1001349 },
      {
        "index": 1,
        "success": false,
        "error": "CRM_FIELD_ERROR_VALUE_NOT_VALID",
        "message": "The value of the \"Created on\" field cannot be in the future"
      }
    ],
    "summary": { "total": 2, "succeeded": 1, "failed": 1 }
  }
}
```

## Error response example

400 — the `items` array is missing:

```json
{
  "success": false,
  "error": {
    "code": "IMPORT_ITEM_VALIDATION",
    "message": "items must be a non-empty array of lead objects"
  }
}
```

## Errors

| HTTP | Code | When |
|------|-----|-------|
| 400 | `IMPORT_ITEM_VALIDATION` | `items` is missing, empty or not an array; an element is not an object or is empty; an element carries a field that is not writable, including an object under `fields` when the schema does not declare it. The message names the position and the `NESTED_FIELDS_ENVELOPE` reason: `Item at index N: …` |
| 400 | `IMPORT_LIMIT_EXCEEDED` | More than 100 records in one request. Split the payload and send the parts in order. |
| 400 | `INVALID_DYNAMIC_PARAM` | Invalid `entityTypeId` in the `/v1/items/{entityTypeId}/import` path, or that type has a route of its own — deals, for example, use `/v1/deals/import`. |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is in read-only mode. |
| 403 | `SCOPE_DENIED` | The key has no `crm` scope. |
| 429 | `RATE_LIMITED` | Requests are too frequent. Import is rate-limited per Bitrix24 account so a bulk transfer cannot block other integrations. Retry after the interval in the `Retry-After` header. |
| 404 | — | This entity does not support import. |

Per-record errors arrive inside the `200`, in `results[]` — those codes come from Bitrix24 itself, plus the two platform verdict codes, `STAGE_NOT_APPLIED` and `AMOUNT_NOT_APPLIED`.

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

## Known specifics

### How import differs from create

| | `POST /v1/{entity}` | `POST /v1/{entity}/import` |
|---|---|---|
| Bitrix24 permission | permission to add | **permission to import** — a separate one, granted by the Bitrix24 account administrator |
| Automation rules and business processes | run | **do not run** |
| Record author and dates | set by Bitrix24 | you may supply your own — the service fields in "Request fields" |
| Per request | one record | up to 100 records |
| Response | the created record in full | the outcome for each record |

### The date window

`createdTime` has a window set by Bitrix24, and it cannot be worked around:

- the date may not be **later than the current moment**.
- the date may not be **earlier than that of the newest existing record of this object**.

So you can transfer the full history into an empty CRM, or into one whose records are all older than the ones you are moving. Bitrix24 will not let you backdate records into a populated CRM — it answers `CRM_FIELD_ERROR_VALUE_NOT_VALID` with an explanation. Every successfully imported record raises the lower bound of the window, so move your batches oldest-first.

`updatedTime` must be no earlier than `createdTime`, and `movedTime` must fall between the two.

If you do not need the history, simply omit the dates — the author can be set without them.

### Checking the import outcome

**Always check `summary.failed`.** Status `200` means "the request was processed", not "every record was created".

**The stage and the manual amount are verified after the import.** Bitrix24 accepts an import with a nonexistent stage and silently places the record on the default stage. A manual amount mode on a type without product rows is lost just as silently. So after all batches the platform re-reads the created records — one list page per 50 records — and marks those whose stage, pipeline or explicitly requested `isManualOpportunity: true` did not apply with `STAGE_NOT_APPLIED` / `AMOUNT_NOT_APPLIED` — keeping the `id`: the record exists, do not repeat it, correct the value with `PATCH` or `/move`. Only records that carried a stage, a pipeline or an explicit manual amount mode are re-read. An import without them costs exactly what it did before. Import runs no automation rules, so the re-read record is the outcome of the import itself, not of an automation rule. If the re-read fails, the rows stay as Bitrix24 reported them. An empty stage (`null`, `""`) is not verified — the record lands on the default stage. An amount without an explicit `isManualOpportunity: true` is not checked. A lead Bitrix24 itself converted in simple CRM mode is not counted as an unapplied stage.

**A repeated import creates duplicates** — the operation is not idempotent. If repeats are possible, write the record identifier from the external system into `originatorId` and `originId`: they let you find what has already been transferred.

### Things to plan for when moving a database

- **One stream at a time.** Order is guaranteed within a single request but not between parallel requests, and Bitrix24 requires non-decreasing creation dates. Import sequentially.
- **Throughput.** A request with a hundred records takes roughly 12–15 seconds. The rate limit allows about a thousand records per minute per Bitrix24 account. Budget time for a database of tens of thousands of records.
- **A failure mid-batch.** If the connection to Bitrix24 drops after some records were already created, the response still comes back with status `200`: the created ones carry identifiers, the rest are marked as failed. That response is your only evidence of what went through — check it before retrying.
- **Phone and email.** Pass them in the same form as on create (`"phone": "+15551234567"` or an array of objects) — the platform converts them into the format that import accepts.

## See also

- [Create lead](/docs/entities/leads/create)
- [Lead fields](/docs/entities/leads/fields)
- [Batch calls](/docs/batch)
