Skip to content

SAP PM Connector

The SapPmConnector integrates Machina with SAP Plant Maintenance (SAP PM) on SAP S/4HANA, reading and creating maintenance data via OData REST APIs.

Prerequisites

  • SAP S/4HANA system with OData services enabled
  • API access to at least: API_EQUIPMENT, API_MAINTENANCEORDER, API_MAINTENANCEPLAN
  • OAuth 2.0 Client Credentials (recommended) or HTTP Basic authentication

Installation

pip install machina-ai[cmms-rest]

Configuration

from machina.connectors import SapPM
from machina.connectors.cmms import OAuth2ClientCredentials

connector = SapPM(
    url="https://sap.example.com/sap/opu/odata/sap",
    auth=OAuth2ClientCredentials(
        token_url="https://sap.example.com/oauth/token",
        client_id="my-client",
        client_secret="my-secret",
    ),
    sap_client="100",
)
await connector.connect()
from machina.connectors import SapPM
from machina.connectors.cmms import BasicAuth

connector = SapPM(
    url="https://sap.example.com/sap/opu/odata/sap",
    auth=BasicAuth(username="SAP_USER", password="SAP_PASS"),
    sap_client="100",
)
await connector.connect()
connectors:
  cmms:
    type: sap_pm
    url: https://sap.example.com/sap/opu/odata/sap
    sap_client: "100"
    auth:
      type: oauth2_client_credentials
      token_url: https://sap.example.com/oauth/token
      client_id: ${SAP_CLIENT_ID}
      client_secret: ${SAP_CLIENT_SECRET}

Capabilities

Capability Description
read_assets Read equipment master records (API_EQUIPMENT/Equipment)
read_work_orders Read maintenance orders — filter by asset_id and/or status (accepts WorkOrderStatus enum or raw SAP code)
create_work_order Create maintenance orders (CSRF token handled automatically)
update_work_order Update status, assignee, or description via PATCH (CSRF-safe)
read_spare_parts Read BOM / material data (configurable endpoint, default API_BILL_OF_MATERIAL_SRV/BillOfMaterialItem)
read_maintenance_plans Read preventive-maintenance plans (API_MAINTENANCEPLAN/MaintenancePlan)

Convenience methods

These methods are available but are not declared as agent-discoverable capabilities:

Method Description
get_work_order(id) Fetch a single maintenance order by number
close_work_order(id) Transition to CLOSED (SAP TECO) via update_work_order
cancel_work_order(id) Transition to CANCELLED (SAP DLFL) via update_work_order

Usage Examples

Read assets

assets = await connector.read_assets()
for asset in assets:
    print(f"{asset.id}: {asset.name} ({asset.criticality})")

Read work orders with Machina enum filter

from machina.domain.work_order import WorkOrderStatus

wos = await connector.read_work_orders(
    asset_id="10000001",
    status=WorkOrderStatus.IN_PROGRESS,  # auto-mapped to SAP "PCNF"
)

Get a single work order

wo = await connector.get_work_order("4000001")
if wo:
    print(f"{wo.id}: {wo.status}{wo.failure_mode}")

Create a work order

from datetime import datetime, timezone
from machina.domain import WorkOrder, WorkOrderType, Priority

wo = WorkOrder(
    id="",
    type=WorkOrderType.CORRECTIVE,
    priority=Priority.HIGH,
    asset_id="10000001",
    description="Replace bearing on drive end",
    created_at=datetime.now(tz=timezone.utc),
    updated_at=datetime.now(tz=timezone.utc),
)
created = await connector.create_work_order(wo)
print(f"Created: {created.id}")

Update / close a work order

from machina.domain.work_order import WorkOrderStatus

# Update specific fields
updated = await connector.update_work_order(
    "4000001",
    status=WorkOrderStatus.COMPLETED,
    assigned_to="TECH_SMITH",
)

