IT CITY CRM — API Documentation

Повна документація REST API для інтеграції з CRM-системою IT CITY

Base URL
https://crm.its.te.ua/api
Автентифікація
API Key (Bearer) або Session Cookie
Формат
JSON (Content-Type: application/json)
Версія
v5.3.0

🔐 Автентифікація

Два способи авторизації: API ключ (рекомендовано для інтеграцій) або Session Cookie (браузер).

KEY API ключі (Bearer Token) — Рекомендований спосіб для Postman, скриптів, інтеграцій

Як отримати

Налаштування → API ключі → Створити ключ (тільки ADMIN)

Використання

GET /api/tickets
Authorization: Bearer itc_a1b2c3d4e5f6789...
Content-Type: application/json

Параметри ключа

ПолеОпис
НазваДовільна назва (Postman, Integration, Monitoring...)
Термін дії7д / 30д / 90д / 1 рік / безстроковий

API ендпоінти для управління ключами

МетодURLОпис
GET/api/api-keysСписок активних ключів
POST/api/api-keysСтворити новий ключ
DEL/api/api-keys?id=NВідкликати ключ

⚠️ Ключ показується тільки при створенні. Зберігається як SHA-256 хеш.

Реєстрація, відновлення пароля та управління сесіями. Ендпоінти мають підвищений rate limiting.

POST /api/auth/register — Реєстрація нового користувача Без авторизації

Тіло запиту

{
  "name": "Іван Петренко",
  "email": "ivan@example.com",
  "password": "securePassword123",
  "role": "TECH"
}

Параметри

ПолеТипОпис
namestring requiredПовне ім'я користувача
emailstring requiredEmail адреса
passwordstring requiredПароль (мінімум 8 символів)
roleenumADMIN | MANAGER | TECH (за замовчуванням TECH)

Відповідь (201)

{
  "id": "clx1abc...",
  "name": "Іван Петренко",
  "email": "ivan@example.com",
  "role": "TECH"
}
Rate Limit: 5 запитів на 15 хвилин з однієї IP-адреси
POST /api/auth/forgot-password — Запит на скидання пароля Без авторизації

Тіло запиту

{
  "email": "ivan@example.com"
}

Відповідь (200)

{
  "message": "Якщо email існує, лист з інструкціями відправлено"
}
Примітка: Відповідь завжди 200, незалежно від існування email (захист від перебору)
POST /api/auth/reset-password — Скидання пароля за токеном Без авторизації

Тіло запиту

{
  "token": "reset-token-from-email",
  "password": "newSecurePassword456"
}

Відповідь (200)

{
  "message": "Пароль успішно змінено"
}
Rate Limit: 3 запити на 15 хвилин з однієї IP-адреси. Токен діє 1 годину.

📋 Заявки (Tickets)

CRUD-операції із заявками, масові дії, обладнання, фото та коментарі.

GET /api/tickets — Список заявок з фільтрацією та пагінацією Потрібна авторизація

Query-параметри

ПараметрТипОпис
statusstringФільтр за статусом: NEW, IN_PROGRESS, COMPLETED, CANCELLED
assigneeIdstringID виконавця
storeIdstringID торгової точки
qstringПошук за заголовком, описом, номером
pagenumberНомер сторінки (за замовчуванням 1)
sortFieldstringПоле сортування: createdAt, updatedAt, status, number
sortDirstringНапрямок: asc | desc

Відповідь (200)

{
  "tickets": [
    {
      "id": "clx1abc...",
      "number": 1042,
      "title": "Не працює принтер",
      "status": "NEW",
      "store": { "id": "...", "name": "Магазин #5" },
      "assignee": { "id": "...", "name": "Олег Коваль" },
      "createdAt": "2026-03-28T10:00:00.000Z"
    }
  ],
  "total": 156,
  "page": 1,
  "totalPages": 16
}
POST /api/tickets — Створення нової заявки Потрібна авторизація

Тіло запиту

{
  "title": "Заміна картриджа",
  "description": "Потрібно замінити картридж у принтері HP LaserJet",
  "storeId": "clx1store...",
  "assigneeId": "clx1user...",
  "priority": "HIGH",
  "categoryId": "clx1cat...",
  "scheduledAt": "2026-04-05T10:00:00.000Z",
  "scheduledEndAt": "2026-04-05T12:00:00.000Z",
  "isPlanned": true
}
ПолеТипОпис
titlestring requiredЗаголовок заявки
descriptionstringДетальний опис
storeIdstring requiredID торгової точки
assigneeIdstringID виконавця
priorityenumLOW | MEDIUM | HIGH | CRITICAL
categoryIdstringID категорії заявки
scheduledAtdatetimeДата та час запланованого виїзду (ISO 8601)
scheduledEndAtdatetimeДата та час завершення (ISO 8601)
isPlannedbooleanПозначити як плановий виїзд

Відповідь (201)

