Перейти до вмісту

Завантаження дзвінків і чатів

Є два способи завантажити запис дзвінка (або текст чату) і створити з нього запис в Aelo — який використовувати, залежить від того, де вже знаходиться ваше аудіо.

ЕндпоінтТілоВикористовуйте, коли…
POST /recordsJSONУ вас є fileUrl, за яким Aelo може скачати файл (або текст чату)
POST /records/uploadmultipart/form-dataАудіо вже у вас на руках, і розмістити його ніде

Обидва ендпоінти вимагають скоуп records:write.

Обидва також вимагають, щоб цільовий проєкт мав статус active. Проєкт зі статусом paused або archived відповідає project_not_accepting_records (409) замість прийому запису — перш ніж projectId знову зможе приймати записи, адміністратор організації має реактивувати проєкт.

ШляхЛімітЧому
POST /records (fileUrl)100 МБ, або 25 МБ, якщо джерело не повідомляє Content-LengthAelo скачує файл потоком напряму у сховище; джерело, яке не повідомляє довжину, потоком передати не можна — воно тримається в пам’яті
POST /records (JSON-тіло / textContent)10 МБУсе JSON-тіло читається в пам’ять цілком до валідації, тому стеля збігається з multipart-лімітом для чату нижче
POST /records/upload (multipart)25 МБMultipart-тіло потрібно повністю буферизувати в пам’яті перед читанням, тому стеля значно нижча

Якщо ваш файл більший за 25 МБ, розмістіть його там, куди Aelo може звернутися за HTTPS, і викличте POST /records з fileUrl замість передачі самих байтів.

Terminal window
curl -X POST https://api.aelo.cloud/api/public/v1/records \
-H "Authorization: Bearer aelo_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "your-project-id",
"type": "call",
"externalId": "crm-call-12345",
"fileUrl": "https://your-storage.example.com/call.mp3",
"agentId": "42",
"agentName": "Ivan Petrov",
"durationSeconds": 184,
"callDirection": "outbound",
"crmEntityType": "deal",
"crmEntityId": "9001"
}'

fileUrl має бути https:// і вказувати на публічну адресу — Aelo відхиляє URL, що ведуть у приватні або link-local мережі. Aelo чекає на завантаження до 30 секунд; повільніше або недоступне джерело повернеться як fetch_failed (проблема в самому URL) або resolution_unavailable (проблема на боці резолвера Aelo, тимчасова — повторіть запит).

Запит зобов’язаний оголосити Content-Length. Тіло з chunked-кодуванням (Transfer-Encoding: chunked) відхиляється з 400 ще до спроби розбору — те саме правило, що й для multipart-завантаження нижче. Це стосується всього запиту, а не лише type: "chat": тіло з fileUrl крихітне і ніколи не наближається до ліміту в 10 МБ, але перевірка виконується раніше, ніж Aelo дізнається, яка це гілка.

type — це "call" або "chat":

  • type: "call" вимагає fileUrl і відхиляє textContent.
  • type: "chat" вимагає textContent (текст транскрипту) і відхиляє fileUrl.

Завантаження файлу напряму

Section titled “Завантаження файлу напряму”
Terminal window
curl -X POST https://api.aelo.cloud/api/public/v1/records/upload \
-H "Authorization: Bearer aelo_sk_YOUR_KEY" \
-F "file=@call.mp3" \
-F "projectId=your-project-id" \
-F "externalId=crm-call-12345" \
-F "agentId=42" \
-F "agentName=Ivan Petrov" \
-F "callDirection=outbound"

Аудіо передається у частині з ім’ям file; усі інші частини — поля метаданих під тими самими іменами, що й у JSON-тілі (значення надходять рядками і приводяться до потрібного типу там, де треба — наприклад, durationSeconds). Так можна завантажувати лише дзвінки: type за замовчуванням дорівнює "call", а type=chat відхиляється — надішліть текст чату на POST /records з textContent. Якщо у формі випадково опинилася друга частина з ім’ям file, прочитана буде лише перша — решта мовчки ігнорується.

Запит зобов’язаний оголосити Content-Length. Тіло з chunked-кодуванням (Transfer-Encoding: chunked) відхиляється з 400 ще до спроби розбору. Будь- який звичайний клієнт виставляє цей заголовок автоматично — curl -F, fetch із тілом File/Blob, multipart-хелпер будь-якого SDK, — тож це стосується лише запиту, вручну зібраного поверх ReadableStream.