# Convenience wrappers
await connector.close_work_order("4000001")
await connector.cancel_work_order("4000002")

Configurable BOM Endpoint

The spare-parts endpoint varies across SAP versions. The default targets API_BILL_OF_MATERIAL_SRV/BillOfMaterialItem (S/4HANA Cloud). Override for on-premise or legacy systems:

connector = SapPM(
    url="https://sap.example.com/sap/opu/odata/sap",
    auth=auth,
    bom_service="API_EQUIPMENT",           # legacy service
    bom_entity_set="EquipmentBOM",         # legacy entity set
    bom_material_field="Material",         # field name for SKU filter
    bom_equipment_field="Equipment",       # field name for asset filter
)

Entity Mapping

SAP Field Machina Field
Equipment Asset.id
EquipmentName Asset.name
EquipmentCategory Asset.type (M→Rotating, E→Electrical, I→Instrument, …)
FunctionalLocation Asset.location
ABCIndicator Asset.criticality (A/B/C)
MaintenanceOrder WorkOrder.id
MaintenanceOrderType WorkOrder.type (PM01→Corrective, PM02→Preventive, PM03→Predictive, PM04→Improvement)
MaintPriority WorkOrder.priority (1→Emergency, 2→High, 3→Medium, 4→Low)
MaintenanceOrderSystemStatus WorkOrder.status (CRTD, REL, PCNF, CNF, TECO, CLSD, DLFL)
MaintenanceActivityType WorkOrder.failure_mode
MaintenanceCause / MaintNotifCause WorkOrder.failure_cause

Resilience

All HTTP calls route through a shared retry helper with exponential backoff. Retries are triggered on:

  • 429 Too Many Requests — honours the Retry-After header
  • 503 Service Unavailable — transient upstream failures
  • Network errorsTimeoutException, ConnectError, ReadError

Default: 3 retries, 0.5 s → 8 s backoff cap.

Known Limitations

  • OData v2 vs v4: The connector handles both response formats (d.results and value). Your SAP system may use either depending on the service version.
  • Custom fields: SAP Z-fields are stored in metadata dict; access them via asset.metadata["ZZ_CUSTOM_FIELD"].
  • CSRF tokens: Write operations (create, update) automatically fetch a CSRF token within the same HTTP session to ensure cookie-based session affinity.
  • Functional locations: Currently read as part of the Asset.location field. A dedicated functional-location hierarchy is planned for a future release.

API Reference

SapPmConnector

SapPmConnector(*, url: str, auth: _AuthUnion, sap_client: str = '', bom_service: str = 'API_BILL_OF_MATERIAL_SRV', bom_entity_set: str = 'BillOfMaterialItem', bom_material_field: str = 'BillOfMaterialComponent', bom_equipment_field: str = '')

Connector for SAP Plant Maintenance (S/4HANA).

Provides integration with SAP's OData APIs for reading and creating maintenance data.

Parameters:

Name Type Description Default
url str