{
  "id": "clx1new...",
  "number": 1043,
  "title": "Заміна картриджа",
  "status": "NEW",
  "createdAt": "2026-03-28T12:00:00.000Z"
}
Telegram: Автоматичне сповіщення виконавцю при призначенні
PATCH /api/tickets/[id] — Оновлення заявки, зміна статусу, GPS Потрібна авторизація

Тіло запиту

{
  "status": "COMPLETED",
  "completionNote": "Картридж замінено",
  "gpsLat": 49.5535,
  "gpsLng": 25.5948
}
ПолеТипОпис
statusenumНовий статус заявки
completionNotestringПримітка при закритті
gpsLatnumberGPS широта (при закритті)
gpsLngnumberGPS довгота (при закритті)
titlestringОновлений заголовок
assigneeIdstringНовий виконавець
scheduledAtdatetimeДата та час запланованого виїзду (ISO 8601)
scheduledEndAtdatetimeДата та час завершення (ISO 8601)
Аудит: Всі зміни статусу записуються в журнал аудиту
State Machine: Зміна статусу валідується через state machine — дозволені лише певні переходи (наприклад, NEW → IN_PROGRESS → COMPLETED). Невалідний перехід поверне 400 Bad Request з описом дозволених переходів.
POST /api/tickets/bulk — Масові операції з заявками Потрібна авторизація

Тіло запиту

{
  "action": "assign",
  "ticketIds": ["clx1a...", "clx1b...", "clx1c..."],
  "assigneeId": "clx1user..."
}

// Або зміна статусу:
{
  "action": "status",
  "ticketIds": ["clx1a...", "clx1b..."],
  "status": "COMPLETED"
}

// Або видалення:
{
  "action": "delete",
  "ticketIds": ["clx1a...", "clx1b..."]
}
ПолеТипОпис
actionenum requiredassign | status | delete
ticketIdsstring[] requiredМасив ID заявок
assigneeIdstringID виконавця (для action=assign)
statusenumСтатус (для action=status)

Відповідь (200)

{
  "updated": 3,
  "message": "Успішно оновлено 3 заявки"
}
GET POST /api/tickets/[id]/equipment — Обладнання заявки Потрібна авторизація

GET — Список обладнання прив'язаного до заявки

GET /api/tickets/clx1abc.../equipment

POST — Прив'язати обладнання до заявки

{
  "equipmentId": "clx1eq...",
  "action": "install"
}

Відповідь (200)

[
  {
    "id": "clx1eq...",
    "name": "HP LaserJet Pro",
    "serialNumber": "VNB3K12345",
    "type": "PRINTER"
  }
]
GET POST /api/tickets/[id]/photos — Фото заявки Потрібна авторизація

POST — Завантаження фото (multipart/form-data)

Content-Type: multipart/form-data

photo: [binary file]
description: "Фото пошкодженого принтера"

Відповідь (201)

{
  "id": "clx1ph...",
  "url": "/uploads/tickets/clx1abc.../photo1.jpg",
  "description": "Фото пошкодженого принтера",
  "createdAt": "2026-03-28T12:30:00.000Z"
}
GET POST DEL /api/tickets/[id]/comments — Коментарі до заявки Потрібна авторизація

POST — Додавання коментаря

Підтримує application/json та multipart/form-data (з файлами).

{
  "text": "Замовив новий картридж, доставка завтра"
}

DELETE /api/tickets/[id]/comments/[commentId] — Видалення коментаря

Автор може видалити свій коментар, ADMIN — будь-який. Створює запис в журналі змін заявки.

PATCH /api/tickets/[id]/comments/[commentId] — Редагування коментаря

Тільки автор, зберігає попередні версії в editHistory.

Відповідь (201)

{
  "id": 42,
  "text": "Замовив новий картридж, доставка завтра",
  "author": { "id": 1, "name": "Олег Коваль" },
  "files": [],
  "createdAt": "2026-03-28T13:00:00.000Z"
}
GET /api/tickets/[id]/calendar — Завантаження .ics календарного файлу Потрібна авторизація

Опис

Повертає файл у форматі iCalendar (.ics) з подією VEVENT для запланованого виїзду. Файл містить нагадування VALARM за 1 день до події. Сумісний з Google Calendar, Apple Calendar та Outlook.

Відповідь (200)

Content-Type: text/calendar

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//IT CITY CRM//Tickets//UK
BEGIN:VEVENT
UID:ticket-clx1abc@crm.its.te.ua
DTSTART:20260405T100000Z
DTEND:20260405T120000Z
SUMMARY:Заявка #1043 — Заміна картриджа
DESCRIPTION:Магазин #5\nПріоритет: HIGH\nhttps://crm.its.te.ua/tickets/clx1abc
LOCATION:вул. Центральна, 15
BEGIN:VALARM
TRIGGER:-P1D
ACTION:DISPLAY
DESCRIPTION:Нагадування: завтра плановий виїзд — Заявка #1043
END:VALARM
END:VEVENT
END:VCALENDAR
Примітка: Якщо заявка не має запланованої дати (scheduledAt), повертається 404. Кнопка "Додати в Google Calendar" генерує URL з параметрами для calendar.google.com.

