База знаний WiFly

Подключение тикет-системы Партнёра к порталу техподдержки WiFly по REST API

Портал: support.wifly.net (MantisBT 2.21.0, REST API)

Кому: разработчикам интеграций на стороне Партнёра

Что получится: заявки создаются и ведутся из вашей тикет-системы, статусы и ответы поддержки возвращаются к вам

Партнёры WiFly могут подключить свою тикет-систему (CRM, helpdesk, ITSM) к нашему порталу техподдержки: операторы работают в одном привычном интерфейсе, а обращения автоматически заводятся на портале, обмениваются комментариями и вложениями и получают актуальные статусы. Эта статья — сокращённая версия руководства по интеграции; полный комплект (PDF на 25 страниц, OpenAPI-спецификация, готовые примеры на curl / Python / C#, docker-compose стенда) скачивается одним архивом:

Модель интеграции

Портал построен на MantisBT и наружу отдаёт его штатный REST API — проприетарной обёртки нет, готовые клиентские библиотеки для MantisBT применимы. Весь обмен покрывают три вызова:

Действие Вызов Направление
Создать обращение POST /api/rest/issues ваша система → портал
Комментарий / вложение POST /api/rest/issues/{id}/notes ваша система → портал
Статусы и ответы поддержки GET /api/rest/issues?filter_id=reported (опрос раз в 5 минут) ваша система запрашивает у портала

Каждая система Партнёра работает от отдельной сервисной учётной записи с доступом только к своему проекту: клиент видит и изменяет только обращения, заведённые от его имени. Портал не умеет искать по номеру вашего тикета — связку систем держит маппинг «ваш номер → id обращения» в вашей БД, а кастомное поле external_ticket_id дублирует его на портале для ручного разбора.

Подключение

При подключении мы: заводим проект (или подключаем к существующему), создаём сервисную учётку svc_<client>_integration с уровнем reporter, выпускаем API-токен и передаём его по защищённому каналу, настраиваем кастомные поля и согласуем окно проверочного прогона. Вы получаете: базовый URL https://support.wifly.net/api/rest, свой project_id, токен, список категорий, имена кастомных полей (external_ticket_id, contact_email, contact_phone) и лимит вложения (5 000 000 байт ≈ 4,8 МБ до base64). Контакт по вопросам интеграции — welcome@wifly.net: при эскалации указывайте время с часовым поясом, метод и путь, код ответа, тело ошибки, X-Mantis-Version и ваш номер тикета; токен не прикладывайте.

Тестового контура нет. Портал существует в единственном экземпляре, любое обращение через API видит дежурная смена. Отладка ведётся на вашем локальном стенде (MantisBT в Docker или мок по OpenAPI — оба в архиве), на боевом портале выполняется только короткий согласованный прогон.

Аутентификация: без префикса Bearer

API-токен передаётся в заголовке Authorization как есть:

POST /api/rest/issues HTTP/1.1
Host: support.wifly.net
Authorization: <API-TOKEN>
Content-Type: application/json

⚠ Поддержка схемы Authorization: Bearer <токен> появилась в MantisBT позже версии портала. Портал считает токеном всё значение заголовка, поэтому Bearer abc123 даст 403 API token not found. Многие HTTP-клиенты добавляют Bearer сами (в .NET — AuthenticationHeaderValue, в Python — HTTPBearerAuth) — здесь заголовок задаётся вручную.

Проверка доступа: GET /api/rest/users/me должен вернуть 200 и вашу сервисную учётку. Токен храните в секрет-хранилище (не в коде и не в git), ротация — раз в 12 месяцев и немедленно при компрометации, TLS 1.2+.

Создание обращения

Обязательные поля: summary (до 128 символов), description, category, project. Вложения — внутри JSON в base64 (одной строкой, без переносов и префикса data:).

{
  "project":  { "id": 1 },
  "category": { "name": "Инцидент" },
  "summary":  "Не открывается модуль отчётности",
  "description": "При переходе в раздел «Отчёты» появляется ошибка 500.",
  "priority": { "name": "high" },
  "severity": { "name": "major" },
  "custom_fields": [
    { "field": { "name": "external_ticket_id" }, "value": "CRM-2026-000123" },
    { "field": { "name": "contact_email" },      "value": "ivanov@company.example" }
  ],
  "files": [ { "name": "error-500.png", "content": "iVBORw0KGgoAAAANSUhEUg…" } ]
}

Ответ 201 Created содержит issue.idсохраните его у себя: это единственный надёжный способ впоследствии обратиться к обращению.

Категорию портал не проверяет. Несуществующее имя категории не даёт ошибку — обращение создаётся с 201 и попадает в чужую очередь, где его никто не ждёт. Сверяйте имя со справочником проекта до отправки (GET /api/rest/projects/{id}, поле categories[].name) и кэшируйте список у себя (обновление раз в сутки достаточно). Опечатка в категории — самая тихая из возможных ошибок интеграции.

Остальные вызовы

  • GET /issues/{id} — обращение целиком: поля, история, заметки (notes), вложения; отдаёт ETag для условных запросов. Приватные заметки поддержки клиентской учётке не возвращаются.
  • GET /issues?project_id=…&filter_id=reported&page=…&page_size=50 — список своих обращений; filter_id=reported — заведённые данной учёткой. Параметр select порталом не поддерживается, ответ всегда приходит целиком.
  • POST /issues/{id}/notes — комментарий (view_state: public), файлы — в поле files заметки. Отдельного эндпойнта чтения заметок нет — они приходят при чтении обращения.
  • POST /issues/{id}/files / GET /issues/{id}/files/{file_id} — вложения к обращению.
  • GET /projects/{id} — справочники: категории, версии, кастомные поля.

Маппинг полей и статусы

У вас На портале Обяз.
Тема summary (≤128) да
Описание description да
Тип обращения category.name — только из списка проекта да
Номер вашего тикета custom_fields[external_ticket_id] да
Контакт custom_fields[contact_email] (невалидный e-mail → 400), contact_phone e-mail — да, по регламенту*
Срочность / влияние priority.name / severity.name нет
Статус status.nameтолько чтение: жизненным циклом управляет поддержка

* Портал технически не проверяет наличие contact_email (обращение создастся и без него) — обязательность здесь требование регламента поддержки, а не API. Не рассчитывайте на серверную проверку: заполняйте поле на своей стороне. Валидность формата при этом проверяется — невалидный адрес даёт 400.

Статусы: new (10) → acknowledged (30) → confirmed (40) → assigned (50) → resolved (80) → closed (90).

Отдельно стоит feedback (20) — требуется уточнение от клиента. Это не ступень между new и acknowledged: поддержка может перевести обращение в feedback в любой момент, когда ей нужен ответ. Для интеграции это самый значимый статус — он требует действия с вашей стороны: покажите вопрос пользователю и передайте его ответ комментарием (POST /issues/{id}/notes).

Рекомендуемое соответствие приоритетов: P1 → immediate/block, P2 → urgent/crash|major, P3 → high/major, P4 → normal/minor, консультация → low/feature. Зафиксируйте выбранный маппинг у себя — поддержка ориентируется на присланные значения при расчёте времени реакции.

Получение статусов: polling (и вебхуки как опция)

Базовый способ — опрос раз в 5 минут с ETag: GET /issues?project_id=…&filter_id=reported с заголовком If-None-Match: <сохранённый ETag>; ответ 304 — изменений нет. Для каждого обращения сравнивайте updated_at с сохранённым (даты приводите к UTC), импортируйте новые публичные заметки по note.id. Закрытые обращения снимайте с опроса через 14 дней.

Push-уведомления (вебхуки) — опция, включается по заявке: в ядре MantisBT их нет, они реализуются плагином на нашей стороне. Контракт: события issue.created / updated / status_changed / note_added / closed, подпись HMAC-SHA256 в X-Support-Signature, идемпотентность по X-Support-Delivery, ответ 2xx за 5 секунд, ретраи с паузами 1–625 с. Даже с вебхуками оставляйте polling раз в час как подстраховку.

Ошибки, дубли и ловушки

Код Значение Ретрай
400 Некорректное тело, неизвестное кастомное поле, невалидный e-mail, превышен лимит вложения нет — исправить данные
401 / 403 Нет заголовка / токен не найден (часто — префикс Bearer) / нет прав нет
404 Обращение удалено или недоступно нет — снять с синхронизации
429 Слишком много запросов да, по Retry-After
500 / 503 Ошибка портала / режим обслуживания да, с backoff (1→2→4→8→16 с + джиттер)
200 + text/html Отказ в доступе, замаскированный под успех нет — трактовать как 403

Главная ловушка: при обращении к чужому проекту портал возвращает не 403, а 200 OK с HTML-страницей «Доступ запрещён». Код, который сразу вызывает response.json(), упадёт с ошибкой разбора и может увести интеграцию в бесконечные ретраи. Перед разбором тела проверяйте Content-Type: application/json и наличие заголовков X-Mantis-*.

Дубли. Заголовок идемпотентности MantisBT не поддерживает — отсутствие дублей обеспечивает ваша система: ведите маппинг «ваш номер → issue.id»; при неопределённом исходе (таймаут, обрыв) не повторяйте отправку вслепую — сначала найдите своё external_ticket_id в последних обращениях filter_id=reported. Отправку ставьте в очередь pending → sent → confirmed.

Обязательные предохранители в коде: kill switch (флаг, мгновенно отключающий отправку), ограничитель частоты, жёсткий предел числа обращений за запуск. Нагрузочное тестирование без согласования недопустимо. В логи пишите метод, путь, код, issue.id, X-Mantis-Version — и никогда токен или содержимое вложений.

Отладка и вывод в прод

  1. Локальный стенд — MantisBT в Docker (готовый docker-compose.yml в архиве, порт 8989): настоящие коды, id, категории, ETag, права. Назовите категории и кастомные поля как в боевом проекте — тогда переход в прод сводится к смене URL, токена и project_id.
  2. Мок по OpenAPI (Prism) — для автотестов в CI: проверяет форму запросов, но не бизнес-логику.
  3. Согласованный прогон на портале — сначала проверки без записи (/users/me, /projects/{id}, сверка категорий и полей), затем 3–5 тестовых обращений в согласованное окно: префикс [TEST] в теме, приоритет low. Номера пришлите поддержке — мы их закроем (право DELETE клиентским учёткам не выдаётся).
  4. Пилот — 3–5 рабочих дней параллельно с обычным каналом, затем полный переход; старый канал остаётся резервным, kill switch — в конфигурации навсегда.

Версия стенда. Портал работает на MantisBT 2.21.0, в стенде используется ближайший образ 2.24.1 — по ключевым для интеграции особенностям (Authorization без Bearer, нет select) он ведёт себя так же. Образ 2.28+ начнёт принимать Bearer — и код, отлаженный на нём, получит 403 на портале. Сверяйте X-Mantis-Version на обоих концах. Клиент, сгенерированный по свежей OpenAPI-спецификации MantisBT, будет содержать методы, которых на портале нет (move, DELETE files, управление токенами и др.).

Что в архиве

Файл Что показывает
api-integration-guide.pdf полное руководство: справочник API, маппинг, сценарии, приёмочный чек-лист
openapi/mantisbt_openapi.yaml спецификация для генерации клиента / импорта в Postman
examples/curl/create-issue.sh, add-note.sh, poll-updates.sh создание с вложением, комментарий, опрос с ETag
examples/python/mantis_client.py клиент: ретраи, polling, защита от дублей, проверка категории
examples/csharp/MantisSupportClient.cs клиент на C# (.NET 8)
examples/sandbox/docker-compose.yml локальный стенд MantisBT для отладки

Хотите подключить свою тикет-систему?

Напишите на welcome@wifly.net или в поддержку — заведём проект и сервисную учётку, выдадим токен и параметры, согласуем проверочный прогон.

Запросить подключение → Скачать комплект (.zip)

Частые вопросы

Почему портал отвечает «403 API token not found», хотя токен верный?

Почти всегда — префикс Bearer в заголовке Authorization. Портал принимает токен только без схемы; уберите префикс и задавайте заголовок вручную, минуя встроенные механизмы Bearer-авторизации HTTP-клиента.

Можно ли получить тестовый доступ к порталу?

Отдельного тестового контура нет. Отладка ведётся на вашем локальном стенде (Docker-compose в архиве), а на боевом портале выполняется короткий согласованный прогон с помеченными [TEST] обращениями.

Как узнать, что поддержка ответила или сменила статус?

Базово — опрос GET /issues?filter_id=reported раз в 5 минут с ETag. По заявке включаются push-уведомления (вебхуки) с подписью HMAC — но polling раз в час стоит оставить как подстраховку.

Может ли наша система менять статус или закрывать обращения?

По умолчанию нет: сервисная учётка создаётся с уровнем reporter, статус для неё — только чтение, жизненным циклом управляет служба поддержки. Если вашему сценарию нужна смена статусов, уровень updater выдаётся по запросу с обоснованием — напишите на welcome@wifly.net. Ответ пользователя в любом случае передаётся комментарием (POST /issues/{id}/notes).

Какой лимит на вложения?

5 000 000 байт на файл (~4,8 МБ до base64-кодирования; base64 добавляет ~33 % объёма). Крупные логи архивируйте. Превышение даёт 400 Bad Request.