Ghid de integrare cu ERP și alte sisteme

Cum stabiliți perimetrul unei integrări înainte de a o construi: ce atinge API-ul, ce nu atinge și unde sunt limitele

9 min citire
Tipărește această secțiune

Acest ghid se adresează dezvoltatorului care stabilește perimetrul unei integrări între FreeMaint și un alt sistem — un ERP, un historian SCADA, o aplicație de gestiune a depozitului sau una de contabilitate. Celelalte articole din această secțiune explică felul în care se fac apelurile. Acesta vorbește în mod deliberat despre limite, pentru că greșeala costisitoare nu este un antet greșit: costisitor este să descoperiți la jumătatea construcției că o capabilitate pe care ați presupus-o nu există. Fiecare limită de mai jos este enunțată intenționat.

O cheie, trei uși

O cheie API (începe cu fmk_) este creată în Setări Companie > API (/company-settings/api) de un administrator sau de o persoană pentru sine în Profil > Securitate > Chei API și se trimite ca Authorization: Bearer fmk_... . Ea autentifică exact trei lucruri: punctul final GraphQL de la POST /api/v1/graphql, punctul final de preluare a datelor de energie și /public-api/whoami împreună cu /public-api/ping. NU autentifică rutele REST de sub /api/v1/{resource} — acelea sunt API-ul propriu al aplicației și cer o sesiune de utilizator (JWT). Construiți-vă integrarea pe GraphQL: aceasta este suprafața la care ajunge o cheie.

Ce vă oferă o cheie API

Cheia în clar este afișată o singură dată, la creare, și nu mai poate fi recuperată niciodată după aceea: se păstrează doar un hash SHA-256, împreună cu cele 6 caractere care urmează după fmk_, pentru ca lista să poată identifica cheia pe ecran.

Nume

O etichetă lizibilă pentru oameni — „Punte ERP Oracle”, „Chioșc linia 2”. Emiteți câte o cheie pentru fiecare sistem consumator, ca să puteți revoca una fără a le deranja pe celelalte.

Nivel de acces

READ, READ_STOCK sau FULL, o ierarhie ordonată peste permisiunile proprietarului. READ execută doar interogări și este valoarea implicită. READ_STOCK adaugă mutația adjustStock. FULL permite orice operațiune permisă de rolul proprietarului, inclusiv creare, modificare și ștergere — precum și punctul de ingestie a datelor de energie atunci când proprietarul poate crea citiri de energie.

Expirare

Un expiresAt opțional. După acel moment, cheia încetează să mai autentifice. Lăsați-l gol pentru o integrare de serviciu permanentă; completați-l pentru un colaborator extern sau pentru un pilot.

Revocare

isActive este un comutator pentru fiecare cheie în parte, care își face efectul în câteva secunde. Înregistrarea se păstrează pentru audit, așa că o cheie revocată rămâne trasabilă în loc să dispară.

Ultima utilizare

lastUsedAt reține cel mai recent apel autentificat — suficient ca să depistați o cheie pe care nu o mai folosește nimeni.

Proprietar

Persoana în numele căreia acționează cheia. Fiecare apel poartă permisiunile și vizibilitatea ei asupra datelor, iar numele ei semnează ce scrie cheia. Pentru un ERP, creați un utilizator dedicat — „Integrare ERP” — cu un rol care acoperă exact ce îi trebuie punții, și dați-i lui cheia.

Ce nu vă oferă o cheie API

