ERP・システム連携ガイド

作り始める前に連携の範囲を見極める。API が届く範囲、届かない範囲、そして境界がどこにあるか

9分で読めます
このセクションを印刷

本ガイドは、FreeMaint と別のシステム — ERP、SCADA ヒストリアン、倉庫管理や会計のパッケージ — をつなぐ連携の範囲を見積もる開発者に向けたものです。本セクションの他の記事は「どう呼び出すか」を説明します。この記事はあえて境界の話をします。高くつく失敗はヘッダーの書き間違いではなく、あると思い込んでいた機能が存在しないと開発の途中で気づくことだからです。以下に挙げる制限は、すべて意図して明示しています。

1 本のキー、3 つの扉

API キー(fmk_ で始まります)は、管理者が「会社の設定 > API」(/company-settings/api)で作成するか、ユーザー本人が自分用に「プロフィール > セキュリティ > APIキー」で作成し、Authorization: Bearer fmk_... として送信します。API キーで認証できるのは次の 3 つだけです:POST /api/v1/graphql の GraphQL エンドポイント、エネルギーデータ取り込みエンドポイント、そして /public-api/whoami と /public-api/ping。/api/v1/{resource} 配下の REST ルートは認証できません。これらはアプリケーション自体の API で、ユーザーセッション(JWT)が必要です。連携は GraphQL 上に構築してください。API キーでアクセスできるのは GraphQL です。

API キーで得られるもの

生のキーが表示されるのは作成時の一度だけで、以後は復元できません。保存されるのは SHA-256 ハッシュと、画面の一覧でキーを見分けるための fmk_ に続く 6 文字だけです。

名前

人が読むためのラベルです —「Oracle ERP ブリッジ」「2 号ライン端末」。利用するシステムごとに 1 本ずつ発行しておけば、他に影響を与えずに 1 本だけ失効させられます。

アクセスレベル

READ、READ_STOCK、FULL は、所有者の権限の上にかかる順序付き階層です。READ はクエリのみを実行し、既定値です。READ_STOCK はこれに adjustStock ミューテーションを追加します。FULL は、作成・更新・削除を含め、所有者のロールが許可するすべての操作を許可します。所有者がエネルギーの読み取り値を作成できる場合は、エネルギー取り込みエンドポイントも含まれます。

有効期限

任意の expiresAt です。その時刻を過ぎるとキーは認証しなくなります。恒久的なサービス連携では空のままにし、協力会社やパイロットには期限を設定してください。

失効

isActive はキーごとのスイッチで、数秒以内に反映されます。レコードは監査のために残るため、失効したキーは消えるのではなく追跡可能なまま残ります。

最終利用

lastUsedAt は認証済みの最後の呼び出しを記録します。もう誰も使っていないキーを見つけるにはこれで十分です。

所有者

キーがその人として動作する人物です。すべての呼び出しにその人の権限とデータの可視範囲が適用され、キーが書き込んだ内容にはその人の名前が記録されます。ERP 向けには専用ユーザー(「ERP 連携」)を作成し、ブリッジに必要な範囲だけをカバーするロールを割り当てたうえで、そのユーザーにキーを持たせてください。

API キーでは得られないもの

以下は現在の実装における本当の制限であり、このページの書き漏らしではありません。セキュリティレビューがこれらのいずれかに依存するなら、作り終えてからではなく今のうちに回避策を設計してください。

  • キーごとのモジュール指定はありません。キーは所有者のロールが許可するものを読み書きします。範囲を狭めるには、ロールの範囲がより狭い所有者にキーを持たせてください。その上で適用されるのは 3 つのアクセスレベルだけです。キー自体に、利用できるモジュールを列挙することはできません。
  • IP 許可リストはありません。キーはインターネット上のどこからでも有効です。機密情報として扱ってください。保管先は金庫やプラットフォームのシークレットストアであり、リポジトリや共有ドライブ、サポートチケットは論外です。
  • キー単位の利用ログはありません。保持されるのは lastUsedAt だけです。キー単位の呼び出し履歴も、キー単位のクォータも、どのキーがどの操作を行ったかの記録もありません。1 日あたりのクォータカウンターは会社単位で、すべてのキーが共有します。したがって暴走した連携が 1 つあるだけで、他のすべての連携のクォータを使い切ってしまいます。
  • 他人に代わる承認はできません。所有者自身が承認できる場合でも、キーがワークフローステップを承認することはありません。承認はアプリ内で人が行う行為のままです。
  • 1 本のキーにつき 1 社です。キーは作成された会社に紐づき、他社のデータにはアクセスできません。他社のレコードは単に「見つからない」扱いになります。

読み取れるもの

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 フィールドに含まれます。

キーの確認と作業指示の読み取りを 1 回の呼び出しで行う: 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 } }"}'

書き込めるもの