🖨️ Обладнання (Equipment)

Управління обладнанням, серійними номерами та переміщеннями між торговими точками.

GET /api/equipment — Список обладнання Потрібна авторизація

Query-параметри

ПараметрТипОпис
typestringТип: PRINTER, SCANNER, PC, MONITOR, OTHER
storeIdstringФільтр за торговою точкою
qstringПошук за назвою, серійним номером
statusstringACTIVE | REPAIR | DECOMMISSIONED
pagenumberНомер сторінки

Відповідь (200)

{
  "equipment": [
    {
      "id": "clx1eq...",
      "name": "HP LaserJet Pro M404dn",
      "type": "PRINTER",
      "serialNumber": "VNB3K12345",
      "status": "ACTIVE",
      "store": { "id": "...", "name": "Магазин #5" }
    }
  ],
  "total": 84,
  "page": 1
}
POST /api/equipment — Створення обладнання Потрібна авторизація

Тіло запиту

{
  "name": "HP LaserJet Pro M404dn",
  "type": "PRINTER",
  "serialNumber": "VNB3K12345",
  "storeId": "clx1store...",
  "notes": "Нове обладнання, гарантія до 2027"
}

Відповідь (201)

{
  "id": "clx1new...",
  "name": "HP LaserJet Pro M404dn",
  "type": "PRINTER",
  "serialNumber": "VNB3K12345",
  "status": "ACTIVE"
}
PATCH /api/equipment/[id] — Оновлення обладнання Потрібна авторизація

Тіло запиту

{
  "name": "HP LaserJet Pro M404dn (оновлено)",
  "serialNumber": "VNB3K12345-NEW",
  "status": "REPAIR",
  "notes": "Відправлено на ремонт"
}
POST /api/equipment/[id]/move — Переміщення обладнання Потрібна авторизація

Тіло запиту

{
  "toStoreId": "clx1store2...",
  "reason": "Переміщення на іншу точку за запитом клієнта"
}

Відповідь (200)

{
  "id": "clx1eq...",
  "store": { "id": "clx1store2...", "name": "Магазин #12" },
  "movement": {
    "id": "clx1mv...",
    "fromStore": "Магазин #5",
    "toStore": "Магазин #12",
    "movedAt": "2026-03-28T14:00:00.000Z",
    "movedBy": "Олег Коваль"
  }
}
Аудит: Всі переміщення фіксуються в історії обладнання

💰 Фінанси (Finance)

Грошовий потік, регулярні платежі, закриття місяця, виплати та категорії.

GET POST /api/cash-flow — Грошовий потік з розрахунком податків Потрібна авторизація

GET — Query-параметри

ПараметрТипОпис
monthnumberМісяць (1-12)
yearnumberРік
typestringINCOME | EXPENSE
categoryIdstringID категорії

POST — Створення запису

{
  "type": "INCOME",
  "amount": 15000,
  "description": "Оплата за обслуговування - Березень",
  "categoryId": "clx1cat...",
  "date": "2026-03-28",
  "organizationId": "clx1org..."
}

Відповідь (200)

{
  "id": "clx1cf...",
  "type": "INCOME",
  "amount": 15000,
  "tax": 750,
  "netAmount": 14250,
  "description": "Оплата за обслуговування - Березень",
  "date": "2026-03-28T00:00:00.000Z"
}
Податки: Автоматичний розрахунок ЄП (5%) при створенні запису типу INCOME
GET /api/cash-flow/summary — Зведена інформація за місяць та квартал Потрібна авторизація

Query-параметри

ПараметрТипОпис
monthnumber requiredМісяць (1-12)
yearnumber requiredРік

Відповідь (200)

{
  "month": {
    "income": 85000,
    "expense": 42000,
    "tax": 4250,
    "profit": 38750
  },
  "quarter": {
    "income": 240000,
    "expense": 118000,
    "tax": 12000,
    "profit": 110000
  }
}
GET POST /api/recurring-items — Регулярні платежі Потрібна авторизація

POST — Створення регулярного платежу

{
  "name": "Оренда офісу",
  "amount": 8000,
  "type": "EXPENSE",
  "frequency": "MONTHLY",
  "categoryId": "clx1cat...",
  "dayOfMonth": 5
}

Відповідь (200)

{
  "items": [
    {
      "id": "clx1ri...",
      "name": "Оренда офісу",
      "amount": 8000,
      "type": "EXPENSE",
      "frequency": "MONTHLY",
      "nextDate": "2026-04-05",
      "isActive": true
    }
  ]
}
GET POST /api/month-close — Закриття/відкриття місяця Потрібна авторизація

POST — Закрити або відкрити місяць

{
  "month": 3,
  "year": 2026,
  "action": "close"
}
ПолеТипОпис
monthnumber requiredМісяць (1-12)
yearnumber requiredРік
actionenum requiredclose | reopen

Відповідь (200)

