Przewodnik po integracji z ERP i innymi systemami

Jak wyznaczyć zakres integracji, zanim ją zbudujesz: do czego API sięga, do czego nie i gdzie przebiegają granice

Czas czytania: 9 min
Drukuj tę sekcję

Ten przewodnik jest przeznaczony dla programisty wyznaczającego zakres integracji między FreeMaint a innym systemem — ERP, historianem SCADA, systemem magazynowym albo księgowym. Pozostałe artykuły w tej sekcji wyjaśniają, jak wykonywać wywołania. Ten mówi celowo o granicach, ponieważ kosztownym błędem nie jest zły nagłówek: kosztowne jest odkrycie w połowie budowy, że możliwość, którą przyjęto za pewnik, nie istnieje. Każde ograniczenie wymienione poniżej znalazło się tutaj celowo.

Jeden klucz, troje drzwi

Klucz API (zaczyna się od fmk_) tworzy administrator w Ustawienia firmy > API (/company-settings/api) albo osoba dla siebie w Profil > Bezpieczeństwo > Klucze API, a wysyła się go jako Authorization: Bearer fmk_... . Uwierzytelnia on dokładnie trzy rzeczy: punkt końcowy GraphQL pod POST /api/v1/graphql, punkt przyjmowania danych energetycznych oraz /public-api/whoami i /public-api/ping. NIE uwierzytelnia tras REST pod /api/v1/{resource} — to własne API aplikacji, które wymaga sesji użytkownika (JWT). Buduj integrację na GraphQL: to jest powierzchnia, do której klucz sięga.

Co daje klucz API

Klucz w postaci jawnej pokazywany jest jeden raz, przy tworzeniu, i później nie da się go odzyskać: przechowywany jest wyłącznie skrót SHA-256 oraz 6 znaków następujących po fmk_, aby lista mogła rozpoznać klucz na ekranie.

Nazwa

Czytelna dla człowieka etykieta — „Most do Oracle ERP”, „Kiosk na linii 2”. Wydawaj po jednym kluczu na każdy korzystający system, żeby móc unieważnić jeden bez naruszania pozostałych.

Poziom dostępu

READ, READ_STOCK albo FULL — uporządkowana hierarchia nałożona na uprawnienia właściciela. READ wykonuje wyłącznie zapytania i jest wartością domyślną. READ_STOCK dodaje mutację adjustStock. FULL pozwala na każdą operację, na którą pozwala rola właściciela, w tym tworzenie, modyfikowanie i usuwanie — oraz na punkt przyjmowania danych energetycznych, jeśli właściciel może tworzyć odczyty energii.

Wygaśnięcie

Opcjonalne expiresAt. Po tym momencie klucz przestaje uwierzytelniać. Zostaw je puste dla stałej integracji usługowej; ustaw je dla wykonawcy zewnętrznego albo pilotażu.

Unieważnienie

isActive to przełącznik na poziomie pojedynczego klucza, działający w ciągu kilku sekund. Wpis zostaje zachowany na potrzeby audytu, więc unieważniony klucz pozostaje możliwy do prześledzenia, zamiast zniknąć.

Ostatnie użycie

lastUsedAt zapisuje najnowsze uwierzytelnione wywołanie — tyle wystarczy, by wychwycić klucz, którego już nikt nie używa.

Właściciel

Osoba, w imieniu której działa klucz. Każde wywołanie niesie jej uprawnienia i jej widoczność danych, a jej nazwisko podpisuje to, co klucz zapisuje. Dla systemu ERP utwórz osobnego użytkownika — „Integracja ERP” — z rolą obejmującą dokładnie to, czego potrzebuje most, i przekaż mu klucz.

Czego klucz API nie daje