ミューテーションは 12 個です。作業指示・資産・部品に対する作成、更新、削除に加え、符号付きの在庫移動を行う adjustStock があります。これらはアプリケーション自身が使うのと同じサービスを通るため、参照番号(WO-…、ASS-…、PART-…)、監査レコード、通知は画面上の操作とまったく同じように振る舞います。在庫については adjustStock を選んでください。同一トランザクション内で StockMovement の監査レコードを書き込み、数量がゼロを下回ることを拒否します。一方 updatePart(quantity) は絶対値を設定し、差分を調整として記録します。在庫をロケーションごとに管理している部品では、adjustStock に locationId を渡してください。そうした部品では updatePart(quantity) は拒否されます。11 個目の addMeterReading は、メーター画面と同じサービスを通してメーターの検針値を記録するため、メータートリガーとメーターベースの予防保全はアプリで入力した検針値とまったく同じように反応します。12 個目の setPurchaseOrderErpReference は、ERP 側の伝票番号を発注書に保存し、後で purchaseOrder(erpReference: …) で両者を突き合わせられるようにします。FULL アクセスのキーが必要で、意図的に Webhook を発生させません。ERP が自分の書き込みをイベントとして受け取ることはありません。

API がまったくカバーしていないもの

GraphQL スキーマに含まれるのは、作業指示・設備・設備停止・部品とその在庫変動・メーター・発注書・ロケーション・仕入先・顧客・フロアプランです。それ以外はありません。以下は FreeMaint で完全に動作しているのに、現時点で API 面を一切持たない機能です。連携から読み取ることも書き込むことも購読することもできず、既存クエリのどの組み合わせでも到達できません。

  • 点検 — テンプレートも、予定された実施も、記録された結果も、アクションコードも対象外です。点検のクエリも、ミューテーションも、Webhook イベントも存在しません。
  • ユーザー、ロール、チーム — この API ではユーザーの一覧取得も作成も無効化もできません。ユーザーのプロビジョニングはアプリ内で行います。Enterprise プランでは SCIM で自動化できますが、それは別個の仕組みであり、この API の一部ではありません。
  • プロジェクト — クエリもミューテーションもありません。
  • 購買依頼 — クエリもミューテーションもありません。発注書は読み取れ(purchaseOrders、purchaseOrder)、ERP は自身の参照番号を書き戻せ(setPurchaseOrderErpReference)、purchase_order.* の Webhook で通知も届きます。ただし API から発注書を作成・編集・入荷処理することはできず、それはアプリで行います。
  • ファイル、写真、添付 — ドキュメントや写真をアップロードすることも、読み戻すこともできません。作業指示の写真、部品の画像、点検の証跡はいずれも API の外にあります。レスポンスに現れる URL、たとえば部品の imageUrl は、アップロード用の経路ではありません。
  • 予防保全のスケジュール、作業時間の記録、タスク、チェックリスト — 公開されていません。

重要

開発範囲は、ブラウザ上で製品ができることではなく、このリストを基準に見積もってください。FreeMaint 自身の Web アプリとモバイルアプリは、API キーでは届かない内部 REST API の上で動いています。したがって画面に見えている機能があることは、連携からそれを操作できる証拠にはなりません。

Webhook — イベントカタログ

Webhook は連携の「プッシュ」側です。ポーリングする代わりに、何かが起きた瞬間に FreeMaint があなたの URL へ POST します。設定は「会社設定 > Webhooks」(/company-settings/webhooks)で行い、Webhook ごとに必要なイベントを購読します。カタログは固定リストです。

  • 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 — 会社ごとに 1 日 1 通のまとめ。発注点以下の部品を、一度も動いていない部品も含めて列挙する
  • part.out_of_stock_summary — 在庫ゼロの部品を対象とした、同じ日次まとめ
  • part.consumed — 部品が作業指示で消費された、または作業指示から返却された。quantity は符号付き
  • compliance.chain_divergence — 監査ハッシュチェーンが途切れた(Enterprise)
  • asset.downtime_started — 設備停止が開始された
  • asset.downtime_ended — 設備停止が終了した。停止時間を計上できる
  • report.daily_summary — その日に完了した作業を、指定した時刻に 1 日 1 回
  • 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 になります

警告

点検のイベントはなく、不具合や指摘事項のイベントもありません。「点検が不合格になったら自社の ERP が反応する」という運用であれば、今日それを起点に発火するものは何もありません。近いイベントがあるはずだと前提を置かず、この欠落を明示的に織り込んで計画してください。

配信のエンベロープ

GENERIC の配信はすべて POST で、ボディは常に同じ 3 キーのエンベロープです。event、timestamp、data の 3 つで、data はエンティティ名をキーに持ちます。ヘッダーは 3 つ付きます。X-FreeMaint-Signature は t=<unix 秒>,v1=<hex> の形式で、v1 は該当 Webhook のシークレットを鍵として文字列「<t>.<生ボディ>」そのものを HMAC-SHA256 した値です。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" } } }

信頼性と監視