{
  "month": 3,
  "year": 2026,
  "isClosed": true,
  "closedAt": "2026-04-01T00:00:00.000Z",
  "closedBy": "Адмін"
}
Увага: Закриття місяця блокує редагування фінансових записів за цей період. Тільки ADMIN може закривати/відкривати.
GET /api/month-close/preview — Попередній перегляд перед закриттям Потрібна авторизація

Query-параметри

ПараметрТипОпис
monthnumber requiredМісяць (1-12)
yearnumber requiredРік

Відповідь (200)

{
  "month": 3,
  "year": 2026,
  "recurringItems": [
    {
      "id": "clx1ri...",
      "name": "Оренда офісу",
      "amount": 8000,
      "type": "EXPENSE",
      "category": "Оренда"
    }
  ],
  "totals": {
    "income": 85000,
    "expense": 42000,
    "recurringExpense": 18000,
    "recurringIncome": 5000
  }
}
Примітка: Використовуйте цей ендпоінт для відображення чекбокс-списку рекурентних платежів перед закриттям місяця
POST /api/payment-categories/reorder — Зміна порядку категорій Потрібна авторизація

Тіло запиту

{
  "orderedIds": ["clx1cat1...", "clx1cat2...", "clx1cat3..."]
}
ПолеТипОпис
orderedIdsstring[] requiredМасив ID категорій у новому порядку

Відповідь (200)

{
  "success": true,
  "message": "Порядок категорій оновлено"
}
GET POST /api/payments — Виплати (зарплати, бонуси) Потрібна авторизація

POST — Створення виплати

{
  "userId": "clx1user...",
  "amount": 25000,
  "type": "SALARY",
  "month": 3,
  "year": 2026,
  "note": "Зарплата за березень"
}

Відповідь (201)

{
  "id": "clx1pay...",
  "userId": "clx1user...",
  "amount": 25000,
  "type": "SALARY",
  "createdAt": "2026-04-01T00:00:00.000Z"
}
Telegram: Автоматичне сповіщення працівнику про нарахування зарплати
GET POST /api/payment-categories — Категорії платежів Потрібна авторизація

POST — Створення категорії

{
  "name": "Витратні матеріали",
  "type": "EXPENSE",
  "parentId": null
}

GET — Відповідь (200)

{
  "categories": [
    {
      "id": "clx1cat...",
      "name": "Витратні матеріали",
      "type": "EXPENSE",
      "parentId": null,
      "children": [
        { "id": "clx1cat2...", "name": "Картриджі" },
        { "id": "clx1cat3...", "name": "Папір" }
      ]
    }
  ]
}

📊 Звіти (Reports)

Щомісячна статистика, деталізовані звіти та експорт у Excel.

GET /api/reports — Щомісячна статистика Потрібна авторизація

Query-параметри

ПараметрТипОпис
monthnumberМісяць (1-12)
yearnumberРік

Відповідь (200)

{
  "tickets": {
    "total": 156,
    "new": 12,
    "inProgress": 28,
    "completed": 110,
    "cancelled": 6
  },
  "technicians": [
    { "name": "Олег Коваль", "completed": 45, "avgTime": "2.3 год" }
  ],
  "finance": {
    "income": 85000,
    "expense": 42000,
    "profit": 43000
  }
}
GET /api/reports/detailed — Деталізовані звіти за період Потрібна авторизація

Query-параметри

ПараметрТипОпис
fromstring requiredПочаткова дата (YYYY-MM-DD)
tostring requiredКінцева дата (YYYY-MM-DD)
typeenum requiredТип звіту (див. нижче)

Типи звітів

ТипОпис
summaryЗагальна зведена інформація
taxesРозрахунок податків за період
salariesЗарплати та виплати
incomeДеталізація доходів
categoriesРозбивка за категоріями
contractorsЗвіт по підрядниках
monthlyПомісячна розбивка

Приклад запиту

GET /api/reports/detailed?from=2026-01-01&to=2026-03-31&type=summary

Відповідь (200)

{
  "period": { "from": "2026-01-01", "to": "2026-03-31" },
  "type": "summary",
  "data": {
    "totalIncome": 240000,
    "totalExpense": 118000,
    "totalTax": 12000,
    "netProfit": 110000,
    "ticketsCompleted": 312,
    "avgTicketTime": "2.1 год"
  }
}
GET /api/reports/detailed/export — Експорт звіту у Excel (.xlsx) Потрібна авторизація

Query-параметри

Ті самі параметри, що й для /api/reports/detailed

Відповідь

Бінарний файл application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

Content-Disposition: attachment; filename="report-2026-Q1.xlsx"
Формат: Excel з форматованими таблицями, автофільтрами та підсумковими рядками
POST /api/reports/telegram/test — Тестова відправка звіту в Telegram Потрібна авторизація

Тіло запиту

{
  "type": "monthly",
  "month": 3,
  "year": 2026
}
ПолеТипОпис
typeenum requiredТип звіту: monthly | weekly
monthnumberМісяць (для monthly)
yearnumberРік (для monthly)

Відповідь (200)