Acestea sunt limite reale ale implementării actuale, nu omisiuni ale acestei pagini. Dacă analiza dumneavoastră de securitate depinde de vreuna dintre ele, proiectați ocolind-o acum, nu după ce ați construit.

  • Fără listă de module pe cheie. O cheie citește și scrie ce permite rolul proprietarului ei — pentru a o restrânge, dați-o unui proprietar cu un rol mai restrâns. Peste acestea se aplică doar cele trei niveluri de acces: nu puteți enumera, pe cheia însăși, modulele pe care le poate folosi.
  • Fără listă de adrese IP permise. O cheie este valabilă de oriunde de pe internet. Tratați-o ca pe un secret: un seif de parole sau magazinul de secrete al platformei dumneavoastră, niciodată un depozit de cod, un disc partajat sau un tichet de suport.
  • Fără jurnal de utilizare pentru fiecare cheie. Se păstrează doar lastUsedAt. Nu există istoric al apelurilor pentru o cheie, nici cotă pentru o cheie și nicio evidență a cheii care a executat o anumită operație. Contorul cotei zilnice este ținut per COMPANIE și este împărțit de toate cheile, așa că o singură integrare scăpată de sub control poate epuiza cota tuturor celorlalte.
  • Fără aprobare în numele altcuiva. O cheie nu aprobă niciodată un pas al fluxului de lucru, chiar dacă proprietarul ei ar putea: aprobările rămân un act uman în aplicație.
  • O singură companie pentru fiecare cheie. O cheie este legată de compania în care a fost creată și nu poate ajunge la datele altei companii: o înregistrare a altei companii pur și simplu nu este găsită.

Ce puteți citi

GraphQL expune 16 interogări de citire: whoami, workOrders, workOrder(id), assets, asset(id), assetDowntimes, meters(assetId), parts, part (după id sau cod de bare), stockMovements, locations, vendors, customers, purchaseOrders, purchaseOrder(id | reference | erpReference) și floorPlans(locationId). Orice listă poate fi parcursă până la capăt: workOrders, assets, assetDowntimes, meters, parts, purchaseOrders, locations, vendors și customers acceptă toate limit (de la 1 la 200) plus offset, iar workOrders, assets, parts, purchaseOrders, locations, vendors și customers acceptă și updatedSince pentru sincronizări incrementale. Pagina implicită are 50 de rânduri, cu excepția locations, vendors și customers, care își păstrează vechea valoare implicită de 200. Fiecare listă este ordonată cu un criteriu de departajare pe id, astfel încât o limită de pagină nu sare și nu repetă niciodată un rând. stockMovements este registrul de stoc pe baza căruia faceți reconcilierea: se citește de la cea mai veche mișcare și se paginează cu afterId — transmiteți id-ul ultimei mișcări primite —, ceea ce rămâne exact chiar dacă între timp se scriu mișcări noi, iar since vă permite să porniți o recuperare a istoricului de la o dată. Comenzile de lucru din Coșul de gunoi nu sunt returnate niciodată. purchaseOrders se filtrează și după status și vendorId; sumele sunt în moneda companiei, pe care fiecare comandă o poartă în câmpul currency.

Verificați cheia și citiți comenzi de lucru într-un singur apel: 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 } }"}'

Ce puteți scrie

Douăsprezece mutații: creare, actualizare și ștergere pentru ordine de lucru, active și piese, plus adjustStock pentru o mișcare de stoc cu semn. Ele trec prin aceleași servicii pe care le folosește însăși aplicația, așa că referințele (WO-…, ASS-…, PART-…), înregistrările de audit și notificările se comportă exact ca în interfață. Pentru stoc, preferați adjustStock: scrie o înregistrare de audit StockMovement în aceeași tranzacție și refuză să coboare o cantitate sub zero, în timp ce updatePart(quantity) setează o valoare absolută și consemnează diferența ca ajustare. Pe o piesă al cărei stoc este urmărit pe locație, transmiteți locationId către adjustStock; acolo updatePart(quantity) este refuzat. A unsprezecea, addMeterReading, înregistrează o citire de contor prin același serviciu ca ecranul de contoare, deci declanșatoarele și mentenanța preventivă pe contor reacționează exact ca pentru o citire introdusă în aplicație. A douăsprezecea, setPurchaseOrderErpReference, salvează numărul de document din ERP-ul dvs. pe o comandă de achiziție, pentru ca cele două înregistrări să poată fi potrivite ulterior cu purchaseOrder(erpReference: …). Necesită o cheie cu acces FULL și, intenționat, nu declanșează niciun webhook: ERP-ul dvs. nu își primește niciodată propria scriere ca eveniment.

Ce nu acoperă deloc API-ul