To są rzeczywiste ograniczenia obecnej implementacji, a nie przeoczenia tej strony. Jeśli Twój przegląd bezpieczeństwa zależy od któregokolwiek z nich, zaprojektuj rozwiązanie z ich uwzględnieniem teraz, a nie po zbudowaniu integracji.

  • Brak listy modułów dla pojedynczego klucza. Klucz odczytuje i zapisuje to, na co pozwala rola jego właściciela — aby go zawęzić, przekaż go właścicielowi o węższej roli. Ponad to obowiązują jedynie trzy poziomy dostępu: na samym kluczu nie można wskazać modułów, z których może korzystać.
  • Brak listy dozwolonych adresów IP. Klucz jest ważny z dowolnego miejsca w internecie. Traktuj go jak sekret: sejf haseł albo magazyn sekretów Twojej platformy, nigdy repozytorium kodu, dysk współdzielony ani zgłoszenie do wsparcia.
  • Brak dziennika użycia dla pojedynczego klucza. Przechowywane jest wyłącznie lastUsedAt. Nie ma historii wywołań dla klucza, nie ma limitu przypisanego do klucza ani zapisu, który klucz wykonał którą operację. Licznik dziennego limitu prowadzony jest na poziomie FIRMY i współdzielony przez wszystkie klucze, więc jedna rozbiegana integracja może wyczerpać limit wszystkich pozostałych.
  • Brak zatwierdzania w czyimś imieniu. Klucz nigdy nie zatwierdza etapu przepływu pracy, nawet jeśli jego właściciel mógłby to zrobić: zatwierdzenia pozostają czynnością człowieka w aplikacji.
  • Jedna firma na klucz. Klucz jest związany z firmą, w której powstał, i nie sięga do danych innej firmy: rekord innej firmy po prostu nie zostanie znaleziony.

Co można odczytać

GraphQL udostępnia 16 zapytań odczytu: whoami, workOrders, workOrder(id), assets, asset(id), assetDowntimes, meters(assetId), parts, part (po id lub kodzie kreskowym), stockMovements, locations, vendors, customers, purchaseOrders, purchaseOrder(id | reference | erpReference) oraz floorPlans(locationId). Każdą listę da się przejrzeć stronami do samego końca: workOrders, assets, assetDowntimes, meters, parts, purchaseOrders, locations, vendors i customers przyjmują limit (od 1 do 200) oraz offset, a workOrders, assets, parts, purchaseOrders, locations, vendors i customers przyjmują dodatkowo updatedSince do synchronizacji przyrostowej. Domyślna strona ma 50 wierszy, z wyjątkiem locations, vendors i customers, które zachowują dotychczasową wartość domyślną 200. Każda lista jest sortowana z dodatkowym kluczem rozstrzygającym po id, więc granica strony nigdy nie pomija ani nie powtarza wiersza. stockMovements to rejestr magazynowy, na podstawie którego przeprowadzasz uzgodnienia: czyta się go od najstarszych wpisów i stronicuje przez afterId — przekaż id ostatniego otrzymanego ruchu — co pozostaje dokładne nawet wtedy, gdy zapisywane są nowe ruchy, a since pozwala rozpocząć uzupełnianie danych historycznych od wybranej daty. Zlecenia pracy znajdujące się w Koszu nigdy nie są zwracane. purchaseOrders filtruje się też po status i vendorId; kwoty są w walucie firmy, którą każde zamówienie podaje w polu currency.

Sprawdź klucz i odczytaj zlecenia pracy jednym wywołaniem: 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 } }"}'

Co można zapisać

Dwanaście mutacji: tworzenie, aktualizacja i usuwanie zleceń pracy, zasobów i części, a do tego adjustStock dla ruchu magazynowego ze znakiem. Przechodzą one przez te same usługi, z których korzysta sama aplikacja, więc oznaczenia (WO-…, ASS-…, PART-…), wpisy audytowe i powiadomienia zachowują się dokładnie tak jak w interfejsie. Dla stanów magazynowych wybieraj adjustStock: zapisuje wpis audytowy StockMovement w tej samej transakcji i odmawia zejścia z ilością poniżej zera, podczas gdy updatePart(quantity) ustawia wartość bezwzględną i zapisuje różnicę jako korektę. W przypadku części, której stan jest śledzony według lokalizacji, przekaż locationId do adjustStock; updatePart(quantity) jest tam odrzucane. Jedenasta, addMeterReading, zapisuje odczyt licznika przez tę samą usługę co ekran liczników, więc wyzwalacze i konserwacja zapobiegawcza oparta na licznikach reagują dokładnie tak, jak przy odczycie wpisanym w aplikacji. Dwunasta, setPurchaseOrderErpReference, zapisuje numer dokumentu z Twojego ERP w zamówieniu zakupu, aby później można było dopasować oba rekordy przez purchaseOrder(erpReference: …). Wymaga klucza z dostępem FULL i celowo nie wywołuje żadnego webhooka: Twój ERP nigdy nie otrzymuje własnego zapisu jako zdarzenia.

Czego API nie obejmuje w ogóle