{
  "success": true,
  "message": "Тестовий звіт відправлено в Telegram"
}
Примітка: Надсилає звіт у тестовий Telegram-чат, налаштований в системних параметрах
GET /api/settings/reports — Налаштування розкладу звітів Потрібна авторизація

Відповідь (200)

{
  "weeklyReport": {
    "enabled": true,
    "dayOfWeek": 1,
    "time": "09:00",
    "chatIds": ["-100123456789"]
  },
  "monthlyReport": {
    "enabled": true,
    "dayOfMonth": 1,
    "time": "10:00",
    "chatIds": ["-100123456789"]
  }
}
Cron: Звіти генеруються автоматично за розкладом та надсилаються у визначені Telegram-чати

🔗 Інтеграції (Integrations)

Вебхуки, Telegram-бот, Google Maps та магічні посилання.

POST /api/webhooks — Вебхук від Eddy Helpdesk Верифікація підпису

Заголовки

ЗаголовокОпис
X-Webhook-SignatureHMAC-SHA256 підпис тіла запиту
Content-Typeapplication/json

Тіло запиту (від Eddy)

{
  "event": "ticket.created",
  "data": {
    "id": 12345,
    "subject": "Не працює каса",
    "description": "Каса не включається після оновлення",
    "priority": "high",
    "store": {
      "name": "Магазин Електроніка #7",
      "address": "вул. Центральна, 15"
    },
    "contact": {
      "name": "Марія Коваленко",
      "phone": "+380501234567"
    }
  }
}

Маппінг полів Eddy → CRM

EddyCRM
data.subjecttitle
data.descriptiondescription
data.prioritypriority (high→HIGH, medium→MEDIUM, low→LOW)
data.store.nameПошук store за назвою
data.contact.nameКонтактна особа в описі

Відповідь (200)

{
  "success": true,
  "ticketId": "clx1new...",
  "ticketNumber": 1044
}
Безпека: Запити без валідного підпису повертають 401 Unauthorized
BOT Telegram Bot — Автоматичні сповіщення

Типи сповіщень

ПодіяОтримувачОпис
Нова заявкаВиконавецьПовідомлення з деталями заявки та посиланням
Зміна статусуАвтор / МенеджерОповіщення при зміні статусу заявки
Нарахування ЗППрацівникПовідомлення про нараховану зарплату
Щотижневий звітМенеджериЗведення по заявках за тиждень (понеділок)
Щомісячний звітАдміниФінансове зведення за місяць (1 числа)

Формат повідомлення (приклад)

🔔 Нова заявка #1044

📋 Не працює каса
🏪 Магазин Електроніка #7
⚡ Пріоритет: Високий

🔗 https://crm.its.te.ua/tickets/clx1new...
LINK /api/magic-link/[token] — Магічне посилання для підрядників Без авторизації (токен)

Опис

Одноразове посилання для закриття заявки зовнішнім підрядником без облікового запису в CRM.

Приклад URL

https://crm.its.te.ua/magic/abc123def456...

Процес

КрокОпис
1Менеджер генерує Magic Link для заявки
2Посилання надсилається підряднику (email/Telegram)
3Підрядник відкриває посилання, бачить форму закриття
4Заповнює результат роботи та закриває заявку
Безпека: Токен одноразовий, діє 72 години. Після використання або закінчення терміну — деактивується.
MAP Google Maps Integration — Карти та маршрути

Можливості

ФункціяОпис
Геолокація точокКоординати магазинів зберігаються при створенні (geocoding API)
Карта заявокВідображення всіх відкритих заявок на карті з маркерами
Побудова маршрутуОптимальний маршрут між кількома точками (Directions API)
GPS при закриттіФіксація GPS-координат техніка при закритті заявки

👤 Користувачі (Users)

Управління обліковими записами, ролями та статусами користувачів.

DELETE /api/users/[id] — Видалення користувача (заблоковано) Потрібна авторизація

Відповідь (403)

{
  "error": "Видалення користувачів заборонено. Використовуйте деактивацію.",
  "hint": "PATCH /api/users/[id] з { \"isActive\": false }"
}
Безпека: Видалення користувачів заблоковано для збереження цілісності аудит-логу та історії заявок. Замість видалення використовуйте деактивацію через PATCH.

📚 Тестування (Quizzes)

Створення, призначення та проходження тестів для перевірки знань техніків.

GET POST /api/quizzes — Список тестів / Створення тесту Потрібна авторизація

GET — Список тестів

Для техніка: тільки обов'язкові + призначені. Для адміна: всі опубліковані.

POST — Створення тесту (ADMIN)

{
  "title": "Основи мереж",
  "description": "Базові знання мережевих технологій",
  "level": "BEGINNER",
  "category": "networks",
  "passingScore": 80,
  "timeLimitMin": 15,
  "isRequired": false,
  "questions": [
    {
      "text": "Який порт використовує HTTPS?",
      "type": "single",
      "options": ["80", "443", "8080", "22"],
      "correctAnswer": [1],
      "explanation": "HTTPS працює на порті 443"
    }
  ]
}
GET PATCH DEL /api/quizzes/[id] — Деталі / Редагування / Видалення тесту