Base URL of the SAP OData gateway (e.g. https://sap.example.com/sap/opu/odata/sap).

required
auth _AuthUnion

Authentication strategy — :class:OAuth2ClientCredentials or :class:BasicAuth.

required
sap_client str

SAP client number (sent as sap-client header).

''
bom_service str

OData service group used by :meth:read_spare_parts. Defaults to "API_BILL_OF_MATERIAL_SRV" (standard S/4HANA Cloud service). Override for on-premise systems with different service names.

'API_BILL_OF_MATERIAL_SRV'
bom_entity_set str

Entity set inside bom_service. Defaults to "BillOfMaterialItem".

'BillOfMaterialItem'
bom_material_field str

Name of the material / component field on bom_entity_set used by the sku filter. Defaults to "BillOfMaterialComponent".

'BillOfMaterialComponent'
bom_equipment_field str

Name of the equipment field on bom_entity_set used by the asset_id filter. Defaults to the empty string, which disables server-side filtering by asset (because the default BillOfMaterialItem entity does not directly expose an Equipment key). Set this to a valid field name if your SAP version exposes one.

''
Example
from machina.connectors import SapPM
from machina.connectors.cmms import OAuth2ClientCredentials

connector = SapPM(
    url="https://sap.example.com/sap/opu/odata/sap",
    auth=OAuth2ClientCredentials(
        token_url="https://sap.example.com/oauth/token",
        client_id="my-client",
        client_secret="my-secret",
    ),
    sap_client="100",
)
await connector.connect()
assets = await connector.read_assets()

connect async

connect() -> None

Verify connection to SAP OData gateway.

For :class:OAuth2ClientCredentials, fetches the access token first. Then issues a $metadata request to verify reachability.

Raises:

Type Description
ConnectorAuthError

If authentication fails.

ConnectorError

If the OData gateway is unreachable.

disconnect async

disconnect() -> None

Close the connector.

health_check async

health_check() -> ConnectorHealth

Return current health status.

read_assets async

read_assets() -> list[Asset]

Return all equipment master records from SAP PM.

get_asset async

get_asset(asset_id: str) -> Asset | None

Look up a single equipment record by number.

read_work_orders async

read_work_orders(*, asset_id: str = '', status: WorkOrderStatus | str = '') -> list[WorkOrder]

Read maintenance orders from SAP PM.

Parameters:

Name Type Description Default
asset_id str

Filter by equipment number.

''
status WorkOrderStatus | str

Filter by status — accepts a :class:WorkOrderStatus enum (automatically reverse-mapped to the SAP code) or a raw SAP status string like "REL" for backward compatibility.

''

get_work_order async

get_work_order(work_order_id: str) -> WorkOrder | None

Look up a single maintenance order by number.

create_work_order async

create_work_order(work_order: WorkOrder) -> WorkOrder

Create a new maintenance order in SAP PM.

Parameters:

Name Type Description Default
work_order WorkOrder

Machina :class:WorkOrder to create.

required

Returns:

Type Description
WorkOrder

The created work order with the server-assigned ID.

update_work_order async

update_work_order(work_order_id: str, *, status: WorkOrderStatus | None = None, assigned_to: str | None = None, description: str | None = None) -> WorkOrder

Update an existing maintenance order in SAP PM.

Only non-None fields are included in the PATCH payload.

Parameters:

Name Type Description Default
work_order_id str

SAP maintenance order number.

required
status WorkOrderStatus | None

New :class:WorkOrderStatus (reverse-mapped to SAP code).

None
assigned_to str | None

New responsible person.

None
description str | None

New order description.

None

Returns:

Type Description
WorkOrder

The updated work order.

close_work_order async

close_work_order(work_order_id: str) -> WorkOrder

Transition a work order to CLOSED status.

cancel_work_order async

cancel_work_order(work_order_id: str) -> WorkOrder

Transition a work order to CANCELLED status.

read_spare_parts async

read_spare_parts(*, asset_id: str = '', sku: str = '') -> list[SparePart]

Read material / spare-part data from SAP's BOM service.

The service group, entity set, and filter field names are configured on the connector (see constructor args bom_service, bom_entity_set, bom_material_field, bom_equipment_field). Defaults target the standard S/4HANA API_BILL_OF_MATERIAL_SRV/BillOfMaterialItem entity.

Parameters:

Name Type Description Default
asset_id str

Optional Equipment identifier. Applied as a server-side OData $filter clause only when bom_equipment_field is configured; otherwise logged and ignored.

''
sku str

Optional material number, translated to a server-side $filter on bom_material_field.

''

read_maintenance_plans async

read_maintenance_plans() -> list[MaintenancePlan]

Read preventive-maintenance plans from SAP PM.

read_maintenance_history async

read_maintenance_history(asset_id: str) -> list[WorkOrder]

Return completed/closed maintenance orders for an asset.