Schemat GraphQL zawiera zlecenia pracy, zasoby, postoje maszyn, części wraz z ich ruchami magazynowymi, liczniki, zamówienia zakupu, lokalizacje, dostawców, klientów i plany. Nic poza tym. Poniższe elementy to w pełni działające części FreeMaint, które dziś NIE mają żadnej powierzchni API: nie można ich odczytać, zapisać ani subskrybować z integracji, a żadna kombinacja istniejących zapytań do nich nie dociera.

  • Inspekcje — ani szablony, ani zaplanowane przebiegi, ani zapisane wyniki, ani kody działań. Nie istnieje żadne zapytanie o inspekcję, żadna mutacja inspekcji ani żadne zdarzenie webhooka dotyczące inspekcji.
  • Użytkownicy, role i zespoły — przez to API nie można wylistować, utworzyć ani dezaktywować użytkownika. Zakładanie kont odbywa się w aplikacji; w planie Enterprise można je zautomatyzować przez SCIM, który jest osobnym mechanizmem i nie stanowi części tego API.
  • Projekty — brak zapytania i brak mutacji.
  • Zapotrzebowania zakupowe — brak zapytania i brak mutacji. Zamówienia zakupu można odczytać (purchaseOrders, purchaseOrder), Twój ERP może zapisać w nich swój numer (setPurchaseOrderErpReference), a webhooki purchase_order.* o nich informują, ale zamówienia nie da się przez API utworzyć, zmienić ani przyjąć: to pozostaje w aplikacji.
  • Pliki, zdjęcia i załączniki — nie można wgrać dokumentu ani zdjęcia i nie można ich odczytać z powrotem. Zdjęcia zleceń pracy, obrazy części i dowody z inspekcji leżą w całości poza API. Adres URL pojawiający się w odpowiedzi, na przykład imageUrl części, nie jest ścieżką wgrywania.
  • Harmonogramy konserwacji zapobiegawczej, rejestry czasu pracy, zadania i listy kontrolne — nieudostępnione.

Ważne

Zakres swojej pracy wyznacz według tej listy, a nie według tego, co produkt potrafi w przeglądarce. Własne aplikacje webowa i mobilna FreeMaint działają na wewnętrznym API REST, do którego klucz API nie sięga, więc to, że funkcja jest widoczna na ekranie, nie dowodzi, że integracja może nią sterować.

Webhooki — katalog zdarzeń

Webhooki to „pchająca” połowa integracji: zamiast odpytywania FreeMaint wysyła POST na Twój adres URL w chwili, gdy coś się dzieje. Konfigurujesz je w Ustawieniach firmy > Webhooki (/company-settings/webhooks) i subskrybujesz w każdym webhooku te zdarzenia, których potrzebujesz. Katalog to zamknięta lista.

  • work_order.created — zlecenie pracy zostało utworzone
  • work_order.completed — zlecenie pracy osiągnęło stan Zakończone
  • work_order.status_changed — zlecenie pracy zmieniło status na dowolny; ładunek zawiera new_status. Uruchamia się obok work_order.completed, które pozostaje bez zmian
  • request.created — zgłoszenie konserwacyjne zostało przesłane
  • request.status_changed — zgłoszenie zostało zatwierdzone, odrzucone albo anulowane
  • part.low_stock — część spadła do swojego minimum lub poniżej
  • part.out_of_stock — część osiągnęła zero; osobne zdarzenie, nie odmiana low_stock
  • part.low_stock_summary — jedno dzienne zestawienie na firmę z częściami w punkcie zamówienia lub poniżej, łącznie z tymi, które nigdy się nie poruszyły
  • part.out_of_stock_summary — to samo dzienne zestawienie, dla części na zerze
  • part.consumed — część została zużyta na zleceniu pracy albo z niego zwrócona; quantity ma znak
  • compliance.chain_divergence — łańcuch skrótów audytu został przerwany (Enterprise)
  • asset.downtime_started — otwarto postój maszyny
  • asset.downtime_ended — zamknięto postój maszyny; czas trwania można już zaksięgować
  • report.daily_summary — zakończone prace z danego dnia, raz dziennie o wybranej przez Ciebie godzinie
  • purchase_order.created — utworzono zamówienie zakupu; ładunek zawiera zamówienie (reference, status, total_cost, currency, expected_date, erp_reference) i jego dostawcę
  • purchase_order.status_changed — zamówienie zakupu zmieniło status (zatwierdzone, zamówione, anulowane…); ładunek zawiera previous_status i new_status
  • purchase_order.received — przyjęto dostawę do zamówienia zakupu (wysyłane przy każdej dostawie, także częściowej); ładunek wymienia każdą pozycję z quantity_ordered, quantity_received (ta dostawa), quantity_received_total i quantity_remaining oraz receipt_id, fully_received i closed_short; new_status to PARTIALLY_RECEIVED, dopóki oczekiwany jest towar, i RECEIVED, gdy zamówienie jest kompletne lub zamknięte