GET — повертає тест з питаннями (для техніка без правильних відповідей).

PATCH — оновлення тесту та питань (ADMIN). DELETE — видалення (ADMIN).

POST /api/quizzes/[id]/attempt — Відправити відповіді на тест
{
  "answers": {
    "1": [1],
    "2": [0, 2],
    "3": [0]
  }
}

Повертає результат: score, passed, детальний розбір кожного питання з правильними відповідями.

POST /api/quizzes/[id]/assign — Призначити тест працівникам (ADMIN)
{
  "userIds": [4, 5, 6],
  "deadline": "2026-04-15"
}

Надсилає Telegram та in-app сповіщення кожному призначеному користувачу.

POST /api/quizzes/seed — Створити готові шаблони тестів (ADMIN)

Створює 6 шаблонів: Мережі, Принтери, Windows, Сервери, Безпека, Hardware (54 питання).

GET /api/quizzes/results — Зведена таблиця результатів (ADMIN)

Повертає матрицю: користувачі × тести з найкращими балами та кількістю спроб.

🔔 Сповіщення (Notifications)

In-app сповіщення з дзвіночком, звуком та polling кожні 30 секунд.

GET PATCH /api/notifications — Список сповіщень / Позначити прочитаним Потрібна авторизація

GET

Параметр ?unread=1 — тільки непрочитані. Повертає { notifications, unreadCount }.

PATCH — Позначити прочитаним

// Одне сповіщення
{ "id": 42 }
// Все прочитано
{ "markAll": true }

Типи сповіщень

ТипОпис
TICKET_CREATEDНова заявка (адмінам/менеджерам)
TICKET_ASSIGNEDПризначено заявку
STATUS_CHANGEDЗміна статусу заявки
TICKET_COMMENTНовий коментар
QUIZ_ASSIGNEDПризначено тест
SLA_WARNINGSLA наближається/прострочено

📢 Розсилки (Broadcast)

Масові розсилки через Telegram та Viber з фільтрацією аудиторії.

POST /api/broadcast — Створення розсилки Потрібна авторизація

Тіло запиту

{
  "name": "Акція березень",
  "message": "Шановні клієнти! Знижка 20% на обслуговування до кінця місяця.",
  "channel": "telegram",
  "filters": {
    "organizationId": "clx1org...",
    "tags": ["active"]
  }
}
ПолеТипОпис
namestring requiredНазва розсилки
messagestring requiredТекст повідомлення
channelenum requiredtelegram | viber
filtersobjectФільтри аудиторії (організація, теги)

Відповідь (201)

{
  "id": "clx1br...",
  "name": "Акція березень",
  "status": "DRAFT",
  "recipientCount": 45,
  "createdAt": "2026-04-01T10:00:00.000Z"
}
GET /api/broadcast — Список розсилок Потрібна авторизація

Відповідь (200)

{
  "broadcasts": [
    {
      "id": "clx1br...",
      "name": "Акція березень",
      "status": "SENT",
      "channel": "telegram",
      "recipientCount": 45,
      "sentCount": 42,
      "failedCount": 3,
      "createdAt": "2026-04-01T10:00:00.000Z",
      "sentAt": "2026-04-01T10:05:00.000Z"
    }
  ]
}
POST /api/broadcast/:id/send — Відправити розсилку Потрібна авторизація

Опис

Запускає відправку розсилки всім отримувачам. Статус змінюється на SENDING, потім на SENT після завершення.

Відповідь (200)

{
  "id": "clx1br...",
  "status": "SENDING",
  "recipientCount": 45,
  "message": "Розсилка запущена"
}
Увага: Розсилку можна відправити лише один раз. Після відправки статус змінюється на SENT.

🔧 Утиліти (Utilities)

Import/Export, шаблони заявок, планове ТО, підписки та логування помилок.

POST /api/import — CSV імпорт (контакти, обладнання, організації) Потрібна авторизація

Опис

Імпорт даних з CSV-файлу. Підтримує контакти, обладнання та організації. Формат: multipart/form-data.

Тіло запиту (multipart/form-data)

Content-Type: multipart/form-data

file: [CSV file]
type: "contacts"  // contacts | equipment | organizations
ПолеТипОпис
filefile requiredCSV-файл з даними
typeenum requiredcontacts | equipment | organizations

Відповідь (200)

{
  "imported": 42,
  "skipped": 3,
  "errors": [
    { "row": 5, "error": "Дублікат email" }
  ]
}
GET /api/export?type=contacts&format=csv — CSV експорт Потрібна авторизація

Query-параметри

ПараметрТипОпис
typeenum requiredcontacts | equipment | organizations
formatenumcsv (за замовчуванням)

Відповідь

Бінарний файл text/csv

Content-Disposition: attachment; filename="contacts-2026-04-01.csv"
GET /api/ticket-templates — Список шаблонів заявок Потрібна авторизація

Відповідь (200)