Schema GraphQL conține comenzi de lucru, echipamente, opriri de mașină, piese și mișcările lor de stoc, contoare, comenzi de achiziție, amplasamente, furnizori, clienți și planuri. Nimic altceva. Următoarele sunt părți pe deplin funcționale din FreeMaint care astăzi NU au nicio suprafață API: nu pot fi citite, scrise sau abonate dintr-o integrare, iar nicio combinație a interogărilor existente nu le atinge.

  • Inspecțiile — nici șabloanele, nici rundele programate, nici rezultatele înregistrate, nici codurile de acțiune. Nu există nicio interogare de inspecție, nicio mutație de inspecție și niciun eveniment webhook de inspecție.
  • Utilizatorii, rolurile și echipele — nu puteți lista, crea sau dezactiva un utilizator prin acest API. Provizionarea utilizatorilor se face în aplicație; în planul Enterprise poate fi automatizată cu SCIM, care este un mecanism separat și nu face parte din acest API.
  • Proiectele — nicio interogare și nicio mutație.
  • Cererile de achiziție — nicio interogare și nicio mutație. Comenzile de achiziție pot fi citite (purchaseOrders, purchaseOrder), ERP-ul dvs. își poate scrie înapoi referința (setPurchaseOrderErpReference), iar webhook-urile purchase_order.* le anunță, dar o comandă nu poate fi creată, modificată sau recepționată prin API: asta rămâne în aplicație.
  • Fișierele, fotografiile și atașamentele — nu puteți încărca un document sau o fotografie și nu puteți citi înapoi niciunul. Fotografiile comenzilor de lucru, imaginile pieselor și dovezile de inspecție se află toate în afara API-ului. O adresă URL care apare într-un răspuns, precum imageUrl al unei piese, nu este o cale de încărcare.
  • Planurile de mentenanță preventivă, pontajele, sarcinile și listele de verificare — neexpuse.

Important

Stabiliți perimetrul lucrării după această listă, nu după ce face produsul într-un browser. Aplicațiile web și mobilă proprii ale FreeMaint rulează pe un API REST intern la care o cheie API nu ajunge, așa că faptul că o funcționalitate se vede pe ecran nu dovedește că o integrare o poate comanda.

Webhook-uri — catalogul de evenimente

Webhook-urile sunt jumătatea „push” a unei integrări: în loc să interogați periodic, FreeMaint trimite un POST către adresa dumneavoastră URL în clipa în care se întâmplă ceva. Le configurați în Setări companie > Webhook-uri (/company-settings/webhooks) și abonați fiecare webhook la evenimentele de care aveți nevoie. Catalogul este o listă fixă.

  • work_order.created — o comandă de lucru a fost creată
  • work_order.completed — o comandă de lucru a ajuns la Finalizată
  • work_order.status_changed — o comandă de lucru a trecut în orice stare; payload-ul conține new_status. Se declanșează pe lângă work_order.completed, care rămâne neschimbat
  • request.created — o solicitare de mentenanță a fost trimisă
  • request.status_changed — o solicitare a fost aprobată, respinsă sau anulată
  • part.low_stock — o piesă a coborât la minimul său sau sub el
  • part.out_of_stock — o piesă a ajuns la zero; este un eveniment separat, nu o variantă a low_stock
  • part.low_stock_summary — un rezumat zilnic pe companie cu piesele aflate la pragul de reaprovizionare sau sub acesta, inclusiv cele care nu s-au mișcat niciodată
  • part.out_of_stock_summary — același rezumat zilnic, pentru piesele ajunse la zero
  • part.consumed — o piesă a fost consumată pe o comandă de lucru sau returnată de pe ea; quantity are semn
  • compliance.chain_divergence — lanțul de hash-uri al auditului s-a rupt (Enterprise)
  • asset.downtime_started — a fost deschisă o oprire de mașină
  • asset.downtime_ended — a fost închisă o oprire de mașină; durata poate fi acum contabilizată
  • report.daily_summary — lucrările finalizate ale zilei, o dată pe zi, la ora pe care o alegeți
  • purchase_order.created — a fost creată o comandă de achiziție; conținutul poartă comanda (reference, status, total_cost, currency, expected_date, erp_reference) și furnizorul
  • purchase_order.status_changed — o comandă de achiziție și-a schimbat starea (aprobată, comandată, anulată…); conținutul poartă previous_status și new_status
  • purchase_order.received — a fost recepționată o livrare pe o comandă de achiziție (trimis la fiecare livrare, inclusiv parțială); încărcătura listează fiecare linie cu quantity_ordered, quantity_received (această livrare), quantity_received_total și quantity_remaining, plus receipt_id, fully_received și closed_short; new_status este PARTIALLY_RECEIVED cât timp se mai așteaptă marfă și RECEIVED când comanda este completă sau închisă