Ostrzeżenie

Nie ma zdarzenia inspekcji ani zdarzenia usterki czy ustalenia. Jeśli Twój proces brzmi „inspekcja wypada negatywnie, mój ERP reaguje”, dziś nic się dla niego nie uruchomi. Zaplanuj tę lukę wprost, zamiast zakładać, że istnieje wystarczająco podobne zdarzenie.

Koperta dostarczenia

Każde dostarczenie GENERIC to POST, którego treść ma zawsze tę samą kopertę z trzech kluczy: event, timestamp i data, przy czym data jest indeksowane encją. Towarzyszą mu trzy nagłówki. X-FreeMaint-Signature ma postać t=<sekundy uniksowe>,v1=<hex>, gdzie v1 to HMAC-SHA256 dokładnego ciągu "<t>.<surowa treść>" podpisanego sekretem tego webhooka; X-FreeMaint-Event powtarza nazwę zdarzenia; X-FreeMaint-Delivery-Id identyfikuje dostarczenie. Weryfikuj podpis względem SUROWEJ treści, przed parsowaniem — ponowna serializacja JSON zmienia bajty i podpis przestanie się zgadzać.

{ "event": "work_order.created", "timestamp": "2026-09-05T08:14:02.371Z", "data": { "work_order": { "id": 5521, "title": "Wyciek na pompie nr 3", "status": "OPEN", "priority": "HIGH", "dueDate": "2026-09-08T00:00:00.000Z" } } }

Niezawodność i monitorowanie

Dostarczenie jest próbowane 3 razy, z wykładniczym odczekiwaniem o podstawie 2 sekund — mniej więcej 2 s, potem 4 s, potem 8 s — a każda próba przerywana jest po 10 sekundach. Odpowiedź 2xx oznacza je jako dostarczone. Odpowiedzi 5xx i błędy sieci są ponawiane. Odpowiedź 4xx traktowana jest jako trwała odmowa i NIE jest ponawiana w ogóle: jeśli Twój punkt końcowy odpowie 400 albo 401 z powodu błędu po Twojej stronie, to dostarczenie kończy się w tym miejscu i odzyska je wyłącznie ręczne powtórzenie.

  • Każde dostarczenie jest zapisywane: zdarzenie, dokładnie wysłany ładunek, status, zwrócony przez Ciebie kod HTTP, pierwsze ok. 500 znaków treści Twojej odpowiedzi, liczba prób i komunikat błędu.
  • Statusy to PENDING, DELIVERED, FAILED, RATE_LIMITED (osiągnięto limit 100 dostarczeń dziennie z planu Starter) oraz SKIPPED_TIER (plan nie uprawnia już do tego celu).
  • Zapisane dostarczenie można powtórzyć z historii webhooka. Wysyła ono ponownie zachowany ładunek bez zmian, więc klucze biznesowe w jego wnętrzu pozostają identyczne, a odbiorca poprawnie deduplikujący nie zaksięguje go dwa razy.
  • Każdy webhook niesie lastTriggeredAt, lastSuccessAt, lastFailureAt oraz failureCount, co wystarcza do kontroli kondycji bez otwierania pełnej historii.
  • Lista webhooków i jej dziennik dostarczeń są czytelne w każdym planie, więc integracja zatrzymana przez obniżenie planu pozostaje widoczna, zamiast po cichu umrzeć.
  • Nic Cię nie ostrzeże, gdy dostarczenia zaczną się nie udawać — żadnego e-maila, żadnego powiadomienia. Jeśli cicha awaria miałaby znaczenie, odpytuj dziennik dostarczeń z własnego systemu monitoringu.

Bezpieczeństwo