{
  "templates": [
    {
      "id": "clx1tmpl...",
      "name": "Заміна картриджа",
      "title": "Заміна картриджа — {printer}",
      "description": "Потрібна заміна картриджа",
      "priority": "MEDIUM",
      "categoryId": "clx1cat..."
    }
  ]
}
POST /api/ticket-templates — Створення шаблону заявки Потрібна авторизація

Тіло запиту

{
  "name": "Заміна картриджа",
  "title": "Заміна картриджа — {printer}",
  "description": "Потрібна заміна картриджа в принтері",
  "priority": "MEDIUM",
  "categoryId": "clx1cat..."
}

Відповідь (201)

{
  "id": "clx1tmpl...",
  "name": "Заміна картриджа",
  "createdAt": "2026-04-01T10:00:00.000Z"
}
GET /api/planned-maintenance — Список планового ТО Потрібна авторизація

Відповідь (200)

{
  "items": [
    {
      "id": "clx1pm...",
      "name": "Щомісячна перевірка принтерів",
      "frequency": "MONTHLY",
      "templateId": "clx1tmpl...",
      "storeId": "clx1store...",
      "nextRunAt": "2026-05-01T09:00:00.000Z",
      "isActive": true
    }
  ]
}
POST /api/planned-maintenance — Створення планового ТО Потрібна авторизація

Тіло запиту

{
  "name": "Щомісячна перевірка принтерів",
  "frequency": "MONTHLY",
  "dayOfMonth": 1,
  "templateId": "clx1tmpl...",
  "storeId": "clx1store...",
  "assigneeId": "clx1user..."
}
ПолеТипОпис
namestring requiredНазва планового ТО
frequencyenum requiredDAILY | WEEKLY | MONTHLY | QUARTERLY
templateIdstringID шаблону заявки
storeIdstringID об'єкта
assigneeIdstringID виконавця

Відповідь (201)

{
  "id": "clx1pm...",
  "name": "Щомісячна перевірка принтерів",
  "nextRunAt": "2026-05-01T09:00:00.000Z"
}
Автоматизація: Cron-задача автоматично створює заявки за розкладом на основі шаблону
POST /api/subscription/pay — Створення платежу (LiqPay/WayForPay) Потрібна авторизація

Тіло запиту

{
  "provider": "liqpay",
  "plan": "professional",
  "period": "monthly"
}
ПолеТипОпис
providerenum requiredliqpay | wayforpay
planenum requiredstarter | professional | enterprise
periodenummonthly | yearly

Відповідь (200)

{
  "paymentUrl": "https://www.liqpay.ua/checkout/...",
  "orderId": "ord_abc123"
}
POST /api/error-log — Клієнтське логування помилок Потрібна авторизація

Тіло запиту

{
  "message": "TypeError: Cannot read property 'id' of undefined",
  "stack": "at TicketList.render (tickets.js:42:15)...",
  "url": "/tickets",
  "userAgent": "Mozilla/5.0..."
}
ПолеТипОпис
messagestring requiredТекст помилки
stackstringStack trace
urlstringURL сторінки де виникла помилка
userAgentstringUser Agent браузера

Відповідь (201)

{
  "id": "clx1err...",
  "message": "Помилку зафіксовано"
}
Моніторинг: Помилки зберігаються в системному логу та доступні в розділі Налаштування

🔧 Система (System)

Службові ендпоінти для моніторингу стану системи.

GET /api/health — Перевірка стану системи Без авторизації

Відповідь (200)

{
  "status": "ok",
  "version": "4.0.0",
  "uptime": 1234567,
  "database": "connected",
  "timestamp": "2026-04-01T12:00:00.000Z"
}
Моніторинг: Використовується для uptime-моніторингу та health checks. Повертає 503 якщо з'єднання з базою даних відсутнє.

📖 Гайд з інтеграції

Покрокові інструкції для підключення зовнішніх систем до IT CITY CRM.

1. Автентифікація API

  1. Авторизуйтесь через POST /api/auth/callback/credentials
  2. Отримайте session cookie (next-auth.session-token)
  3. Передавайте cookie в кожному запиті
  4. Або використовуйте Bearer-токен у заголовку Authorization
// Приклад з Bearer токеном
fetch('https://crm.its.te.ua/api/tickets', {
  headers: {
    'Authorization': 'Bearer your-session-token',
    'Content-Type': 'application/json'
  }
})

2. Налаштування вебхуку

  1. Отримайте секретний ключ від адміністратора CRM
  2. Налаштуйте URL вебхуку: https://crm.its.te.ua/api/webhooks
  3. Підписуйте тіло запиту HMAC-SHA256
  4. Надсилайте підпис у заголовку X-Webhook-Signature
// Генерація підпису
const crypto = require('crypto');
const signature = crypto
  .createHmac('sha256', WEBHOOK_SECRET)
  .update(JSON.stringify(body))
  .digest('hex');

