Импорт клиентов через API
Подробный гайд по эндпоинту POST /api/v1/integration/leads/bulk — массовому импорту клиентов в вашу CRM-базу Notix из внешней системы. Эндпоинт идемпотентный по phone: повторный вызов с тем же телефоном обновит существующего клиента, а не создаст дубль.
Когда это нужно
- Разовый перенос базы из старой CRM (amoCRM, Bitrix24, Excel) в Notix
- Периодическая синхронизация: ночной крон сверяет клиентов и доливает новых
- Импорт после вебинара / офлайн-мероприятия: CSV → скрипт → Notix
Эндпоинт
POST /api/v1/integration/leads/bulkТребуется Bearer-токен со scope leads.bulk. До 100 лидов за один запрос — это ограничение защищает базу от случайного залива миллиона записей. Если у вас больше 100 клиентов — разбивайте на пачки.
Схема payload
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| leads | array<object> | Да | Массив лидов. От 1 до 100 элементов |
| leads[].name | string | Да | Имя клиента. Если пусто или не передано — будет «Клиент» |
| leads[].phone | string | Нет* | Телефон в любом формате: +7 917 000-00-00, 89170000000, 8 (917) 000-00-00. Нормализуется автоматически |
| leads[].email | string | Нет* | Email. Регистр игнорируется, обрезаются пробелы |
| leads[].source_ref_id | string | Нет | Внешний ID лида (из вашей системы) — сохраняется в client.source_ref_id для аудита |
* Что считается идентификатором клиента
Notix считает двух клиентов одним и тем же, если у них совпадает нормализованный телефон (последние 10 цифр с кодом страны) или email. Если передан только name без контактов — будет создан клиент с пустыми контактами, но при следующем вызове с тем же именем и контактами Notix склеит их по контактам, а не по имени.
Пример запроса
curl -X POST https://notix-hub.ru/api/v1/integration/leads/bulk \
-H "Authorization: Bearer ntx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"leads": [
{
"name": "Иван Петров",
"phone": "+7 917 000-00-00",
"email": "ivan@example.com",
"source_ref_id": "old-crm-1247"
},
{
"name": "Мария Сидорова",
"phone": "+7 (916) 555-12-34"
},
{
"name": "Без контактов",
"source_ref_id": "webinar-row-12"
}
]
}'Ответ
{
"processed": 3,
"results": [
{"ok": true, "client_id": "01a0393d-...", "created": true},
{"ok": true, "client_id": "01a0393e-...", "created": true},
{"ok": true, "client_id": "01a0393f-...", "created": true}
]
}created: true — клиент создан. created: false — клиент с таким телефоном/email уже был в базе, мы обновили last_seen_at и (если было пусто) контактные данные.
Идемпотентность и дедупликация
Notix использует phone_hash (sha256 от нормализованного телефона) как основной ключ дедупликации. Это значит:
- Повторный вызов с тем же +7 917 000-00-00 не создаст второго клиента — обновится last_seen_at существующего
- Варианты формата телефона (89170000000, +79170000000, 8 917 000 00 00) считаются одним номером
- Если у клиента был пустой телефон, а в новом лиде он есть — телефон дописывается
- Если email был пустой, а в новом лиде есть — email дописывается
Обработка ошибок
Если в одном из элементов массива нет обязательного поля name, этот лид пропускается, но остальные продолжают обрабатываться:
{
"processed": 3,
"results": [
{"ok": true, "client_id": "...", "created": true},
{"ok": false, "error": "name required", "payload": {"phone": "+7 999 ..."}},
{"ok": true, "client_id": "...", "created": false}
]
}Лимит на запрос — 100 лидов. Если передадите больше, получите 422 Unprocessable:
{
"message": "Too many leads in single request",
"max": 100,
"received": 150
}Разбивайте большие CSV на батчи по 100 и отправляйте последовательно.
Пример на PHP: импорт CSV
<?php
/**
* Скрипт импорта CSV в Notix через /leads/bulk.
*
* Формат CSV (заголовок):
* name,phone,email,source_ref_id
*
* Запуск:
* php import.php clients.csv
*/
$token = getenv('NOTIX_API_TOKEN') ?: 'ntx_live_...';
$base = 'https://notix-hub.ru/api/v1/integration/leads/bulk';
$file = $argv[1] ?? null;
if (! $file || ! is_readable($file)) {
fwrite(STDERR, "Usage: php import.php <file.csv>\n");
exit(1);
}
$fh = fopen($file, 'r');
$header = fgetcsv($fh);
if (! $header) {
fwrite(STDERR, "Empty CSV\n");
exit(1);
}
$batchSize = 100;
$batch = [];
$totalCreated = 0;
$totalUpdated = 0;
$totalErrors = 0;
while (($row = fgetcsv($fh)) !== false) {
$row = array_combine($header, $row);
$batch[] = array_filter([
'name' => $row['name'] ?? null,
'phone' => $row['phone'] ?? null,
'email' => $row['email'] ?? null,
'source_ref_id' => $row['source_ref_id'] ?? null,
], fn ($v) => $v !== null && $v !== '');
if (count($batch) >= $batchSize) {
[$created, $updated, $errors] = sendBatch($base, $token, $batch);
$totalCreated += $created;
$totalUpdated += $updated;
$totalErrors += $errors;
$batch = [];
}
}
if ($batch) {
[$created, $updated, $errors] = sendBatch($base, $token, $batch);
$totalCreated += $created;
$totalUpdated += $updated;
$totalErrors += $errors;
}
fclose($fh);
echo "Created: $totalCreated, Updated: $totalUpdated, Errors: $totalErrors\n";
function sendBatch(string $url, string $token, array $batch): array
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['leads' => $batch], JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 201) {
fwrite(STDERR, "Batch failed (HTTP $code): $response\n");
return [0, 0, count($batch)];
}
$data = json_decode($response, true);
$results = $data['results'] ?? [];
$created = count(array_filter($results, fn ($r) => $r['ok'] && ($r['created'] ?? false)));
$updated = count(array_filter($results, fn ($r) => $r['ok'] && ! ($r['created'] ?? false)));
$errors = count(array_filter($results, fn ($r) => ! $r['ok']));
return [$created, $updated, $errors];
}Что дальше
- Интеграция с CRM через REST API — общий обзор: аутентификация, scopes, список эндпоинтов, коды ошибок
- Webhook API — обратный канал (вы → Notix): отправка уведомлений и лидов