Обов’язкові та необов’язкові поля

Section titled “Обов’язкові та необов’язкові поля”
ПолеОбов’язковоПримітка
projectIdТакПробіли по краях обрізаються перед пошуком; значення лише з пробілів відхиляється
typeJSON: так · multipart: ніcall або chat; у multipart за замовчуванням call, chat не приймається
fileUrlJSON, лише дзвінкиНе приймається для type: "chat"
textContentJSON, лише чатиНе приймається для type: "call"
externalIdНіВаш власний id запису — див. Ідемпотентність нижче
occurredAtНіISO-8601. Бізнес-час дзвінка — див. примітку про дашборди нижче. Відхиляється раніше за 2000-01-01 і далі ніж на 24 год у майбутнє
agentIdНіДовільний формат: id із CRM зазвичай числовий рядок, id із телефонії — id оператора. Див. Попередження
agentName, participantName, participantPhone, agentPhoneНі
durationSecondsНіНе передавайте, якщо значення немає — див. Попередження. 0–86400 (24 год); поза діапазоном запит відхиляється
callDirectionНіinbound або outbound
languageНі2–8 символів
crmEntityTypeНіdeal, lead, contact або company
crmEntityId, crmEntityName, crmActivityNameНі

Будь-яке нерозпізнане поле цей API відхиляє з invalid_request, а не відкидає мовчки — помилка на кшталт projectID дає явну помилку, а не тиху втрату даних.

Порожнє необов’язкове поле означає «не надіслано». Якщо поле форми приходить порожнім або складається лише з пробілів (-F "agentId=", або невстановлена змінна оточення, підставлена у форму), Aelo трактує це як відсутність значення, а не як порожній рядок — включно з externalId і agentId. З обов’язковим полем усе інакше: порожнє значення — це 400, воно не вважається «не надісланим». Ця відмінність стосується лише multipart-тіл; JSON "" — значення, введене навмисно, і обробляється як є.

externalId — ваш власний id запису: id активності з CRM, id дзвінка з телефонії — те, що вже використовується у вашій системі. Надішліть той самий externalId повторно, і Aelo поверне наявний запис замість створення дубліката (200 замість 201).

Унікальність діє в межах організації, а не проєкту. Якщо ви надішлете однаковий externalId під двома різними projectId в одній організації, другий виклик не створить другий запис — він поверне запис першого проєкту. Якщо ваша інтеграція перевикористовує id дзвінків між різними проєктами, або зробіть їх унікальними на своєму боці, або закладайтеся на те, що друге завантаження «схлопнеться» в запис першого проєкту.

Успішне завантаження (200 або 201) все одно може містити warnings — запис створено, але частина аналітики його не побачить:

КодКолиПовідомлення
missing_agent_idНемає agentId«No agentId supplied: this record is excluded from agent ratings (they filter on agent_id IS NOT NULL).»
missing_crm_entityНемає пари crmEntityType/crmEntityId«No crmEntityType/crmEntityId pair: this record joins no call series, so it appears in no funnel or deal outcome.»
missing_durationtype: "call" без durationSeconds«No durationSeconds: leaderboard totals and short-call detection ignore this record.»

warnings з’являється лише у відповідях обох ендпоінтів завантаження — цього поля немає у форматі запису, який повертають GET-ендпоінти. Порожнє значення durationSeconds у multipart-формі трактується так само, як його відсутність: Aelo ніколи не збереже його як 0, і попередження missing_duration все одно спрацює.

Дашборди групують за часом завантаження

Section titled “Дашборди групують за часом завантаження”

Aelo зберігає occurredAt у записі, але дашборди й звіти в v1 групують дзвінки за часом, коли Aelo їх отримала (createdAt), а не за occurredAt. Якщо ви заднім числом завантажуєте історичні дзвінки з occurredAt тижневої чи місячної давнини, вони потраплять у сьогоднішні цифри, а не в той період, коли сталися насправді. Тому масовий історичний імпорт у v1 не підтримується — цей API призначений для завантаження дзвінків і чатів у міру їх появи.