Avertisment

Nu există niciun eveniment de inspecție și niciun eveniment de defect sau de constatare. Dacă procesul dumneavoastră este „o inspecție pică, ERP-ul meu reacționează”, astăzi nu se declanșează nimic pentru el. Planificați explicit această lipsă, în loc să presupuneți că există un eveniment suficient de apropiat.

Plicul livrării

Fiecare livrare GENERIC este un POST al cărui corp este același plic cu trei chei: event, timestamp și data, unde data este indexat după entitate. Îl însoțesc trei anteturi. X-FreeMaint-Signature are forma t=<secunde unix>,v1=<hex>, unde v1 este HMAC-SHA256 al șirului exact "<t>.<corp brut>" semnat cu secretul acelui webhook; X-FreeMaint-Event repetă numele evenimentului; X-FreeMaint-Delivery-Id identifică livrarea. Verificați semnătura față de corpul BRUT, înainte de parsare — reserializarea JSON schimbă octeții, iar semnătura nu va mai corespunde.

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

Fiabilitate și monitorizare

O livrare este încercată de 3 ori, cu retragere exponențială pe o bază de 2 secunde — aproximativ 2 s, apoi 4 s, apoi 8 s — și fiecare încercare expiră după 10 secunde. Un 2xx o marchează ca livrată. Răspunsurile 5xx și erorile de rețea sunt reîncercate. Un 4xx este tratat ca refuz definitiv și NU este reîncercat deloc: dacă punctul dumneavoastră final răspunde 400 sau 401 din cauza unei erori de partea dumneavoastră, livrarea se oprește acolo și doar o reluare manuală o va recupera.

  • Fiecare livrare este stocată: evenimentul, conținutul exact trimis, statusul, codul HTTP pe care l-ați returnat, primele ~500 de caractere din corpul răspunsului dumneavoastră, numărul de încercări și mesajul de eroare.
  • Statusurile sunt PENDING, DELIVERED, FAILED, RATE_LIMITED (plafonul Starter de 100 de livrări pe zi a fost atins) și SKIPPED_TIER (abonamentul nu mai dă dreptul la acea destinație).
  • O livrare stocată poate fi reluată din istoricul webhook-ului. Retrimite conținutul salvat nemodificat, deci cheile de business din interiorul lui rămân identice, iar un receptor care deduplică corect nu va înregistra de două ori.
  • Fiecare webhook poartă lastTriggeredAt, lastSuccessAt, lastFailureAt și un failureCount, ceea ce ajunge pentru o verificare de sănătate fără a deschide istoricul complet.
  • Lista de webhook-uri și jurnalul ei de livrări se pot citi în orice abonament, așa că o integrare oprită de o retrogradare rămâne vizibilă în loc să moară în tăcere.
  • Nimic nu vă alertează când livrările încep să eșueze — niciun e-mail, nicio notificare. Dacă o pană tăcută ar conta, interogați jurnalul de livrări din propriul dumneavoastră sistem de monitorizare.

Securitate

