Руководство по интеграции с ERP и другими системами
Оцените объём интеграции до того, как её строить: до чего API дотягивается, до чего нет и где проходят границы
Это руководство — для разработчика, который оценивает объём интеграции между FreeMaint и другой системой: ERP, историзатором SCADA, складским или бухгалтерским пакетом. Остальные статьи этого раздела объясняют, как вызывать API. Эта — намеренно о границах, потому что дорого обходится не неверный заголовок, а обнаружение на середине разработки, что возможности, которую вы считали само собой разумеющейся, попросту нет. Каждое ограничение ниже названо намеренно.
Один ключ, три двери
Ключ API (он начинается с fmk_) создаёт администратор в «Настройки компании > API» (/company-settings/api) или сам сотрудник для себя в «Профиль > Безопасность > API-ключи»; он передаётся как Authorization: Bearer fmk_... . Он аутентифицирует ровно три вещи: эндпоинт GraphQL POST /api/v1/graphql, эндпоинт приёма энергоданных, а также /public-api/whoami и /public-api/ping. Он НЕ аутентифицирует REST-маршруты вида /api/v1/{resource} — это собственный API приложения, и для них нужна пользовательская сессия (JWT). Стройте интеграцию на GraphQL: это та поверхность, до которой дотягивается ключ.
Что даёт ключ API
Ключ в открытом виде показывается один раз, при создании, и после этого его уже не восстановить: хранится только хеш SHA-256 и 6 символов, идущих сразу за fmk_, чтобы ключ можно было опознать в списке на экране.
Название
Человекочитаемая метка — «Мост к Oracle ERP», «Киоск линии 2». Выпускайте по одному ключу на каждую систему-потребитель: тогда один можно отозвать, не тревожа остальные.
Уровень доступа
READ, READ_STOCK или FULL — упорядоченная иерархия поверх разрешений владельца. READ выполняет только запросы и используется по умолчанию. READ_STOCK добавляет мутацию adjustStock. FULL разрешает любую операцию, которую разрешает роль владельца, включая создание, изменение и удаление, — а также эндпоинт приёма энергоданных, если владельцу разрешено создавать показания по энергии.
Срок действия
Необязательный expiresAt. После этого момента ключ перестаёт аутентифицировать. Оставьте поле пустым для постоянной служебной интеграции; задайте его для подрядчика или пилотного проекта.
Отзыв
isActive — это переключатель на уровне отдельного ключа, срабатывающий за считаные секунды. Запись сохраняется для аудита: отозванный ключ остаётся прослеживаемым, а не исчезает.
Последнее использование
lastUsedAt фиксирует последний аутентифицированный вызов — этого достаточно, чтобы заметить ключ, которым уже никто не пользуется.
Владелец
Человек, от имени которого действует ключ. Каждый вызов несёт его разрешения и его видимость данных, а его имя подписывает всё, что записывает ключ. Для ERP создайте отдельного пользователя — «Интеграция ERP» — с ролью, покрывающей ровно то, что нужно мосту, и отдайте ключ ему.
Чего ключ API не даёт
Это настоящие ограничения текущей реализации, а не пропуски на этой странице. Если ваша проверка безопасности упирается в любое из них, закладывайте обход сейчас, а не после разработки.
- Никакого списка модулей на уровне ключа. Ключ читает и записывает то, что разрешает роль его владельца, — чтобы сузить его, передайте его владельцу с более узкой ролью. Сверх этого действуют только три уровня доступа: на самом ключе нельзя перечислить модули, которыми он может пользоваться.
- Никакого белого списка IP-адресов. Ключ действует из любой точки интернета. Обращайтесь с ним как с секретом: хранилище секретов вашей платформы, но никогда не репозиторий, общий диск или тикет в поддержку.
- Никакого журнала использования по ключам. Хранится только lastUsedAt. Нет ни истории вызовов по ключу, ни квоты по ключу, ни записи о том, какой ключ выполнил какую операцию. Счётчик суточной квоты ведётся на КОМПАНИЮ и общий для всех ключей, поэтому одна разогнавшаяся интеграция способна исчерпать квоту всех остальных.
- Никаких утверждений от чужого имени. Ключ никогда не утверждает шаг рабочего процесса, даже если его владелец мог бы это сделать: утверждение остаётся действием человека в приложении.
- Одна компания на ключ. Ключ привязан к компании, в которой он создан, и не может обратиться к данным другой компании: запись другой компании просто не находится.
Что можно читать
GraphQL предоставляет 16 запросов на чтение: whoami, workOrders, workOrder(id), assets, asset(id), assetDowntimes, meters(assetId), parts, part (по id или штрихкоду), stockMovements, locations, vendors, customers, purchaseOrders, purchaseOrder(id | reference | erpReference) и floorPlans(locationId). Любой список можно пролистать до конца: workOrders, assets, assetDowntimes, meters, parts, purchaseOrders, locations, vendors и customers принимают limit (от 1 до 200) и offset, а workOrders, assets, parts, purchaseOrders, locations, vendors и customers также принимают updatedSince для инкрементальной синхронизации. Страница по умолчанию — 50 строк, кроме locations, vendors и customers, которые сохраняют прежнее значение по умолчанию — 200. Каждый список упорядочен с дополнительной сортировкой по id, поэтому граница страницы никогда не пропускает и не повторяет строку. stockMovements — это складской журнал, по которому вы выполняете сверку: он читается от старых записей к новым и листается через afterId — передайте id последнего полученного движения, — и такая выборка остаётся точной, даже пока записываются новые движения, а since позволяет начать дозагрузку истории с определённой даты. Заказы-наряды из Корзины не возвращаются никогда. purchaseOrders также фильтруется по status и vendorId; суммы указаны в валюте компании, которую каждый заказ несёт в поле currency.
Проверить ключ и прочитать заказ-наряды одним вызовом: curl -X POST https://freemaint.com/api/v1/graphql -H "Authorization: Bearer $FMK" -H "Content-Type: application/json" -d '{"query":"{ whoami { companyId tier } workOrders(limit: 5, status: \"OPEN\") { id reference title status priority dueDate } }"}'
Что можно записывать
Двенадцать мутаций: создание, изменение и удаление для заказ-нарядов, активов и запчастей, плюс adjustStock для знакового складского движения. Они проходят через те же сервисы, что использует само приложение, поэтому ссылки (WO-…, ASS-…, PART-…), строки аудита и уведомления ведут себя ровно так же, как в интерфейсе. Для склада предпочитайте adjustStock: она пишет строку аудита StockMovement в той же транзакции и отказывается уводить количество ниже нуля, тогда как updatePart(quantity) задаёт абсолютное значение и записывает разницу как корректировку. Для запчасти, запас которой учитывается по местам хранения, передайте locationId в adjustStock; updatePart(quantity) там отклоняется. Одиннадцатая, addMeterReading, записывает показание счётчика через тот же сервис, что и экран счётчиков, поэтому триггеры и обслуживание по наработке реагируют точно так же, как на показание, введённое в приложении. Двенадцатая, setPurchaseOrderErpReference, сохраняет номер документа вашей ERP в заказе на закупку, чтобы потом сопоставить обе записи через purchaseOrder(erpReference: …). Ей нужен ключ с доступом FULL, и она намеренно не вызывает вебхук: ваша ERP никогда не получает собственную запись в виде события.
Чего API не покрывает вовсе
Схема GraphQL содержит заказы-наряды, оборудование, простои оборудования, запчасти и движения их запасов, счётчики, заказы на закупку, площадки, поставщиков, клиентов и планы помещений. Ничего больше. Ниже перечислены полностью работающие части FreeMaint, у которых сегодня НЕТ никакой поверхности API: их нельзя прочитать, записать или подписаться на них из интеграции, и никакая комбинация существующих запросов до них не доходит.
- Инспекции — ни шаблоны, ни запланированные прогоны, ни записанные результаты, ни коды действий. Нет ни запроса, ни мутации, ни события вебхука для инспекций.
- Пользователи, роли и команды — через этот API нельзя ни получить список пользователей, ни создать, ни отключить пользователя. Заведение пользователей происходит в приложении; на тарифе Enterprise его можно автоматизировать через SCIM, но это отдельный механизм, не входящий в этот API.
- Проекты — ни запроса, ни мутации.
- Заявки на закупку — ни запроса, ни мутации. Заказы на закупку читаются (purchaseOrders, purchaseOrder), ваша ERP может записать в них свой номер (setPurchaseOrderErpReference), а вебхуки purchase_order.* сообщают о них, но создать, изменить или принять заказ на склад через API нельзя: это остаётся в приложении.
- Файлы, фотографии и вложения — нельзя ни загрузить документ или фотографию, ни прочитать их обратно. Фотографии заказ-нарядов, изображения запчастей и свидетельства инспекций лежат вне API. URL, появляющийся в ответе, например imageUrl запчасти, — это не путь для загрузки.
- Графики планово-предупредительного обслуживания, учёт времени, задачи и чек-листы — не раскрыты.
Важно
Вебхуки — каталог событий
Вебхуки — это «push»-половина интеграции: вместо опроса FreeMaint сам отправляет POST на ваш URL в тот момент, когда что-то происходит. Их настраивают в «Настройки компании > Вебхуки» (/company-settings/webhooks) и подписывают каждый вебхук на нужные события. Каталог — фиксированный список.
- work_order.created — создан заказ-наряд
- work_order.completed — заказ-наряд достиг статуса «Завершён»
- work_order.status_changed — заказ-наряд перешёл в любой статус; в полезной нагрузке передаётся new_status. Срабатывает дополнительно к work_order.completed, который не изменился
- request.created — подана заявка на обслуживание
- request.status_changed — заявка согласована, отклонена или отменена
- part.low_stock — запчасть опустилась до минимума или ниже
- part.out_of_stock — запчасть дошла до нуля; это отдельное событие, а не разновидность low_stock
- part.low_stock_summary — одна ежедневная сводка на компанию с запчастями на точке заказа или ниже, включая те, что ни разу не двигались
- part.out_of_stock_summary — та же ежедневная сводка для запчастей с нулевым остатком
- part.consumed — запчасть списана на заказ-наряд или возвращена с него; quantity знаковое
- compliance.chain_divergence — нарушена хеш-цепочка аудита (Enterprise)
- asset.downtime_started — открыт простой оборудования
- asset.downtime_ended — простой оборудования закрыт; длительность можно списывать
- report.daily_summary — завершённые за день работы, раз в сутки в выбранный вами час
- purchase_order.created — создан заказ на закупку; полезная нагрузка содержит заказ (reference, status, total_cost, currency, expected_date, erp_reference) и его поставщика
- purchase_order.status_changed — заказ на закупку сменил статус (утверждён, заказан, отменён…); полезная нагрузка содержит previous_status и new_status
- purchase_order.received — по заказу на закупку принята поставка (отправляется при каждой поставке, включая частичные); полезная нагрузка перечисляет каждую позицию с quantity_ordered, quantity_received (эта поставка), quantity_received_total и quantity_remaining, а также receipt_id, fully_received и closed_short; new_status — PARTIALLY_RECEIVED, пока товар ещё ожидается, и RECEIVED, когда заказ выполнен или закрыт
Внимание
Конверт доставки
Каждая доставка GENERIC — это POST, тело которого следует одному и тому же конверту из трёх ключей: event, timestamp и data, где data индексировано по сущности. С ним идут три заголовка. X-FreeMaint-Signature имеет вид t=<секунды unix>,v1=<hex>, где v1 — это HMAC-SHA256 точной строки «<t>.<сырое тело>» с ключом, равным секрету этого вебхука; X-FreeMaint-Event повторяет имя события; X-FreeMaint-Delivery-Id идентифицирует доставку. Проверяйте подпись по СЫРОМУ телу, до разбора: повторная сериализация JSON меняет байты, и подпись не сойдётся.
{ "event": "work_order.created", "timestamp": "2026-09-05T08:14:02.371Z", "data": { "work_order": { "id": 5521, "title": "Утечка на насосе № 3", "status": "OPEN", "priority": "HIGH", "dueDate": "2026-09-08T00:00:00.000Z" } } }
Надёжность и наблюдение
Доставка выполняется 3 раза с экспоненциальной задержкой на базе 2 секунд — примерно 2 с, затем 4 с, затем 8 с, — и каждая попытка обрывается по таймауту через 10 секунд. Ответ 2xx помечает её как доставленную. Ответы 5xx и сетевые ошибки повторяются. Ответ 4xx считается окончательным отказом и НЕ повторяется вовсе: если ваш эндпоинт отвечает 400 или 401 из-за ошибки на вашей стороне, доставка на этом останавливается, и вернуть её сможет только ручной повтор.
- Каждая доставка сохраняется: событие, точная отправленная полезная нагрузка, статус, HTTP-код, который вы вернули, первые ~500 символов вашего тела ответа, число попыток и текст ошибки.
- Статусы такие: PENDING, DELIVERED, FAILED, RATE_LIMITED (достигнут потолок Starter в 100 доставок в сутки) и SKIPPED_TIER (тариф больше не даёт права на это назначение).
- Сохранённую доставку можно повторить из истории вебхука. Она отправляет сохранённую полезную нагрузку без изменений, поэтому бизнес-ключи внутри неё остаются теми же, и приёмник, который корректно дедуплицирует, не проведёт операцию дважды.
- Каждый вебхук несёт lastTriggeredAt, lastSuccessAt, lastFailureAt и failureCount — этого достаточно для проверки состояния, не открывая полную историю.
- Список вебхуков и журнал доставок доступны для чтения на любом тарифе, поэтому интеграция, остановленная понижением тарифа, остаётся видимой, а не умирает втихую.
- Ничто не предупредит вас, когда доставки начнут падать, — ни письмо, ни уведомление. Если тихий отказ для вас критичен, опрашивайте журнал доставок из собственного мониторинга.
Безопасность
URL проверяются при сохранении вебхука, и правила намеренно строгие: вебхук — это исходящий запрос, который делают наши серверы, поэтому мягкие правила стали бы вектором SSRF.
- Ключи хранятся в виде хешей SHA-256 — открытое значение невозможно восстановить из FreeMaint после создания.
- URL вебхука должны использовать https. Обычный http и любая другая схема отклоняются.
- Принимаются только порты 443 и 8443.
- Петлевые, link-local и частные адреса (127.x, 10.x, 192.168.x, 172.16–31.x, ::1 и им подобные) отклоняются: приёмник должен быть достижим из публичного интернета. Системе, которая существует только внутри вашего предприятия, нужен публичный ретранслятор или туннель.
- Учётные данные в URL (https://user:pass@host) отклоняются.
- Редиректы не выполняются — POST получает ровно тот эндпоинт, который вы зарегистрировали.
- У каждого вебхука свой секрет подписи, и есть действие для его ротации. Ротация вступает в силу немедленно, поэтому разверните новый секрет на своей стороне в том же окне.
- Назначения SLACK и TEAMS получают сообщение в формате платформы и НИКАКОГО заголовка HMAC от FreeMaint: для них единственная аутентификация — секретность самого URL. Используйте GENERIC везде, где подпись нужно проверять криптографически.
Схемы, которые сегодня работают хорошо
Вот интеграции, которые текущая поверхность поддерживает чисто, и деталь, делающая каждую из них безопасной в промышленной эксплуатации.
Отражать расход в ERP
Подпишитесь на part.consumed. quantity знаковое — отрицательное при выдаче на работу, положительное при возврате, — а полезная нагрузка несёт stock_movement_id, который остаётся стабильным при повторах и переотправках: используйте его как ключ идемпотентности. Мутация adjustStock намеренно НЕ поднимает это событие — именно это не даёт вашим же записям вернуться к вам эхом и зациклиться.
Отправлять складские корректировки внутрь
Вызывайте adjustStock со знаковой дельтой со своей стороны. Ключ READ_STOCK способен на это, не имея права ничего создавать, изменять или удалять, — точное совпадение для одностороннего моста складских остатков.
Создавать заказ-наряды из другой системы
createWorkOrder по тревоге SCADA, тикету службы поддержки или уведомлению о поломке из ERP. Заказ-наряд получает свою обычную ссылку и поднимает work_order.created, как любой другой.
Держать каталог запчастей синхронным
parts(updatedSince:) возвращает только то, что изменилось с выбранного вами момента. createPart и updatePart принимают customReference — ваш собственный код запчасти, — а part(barcode:) затем по нему сопоставляет, так что запчасть можно найти по идентификатору, который ваша ERP уже использует. Уникальность не проверяется; обеспечивать её — на вашей стороне.
Замкнуть цикл на завершении
work_order.completed несёт заказ-наряд и его предыдущий статус — этого достаточно, чтобы закрыть уведомление об обслуживании на другой стороне.
Что нужно иметь по тарифу
Два отдельных ограничителя: один для API, другой для вебхуков. Компания на тарифе Starter может получать вебхуки, не имея доступа к API.
GraphQL API и ключи API
Business и выше. На Core и Starter ключ создать нельзя вообще.
Суточная квота
5 000 вызовов в сутки на Business, 100 000 на Enterprise. Считается по компании и по операциям, обнуляется в 00:00 UTC — объединение операций в один HTTP-запрос экономит сетевые обращения, но никогда не квоту.
Вебхуки
Starter и выше, с потолком 100 доставок в сутки; без ограничений начиная с Business.
Назначения Slack и Teams
Enterprise. Обычный вебхук доступен уже с Starter.
Совет
Похожие статьи
Эта страница была полезна?