Adresy URL są weryfikowane przy zapisie webhooka, a reguły są celowo surowe: webhook to żądanie wychodzące wykonywane przez nasze serwery, więc reguła pobłażliwa byłaby wektorem SSRF.

  • Klucze przechowywane są jako skróty SHA-256 — wartości jawnej nie da się odzyskać z FreeMaint po utworzeniu.
  • Adresy URL webhooków muszą używać https. Zwykłe http i każdy inny schemat są odrzucane.
  • Akceptowane są wyłącznie porty 443 i 8443.
  • Hosty pętli zwrotnej, link-local oraz z zakresów prywatnych (127.x, 10.x, 192.168.x, 172.16–31.x, ::1 i podobne) są odrzucane: odbiorca musi być osiągalny z publicznego internetu. System istniejący wyłącznie wewnątrz Twojego zakładu potrzebuje publicznego przekaźnika albo tunelu.
  • Poświadczenia w adresie URL (https://user:pass@host) są odrzucane.
  • Przekierowania nie są podążane — punkt końcowy, który rejestrujesz, jest tym, który otrzymuje POST.
  • Każdy webhook ma własny sekret podpisujący i istnieje akcja rotacji sekretu. Rotacja działa natychmiast, więc wdroż nowy sekret po swojej stronie w tym samym oknie czasowym.
  • Cele SLACK i TEAMS otrzymują wiadomość sformatowaną pod daną platformę i ŻADNEGO nagłówka HMAC FreeMaint: dla nich jedynym uwierzytelnieniem jest poufność adresu URL. Używaj GENERIC wszędzie tam, gdzie potrzebujesz weryfikacji kryptograficznej.

Wzorce, które dziś działają dobrze

Oto integracje, które obecna powierzchnia obsługuje czysto, wraz ze szczegółem, który sprawia, że każdą z nich można bezpiecznie prowadzić na produkcji.

Odzwierciedlanie zużycia w systemie ERP

Zasubskrybuj part.consumed. quantity ma znak — ujemny przy wydaniu na zlecenie, dodatni przy zwrocie — a ładunek niesie stock_movement_id, które pozostaje stabilne między ponowieniami i powtórzeniami, więc użyj go jako klucza idempotencji. Mutacja adjustStock celowo NIE wyzwala tego zdarzenia i właśnie to powstrzymuje Twoje własne zapisy przed wracaniem do Ciebie echem w pętli.

Wpychanie korekt magazynowych

Wywołuj adjustStock ze znakowaną deltą po swojej stronie. Klucz READ_STOCK potrafi dokładnie to, nie mogąc niczego utworzyć, zmienić ani usunąć — co czyni go dopasowanym wyborem dla jednokierunkowego mostu magazynowego.

Tworzenie zleceń pracy z innego systemu

createWorkOrder na podstawie alarmu SCADA, zgłoszenia z helpdesku albo powiadomienia o awarii z ERP. Zlecenie pracy otrzymuje swoje zwykłe oznaczenie i wywołuje work_order.created jak każde inne.

Utrzymywanie katalogu części w zgodzie

parts(updatedSince:) zwraca tylko to, co zmieniło się od wybranego przez Ciebie momentu. createPart i updatePart przyjmują customReference — Twój własny kod części — a part(barcode:) następnie po nim dopasowuje, więc możesz wyszukać część po identyfikatorze, którego Twój ERP już używa. Unikalność nie jest wymuszana; to Ty musisz ją zagwarantować.

Zamknięcie pętli przy zakończeniu

work_order.completed niesie zlecenie pracy oraz jego poprzedni status, co wystarcza, by zamknąć powiadomienie konserwacyjne po drugiej stronie.

Czego potrzebujesz w planie

Dwie odrębne bramki: jedna dla API, druga dla webhooków. Firma na planie Starter może odbierać webhooki, nie mając API.

API GraphQL i klucze API

Business i wyżej. Core i Starter nie mogą w ogóle utworzyć klucza.

Limit dzienny

5 000 wywołań dziennie w Business, 100 000 w Enterprise. Liczone na firmę i na operację, z zerowaniem o 00:00 UTC — grupowanie operacji w jednym żądaniu HTTP oszczędza cykle sieciowe, nigdy limit.

Webhooki

Starter i wyżej, z ograniczeniem do 100 dostarczeń dziennie; bez ograniczeń od Business.

Cele Slack i Teams

Enterprise. Ogólny webhook jest dostępny już od Starter.

Wskazówka

Planujesz coś, co uderza w jedną z powyższych luk? Napisz nam na contact@freemaint.com, co budujesz. O tym, która możliwość powstanie następna, decyduje to, na czym klienci naprawdę się blokują.

Czy ta strona była pomocna?