Adresele URL sunt validate la salvarea webhook-ului, iar regulile sunt stricte în mod intenționat: un webhook este o cerere de ieșire făcută de serverele noastre, deci una permisivă ar fi un vector SSRF.

  • Cheile sunt stocate ca hash-uri SHA-256 — valoarea în clar nu mai poate fi recuperată din FreeMaint după creare.
  • Adresele URL de webhook trebuie să folosească https. Simplul http și orice altă schemă sunt refuzate.
  • Sunt acceptate doar porturile 443 și 8443.
  • Gazdele de tip loopback, link-local și cele din intervalele private (127.x, 10.x, 192.168.x, 172.16–31.x, ::1 și altele asemenea) sunt refuzate: receptorul trebuie să fie accesibil din internetul public. Un sistem care există doar în interiorul fabricii dumneavoastră are nevoie de un releu public sau de un tunel.
  • Credențialele în adresa URL (https://user:pass@host) sunt refuzate.
  • Redirecționările nu sunt urmate — punctul final pe care îl înregistrați este cel care primește POST-ul.
  • Fiecare webhook are propriul secret de semnare și există o acțiune de rotire a secretului. Rotirea are efect imediat, așa că puneți în funcțiune noul secret de partea dumneavoastră în aceeași fereastră de timp.
  • Destinațiile SLACK și TEAMS primesc un mesaj formatat pentru platformă și NICIUN antet HMAC FreeMaint: pentru ele, secretul adresei URL este singura autentificare. Folosiți GENERIC pentru orice trebuie verificat criptografic.

Tipare care funcționează bine astăzi

Acestea sunt integrările pe care suprafața actuală le susține curat, împreună cu detaliul care face ca fiecare dintre ele să fie sigură în producție.

Oglindirea consumului într-un ERP

Abonați-vă la part.consumed. quantity are semn — negativ când piesa iese către lucrare, pozitiv la retur — iar conținutul poartă un stock_movement_id care rămâne stabil între reîncercări și reluări, deci folosiți-l drept cheie de idempotență. Mutația adjustStock nu ridică în mod deliberat acest eveniment, iar tocmai asta împiedică propriile dumneavoastră scrieri să se întoarcă la dumneavoastră în ecou și să formeze o buclă.

Împingerea corecțiilor de stoc

Apelați adjustStock cu o deltă cu semn din partea dumneavoastră. O cheie READ_STOCK poate face exact asta, fără a putea crea, modifica sau șterge nimic, ceea ce o face o alegere potrivită pentru o punte de stoc unidirecțională.

Crearea de comenzi de lucru dintr-un alt sistem

createWorkOrder pornind de la o alarmă SCADA, de la un tichet de helpdesk sau de la o notificare de avarie din ERP. Comanda de lucru își primește referința obișnuită și ridică work_order.created ca oricare alta.

Menținerea catalogului de piese la zi

parts(updatedSince:) returnează doar ce s-a schimbat de la momentul ales de dumneavoastră. createPart și updatePart acceptă customReference — codul dumneavoastră propriu pentru piesă — iar part(barcode:) se potrivește apoi după el, deci puteți căuta o piesă după identificatorul pe care sistemul dumneavoastră ERP îl folosește deja. Unicitatea nu este impusă; rămâne în sarcina dumneavoastră să o garantați.

Închiderea buclei la finalizare

work_order.completed poartă comanda de lucru și statusul ei anterior, ceea ce ajunge pentru a închide o notificare de mentenanță de cealaltă parte.

De ce abonament aveți nevoie

Două praguri separate: unul pentru API, altul pentru webhook-uri. O companie pe planul Starter poate primi webhook-uri fără să aibă API-ul.

API-ul GraphQL și cheile API

Business și peste. Core și Starter nu pot crea deloc o cheie.

Cotă zilnică

5.000 de apeluri pe zi la Business, 100.000 la Enterprise. Se numără per companie și per operație, cu resetare la 00:00 UTC — gruparea mai multor operații într-o singură cerere HTTP economisește dus-întorsuri, niciodată cotă.

Webhook-uri

Starter și peste, plafonate la 100 de livrări pe zi; nelimitate începând cu Business.

Destinațiile Slack și Teams

Enterprise. Un webhook generic este disponibil începând cu Starter.

Sfat

Pregătiți ceva care se lovește de una dintre lipsurile de mai sus? Spuneți-ne ce construiți la contact@freemaint.com. Ce capabilitate apare următoarea se decide după ceea ce blochează cu adevărat clienții.

A fost utilă această pagină?