Overview
SAP Field Service Management (now documented as SAP Field Service and Asset Management) plans and runs field service: service calls (tickets), activities (the jobs technicians do) and equipment at customer sites. This connector lets the broker open a service call and an activity when a machine raises an alarm, put run hours and other machine data on the equipment, and close the loop with business actions, so planners and technicians see what the machine sees. The template has two connectors:- SapFsmApi (
SapFieldServiceManagementRestApi.lot), a client connector on your cluster host with 24 events: 13 writes, 10 reads and 1 token event. It has noDELETEorPUTevents; cancel actions are business actions, not deletes. - SapFsmBusinessRuleReceiver (
cmms_sap_fsm_webhook.lot), an optional server connector on port9012that receives calls from a Business Rule Webhook action and publishes them on MQTT.
SAP publishes the FSM API specs on the SAP Business Accelerator Hub, which needs an SAP login, so the events were picked by hand from the SAP Help Portal (Data API v4, Data Model, Query API, Service API, OAuth) and the SAP/fsm-sdk source.
Events
| Event | Topic | Request | Kind | What it does |
|---|---|---|---|---|
GetToken | cmms/sapfsm/token/create | POST /api/oauth2/v2/token | Auth | Get an OAuth 2.0 access token (client credentials, HTTP Basic with the FSM OAuth client id:secret). Reply access_token, token_type, expires_in; send access_token on every other event |
ListServiceCalls | cmms/sapfsm/service-call/list | GET /api/data/v4/ServiceCall?dtos=ServiceCall.27&page={page}&pageSize={page_size} | Read | Service calls, paged (default 20 per page, keep pageSize at 1000 or less). Reply data[].serviceCall plus currentPage / lastPage |
FindServiceCalls | cmms/sapfsm/service-call/find | GET /api/data/v4/ServiceCall?dtos=ServiceCall.27&query={query}&pageSize={page_size} | Read | Service calls matching a URL-encoded Data API query condition, e.g. lastChanged>=<epoch ms> for incremental polls |
GetServiceCall | cmms/sapfsm/service-call/get | GET /api/data/v4/ServiceCall/{id}?dtos=ServiceCall.27 | Read | One service call by cloud id |
GetServiceCallByExternalId | cmms/sapfsm/service-call/get-by-external-id | GET /api/data/v4/ServiceCall/externalId/{external_id}?dtos=ServiceCall.27 | Read | One service call by your externalId (e.g. the alarm id) |
CreateServiceCall | cmms/sapfsm/service-call/create | POST /api/data/v4/ServiceCall?dtos=ServiceCall.27 | Write | Create a service call from an alarm; reply 201 {data:[{serviceCall:{id, code, lastChanged…}}]} |
UpdateServiceCall | cmms/sapfsm/service-call/update | PATCH /api/data/v4/ServiceCall/{id}?dtos=ServiceCall.27&forceUpdate={force_update} | Write | Patch service call fields; send the current lastChanged (optimistic lock) or force_update true. Reply 201 |
UpsertServiceCallByExternalId | cmms/sapfsm/service-call/upsert | PATCH /api/data/v4/ServiceCall/externalId/{external_id}?dtos=ServiceCall.27 | Write | Create or update the service call keyed by your externalId (one service call per alarm id; creating needs all mandatory fields) |
FindActivities | cmms/sapfsm/activity/find | GET /api/data/v4/Activity?dtos=Activity.43&query={query}&pageSize={page_size} | Read | Activities matching a URL-encoded Data API query condition |
GetActivity | cmms/sapfsm/activity/get | GET /api/data/v4/Activity/{id}?dtos=Activity.43 | Read | One activity by cloud id (status, executionStage, responsibles, object -> service call) |
CreateActivity | cmms/sapfsm/activity/create | POST /api/data/v4/Activity?dtos=Activity.43 | Write | Create an activity under a service call (object = {objectId, objectType SERVICECALL}) |
UpdateActivity | cmms/sapfsm/activity/update | PATCH /api/data/v4/Activity/{id}?dtos=Activity.43&forceUpdate={force_update} | Write | Patch activity fields (send lastChanged or force_update true). Use the Service Management actions to change planning state |
FindEquipment | cmms/sapfsm/equipment/find | GET /api/data/v4/Equipment?dtos=Equipment.24&query={query}&pageSize={page_size} | Read | Equipment matching a URL-encoded query condition (e.g. serialNumber=’…’) |
GetEquipment | cmms/sapfsm/equipment/get | GET /api/data/v4/Equipment/{id}?dtos=Equipment.24 | Read | One equipment by cloud id (read lastChanged before a PATCH) |
GetEquipmentByExternalId | cmms/sapfsm/equipment/get-by-external-id | GET /api/data/v4/Equipment/externalId/{external_id}?dtos=Equipment.24 | Read | One equipment by externalId (usually the S/4HANA or ERP equipment number) |
UpdateEquipment | cmms/sapfsm/equipment/update | PATCH /api/data/v4/Equipment/{id}?dtos=Equipment.24&forceUpdate={force_update} | Write | Patch equipment with machine data (remarks, status, UDF values). force_update true skips the lastChanged lock |
UpsertEquipmentByExternalId | cmms/sapfsm/equipment/upsert | PATCH /api/data/v4/Equipment/externalId/{external_id}?dtos=Equipment.24 | Write | Patch equipment by externalId, e.g. a run-hours UDF referenced by its UDF meta externalId |
Query | cmms/sapfsm/query/run | POST /api/query/v1?dtos={dtos}&page={page}&pageSize={page_size} | Read | Read-only CoreSQL query (POST with {“query”: …}); dtos lists every DTO used, separated by ; |
CreateServiceCallWithActivities | cmms/sapfsm/service-call-tree/create | POST /service-management/api/v2/composite-tree/service-calls | Write | Service Management API composite tree: create a service call and its activities in one request |
CancelServiceCall | cmms/sapfsm/service-call/cancel | POST /service-management/api/v2/servicecalls/{id}/actions/cancel | Write | Business action: cancel a service call (not a delete) |
CompleteServiceCall | cmms/sapfsm/service-call/technically-complete | POST /service-management/api/v2/servicecalls/{id}/actions/technically-complete | Write | Business action: mark a service call technically complete |
ReleaseActivity | cmms/sapfsm/activity/release | POST /service-management/api/v2/activities/{id}/actions/release | Write | Business action: release a planned activity to the technician |
CloseActivity | cmms/sapfsm/activity/close | POST /service-management/api/v2/activities/{id}/actions/close | Write | Business action: close an activity |
CancelActivity | cmms/sapfsm/activity/cancel | POST /service-management/api/v2/activities/{id}/actions/cancel | Write | Business action: cancel an activity (not a delete) |
/response. Data API replies wrap objects as {"data": [{"serviceCall": {...}}]} (lower-case DTO name).
Prerequisites
- An SAP FSM company and its cluster:
eu,de,usorau(China usescn.fsm.sapcloud.cn). ChangeBASE_ADDRESStohttps://<cluster>.fsm.cloud.sap. The oldercoresuite.com/coresystems.nethosts are being migrated tofsm.cloud.sap. - An OAuth client created in Account Admin (client id + secret) with a permission group that can read and write service calls, activities and equipment and run the Service Management actions.
- The numeric account id and company id (Account Admin).
- Outbound HTTPS from the broker to the cluster host.
- For Business Rule webhooks only: a public HTTPS URL that forwards to the broker’s port
9012.
Authentication
The client secret and every access token act with the client’s permission group. Store
base64(client_id:client_secret) in the Hub secret SAP_FSM_BASIC_AUTH (and the ids in SAP_FSM_ACCOUNT_ID / SAP_FSM_COMPANY_ID), read them with GET SECRET, and never put them or a token in a connector file.GetToken posts grant_type=client_credentials to /api/oauth2/v2/token with HTTP Basic:
{
"basic": "<base64 of client_id:client_secret (Hub secret SAP_FSM_BASIC_AUTH)>",
"form": "grant_type=client_credentials"
}
access_token, token_type and expires_in. Every other event sends Authorization: Bearer {value.json.access_token}, X-Account-ID and X-Company-ID from the payload, and the fixed X-Client-ID: custom-coreflux-broker / X-Client-Version: 1.0 (SAP asks custom clients to use a custom- prefix). Call GetToken again before expires_in runs out.
Answers seen from the real hosts without valid credentials: a fake client returns 401 {"error":"unauthorized","error_description":"Bad credentials"}, a Data API call without a token 401 INVALID_LOGIN, and any call with a fake token 401 {"error":"invalid_token"}.
Payload conventions
Each payload carries the context and the event’s own fields; the request body comes frombody:
{
"access_token": "<access_token from GetToken>",
"account_id": "<X-Account-ID>",
"company_id": "<X-Company-ID>",
"id": "<ServiceCall id>",
"force_update": "false",
"body": {
"lastChanged": 1759737600000,
"priority": "LOW",
"remarks": "Alarm cleared (Coreflux)"
}
}
idis the 32-character FSM id;external_idis your id (alarm id, ERP equipment number). Both go into the path, so pass only values you trust.queryis a URL-encoded Data API condition, for examplelastChanged%3E%3D1759737600000(lastChanged>=1759737600000) for incremental polls.- Updates use optimistic locking: send the current
lastChangedfrom a GET, or setforce_updateto"true". PATCH .../externalId/<id>creates the object when it does not exist yet (send all mandatory fields).- Pages: default 20,
page_size1000 at most; replies carrycurrentPage,lastPage,totalObjectCount. - DTO versions are pinned:
ServiceCall.27,Activity.43,Equipment.24.
Alarm to service call
{
"access_token": "<access_token from GetToken>",
"account_id": "<X-Account-ID>",
"company_id": "<X-Company-ID>",
"body": {
"subject": "Alarm TT-101 = 92.4 C on compressor C-7 (Coreflux)",
"priority": "HIGH",
"businessPartner": "<BusinessPartner id>",
"equipments": [
"<Equipment id>"
],
"startDateTime": "2026-10-06T08:00:00Z",
"dueDateTime": "2026-10-07T17:00:00Z",
"typeCode": "<service call type code>",
"statusCode": "<service call status code>",
"originCode": "<origin code>",
"remarks": "Opened by Coreflux broker"
}
}
object points to the service call):
{
"access_token": "<access_token from GetToken>",
"account_id": "<X-Account-ID>",
"company_id": "<X-Company-ID>",
"body": {
"subject": "Inspect compressor C-7",
"type": "ASSIGNMENT",
"object": {
"objectId": "<ServiceCall id>",
"objectType": "SERVICECALL"
},
"businessPartner": "<BusinessPartner id>",
"equipment": "<Equipment id>",
"startDateTime": "2026-10-06T09:00:00Z",
"endDateTime": "2026-10-06T10:00:00Z",
"durationInMinutes": 60,
"remarks": "Alarm TT-101 (Coreflux)"
}
}
{
"access_token": "<access_token from GetToken>",
"account_id": "<X-Account-ID>",
"company_id": "<X-Company-ID>",
"body": {
"subject": "Alarm TT-101 on compressor C-7 (Coreflux)",
"priority": "HIGH",
"businessPartner": {
"id": "<BusinessPartner id>"
},
"activities": [
{
"subject": "Inspect compressor C-7",
"durationInMinutes": 60
}
]
}
}
Machine data on equipment
{
"access_token": "<access_token from GetToken>",
"account_id": "<X-Account-ID>",
"company_id": "<X-Company-ID>",
"external_id": "<ERP equipment number>",
"body": {
"udfValues": [
{
"meta": {
"externalId": "<UDF meta externalId, e.g. RUN_HOURS>"
},
"value": "1287.5"
}
]
}
}
udfValues reference a user-defined field you create for the equipment in FSM (here by its UDF meta externalId). UpdateEquipment patches standard fields such as remarks by FSM id.
Query API
{
"access_token": "<access_token from GetToken>",
"account_id": "<X-Account-ID>",
"company_id": "<X-Company-ID>",
"dtos": "ServiceCall.27;Equipment.24",
"page": "1",
"page_size": "100",
"body": {
"query": "SELECT sc.id, sc.code, sc.subject, sc.priority FROM ServiceCall sc WHERE sc.priority = 'HIGH'"
}
}
dtos must list every DTO the CoreSQL statement uses, separated by ;.
Business Rule receiver
FSM has no webhook subscription API. Instead, a Business Rule (Admin > Company > Business Rules) with a Webhook action calls any URL with your headers and body when a service call, activity or other object is created or changed. Point it at:| Event | Path | Publishes on |
|---|---|---|
NotifyServiceCalls | POST /sapfsm/notify/service-calls | cmms/sapfsm/service-call-notification/received |
NotifyActivities | POST /sapfsm/notify/activities | cmms/sapfsm/activity-notification/received |
NotifyEvents | POST /sapfsm/notify/events | cmms/sapfsm/notification/received |
Deploying the receiver opens port
9012. FSM calls out from the SAP cloud, so put a reverse proxy or tunnel with a public TLS certificate in front of it, allow SAP’s FSM egress addresses and check a shared secret header there: the connector does not authenticate callers.Connector
# Coreflux REST_API template — SAP Field Service Management (SAP Field Service and Asset Management) (beta): get an
# OAuth 2.0 token, create service calls from alarms (Data API v4 ServiceCall.27), upsert them by your own alarm id,
# find / read / patch service calls, activities (Activity.43) and equipment (Equipment.24, e.g. run hours as a UDF),
# run read-only CoreSQL queries (Query API v1), create a service call with activities in one call and run the
# Service Management API v2 business actions (cancel / technically complete service calls, release / close / cancel
# activities).
# the Business Accelerator Hub specs need an SAP login). Credentials are placeholders only.
# Docs: https://docs.coreflux.org/latest/lot-language/routes/rest-api-apps/sap-field-service-management
#
# Host: your cluster, https://YOUR_CLUSTER.fsm.cloud.sap with YOUR_CLUSTER = eu | de | us | au (China:
# https://cn.fsm.sapcloud.cn). The older eu.coresuite.com / de.coresystems.net hosts are being migrated to
# fsm.cloud.sap. Use the cluster_url your account shows in Account Admin.
# Auth: OAuth 2.0 client credentials. GetToken posts grant_type=client_credentials ({value.json.form}) to
# /api/oauth2/v2/token with Authorization: Basic {value.json.basic} (base64 of client_id:client_secret; keep it
# in the Hub secret SAP_FSM_BASIC_AUTH). Every other event sends Authorization: Bearer {value.json.access_token}
# plus X-Account-ID {value.json.account_id} and X-Company-ID {value.json.company_id} (numeric, from Account Admin)
# and X-Client-ID custom-coreflux-broker / X-Client-Version 1.0. Tokens expire (expires_in): call GetToken again.
# Live (2026-10-06, eu): fake Basic -> 401 {"error":"unauthorized","error_description":"Bad credentials"};
# Data API without a token -> 401 INVALID_LOGIN; fake Bearer -> 401 invalid_token.
# Objects: Data API v4 DTO versions pinned to the newest on the SAP Help Portal on 2026-10-06 (ServiceCall.27,
# Activity.43, Equipment.24; SAP/fsm-sdk 4.0.1 still ships 26 / 39 / 23). Status, type, origin and priority codes
# are company-configurable: read them from an existing service call first.
# Payload fields (control fields stay out of bodies; request bodies come from {value.json.body}):
# access_token, account_id, company_id, id, external_id, query, page, page_size, force_update, dtos, basic, form, body
# query is a URL-encoded Data API condition (lastChanged%3E%3D1759737600000 = lastChanged>=...). id / external_id
# go into the path: pass FSM ids or external ids you trust, never raw user input.
# CreateServiceCall body = {"subject": "...", "priority": "HIGH", "businessPartner": "<id>", "equipments": ["<id>"]}
# UpdateServiceCall id = <ServiceCall id>, force_update = "false", body = {"lastChanged": <from GET>, "priority": "LOW"}
# UpsertEquipmentByExternalId external_id = <ERP equipment no>, body = {"udfValues": [{"meta": {"externalId": "RUN_HOURS"}, "value": "1287.5"}]}
# Query dtos = "ServiceCall.27", body = {"query": "SELECT sc.id, sc.subject FROM ServiceCall sc"}
# Errors: 401 (token), 403 (permission group), 404, 409 / CA-* codes (e.g. stale lastChanged); Data API pages are
# limited to 1000 objects and 100,000 objects per pagination. Writes run the company's Business Rules and sync to ERP.
#
DEFINE CONNECTOR SapFsmApi WITH TYPE REST_API
ADD REST_API_CONFIG
WITH BASE_ADDRESS "https://YOUR_CLUSTER.fsm.cloud.sap"
WITH ENABLE_CLIENT "true"
WITH USE_SSL "true"
WITH REQUEST_TIMEOUT '30'
WITH DEFAULT_CONTENT_TYPE "application/json"
# Get an OAuth 2.0 access token (client credentials, HTTP Basic with the FSM OAuth client id:secret). Reply access_token, token_type, expires_in; send access_token on every other event
ADD EVENT GetToken
WITH SOURCE_TOPIC "cmms/sapfsm/token/create"
WITH DESTINATION_TOPIC "cmms/sapfsm/token/create/response"
WITH METHOD "POST"
WITH ENDPOINT "/api/oauth2/v2/token"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Basic {value.json.basic}"
ADD "Content-Type" "application/x-www-form-urlencoded"
WITH BODY "{value.json.form}"
# Service calls, paged (default 20 per page, keep pageSize at 1000 or less). Reply data[].serviceCall plus currentPage / lastPage
ADD EVENT ListServiceCalls
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/list"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/list/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/ServiceCall?dtos=ServiceCall.27&page={value.json.page}&pageSize={value.json.page_size}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# Service calls matching a URL-encoded Data API query condition, e.g. lastChanged>=<epoch ms> for incremental polls
ADD EVENT FindServiceCalls
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/find"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/find/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/ServiceCall?dtos=ServiceCall.27&query={value.json.query}&pageSize={value.json.page_size}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# One service call by cloud id
ADD EVENT GetServiceCall
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/get"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/get/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/ServiceCall/{value.json.id}?dtos=ServiceCall.27"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# One service call by your externalId (e.g. the alarm id)
ADD EVENT GetServiceCallByExternalId
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/get-by-external-id"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/get-by-external-id/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/ServiceCall/externalId/{value.json.external_id}?dtos=ServiceCall.27"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# WRITE: Create a service call from an alarm; reply 201 {data:[{serviceCall:{id, code, lastChanged...}}]}
ADD EVENT CreateServiceCall
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/create"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/create/response"
WITH METHOD "POST"
WITH ENDPOINT "/api/data/v4/ServiceCall?dtos=ServiceCall.27"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Patch service call fields; send the current lastChanged (optimistic lock) or force_update true. Reply 201
ADD EVENT UpdateServiceCall
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/update"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/update/response"
WITH METHOD "PATCH"
WITH ENDPOINT "/api/data/v4/ServiceCall/{value.json.id}?dtos=ServiceCall.27&forceUpdate={value.json.force_update}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Create or update the service call keyed by your externalId (one service call per alarm id; creating needs all mandatory fields)
ADD EVENT UpsertServiceCallByExternalId
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/upsert"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/upsert/response"
WITH METHOD "PATCH"
WITH ENDPOINT "/api/data/v4/ServiceCall/externalId/{value.json.external_id}?dtos=ServiceCall.27"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# Activities matching a URL-encoded Data API query condition
ADD EVENT FindActivities
WITH SOURCE_TOPIC "cmms/sapfsm/activity/find"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity/find/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/Activity?dtos=Activity.43&query={value.json.query}&pageSize={value.json.page_size}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# One activity by cloud id (status, executionStage, responsibles, object -> service call)
ADD EVENT GetActivity
WITH SOURCE_TOPIC "cmms/sapfsm/activity/get"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity/get/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/Activity/{value.json.id}?dtos=Activity.43"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# WRITE: Create an activity under a service call (object = {objectId, objectType SERVICECALL})
ADD EVENT CreateActivity
WITH SOURCE_TOPIC "cmms/sapfsm/activity/create"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity/create/response"
WITH METHOD "POST"
WITH ENDPOINT "/api/data/v4/Activity?dtos=Activity.43"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Patch activity fields (send lastChanged or force_update true). Use the Service Management actions to change planning state
ADD EVENT UpdateActivity
WITH SOURCE_TOPIC "cmms/sapfsm/activity/update"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity/update/response"
WITH METHOD "PATCH"
WITH ENDPOINT "/api/data/v4/Activity/{value.json.id}?dtos=Activity.43&forceUpdate={value.json.force_update}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# Equipment matching a URL-encoded query condition (e.g. serialNumber='...')
ADD EVENT FindEquipment
WITH SOURCE_TOPIC "cmms/sapfsm/equipment/find"
WITH DESTINATION_TOPIC "cmms/sapfsm/equipment/find/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/Equipment?dtos=Equipment.24&query={value.json.query}&pageSize={value.json.page_size}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# One equipment by cloud id (read lastChanged before a PATCH)
ADD EVENT GetEquipment
WITH SOURCE_TOPIC "cmms/sapfsm/equipment/get"
WITH DESTINATION_TOPIC "cmms/sapfsm/equipment/get/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/Equipment/{value.json.id}?dtos=Equipment.24"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# One equipment by externalId (usually the S/4HANA or ERP equipment number)
ADD EVENT GetEquipmentByExternalId
WITH SOURCE_TOPIC "cmms/sapfsm/equipment/get-by-external-id"
WITH DESTINATION_TOPIC "cmms/sapfsm/equipment/get-by-external-id/response"
WITH METHOD "GET"
WITH ENDPOINT "/api/data/v4/Equipment/externalId/{value.json.external_id}?dtos=Equipment.24"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
# WRITE: Patch equipment with machine data (remarks, status, UDF values). force_update true skips the lastChanged lock
ADD EVENT UpdateEquipment
WITH SOURCE_TOPIC "cmms/sapfsm/equipment/update"
WITH DESTINATION_TOPIC "cmms/sapfsm/equipment/update/response"
WITH METHOD "PATCH"
WITH ENDPOINT "/api/data/v4/Equipment/{value.json.id}?dtos=Equipment.24&forceUpdate={value.json.force_update}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Patch equipment by externalId, e.g. a run-hours UDF referenced by its UDF meta externalId
ADD EVENT UpsertEquipmentByExternalId
WITH SOURCE_TOPIC "cmms/sapfsm/equipment/upsert"
WITH DESTINATION_TOPIC "cmms/sapfsm/equipment/upsert/response"
WITH METHOD "PATCH"
WITH ENDPOINT "/api/data/v4/Equipment/externalId/{value.json.external_id}?dtos=Equipment.24"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# Read-only CoreSQL query (POST with {"query": ...}); dtos lists every DTO used, separated by ;
ADD EVENT Query
WITH SOURCE_TOPIC "cmms/sapfsm/query/run"
WITH DESTINATION_TOPIC "cmms/sapfsm/query/run/response"
WITH METHOD "POST"
WITH ENDPOINT "/api/query/v1?dtos={value.json.dtos}&page={value.json.page}&pageSize={value.json.page_size}"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Service Management API composite tree: create a service call and its activities in one request
ADD EVENT CreateServiceCallWithActivities
WITH SOURCE_TOPIC "cmms/sapfsm/service-call-tree/create"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call-tree/create/response"
WITH METHOD "POST"
WITH ENDPOINT "/service-management/api/v2/composite-tree/service-calls"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Business action: cancel a service call (not a delete)
ADD EVENT CancelServiceCall
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/cancel"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/cancel/response"
WITH METHOD "POST"
WITH ENDPOINT "/service-management/api/v2/servicecalls/{value.json.id}/actions/cancel"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Business action: mark a service call technically complete
ADD EVENT CompleteServiceCall
WITH SOURCE_TOPIC "cmms/sapfsm/service-call/technically-complete"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call/technically-complete/response"
WITH METHOD "POST"
WITH ENDPOINT "/service-management/api/v2/servicecalls/{value.json.id}/actions/technically-complete"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Business action: release a planned activity to the technician
ADD EVENT ReleaseActivity
WITH SOURCE_TOPIC "cmms/sapfsm/activity/release"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity/release/response"
WITH METHOD "POST"
WITH ENDPOINT "/service-management/api/v2/activities/{value.json.id}/actions/release"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Business action: close an activity
ADD EVENT CloseActivity
WITH SOURCE_TOPIC "cmms/sapfsm/activity/close"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity/close/response"
WITH METHOD "POST"
WITH ENDPOINT "/service-management/api/v2/activities/{value.json.id}/actions/close"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# WRITE: Business action: cancel an activity (not a delete)
ADD EVENT CancelActivity
WITH SOURCE_TOPIC "cmms/sapfsm/activity/cancel"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity/cancel/response"
WITH METHOD "POST"
WITH ENDPOINT "/service-management/api/v2/activities/{value.json.id}/actions/cancel"
WITH HEADERS
ADD "Accept" "application/json"
ADD "Authorization" "Bearer {value.json.access_token}"
ADD "X-Account-ID" "{value.json.account_id}"
ADD "X-Company-ID" "{value.json.company_id}"
ADD "X-Client-ID" "custom-coreflux-broker"
ADD "X-Client-Version" "1.0"
ADD "Content-Type" "application/json"
WITH BODY "{value.json.body}"
# SAP FSM Business Rule receiver — REST_API route (server mode, beta, companion of SapFieldServiceManagementRestApi.lot)
# Receives the HTTP calls that an SAP Field Service Management Business Rule makes with its 'Webhook' action
# (Admin > Company > Business Rules: trigger on create / update of a ServiceCall, Activity, Equipment ..., action
# Webhook, method POST, your URL, header + JSON body you define) and publishes the body on MQTT, so changes made
# in FSM reach the broker without polling. Opt-in: deploying it opens an HTTP port.
# Docs: https://docs.coreflux.org/latest/lot-language/routes/rest-api-apps/sap-field-service-management
#
# FSM calls out from the SAP cloud: put a reverse proxy or tunnel with a public TLS certificate in front of
# SERVER_PORT, allow the SAP FSM cloud egress addresses ('Incoming firewall configuration from SAP Field Service
# Cloud') and check a shared secret header there: this route does not authenticate callers. The body is
# whatever your Business Rule sends (e.g. ${serviceCall} variables); treat it as JSON you defined.
DEFINE CONNECTOR SapFsmBusinessRuleReceiver WITH TYPE REST_API
ADD REST_API_CONFIG
WITH ENABLE_SERVER "true"
WITH SERVER_PORT '9012'
WITH DEFAULT_CONTENT_TYPE "application/json"
# Business Rule Webhook action (POST https://<public host>/sapfsm/notify/service-calls) when a service call is created or changes
ADD EVENT NotifyServiceCalls
WITH ENDPOINT "/sapfsm/notify/service-calls"
WITH METHOD "POST"
WITH DESTINATION_TOPIC "cmms/sapfsm/service-call-notification/received"
# Same for activities (assignment, release, checkout, close)
ADD EVENT NotifyActivities
WITH ENDPOINT "/sapfsm/notify/activities"
WITH METHOD "POST"
WITH DESTINATION_TOPIC "cmms/sapfsm/activity-notification/received"
# Any other Business Rule webhook body (equipment, checklists). Filter on the body in a LoT action
ADD EVENT NotifyEvents
WITH ENDPOINT "/sapfsm/notify/events"
WITH METHOD "POST"
WITH DESTINATION_TOPIC "cmms/sapfsm/notification/received"
Example actions
# Example actions for the SapFsmApi route (docs page). Not part of the route template.
# Secrets: SAP_FSM_BASIC_AUTH (base64 of <OAuth client id>:<client secret>, from Account Admin > Clients),
# SAP_FSM_ACCOUNT_ID and SAP_FSM_COMPANY_ID (numeric ids from Account Admin). The access token is kept on the
# internal topic internal/sapfsm/access_token. Replace the <...> placeholders (business partner, equipment ids).
DEFINE ACTION SapFsmGetToken
ON EVERY 30 MINUTES DO
SET "basic" WITH GET SECRET "SAP_FSM_BASIC_AUTH"
PUBLISH TOPIC "cmms/sapfsm/token/create" WITH { "basic": {basic}, "form": "grant_type=client_credentials" }
DEFINE ACTION SapFsmKeepToken
ON TOPIC "cmms/sapfsm/token/create/response" DO
SET "tok" WITH (GET JSON "access_token" IN PAYLOAD AS STRING)
KEEP TOPIC "internal/sapfsm/access_token" WITH {tok}
DEFINE ACTION SapFsmServiceCallFromAlarm
ON TOPIC "porto/line1/compressor/alarm/raised" DO
SET "tok" WITH GET TOPIC "internal/sapfsm/access_token" AS STRING
SET "acc" WITH GET SECRET "SAP_FSM_ACCOUNT_ID"
SET "comp" WITH GET SECRET "SAP_FSM_COMPANY_ID"
SET "alarm" WITH (GET JSON "alarm" IN PAYLOAD AS STRING)
SET "value" WITH (GET JSON "value" IN PAYLOAD AS STRING)
SET "subj" WITH "Coreflux alarm " + {alarm} + " (value " + {value} + ")"
PUBLISH TOPIC "cmms/sapfsm/service-call/create" WITH { "access_token": {tok}, "account_id": {acc}, "company_id": {comp}, "body": { "subject": {subj}, "priority": "HIGH", "businessPartner": "<BusinessPartner id>", "equipments": ["<Equipment id>"] } }
DEFINE ACTION SapFsmRunHoursToEquipment
ON TOPIC "porto/line1/press01/run-hours" DO
SET "tok" WITH GET TOPIC "internal/sapfsm/access_token" AS STRING
SET "acc" WITH GET SECRET "SAP_FSM_ACCOUNT_ID"
SET "comp" WITH GET SECRET "SAP_FSM_COMPANY_ID"
SET "hours" WITH PAYLOAD AS STRING
PUBLISH TOPIC "cmms/sapfsm/equipment/upsert" WITH { "access_token": {tok}, "account_id": {acc}, "company_id": {comp}, "external_id": "<ERP equipment number>", "body": { "udfValues": [ { "meta": { "externalId": "RUN_HOURS" }, "value": {hours} } ] } }
DEFINE ACTION SapFsmPollChangedServiceCalls
ON EVERY 15 MINUTES DO
SET "tok" WITH GET TOPIC "internal/sapfsm/access_token" AS STRING
SET "acc" WITH GET SECRET "SAP_FSM_ACCOUNT_ID"
SET "comp" WITH GET SECRET "SAP_FSM_COMPANY_ID"
PUBLISH TOPIC "cmms/sapfsm/query/run" WITH { "access_token": {tok}, "account_id": {acc}, "company_id": {comp}, "dtos": "ServiceCall.27", "page": "1", "page_size": "100", "body": { "query": "SELECT sc.id, sc.code, sc.subject, sc.priority, sc.statusName FROM ServiceCall sc WHERE sc.priority = 'HIGH'" } }
Notes
- Writes run the company’s Business Rules and, if connected, sync to SAP S/4HANA or ECC.
- Status, type, origin and priority codes and UDFs are company-specific: read an existing service call first and copy its codes.
- SAP’s own
fsm-sdk4.0.1 still pinsServiceCall.26,Activity.39,Equipment.23. If your company rejects the newer versions, change thedtos=values in the endpoints. - The Service API pages also list
X-Account-NameandX-Company-Name. The connector sends only the ids, like SAP’s SDK; add the name headers if a business action answers400. - For Service Management calls the SAP Help pages use
/service-management/api/v2/...; the SDK calls/service-management/v2/.... Both reach the API gateway; the connector uses the documented form.

