Panduan Integrasi ERP dan Sistem
Tentukan cakupan integrasi sebelum Anda membangunnya: apa yang dijangkau API, apa yang tidak, dan di mana batasnya
Panduan ini ditujukan bagi pengembang yang sedang menentukan cakupan integrasi antara FreeMaint dan sistem lain — sebuah ERP, sebuah historian SCADA, sistem pergudangan, atau paket akuntansi. Artikel lain di bagian ini menjelaskan cara memanggil API; yang satu ini sengaja berbicara tentang batas, karena kesalahan yang mahal bukanlah header yang keliru: melainkan menemukan di tengah pembangunan bahwa kemampuan yang Anda anggap ada ternyata tidak ada. Setiap batas di bawah ini disebutkan dengan sengaja.
Satu kunci, tiga pintu
Kunci API (diawali fmk_) dibuat di Pengaturan Perusahaan > API (/company-settings/api) oleh admin, atau oleh seseorang untuk dirinya sendiri di Profil > Keamanan > Kunci API, dan dikirim sebagai Authorization: Bearer fmk_... . Kunci itu mengautentikasi tepat tiga hal: endpoint GraphQL di POST /api/v1/graphql, endpoint ingest energi, serta /public-api/whoami dan /public-api/ping. Kunci itu TIDAK mengautentikasi rute REST di bawah /api/v1/{resource} — rute tersebut adalah API internal aplikasi sendiri dan memerlukan sesi pengguna (JWT). Bangunlah integrasi Anda di atas GraphQL: itulah permukaan yang dijangkau sebuah kunci.
Apa yang diberikan kunci API
Kunci mentah hanya ditampilkan sekali, saat pembuatan, dan setelah itu tidak pernah dapat dipulihkan: hanya hash SHA-256 yang disimpan, bersama 6 karakter yang mengikuti fmk_ agar daftar dapat mengenali kunci tersebut di layar.
Nama
Label yang mudah dibaca manusia — «Jembatan ERP Oracle», «Kios lini 2». Terbitkan satu kunci untuk setiap sistem pemakai agar Anda dapat mencabut satu kunci tanpa mengganggu yang lain.
Tingkat akses
READ, READ_STOCK atau FULL, sebuah hierarki berurutan di atas izin pemilik. READ hanya menjalankan kueri dan merupakan nilai bawaan. READ_STOCK menambahkan mutasi adjustStock. FULL mengizinkan setiap operasi yang diizinkan peran pemilik, termasuk membuat, memperbarui, dan menghapus — serta endpoint ingest energi bila pemilik boleh membuat pembacaan energi.
Kedaluwarsa
Sebuah expiresAt yang bersifat opsional. Setelah momen itu, kunci berhenti mengautentikasi. Biarkan kosong untuk integrasi layanan permanen; isi untuk kontraktor atau uji coba.
Pencabutan
isActive adalah sakelar per kunci yang berlaku dalam hitungan detik. Barisnya tetap disimpan untuk audit, sehingga kunci yang dicabut tetap dapat dilacak alih-alih lenyap.
Terakhir dipakai
lastUsedAt mencatat panggilan terautentikasi yang paling akhir — cukup untuk mengenali kunci yang sudah tidak dipakai siapa pun.
Pemilik
Orang yang diwakili oleh kunci. Setiap panggilan membawa izin dan visibilitas datanya, dan namanya menandatangani apa yang ditulis kunci. Untuk ERP, buat pengguna khusus — “Integrasi ERP” — dengan peran yang mencakup tepat apa yang dibutuhkan jembatan, lalu berikan kunci kepadanya.
Apa yang tidak diberikan kunci API
Ini adalah batas nyata dari implementasi saat ini, bukan hal yang terlewat dari halaman ini. Jika tinjauan keamanan Anda bergantung pada salah satunya, rancanglah jalan keluarnya sekarang, bukan setelah pembangunan selesai.
- Tidak ada daftar modul per kunci. Sebuah kunci membaca dan menulis apa yang diizinkan peran pemiliknya — untuk mempersempitnya, berikan kunci kepada pemilik yang perannya lebih sempit. Di atas itu hanya tiga tingkat akses yang berlaku: Anda tidak dapat mencantumkan, pada kunci itu sendiri, modul yang boleh dipakainya.
- Tidak ada daftar izin IP. Sebuah kunci berlaku dari mana saja di internet. Perlakukan sebagai rahasia: brankas atau penyimpanan rahasia platform Anda, jangan pernah repositori kode, drive bersama, atau tiket dukungan.
- Tidak ada log penggunaan per kunci. Hanya lastUsedAt yang disimpan. Tidak ada riwayat panggilan per kunci, tidak ada kuota per kunci, dan tidak ada catatan kunci mana yang melakukan operasi mana. Penghitung kuota harian berlaku per PERUSAHAAN dan dipakai bersama oleh setiap kunci, sehingga satu integrasi yang lepas kendali dapat menghabiskan kuota semua kunci lainnya.
- Tidak ada persetujuan atas nama orang lain. Kunci tidak pernah menyetujui langkah alur kerja, bahkan ketika pemiliknya bisa: persetujuan tetap merupakan tindakan manusia di aplikasi.
- Satu perusahaan per kunci. Kunci terikat pada perusahaan tempat ia dibuat dan tidak dapat menjangkau data perusahaan lain: catatan milik perusahaan lain dianggap tidak ditemukan.
Apa yang dapat Anda baca
GraphQL menyediakan 16 kueri baca: whoami, workOrders, workOrder(id), assets, asset(id), assetDowntimes, meters(assetId), parts, part (berdasarkan id atau barcode), stockMovements, locations, vendors, customers, purchaseOrders, purchaseOrder(id | reference | erpReference), dan floorPlans(locationId). Setiap daftar dapat ditelusuri per halaman sampai habis: workOrders, assets, assetDowntimes, meters, parts, purchaseOrders, locations, vendors, dan customers semuanya menerima limit (1 sampai 200) ditambah offset, dan workOrders, assets, parts, purchaseOrders, locations, vendors, dan customers juga menerima updatedSince untuk sinkronisasi inkremental. Ukuran halaman bawaan adalah 50 baris, kecuali locations, vendors, dan customers, yang mempertahankan bawaan lamanya, yaitu 200. Setiap daftar diurutkan dengan id sebagai penentu urutan terakhir, sehingga batas halaman tidak pernah melewatkan atau mengulang satu baris pun. stockMovements adalah buku besar stok yang menjadi dasar rekonsiliasi Anda: dibaca dari yang terlama dan dipaginasi dengan afterId — kirim id pergerakan terakhir yang Anda terima — yang tetap tepat meskipun pergerakan baru sedang ditulis, dan since memungkinkan Anda memulai pengisian data historis dari suatu tanggal. Perintah kerja di Tempat Sampah Daur Ulang tidak pernah dikembalikan. purchaseOrders juga dapat difilter dengan status dan vendorId; nilai uang dalam mata uang perusahaan, yang dibawa setiap pesanan di kolom currency.
Verifikasi kunci dan baca perintah kerja dalam satu panggilan: 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 } }"}'
Apa yang dapat Anda tulis
Dua belas mutasi: membuat, memperbarui, dan menghapus pada perintah kerja, aset, dan suku cadang, ditambah adjustStock untuk pergerakan persediaan bertanda. Semuanya berjalan lewat layanan yang sama dengan yang dipakai aplikasi itu sendiri, sehingga referensi (WO-…, ASS-…, PART-…), baris audit, dan notifikasi berperilaku persis seperti di antarmuka. Untuk stok, utamakan adjustStock: mutasi ini menulis baris audit StockMovement dalam transaksi yang sama dan menolak menurunkan quantity di bawah nol, sedangkan updatePart(quantity) menetapkan nilai absolut dan mencatat selisihnya sebagai penyesuaian. Pada suku cadang yang stoknya dilacak per lokasi, kirim locationId ke adjustStock; di sana updatePart(quantity) ditolak. Yang kesebelas, addMeterReading, mencatat pembacaan meter melalui layanan yang sama dengan layar meter, sehingga pemicu meter dan pemeliharaan preventif berbasis meter bereaksi persis seperti untuk pembacaan yang diketik di aplikasi. Yang kedua belas, setPurchaseOrderErpReference, menyimpan nomor dokumen ERP Anda pada pesanan pembelian agar kedua catatan dapat dicocokkan kemudian dengan purchaseOrder(erpReference: …). Mutasi ini memerlukan kunci berakses FULL dan sengaja tidak memicu webhook apa pun: ERP Anda tidak pernah menerima tulisannya sendiri sebagai peristiwa.
Apa yang sama sekali tidak dicakup API
Skema GraphQL berisi perintah kerja, aset, penghentian mesin, suku cadang beserta pergerakan stoknya, meter, pesanan pembelian, lokasi, pemasok, pelanggan, dan denah lantai. Tidak ada yang lain. Berikut adalah bagian FreeMaint yang berfungsi penuh tetapi hari ini TIDAK memiliki permukaan API: tidak dapat dibaca, ditulis, atau dilanggan dari sebuah integrasi, dan tidak ada kombinasi kueri yang ada yang menjangkaunya.
- Inspeksi — baik templatnya, jadwal pelaksanaannya, hasil yang tercatat, maupun kode tindakannya. Tidak ada kueri inspeksi, tidak ada mutasi inspeksi, dan tidak ada event webhook inspeksi.
- Pengguna, peran, dan tim — Anda tidak dapat menampilkan, membuat, atau menonaktifkan pengguna melalui API ini. Penyediaan pengguna dilakukan di dalam aplikasi; pada paket Enterprise hal ini dapat diotomatiskan dengan SCIM, yang merupakan mekanisme terpisah dan bukan bagian dari API ini.
- Proyek — tidak ada kueri dan tidak ada mutasi.
- Permintaan pembelian — tidak ada kueri dan tidak ada mutasi. Pesanan pembelian dapat dibaca (purchaseOrders, purchaseOrder), ERP Anda dapat menulis balik referensinya (setPurchaseOrderErpReference), dan webhook purchase_order.* mengumumkannya, tetapi pesanan tidak dapat dibuat, diubah, atau diterima lewat API: itu tetap dilakukan di aplikasi.
- Berkas, foto, dan lampiran — Anda tidak dapat mengunggah dokumen atau foto, dan tidak dapat membacanya kembali. Foto perintah kerja, gambar suku cadang, dan bukti inspeksi semuanya berada di luar API. Sebuah URL yang muncul dalam suatu respons, seperti imageUrl sebuah suku cadang, bukanlah jalur unggah.
- Jadwal pemeliharaan preventif, catatan waktu, tugas, dan daftar periksa — tidak diekspos.
Penting
Webhook — katalog event
Webhook adalah sisi push dari sebuah integrasi: alih-alih menjajaki secara berkala, FreeMaint mengirim POST ke URL Anda begitu sesuatu terjadi. Anda mengonfigurasinya di Pengaturan perusahaan > Webhook (/company-settings/webhooks) dan melanggankan setiap webhook pada event yang Anda perlukan. Katalognya adalah daftar tetap.
- work_order.created — sebuah perintah kerja dibuat
- work_order.completed — sebuah perintah kerja mencapai status Selesai
- work_order.status_changed — sebuah perintah kerja berpindah ke status apa pun; payload membawa new_status. Dipicu bersama work_order.completed, yang tidak berubah
- request.created — sebuah permintaan pemeliharaan dikirim
- request.status_changed — sebuah permintaan disetujui, ditolak, atau dibatalkan
- part.low_stock — sebuah suku cadang turun ke batas minimumnya atau ke bawahnya
- part.out_of_stock — sebuah suku cadang mencapai nol; ini event tersendiri, bukan varian dari low_stock
- part.low_stock_summary — satu ringkasan harian per perusahaan yang mencantumkan suku cadang pada titik pemesanan ulang atau di bawahnya, termasuk yang tidak pernah bergerak
- part.out_of_stock_summary — ringkasan harian yang sama, untuk suku cadang yang berada di nol
- part.consumed — sebuah suku cadang dipakai pada, atau dikembalikan dari, sebuah perintah kerja; quantity bertanda
- compliance.chain_divergence — rantai hash audit terputus (Enterprise)
- asset.downtime_started — sebuah penghentian mesin dibuka
- asset.downtime_ended — sebuah penghentian mesin ditutup; durasinya kini bisa dibukukan
- report.daily_summary — pekerjaan yang selesai hari itu, sekali sehari pada jam yang Anda pilih
- purchase_order.created — pesanan pembelian dibuat; payload membawa pesanan (reference, status, total_cost, currency, expected_date, erp_reference) dan pemasoknya
- purchase_order.status_changed — status pesanan pembelian berubah (disetujui, dipesan, dibatalkan…); payload membawa previous_status dan new_status
- purchase_order.received — sebuah pengiriman diterima untuk suatu pesanan pembelian (dikirim pada setiap pengiriman, termasuk sebagian); payload mencantumkan setiap baris dengan quantity_ordered, quantity_received (pengiriman ini), quantity_received_total dan quantity_remaining, serta receipt_id, fully_received dan closed_short; new_status bernilai PARTIALLY_RECEIVED selama barang masih ditunggu dan RECEIVED setelah pesanan lengkap atau ditutup
Peringatan
Amplop pengiriman
Setiap pengiriman GENERIC adalah POST yang badannya memakai amplop tiga kunci yang sama: event, timestamp dan data, dengan data diindeks menurut entitas. Tiga header menyertainya. X-FreeMaint-Signature berbentuk t=<detik unix>,v1=<hex>, dengan v1 adalah HMAC-SHA256 dari string persis "<t>.<badan mentah>" yang ditandatangani memakai rahasia webhook tersebut; X-FreeMaint-Event mengulang nama event; X-FreeMaint-Delivery-Id mengidentifikasi pengiriman itu. Verifikasi tanda tangan terhadap badan MENTAH, sebelum diurai — menyerialkan ulang JSON mengubah bytenya dan tanda tangan tidak akan cocok.
{ "event": "work_order.created", "timestamp": "2026-09-05T08:14:02.371Z", "data": { "work_order": { "id": 5521, "title": "Kebocoran pompa #3", "status": "OPEN", "priority": "HIGH", "dueDate": "2026-09-08T00:00:00.000Z" } } }
Keandalan dan pemantauan
Sebuah pengiriman dicoba 3 kali dengan backoff eksponensial berbasis 2 detik — kira-kira 2 detik, lalu 4 detik, lalu 8 detik — dan setiap percobaan kedaluwarsa setelah 10 detik. Respons 2xx menandainya terkirim. Respons 5xx dan galat jaringan dicoba ulang. Respons 4xx diperlakukan sebagai penolakan permanen dan sama sekali TIDAK dicoba ulang: jika endpoint Anda menjawab 400 atau 401 karena bug di sisi Anda, pengiriman itu berhenti di situ dan hanya pemutaran ulang manual yang dapat memulihkannya.
- Setiap pengiriman disimpan: event-nya, payload persis yang dikirim, statusnya, kode HTTP yang Anda kembalikan, sekitar 500 karakter pertama badan respons Anda, jumlah percobaan, dan pesan galatnya.
- Statusnya adalah PENDING, DELIVERED, FAILED, RATE_LIMITED (batas Starter sebesar 100 pengiriman per hari telah tercapai) dan SKIPPED_TIER (paket tidak lagi memberi hak atas tujuan tersebut).
- Sebuah pengiriman yang tersimpan dapat diputar ulang dari riwayat webhook. Payload yang tersimpan dikirim ulang tanpa perubahan, sehingga kunci bisnis di dalamnya tetap identik dan penerima yang melakukan deduplikasi dengan benar tidak akan mencatatnya dua kali.
- Setiap webhook membawa lastTriggeredAt, lastSuccessAt, lastFailureAt dan sebuah failureCount, yang sudah cukup untuk pemeriksaan kesehatan tanpa membuka riwayat lengkapnya.
- Daftar webhook dan log pengirimannya dapat dibaca pada semua paket, sehingga integrasi yang terhenti karena penurunan paket tetap terlihat alih-alih mati diam-diam.
- Tidak ada apa pun yang memberi peringatan ketika pengiriman mulai gagal — tidak ada surel, tidak ada notifikasi. Jika gangguan senyap akan menjadi masalah, jajakilah log pengiriman dari sistem pemantauan Anda sendiri.
Keamanan
URL divalidasi saat Anda menyimpan webhook, dan aturannya sengaja dibuat ketat: sebuah webhook adalah permintaan keluar yang dibuat oleh server kami, sehingga aturan yang longgar akan menjadi vektor SSRF.
- Kunci disimpan sebagai hash SHA-256 — nilai mentahnya tidak dapat dipulihkan dari FreeMaint setelah pembuatan.
- URL webhook harus memakai https. http biasa dan setiap skema lain ditolak.
- Hanya port 443 dan 8443 yang diterima.
- Host loopback, link-local, dan rentang privat (127.x, 10.x, 192.168.x, 172.16–31.x, ::1 dan sejenisnya) ditolak: penerima harus dapat dijangkau dari internet publik. Sistem yang hanya ada di dalam pabrik Anda memerlukan relai publik atau sebuah terowongan.
- Kredensial di dalam URL (https://user:pass@host) ditolak.
- Pengalihan tidak diikuti — endpoint yang Anda daftarkan adalah endpoint yang menerima POST.
- Setiap webhook memiliki rahasia penandatanganannya sendiri dan tersedia aksi rotasi rahasia. Rotasi langsung berlaku, jadi terapkan rahasia baru di sisi Anda pada jendela waktu yang sama.
- Tujuan SLACK dan TEAMS menerima pesan yang diformat untuk platform tersebut dan TIDAK menerima header HMAC FreeMaint: bagi keduanya, kerahasiaan URL adalah satu-satunya autentikasi. Gunakan GENERIC untuk apa pun yang perlu Anda verifikasi secara kriptografis.
Pola yang berjalan baik hari ini
Inilah integrasi yang didukung dengan rapi oleh permukaan saat ini, beserta detail yang membuat masing-masing aman dijalankan di produksi.
Mencerminkan konsumsi ke dalam ERP
Berlanggananlah pada part.consumed. quantity bertanda — negatif saat dikeluarkan ke pekerjaan, positif saat dikembalikan — dan payload-nya membawa sebuah stock_movement_id yang tetap stabil di seluruh percobaan ulang dan pemutaran ulang, jadi pakailah itu sebagai kunci idempotensi Anda. Mutasi adjustStock secara sengaja TIDAK memunculkan event ini, dan itulah yang mencegah tulisan Anda sendiri bergema kembali kepada Anda dalam sebuah putaran.
Mendorong koreksi stok masuk
Panggil adjustStock dengan delta bertanda dari sisi Anda. Sebuah kunci READ_STOCK dapat melakukan tepat ini dan tidak ada yang lain, sehingga sangat pas untuk jembatan stok satu arah.
Membuat perintah kerja dari sistem lain
createWorkOrder dari alarm SCADA, tiket helpdesk, atau pemberitahuan kerusakan dari ERP. Perintah kerja tersebut menerima referensi normalnya dan memunculkan work_order.created seperti perintah kerja lainnya.
Menjaga katalog suku cadang tetap selaras
parts(updatedSince:) hanya mengembalikan apa yang berubah sejak momen yang Anda pilih. createPart dan updatePart menerima customReference — kode Anda sendiri untuk suku cadang itu — dan part(barcode:) kemudian mencocokkannya, sehingga Anda dapat mencari suku cadang lewat pengenal yang sudah dipakai ERP Anda. Keunikannya tidak dipaksakan; Andalah yang harus menjaminnya.
Menutup lingkaran saat penyelesaian
work_order.completed membawa perintah kerja beserta status sebelumnya, yang sudah cukup untuk menutup notifikasi pemeliharaan di sisi seberang.
Paket yang Anda butuhkan
Dua gerbang yang terpisah: satu untuk API, satu untuk webhook. Perusahaan pada paket Starter dapat menerima webhook tanpa memiliki API.
API GraphQL dan kunci API
Business ke atas. Core dan Starter sama sekali tidak dapat membuat kunci.
Kuota harian
5.000 panggilan per hari pada Business, 100.000 pada Enterprise. Dihitung per perusahaan dan per operasi, disetel ulang pada 00:00 UTC — menggabungkan beberapa operasi ke dalam satu permintaan HTTP menghemat perjalanan bolak-balik, tidak pernah menghemat kuota.
Webhook
Starter ke atas, dibatasi 100 pengiriman per hari; tanpa batas mulai Business.
Tujuan Slack dan Teams
Enterprise. Webhook generik tersedia mulai Starter.
Tips
Artikel terkait
Apakah halaman ini membantu?