3. Формат вебхук-payload

  1. Використовуйте Content-Type: application/json
  2. Обов'язкові поля: event, data
  3. Підтримувані події: ticket.created, ticket.updated
  4. Відповідь 200 = успішно оброблено
{
  "event": "ticket.created",
  "data": {
    "id": 12345,
    "subject": "Тема заявки",
    "description": "Опис проблеми",
    "priority": "high",
    "store": { "name": "..." },
    "contact": { "name": "...", "phone": "..." }
  }
}

4. Rate Limits

  • Загальні ендпоінти: 100 запитів / хвилину
  • Auth ендпоінти: 5 запитів / 15 хвилин
  • Webhooks: 30 запитів / хвилину
  • Експорт: 5 запитів / хвилину

При перевищенні ліміту повертається статус 429 Too Many Requests з заголовком Retry-After.

// Відповідь при перевищенні ліміту
HTTP/1.1 429 Too Many Requests
Retry-After: 60

{
  "error": "Too many requests",
  "retryAfter": 60
}
Підтримка: З питань інтеграції звертайтесь до адміністратора CRM. Для тестування використовуйте staging-середовище.

💬 Chat Inbox API (v5.0)

Омніканальний чат з Telegram та Viber ботами, AI-агентом, базою знань, автопризначенням та SLA.

POST /api/chat/telegram Telegram webhook Public

Приймає повідомлення від Telegram Bot API. Обробляє текст, фото, відео, файли, контакт (request_contact), callback_query (рейтинг).

Логіка

Створює/знаходить ChatSession → зберігає повідомлення → ідентифікує клієнта → Zabbix-перевірка → AI-відповідь (якщо немає оператора) → автопризначення → сповіщення.

POST /api/chat/viber Viber webhook Public

Приймає повідомлення від Viber Bot. Повна паритетність з Telegram: кнопки (Viber Keyboard), рейтинг, Zabbix, AI.

GET /api/chat/sessions Список заявок чату Auth

Query параметри

statusstringФільтр статусу: _all_open, open, closed, deleted або кастомний ID
contactIdnumberФільтр по контакту
storeIdnumberФільтр по об'єкту
operatorIdnumberФільтр по оператору

Відповідь

Масив ChatSession з вкладеними contact, store, operator, останнє повідомлення, _count.messages. Плюс об'єкт counts з кількістю по кожному статусу.

GET /api/chat/sessions/:id Деталі заявки + повідомлення Auth

Повна інформація: всі повідомлення з replyTo, контакт (customFields, stores), оператор, канал, рейтинг.

PATCH /api/chat/sessions/:id Оновлення заявки Auth

Body параметри

statusstringНовий статус. При closed — надсилає запит рейтингу в Telegram/Viber
subjectstringТема заявки
operatorIdnumber|nullПризначити/зняти оператора
contactIdnumberПрив'язати контакт
storeIdnumberПрив'язати об'єкт
POST /api/chat/sessions/:id/message Відправка повідомлення Auth

FormData: text, file (attachment), isComment (внутрішній коментар), replyToId (цитування).

Коментарі не надсилаються клієнту. Звичайні повідомлення надсилаються через Telegram/Viber API.

GET /api/chat/stats Статистика чатів Auth

KPI: всього заявок, закрито, середній час відповіді, середній рейтинг. Графіки по статусах, каналах, операторах. Список відгуків з фільтрацією.

Query

fromdateПочаток періоду (ISO)
todateКінець періоду (ISO)
GET /api/chat/search Пошук: база знань, шаблони, непрочитані Auth

Query параметри

typestringkb — пошук статей, templates — шаблони відповідей, unread — кількість непрочитаних
qstringРядок пошуку (для type=kb)
GET /api/chat/sla-check Перевірка SLA (cron) Cron

Авторизація через x-cron-secret header. Знаходить заявки де останнє повідомлення від клієнта старше SLA, надсилає сповіщення через Telegram та in-app.

📡 Zabbix API

Інтеграція з Zabbix для моніторингу мережевого обладнання на об'єктах.

GET /api/zabbix Статус хоста / проблеми / звіт Auth

Query параметри

actionstringstatus — статус хоста, equipment-report — звіт обладнання
storeIdnumberID об'єкта (для action=status)

Повертає: проблеми (severity: High), inventory роутера (модель, прошивка, S/N), тривалість простою.

📚 База знань API

CRUD статей бази знань з публічним доступом через окремий сервер (порт 3099).

GET /api/knowledge Список статей Auth

Повертає всі статті з title, content, tags, isPublic, slug. Фільтрація по тегах та пошук по заголовку.

POST /api/knowledge Створення статті Auth
titlestringrequired Заголовок
contentstringHTML-контент (rich text)
tagsstring[]Теги для категоризації
isPublicbooleanПублічна доступність (через kb-server)
GET /kb Публічний список статей Public

Окремий сервер (порт 3099). Список публічних статей з пошуком та фільтрацією по тегах. Без авторизації.

GET /kb/:slug Публічна стаття Public

HTML-сторінка статті з повним контентом. Slug генерується транслітерацією з кирилиці.