配信は 2 秒を基数とする指数バックオフで 3 回試行され — おおよそ 2 秒、次に 4 秒、次に 8 秒 — 各試行は 10 秒でタイムアウトします。2xx が返れば配信済みとなります。5xx の応答とネットワークエラーは再試行されます。4xx は恒久的な拒否として扱われ、再試行はいっさい行われません。自社側の不具合でエンドポイントが 400 や 401 を返した場合、その配信はそこで止まり、手動の再送でしか取り戻せません。

  • 配信はすべて保存されます。イベント、実際に送信したペイロード、ステータス、あなたが返した HTTP コード、応答ボディの先頭約 500 文字、試行回数、エラーメッセージが残ります。
  • ステータスは PENDING、DELIVERED、FAILED、RATE_LIMITED(Starter の 1 日 100 配信の上限に達した)、SKIPPED_TIER(プランがその宛先を含まなくなった)です。
  • 保存された配信は Webhook の履歴から再送できます。保存済みのペイロードがそのまま再送されるため、中の業務キーは変わらず、正しく重複排除する受信側が二重に計上することはありません。
  • 各 Webhook は lastTriggeredAt、lastSuccessAt、lastFailureAt、failureCount を持ちます。完全な履歴を開かなくても、ヘルスチェックにはこれで十分です。
  • Webhook の一覧と配信ログはどのプランでも読めます。プランの引き下げで止まった連携も、黙って死ぬのではなく見える形で残ります。
  • 配信が失敗し始めても、知らせてくれるものは何もありません。メールも通知も飛びません。静かな障害が問題になるなら、自社の監視から配信ログをポーリングしてください。

セキュリティ

URL は Webhook の保存時に検証され、その規則は意図して厳格にしてあります。Webhook は当社のサーバーが送信する外向きのリクエストであり、緩い規則は SSRF の経路になるからです。

  • キーは SHA-256 ハッシュとして保存されます。作成後に FreeMaint から生の値を取り出すことはできません。
  • Webhook の URL は https でなければなりません。平文の http とその他のスキームはすべて拒否されます。
  • 受け付けるポートは 443 と 8443 のみです。
  • ループバック、リンクローカル、プライベート範囲のホスト(127.x、10.x、192.168.x、172.16–31.x、::1 など)は拒否されます。受信側はインターネットから到達可能である必要があります。工場の内側にしか存在しないシステムには、公開のリレーかトンネルが必要です。
  • URL に認証情報を含めること(https://user:pass@host)は拒否されます。
  • リダイレクトは追跡しません。登録したエンドポイントが、そのまま POST を受け取るエンドポイントです。
  • Webhook ごとに固有の署名シークレットがあり、シークレットを再発行する操作も用意されています。再発行は即座に有効になるため、新しいシークレットは同じタイミングで自社側にも展開してください。
  • SLACK と TEAMS の宛先には各プラットフォーム向けに整形されたメッセージが届き、FreeMaint の HMAC ヘッダーは付きません。この 2 つでは URL の秘匿性だけが唯一の認証手段です。暗号的に検証したいものには GENERIC を使ってください。

今うまく機能するパターン

現在の API 面がきれいに支えられる連携と、それぞれを本番で安全に運用するための勘所です。

消費を ERP に反映する

part.consumed を購読します。quantity は符号付きで、作業へ払い出すときは負、返却されるときは正になります。ペイロードには再試行や再送をまたいで変わらない stock_movement_id が入っているので、これを冪等キーとして使ってください。adjustStock ミューテーションはこのイベントを意図的に発火しません。これにより、自分が書き込んだ内容がエコーとして戻ってきてループすることを防げます。

在庫の補正を送り込む

自社側から符号付きの差分を指定して adjustStock を呼び出します。READ_STOCK キーは、何も作成・更新・削除できない状態のままこの操作を行えるため、一方向の在庫連携に最適です。

別のシステムから作業指示を作成する

SCADA のアラーム、ヘルプデスクのチケット、ERP の故障通知をきっかけに createWorkOrder を呼びます。作業指示には通常どおりの参照番号が付き、他と同じように work_order.created を発火します。

部品マスタを同期させ続ける

parts(updatedSince:) は、指定した時点以降に変更されたものだけを返します。createPart と updatePart は customReference(その部品に付けた自社独自のコード)を受け付け、part(barcode:) はこの値にも一致するため、ERP ですでに使っている識別子で部品を検索できます。一意性は強制されないため、重複しないよう管理するのは自社側の責任です。

完了で処理を閉じる

work_order.completed は作業指示とその直前のステータスを運びます。相手側の保全通知を閉じるにはこれで十分です。

必要なプラン

別々の 2 つのゲートがあります。1 つは API 用、もう 1 つは Webhook 用です。Starter の会社は、API がなくても Webhook を受け取れます。

GraphQL API と API キー

Business 以上。Core と Starter ではキーをまったく作成できません。

1 日あたりのクォータ

Business は 1 日 5,000 回、Enterprise は 100,000 回です。会社単位かつ操作単位で数えられ、00:00 UTC にリセットされます。複数の操作を 1 回の HTTP リクエストにまとめても、節約できるのは往復回数であってクォータではありません。

Webhook

Starter 以上で、1 日 100 配信が上限です。Business からは無制限になります。

Slack と Teams の宛先

Enterprise。汎用の Webhook は Starter から利用できます。

ヒント

上記の欠落のどれかに突き当たる計画をお持ちですか。作ろうとしているものを contact@freemaint.com までお知らせください。次にどの機能を出すかは、実際にお客様が詰まっている箇所で決めています。

このページは役に立ちましたか?