UpKeep Connector¶
The UpKeepConnector integrates Machina with UpKeep, a leading
cloud-based CMMS platform, via its REST API v2.
Prerequisites¶
- UpKeep account with API access enabled
- API token (generated from Account Settings → API Tokens in the UpKeep web UI)
Installation¶
Configuration¶
Capabilities¶
| Capability | Description |
|---|---|
read_assets |
Read all assets (/api/v2/assets) |
read_work_orders |
Read work orders — filter by asset_id and/or status (accepts WorkOrderStatus enum or raw UpKeep string) |
get_work_order |
Fetch a single work order by ID |
create_work_order |
Create a new work order |
update_work_order |
Update status, assignee, or description via PATCH |
read_spare_parts |
Read the parts inventory (/api/v2/parts), or with asset_id only the parts that asset lists (the asset, then each listed part); sku narrows either in memory — prefers partNumber / barcode as SKU |
read_maintenance_plans |
Read PM templates with their schedules (/api/v2/pm) — one plan per PM schedule |
read_maintenance_history |
Read an asset's completed work orders (/api/v2/work-orders, status complete) |
Convenience methods¶
These methods are available but are not declared as agent-discoverable capabilities:
| Method | Description |
|---|---|
close_work_order(id) |
Transition to CLOSED (maps to UpKeep complete) via update_work_order |
cancel_work_order(id) |
Transition to CANCELLED (maps to UpKeep onHold) via update_work_order |
Usage Examples¶
Read assets¶
Read work orders with Machina enum filter¶
from machina.domain.work_order import WorkOrderStatus
wos = await connector.read_work_orders(
asset_id="asset-123",
status=WorkOrderStatus.IN_PROGRESS, # auto-mapped to "inProgress"
)
Get a single work order¶
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="asset-123",
description="Replace worn bearing",
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
updated = await connector.update_work_order(
"wo-123",
status=WorkOrderStatus.COMPLETED,
assigned_to="tech-user-id",
)
await connector.close_work_order("wo-123")
Entity Mapping¶
| UpKeep Field | Machina Field |
|---|---|
id |
Asset.id |
name |
Asset.name |
category |
Asset.type (mapped to closest AssetType) |
location |
Asset.location |
make |
Asset.manufacturer |
model |
Asset.model |
serialNumber |
Asset.serial_number |
id (work order) |
WorkOrder.id |
title |
WorkOrder.description |
priority (0-3) |
WorkOrder.priority (0→Low, 1→Medium, 2→High, 3→Emergency) |
status |
WorkOrder.status (open→Created, inProgress→InProgress, onHold→Assigned, complete→Completed; filters and updates send the same values, with Closed as complete and Cancelled as onHold) |
partNumber / barcode / id |
SparePart.sku (prefers physical identifier, falls back to record ID) |
name (part) |
SparePart.name |
quantity |
SparePart.stock_quantity |
id / _id (PM schedule) |
MaintenancePlan.id |
asset (PM schedule) |
MaintenancePlan.asset_id |
repeatFrequency × repeatInterval (PM schedule) |
MaintenancePlan.interval (DAILY→days, WEEKLY→weeks, MONTHLY→months, YEARLY→12 months) |
endDate / scheduleHasEnded (PM schedule) |
MaintenancePlan.active (false once the end date has passed or UpKeep marks the schedule ended) |
name (PM template) |
MaintenancePlan.name |
tasks[].name (PM template) |
MaintenancePlan.tasks |
estimatedTime (PM template, hours) |
MaintenancePlan.estimated_duration_hours |
Resilience¶
All HTTP calls route through a shared retry helper with exponential backoff. See SAP PM Connector — Resilience for details.
Known Limitations¶
- Asset criticality: UpKeep does not expose a native criticality field. All assets default to
Criticality.C. - Work order types: UpKeep uses
category("preventive" / "reactive"). The connector maps these toPREVENTIVEandCORRECTIVErespectively. Predictive and improvement types are not natively supported by UpKeep; for custom categories, subclass the connector. - Spare parts by asset:
/api/v2/partshas no asset filter, soread_spare_parts(asset_id=...)reads the asset (GET /api/v2/assets/<ID>), whosepartslist holds the IDs of its parts, then fetches each listed part (GET /api/v2/parts/<ID>). That is 1 + k requests for k listed parts; the inventory is not paged. A part listed twice is fetched and returned once. An asset that lists no parts returns an empty list after the one request.- Limit: an asset listing more than 100 parts is refused with
ConnectorErrorbefore any part request. - Agent prefetch budget: the agent's per-turn context prefetch waits at most 5 seconds for an asset's spare parts from UpKeep. A read still running at the deadline is cancelled where it waits. Its pending request and its HTTP client end with it, and no further part is requested. The prompt then says the parts were not checked because the lookup did not finish in time, and names the
check_spare_partstool, which runs the full read. The budget is a policy for the eager per-turn lookup, not a completeness guarantee. Within it the read is the same all-or-none read. Thecheck_spare_partstool, the MCPmachina_list_spare_partstool, workflows and directread_spare_partscalls have no such deadline. - Refusals: the read returns all of the asset's parts or none. It raises
ConnectorErrorwhen UpKeep has no such asset, or cannot return a part the asset lists (HTTP 404). It raisesConnectorSchemaErrorwhen UpKeep's answer is not a well-formed record of that asset (a record with anotherid, a body without one, or a malformedpartslist), when a part record is malformed or has a field the mapping cannot read (aquantityorcostthat is not a number, a negative stock or cost), or when a part request returns a different part. Only the asset's own record can say it lists no parts. A connection error or a timeout that outlasts the retries propagates as thehttpxerror, as on the connector's other reads; the parts already read are not returned. No refusal message repeats a value from UpKeep, and Machina's retry warnings name a retried part request as/api/v2/parts/<listed part>, without the part's ID. (configure_logging()also keeps httpx's own per-request lines, which carry full URLs, out of the log.) - SKU: a
skunarrows the asset's parts in memory, so both filters hold. Withoutasset_id, the connector pages/api/v2/partsand filters byskuin memory; a part record the mapping cannot read refuses that read too, withConnectorSchemaError. - Not validated live: this follows UpKeep's documented API reference and has not been validated against a live UpKeep instance.
- Limit: an asset listing more than 100 parts is refused with
- Record IDs in paths: an asset, work-order or part ID is sent as one percent-encoded path segment (
/becomes%2F). An empty ID, or one with an empty,.or..part between/or\separators, is refused before any request: withConnectorErrorfor an ID you pass, and withConnectorSchemaError, refusing the whole read, for a part ID in an asset'spartslist. - Failure data: UpKeep has no standard failure-mode fields. Failure-related data may be available in
WorkOrder.metadatadepending on your UpKeep configuration. - Maintenance plans: a PM template can schedule several assets, each with its own recurrence, so the connector returns one plan per schedule (soft-deleted templates are skipped). A meter-triggered schedule maps with an empty interval: the PM payload names the meter but not its unit. Legacy PM triggers (
/api/v2/preventive-maintenance) are not read — their documented responses carry no asset, and PM templates reference them throughlegacyPMId.
API Reference¶
UpKeepConnector ¶
Connector for UpKeep CMMS.
Provides integration with UpKeep's REST API v2 for reading and creating maintenance data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
UpKeep API base URL. Defaults to the production endpoint. |
''
|
api_key
|
str
|
API token from UpKeep (passed as |
''
|
Example
connect
async
¶
Verify credentials against the UpKeep API.
Raises:
| Type | Description |
|---|---|
ConnectorAuthError
|
If the API key is missing or invalid. |
ConnectorError
|
If the API is unreachable. |
disconnect
async
¶
Close the connector (no persistent connections to clean up).
read_work_orders
async
¶
Read work orders, optionally filtered by asset or status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
asset_id
|
str
|
Filter by UpKeep asset ID. |
''
|
status
|
WorkOrderStatus | str
|
Filter by status — accepts a :class: |
''
|
get_work_order
async
¶
Look up a single work order by ID.
create_work_order
async
¶
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 work order in UpKeep via PATCH.
Only non-None fields are included in the PATCH payload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
work_order_id
|
str
|
UpKeep work order ID. |
required |
status
|
WorkOrderStatus | None
|
New status (reverse-mapped to UpKeep string). |
None
|
assigned_to
|
str | None
|
New |
None
|
description
|
str | None
|
New title. |
None
|
Returns:
| Type | Description |
|---|---|
WorkOrder
|
The updated work order. |
close_work_order
async
¶
Transition a work order to CLOSED (maps to 'complete' in UpKeep).
cancel_work_order
async
¶
Transition a work order to CANCELLED (maps to 'onHold' in UpKeep).
read_spare_parts
async
¶
Read spare parts (UpKeep calls them parts).
Without asset_id the whole parts inventory is paged from
/api/v2/parts. With it, the asset is read and each part its
parts list names is fetched (see Note).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
asset_id
|
str
|
Optional UpKeep asset ID: returns only the parts that asset lists. Empty means no asset filter. |
''
|
sku
|
str
|
Optional SKU / part number to filter the result in-memory
after fetching. Matches the parsed :attr: |
''
|
Raises:
| Type | Description |
|---|---|
ConnectorError
|
If a filter is neither |
ConnectorAuthError
|
If UpKeep rejects the API key. |
ConnectorSchemaError
|
If UpKeep does not return a well-formed
record of the asset (another record, or a malformed |
TransportError
|
If a request still fails after its retries (a connection error or a timeout); no partial list is returned. |
Note
UpKeep's /api/v2/parts endpoint has no asset filter; the
asset record (GET /api/v2/assets/<ID>) lists the IDs of its
parts, and each is fetched with GET /api/v2/parts/<ID>: 1 + k
requests for k listed parts, never the whole inventory. A part
listed twice is fetched and returned once. Any part that cannot
be read refuses the whole read rather than returning the others
as the asset's complete list.
read_maintenance_plans
async
¶
Read preventive-maintenance plans from UpKeep's PM templates.
Returns one plan per PM schedule: the asset and the recurrence live
on each schedule, the name and tasks on its template. Legacy PM
triggers (/api/v2/preventive-maintenance) are not read — their
documented responses carry no asset.
read_maintenance_history
async
¶
Return completed work orders for an asset.
Raises:
| Type | Description |
|---|---|
ConnectorError
|
If |