> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Limble CMMS

> Client-mode REST API connector for Limble CMMS: create work orders from alarms, find open tasks, comment and close them, raise and approve work requests, push meter readings into asset fields so meter and threshold PMs fire, log asset events, read assets, locations, parts and users, and register webhooks, through the Limble API v2 with HTTP Basic API keys

Technical Preview

## Overview

[Limble CMMS](https://limblecmms.com/) is a cloud maintenance system for work orders, preventive maintenance (PMs), work requests, assets, parts and purchasing. This connector lets the broker open a work order when an alarm fires, feed run hours, cycle counts and sensor values into asset fields that drive meter-based and threshold PMs, and log machine events on the asset, so maintenance sees what the plant sees without manual entry.

The template has two connectors:

* **LimbleCmmsApi** (`LimbleCmmsRestApi.lot`), a client connector on `https://api.limblecmms.com` with 31 events: 13 writes and 18 reads. It has no `DELETE` events.
* **LimbleCmmsWebhookReceiver** (`cmms_limble-cmms_webhook.lot`), an optional server connector on port `9010` that receives Limble webhooks and publishes them on MQTT.

> [!NOTE]
>
> Limble serves a machine-readable OpenAPI 3.1 spec without authentication at `https://api.limblecmms.com/openapi.json`, and publishes the same reference as [Postman docs](https://apidocs.limblecmms.com/). The events were picked by hand from it (the API has more than 200 operations), and every method and path was checked against it.

## Events

| Event | Topic | Request | Kind | What it does |
| - | - | - | - | - |
| `GetMe` | `cmms/limble/me/get` | `GET /v2/me` | Read | Auth check: customerName and customerPlan of the account the API keys belong to |
| `ListLocations` | `cmms/limble/location/list` | `GET /v2/locations?{query}` | Read | Locations (plants, sites): locationID, name, regionID, timezone, weeklyOperationHours, workRequestPortal; filters locations, name, regions; limit / page |
| `ListUsers` | `cmms/limble/user/list` | `GET /v2/users?{query}` | Read | Users (assignees for tasks: userID, teams per location); filters users, name, roles, teams; cursor / limit |
| `ListTeams` | `cmms/limble/team/list` | `GET /v2/teams?{query}` | Read | Teams (assignmentType team): teamID, name, locationID |
| `ListStatuses` | `cmms/limble/status/list` | `GET /v2/statuses?{query}` | Read | Custom task statuses (statusID, name) for UpdateTask statusID and the ListTasks statusIDs filter |
| `ListPriorities` | `cmms/limble/priority/list` | `GET /v2/priorities?{query}` | Read | Priorities (priorityID, name, priorityLevel, isDefault); CreateTask priority must be one of the priorityLevel values |
| `ListAssets` | `cmms/limble/asset/list` | `GET /v2/assets?{query}` | Read | Assets: assetID, name, locationID, parentAssetID, hoursPerWeek, meta links to fields and tasks; filters assets, name, locations, parentAssetID, start / end (last edited, unix s); limit / page or orderBy / cursor |
| `UpdateAsset` | `cmms/limble/asset/update` | `PATCH /v2/assets/{id}` | Write | Update an asset's top level info: body `{name, hoursPerWeek (-1 = location default), parentAssetID, startedOn unix s, geoLocation}`. Empty 200 on success |
| `ListAssetFields` | `cmms/limble/asset-field/list` | `GET /v2/assets/fields?{query}` | Read | Asset field values (meters are Number fields): valueID, fieldID, assetID, field, value, fieldType; filters assets, fields, values, name, locations; find the valueID to write |
| `UpdateAssetFieldValue` | `cmms/limble/asset-field/update` | `PATCH /v2/assets/fields/{id}` | Write | Meter reading: write one asset field value by valueID (not assetID): body `{value}` typed like the field (number for Number). Adds a field history entry; unit-based and threshold PMs on that field can create tasks |
| `BatchUpdateAssetFields` | `cmms/limble/asset-field/batch-update` | `POST /v2/assets/fields/batch/update` | Write | Several meter values in one call: body array of 2-100 `{valueID, value}` (Text, Date, Number, Currency only); reply success false + updates\[] with reasons if any row fails. Batch limit 20 calls/hour |
| `AttachAssetField` | `cmms/limble/asset-field/attach` | `PUT /v2/assets/{id}/fields` | Write | Attach an existing (suggested) field, e.g. a run hours meter, to an asset: body `{locationID, fieldID}`; then write it with UpdateAssetFieldValue |
| `ListAssetFieldHistory` | `cmms/limble/asset-field-history/list` | `GET /v2/assets/fields/history?{query}` | Read | History of asset field values (meter trend): fieldID, value, valueID, dateUpdated; filters assets, fields, start / end |
| `ListAssetLogs` | `cmms/limble/asset-log/list` | `GET /v2/assets/{id}/logs?{query}` | Read | Log entries of one asset; filters logs, users, logEntry, start / end; limit / page |
| `CreateAssetLog` | `cmms/limble/asset-log/create` | `POST /v2/assets/{id}/logs` | Write | Manual asset log entry, e.g. a machine state change: body `{logEntry (required), userID?}`; 201 `{logID}` |
| `ListTasks` | `cmms/limble/task/list` | `GET /v2/tasks?{query}` | Read | Tasks: query status=0 (open) / 1 (complete), type 1 PM / 2 unplanned WO / 4 planned WO / 6 WR, statusIDs (custom), assets, locations, users, teams, meta1-3, tag, start / end, completedStart / completedEnd (unix s); limit / page or orderBy / cursor |
| `CreateTask` | `cmms/limble/task/create` | `POST /v2/tasks` | Write | Work order from an alarm: body `{name, locationID, due (unix s), type 1 / 2 / 4 / 6 required; assetID, description, priority (a priorityLevel), assignment + assignmentType user / team / multi, templateID, meta1-3, requestName ...}`; 201 `{taskID}` |
| `UpdateTask` | `cmms/limble/task/update` | `PATCH /v2/tasks/{id}` | Write | Update a task: body `{status 1 close / 0 reopen (close needs assignment + assignmentType), statusID (custom, not 1 / 2), priority 0-6, due, name, description, asset, meta1-3 ...}`. Empty 200 on success |
| `ListTaskComments` | `cmms/limble/task-comment/list` | `GET /v2/tasks/{id}/comments?{query}` | Read | Comments of one task (commentID, comment, timestamp, userID) |
| `AddTaskComment` | `cmms/limble/task-comment/create` | `POST /v2/tasks/{id}/comments` | Write | Add a comment with live context: body `{comment (required), showExternalUsers (default true)}` |
| `ListTaskInstructions` | `cmms/limble/task-instruction/list` | `GET /v2/tasks/{id}/instructions?{query}` | Read | Checklist instructions of one task (instructionID, instruction, type, response) |
| `ListTaskLabor` | `cmms/limble/task-labor/list` | `GET /v2/tasks/labor?{query}` | Read | Labor entries (timeSpent seconds, userID, dateLogged); filters tasks, users, locations, start / end |
| `ListWorkRequestSubmissions` | `cmms/limble/work-request/list` | `GET /v2/work-requests/work-request-submissions?{query}` | Read | Work request submissions (workRequestSubmissionId, status Pending / Approved / Rejected); filters locationIDs, statuses (CSV), workRequestIDs, createdDateStart / End, search, orderBy |
| `CreateWorkRequestSubmission` | `cmms/limble/work-request/create` | `POST /v2/work-requests/work-request-submissions` | Write | Raise a work request for review instead of a direct task: body `{locationID, dueDateUnit days / hours required; assetID, priorityID, requesterName, requestTitle, requestDescription, parts, timeUnix}` |
| `ApproveWorkRequestSubmission` | `cmms/limble/work-request/approve` | `POST /v2/work-requests/work-request-submissions/{id}/approve` | Write | Approve a pending submission; Limble generates a task from it (body `{}`) |
| `DeclineWorkRequestSubmission` | `cmms/limble/work-request/decline` | `POST /v2/work-requests/work-request-submissions/{id}/decline` | Write | Decline a pending submission: body `{declineReason?, additionalNotes?}` |
| `ListParts` | `cmms/limble/part/list` | `GET /v2/parts?{query}` | Read | Spare parts and stock (partID, number, name, generalStock, minQtyStatus, minQtyThreshold); filters parts, locations, name, numbers |
| `ListPartsUsage` | `cmms/limble/part-usage/list` | `GET /v2/parts/usage?{query}` | Read | Parts used on tasks (partID, taskID, usedCount, usedPrice, usedOn); filters parts, tasks, start / end |
| `ListWebhooks` | `cmms/limble/webhook/list` | `GET /v2/webhooks?{query}` | Read | Registered webhooks (webhookID, endpoint, type, enabled 0 / 1); filters webhooks, endpoint, type, enabled |
| `CreateWebhook` | `cmms/limble/webhook/create` | `POST /v2/webhooks` | Write | Register a webhook: body `{endpoint (required), type (required) asset / assetField / location / part / po / poItem / task / instruction, enabled 1 / 0}`; 201 `{webhookID}`. One type per webhook; receive with LimbleCmmsWebhookReceiver |
| `UpdateWebhook` | `cmms/limble/webhook/update` | `PATCH /v2/webhooks/{id}` | Write | Update a webhook, e.g. body `{enabled: 1}` to re-enable one Limble disabled after failed retries; also endpoint, type |

Replies arrive on the same topic with `/response` appended.

## Prerequisites

* A Limble account and API keys. A **Super Administrator** creates them under **Settings > Configuration > API Settings > Generate API Keys**. The client secret is shown only once.
* The API host of your account's region. The connector uses the default host; for other regions change `BASE_ADDRESS`:

| Limble account | API host |
| - | - |
| Default | `https://api.limblecmms.com` |
| Canada | `https://ca-api.limblecmms.com` |
| Australia | `https://au-api.limblecmms.com` |
| Europe | `https://eu-api.limblecmms.com` |
| 21 CFR Part 11 | `https://21cfr-api.limblecmms.com` |

* Outbound HTTPS from the broker to that host.
* For webhooks only: a public HTTPS URL that forwards to the broker's port `9010`.

## Authentication

> [!WARNING]
>
> The API keys act for the whole Limble account. Store `base64("<client id>:<client secret>")` in the Hub secret `LIMBLE_BASIC_AUTH`, read it with `GET SECRET`, and never put the keys or their base64 in a connector file or a retained topic.

Limble uses **HTTP Basic** authentication with the API Client ID and Client Secret. Every event sends `Authorization: Basic {value.json.basic_auth}`, so each payload carries the base64 string in `basic_auth`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "basic_auth": "<base64 of client_id:client_secret (Hub secret LIMBLE_BASIC_AUTH)>"
}
```

`GetMe` (`GET /v2/me`) returns `customerName` and `customerPlan`. Use it to check the keys.

Answers seen from the real API without valid keys: no `Authorization` header returns `401` `{"error":401,"success":false,"msg":"No Credentials Provided"}`; wrong keys return `403` `{"error":403,"success":false,"msg":"Invalid credentials."}`.

## Payload conventions

* `basic_auth`: base64 of `client_id:client_secret`.
* `id`: the numeric Limble ID in the path: `taskID`, `assetID`, `valueID` (an asset field value, not the asset), `webhookID` or `workRequestSubmissionID`.
* `query`: the whole query string for list events, already URL-encoded, or `""` for none. For example `status=0&type=2,4&locations=205&limit=100&page=1` for open work orders.
* `body`: the JSON request body, sent unchanged on `POST`, `PATCH` and `PUT` events.

All dates are **Unix time in seconds**. Lists come back as JSON arrays. Creates answer `201` with the new ID (for example `{"taskID": 8261}`); `PATCH` and `PUT` answer an empty `200`.

Task types are `1` PM, `2` unplanned work order, `4` planned work order and `6` work request. The `status` filter on `ListTasks` is `0` (incomplete) or `1` (complete); custom statuses such as "In Progress" filter with `statusIDs`.

Create a work order from an alarm (`priority` must be one of the `priorityLevel` values from `ListPriorities`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "basic_auth": "<base64 of client_id:client_secret (Hub secret LIMBLE_BASIC_AUTH)>",
  "body": {
    "name": "Line 1 compressor over-temperature",
    "locationID": "<location id>",
    "type": 2,
    "due": 1791381600,
    "assetID": "<asset id>",
    "description": "Alarm TT-101 = 92.4 C at 2026-10-06T06:15:00Z (Coreflux)",
    "priority": 3,
    "meta1": "TT-101"
  }
}
```

Close it (closing needs `assignment` and `assignmentType`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "basic_auth": "<base64 of client_id:client_secret (Hub secret LIMBLE_BASIC_AUTH)>",
  "id": "<task id>",
  "body": {
    "status": 1,
    "assignment": "<user id>",
    "assignmentType": "user"
  }
}
```

## Meter readings

Limble keeps meters as **asset fields** (Number fields such as "Hours Ran"). PMs set to "Every X number of units" or to a threshold on that field create tasks when a new value arrives. To push a reading:

1. Find the field's `valueID` with `ListAssetFields` (`query`: `assets=<assetID>`). If the asset does not have the field yet, attach it with `AttachAssetField`.
2. Write the value with `UpdateAssetFieldValue`, using the `valueID` as `id`. The value must match the field type (a number for a Number field). Each write adds an entry to the field history (`ListAssetFieldHistory`).

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "basic_auth": "<base64 of client_id:client_secret (Hub secret LIMBLE_BASIC_AUTH)>",
  "id": "<asset field valueID>",
  "body": {
    "value": 1287.5
  }
}
```

To write 2 to 100 values in one call, use `BatchUpdateAssetFields` (Text, Date, Number and Currency fields only, 20 calls per hour). It returns `{"success": false, "updates": [...]}` with a reason per row if any row fails, so check the body as well as the HTTP status.

## Webhook receiver

Deploy `cmms_limble-cmms_webhook.lot` only if you want Limble to push changes to the broker. It opens HTTP port `9010`.

| Event | Request | Published on | What it receives |
| - | - | - | - |
| `WebhookTasks` | `POST /limble/webhook/tasks` | `cmms/limble/webhook-task/received` | Task webhooks (type task: CREATED, COMPLETE, CHANGED DUE DATE, CHANGED ASSIGNMENT, ADDED COMMENT TO TASK, ...): body `{taskID, status, category, user}` |
| `WebhookAssets` | `POST /limble/webhook/assets` | `cmms/limble/webhook-asset/received` | Asset and asset field webhooks (types asset, assetField): body `{assetID / valueID, status, category, user}` |
| `WebhookParts` | `POST /limble/webhook/parts` | `cmms/limble/webhook-part/received` | Part webhooks (type part: CREATED, QUANTITY INCREASED, QUANTITY DECREASED, PRICE UPDATED, ...): body `{partID, status, category, user}` |
| `WebhookEvents` | `POST /limble/webhook/events` | `cmms/limble/webhook-event/received` | Any other Limble webhook (types location, po, poItem, instruction). Filter on body.category in a LoT action |

Each webhook in Limble has one type (`asset`, `assetField`, `location`, `part`, `po`, `poItem`, `task` or `instruction`). Register one per type with `CreateWebhook` (or in **Settings > Configuration > API Settings**), using the public URL of the matching path:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "basic_auth": "<base64 of client_id:client_secret (Hub secret LIMBLE_BASIC_AUTH)>",
  "body": {
    "endpoint": "https://<public host>/limble/webhook/tasks",
    "type": "task",
    "enabled": 1
  }
}
```

Limble posts a short JSON body such as `{"taskID": 8203, "status": "COMPLETE", "category": "task", "user": "..."}`. Read the full object back with `ListTasks` (`query`: `tasks=8203`) or the matching list event. Limble needs at least one active API key for webhooks to work. Your endpoint must answer within 10 seconds; Limble retries after 10 minutes, 1, 2 and 4 hours and then disables the webhook. Send `UpdateWebhook` with `{"enabled": 1}` to turn it back on.

> [!WARNING]
>
> Limble signs each delivery with `signature`, `timestamp` and `token` headers: `signature` is HMAC-SHA256 of `timestamp + token`, keyed with the hex SHA-256 of the account's **oldest** API key. The receiver connector does not check the signature and does not authenticate callers. Put a reverse proxy with a public TLS certificate in front of port `9010` and verify the signature there, or at least use a hard-to-guess path.

## Connector

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Coreflux REST_API template — Limble CMMS API v2 (beta): create work orders (tasks) from alarms, find open tasks by
#   completed status / type / custom status, close tasks and add comments, raise / approve / decline work requests,
#   push meter readings into asset fields (single or batch; unit-based and threshold PMs fire from them), log asset
#   events, read assets, field history, locations, parts, users, teams, statuses and priorities, and register webhooks
#   (received by the companion route in cmms_limble-cmms_webhook.lot).
# Hugo catalog #168, Tier 3 (cmms). Source: Limble's own OpenAPI 3.1 spec (https://api.limblecmms.com/openapi.json,
#   served without auth) and the public Postman docs https://apidocs.limblecmms.com (cached in
#   openapi-cache/limble-cmms/). Credentials are placeholders only.
# Docs: https://docs.coreflux.org/latest/lot-language/routes/rest-api-apps/limble-cmms
#
# Host: Limble cloud, default region https://api.limblecmms.com (endpoints carry /v2). Limble documents one API host
#   per account region / type. To use another one, fork and change BASE_ADDRESS:
#     Canada https://ca-api.limblecmms.com | Australia https://au-api.limblecmms.com |
#     Europe https://eu-api.limblecmms.com | 21 CFR Part 11 https://21cfr-api.limblecmms.com
#   Keys only work on the host of the account's region.
# Auth: HTTP Basic with the account's API Client ID and Client Secret (Settings > Configuration > API Settings >
#   Generate API Keys, as a Super Administrator; the secret is shown once). Every event sends
#   Authorization: Basic {value.json.basic_auth}, where basic_auth is base64("<client id>:<client secret>"). Keep that
#   string in the Hub secret LIMBLE_BASIC_AUTH and read it with GET SECRET. The keys act for the whole account.
#   Live (2026-10-06): no Authorization -> 401 {"error":401,"success":false,"msg":"No Credentials Provided"}; wrong keys
#   -> 403 {"error":403,"success":false,"msg":"Invalid credentials."}. Auth runs before routing, for every path.
# Payload fields (control fields stay out of bodies; request bodies come from {value.json.body}):
#   basic_auth, id, query, body
#   id is the numeric Limble ID in the path: taskID, assetID, valueID (asset field value, NOT the asset), webhookID or
#   workRequestSubmissionID. query is the whole pre-encoded query string, "" for none, for example
#   status=0&type=2,4&locations=<id>&limit=100&page=1 (tasks: status 0 = incomplete, 1 = complete; type 1 PM,
#   2 unplanned WO, 4 planned WO, 6 work request). All dates are Unix time in SECONDS.
#     CreateTask             body = {"name": "...", "locationID": 205, "type": 2, "due": 1791381600, "assetID": 10836, "priority": 3}
#     UpdateTask             id = <taskID>, body = {"status": 1, "assignment": 341, "assignmentType": "user"}   (close)
#     UpdateAssetFieldValue  id = <valueID>, body = {"value": 1287.5}   (meter reading; type must match the field)
#     BatchUpdateAssetFields body = [{"valueID": 46456, "value": "1287.5"}, {"valueID": 46457, "value": "71.8"}]   (2-100 rows)
#     CreateWebhook          body = {"endpoint": "https://<public host>/limble/webhook/tasks", "type": "task", "enabled": 1}
# Replies: lists are JSON arrays; POST creates answer 201 with the new ID ({"taskID": 8261}) and a Location header;
#   PATCH / PUT answer an empty 200. Errors: 400 validation, 401 / 403 auth, 404 unknown ID, 409 conflict, 422 limit
#   above the endpoint maximum, 429 rate limit (GET 4000/h, PATCH 2000/h, POST and PUT 1000/h, batch 20/h).
#

DEFINE ROUTE LimbleCmmsApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://api.limblecmms.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '30'
        WITH DEFAULT_CONTENT_TYPE "application/json"
        # Smoke: GetMe -> ListLocations / ListPriorities -> CreateTask -> ListTasks (status=0) -> AddTaskComment -> UpdateTask (close) -> ListAssetFields -> UpdateAssetFieldValue. Never commit the client secret or its base64.
    # Auth check: customerName and customerPlan of the account the API keys belong to
    ADD EVENT GetMe
        WITH SOURCE_TOPIC "cmms/limble/me/get"
        WITH DESTINATION_TOPIC "cmms/limble/me/get/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/me"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Locations (plants, sites): locationID, name, regionID, timezone, weeklyOperationHours, workRequestPortal; filters locations, name, regions; limit / page
    ADD EVENT ListLocations
        WITH SOURCE_TOPIC "cmms/limble/location/list"
        WITH DESTINATION_TOPIC "cmms/limble/location/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/locations?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Users (assignees for tasks: userID, teams per location); filters users, name, roles, teams; cursor / limit
    ADD EVENT ListUsers
        WITH SOURCE_TOPIC "cmms/limble/user/list"
        WITH DESTINATION_TOPIC "cmms/limble/user/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/users?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Teams (assignmentType team): teamID, name, locationID
    ADD EVENT ListTeams
        WITH SOURCE_TOPIC "cmms/limble/team/list"
        WITH DESTINATION_TOPIC "cmms/limble/team/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/teams?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Custom task statuses (statusID, name) for UpdateTask statusID and the ListTasks statusIDs filter
    ADD EVENT ListStatuses
        WITH SOURCE_TOPIC "cmms/limble/status/list"
        WITH DESTINATION_TOPIC "cmms/limble/status/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/statuses?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Priorities (priorityID, name, priorityLevel, isDefault); CreateTask priority must be one of the priorityLevel values
    ADD EVENT ListPriorities
        WITH SOURCE_TOPIC "cmms/limble/priority/list"
        WITH DESTINATION_TOPIC "cmms/limble/priority/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/priorities?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Assets: assetID, name, locationID, parentAssetID, hoursPerWeek, meta links to fields and tasks; filters assets, name, locations, parentAssetID, start / end (last edited, unix s); limit / page or orderBy / cursor
    ADD EVENT ListAssets
        WITH SOURCE_TOPIC "cmms/limble/asset/list"
        WITH DESTINATION_TOPIC "cmms/limble/asset/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/assets?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # WRITE: Update an asset's top level info: body {name, hoursPerWeek (-1 = location default), parentAssetID, startedOn unix s, geoLocation}. Empty 200 on success
    ADD EVENT UpdateAsset
        WITH SOURCE_TOPIC "cmms/limble/asset/update"
        WITH DESTINATION_TOPIC "cmms/limble/asset/update/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/v2/assets/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # Asset field values (meters are Number fields): valueID, fieldID, assetID, field, value, fieldType; filters assets, fields, values, name, locations; find the valueID to write
    ADD EVENT ListAssetFields
        WITH SOURCE_TOPIC "cmms/limble/asset-field/list"
        WITH DESTINATION_TOPIC "cmms/limble/asset-field/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/assets/fields?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # WRITE: Meter reading: write one asset field value by valueID (not assetID): body {value} typed like the field (number for Number). Adds a field history entry; unit-based and threshold PMs on that field can create tasks
    ADD EVENT UpdateAssetFieldValue
        WITH SOURCE_TOPIC "cmms/limble/asset-field/update"
        WITH DESTINATION_TOPIC "cmms/limble/asset-field/update/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/v2/assets/fields/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: Several meter values in one call: body array of 2-100 {valueID, value} (Text, Date, Number, Currency only); reply success false + updates[] with reasons if any row fails. Batch limit 20 calls/hour
    ADD EVENT BatchUpdateAssetFields
        WITH SOURCE_TOPIC "cmms/limble/asset-field/batch-update"
        WITH DESTINATION_TOPIC "cmms/limble/asset-field/batch-update/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/assets/fields/batch/update"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: Attach an existing (suggested) field, e.g. a run hours meter, to an asset: body {locationID, fieldID}; then write it with UpdateAssetFieldValue
    ADD EVENT AttachAssetField
        WITH SOURCE_TOPIC "cmms/limble/asset-field/attach"
        WITH DESTINATION_TOPIC "cmms/limble/asset-field/attach/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/v2/assets/{value.json.id}/fields"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # History of asset field values (meter trend): fieldID, value, valueID, dateUpdated; filters assets, fields, start / end
    ADD EVENT ListAssetFieldHistory
        WITH SOURCE_TOPIC "cmms/limble/asset-field-history/list"
        WITH DESTINATION_TOPIC "cmms/limble/asset-field-history/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/assets/fields/history?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Log entries of one asset; filters logs, users, logEntry, start / end; limit / page
    ADD EVENT ListAssetLogs
        WITH SOURCE_TOPIC "cmms/limble/asset-log/list"
        WITH DESTINATION_TOPIC "cmms/limble/asset-log/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/assets/{value.json.id}/logs?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # WRITE: Manual asset log entry, e.g. a machine state change: body {logEntry (required), userID?}; 201 {logID}
    ADD EVENT CreateAssetLog
        WITH SOURCE_TOPIC "cmms/limble/asset-log/create"
        WITH DESTINATION_TOPIC "cmms/limble/asset-log/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/assets/{value.json.id}/logs"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # Tasks: query status=0 (open) | 1 (complete), type 1 PM | 2 unplanned WO | 4 planned WO | 6 WR, statusIDs (custom), assets, locations, users, teams, meta1-3, tag, start / end, completedStart / completedEnd (unix s); limit / page or orderBy / cursor
    ADD EVENT ListTasks
        WITH SOURCE_TOPIC "cmms/limble/task/list"
        WITH DESTINATION_TOPIC "cmms/limble/task/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/tasks?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # WRITE: Work order from an alarm: body {name, locationID, due (unix s), type 1 | 2 | 4 | 6 required; assetID, description, priority (a priorityLevel), assignment + assignmentType user | team | multi, templateID, meta1-3, requestName ...}; 201 {taskID}
    ADD EVENT CreateTask
        WITH SOURCE_TOPIC "cmms/limble/task/create"
        WITH DESTINATION_TOPIC "cmms/limble/task/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/tasks"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: Update a task: body {status 1 close | 0 reopen (close needs assignment + assignmentType), statusID (custom, not 1 / 2), priority 0-6, due, name, description, asset, meta1-3 ...}. Empty 200 on success
    ADD EVENT UpdateTask
        WITH SOURCE_TOPIC "cmms/limble/task/update"
        WITH DESTINATION_TOPIC "cmms/limble/task/update/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/v2/tasks/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # Comments of one task (commentID, comment, timestamp, userID)
    ADD EVENT ListTaskComments
        WITH SOURCE_TOPIC "cmms/limble/task-comment/list"
        WITH DESTINATION_TOPIC "cmms/limble/task-comment/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/tasks/{value.json.id}/comments?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # WRITE: Add a comment with live context: body {comment (required), showExternalUsers (default true)}
    ADD EVENT AddTaskComment
        WITH SOURCE_TOPIC "cmms/limble/task-comment/create"
        WITH DESTINATION_TOPIC "cmms/limble/task-comment/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/tasks/{value.json.id}/comments"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # Checklist instructions of one task (instructionID, instruction, type, response)
    ADD EVENT ListTaskInstructions
        WITH SOURCE_TOPIC "cmms/limble/task-instruction/list"
        WITH DESTINATION_TOPIC "cmms/limble/task-instruction/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/tasks/{value.json.id}/instructions?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Labor entries (timeSpent seconds, userID, dateLogged); filters tasks, users, locations, start / end
    ADD EVENT ListTaskLabor
        WITH SOURCE_TOPIC "cmms/limble/task-labor/list"
        WITH DESTINATION_TOPIC "cmms/limble/task-labor/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/tasks/labor?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Work request submissions (workRequestSubmissionId, status Pending | Approved | Rejected); filters locationIDs, statuses (CSV), workRequestIDs, createdDateStart / End, search, orderBy
    ADD EVENT ListWorkRequestSubmissions
        WITH SOURCE_TOPIC "cmms/limble/work-request/list"
        WITH DESTINATION_TOPIC "cmms/limble/work-request/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/work-requests/work-request-submissions?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # WRITE: Raise a work request for review instead of a direct task: body {locationID, dueDateUnit days | hours required; assetID, priorityID, requesterName, requestTitle, requestDescription, parts, timeUnix}
    ADD EVENT CreateWorkRequestSubmission
        WITH SOURCE_TOPIC "cmms/limble/work-request/create"
        WITH DESTINATION_TOPIC "cmms/limble/work-request/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/work-requests/work-request-submissions"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: Approve a pending submission; Limble generates a task from it (body {})
    ADD EVENT ApproveWorkRequestSubmission
        WITH SOURCE_TOPIC "cmms/limble/work-request/approve"
        WITH DESTINATION_TOPIC "cmms/limble/work-request/approve/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/work-requests/work-request-submissions/{value.json.id}/approve"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: Decline a pending submission: body {declineReason?, additionalNotes?}
    ADD EVENT DeclineWorkRequestSubmission
        WITH SOURCE_TOPIC "cmms/limble/work-request/decline"
        WITH DESTINATION_TOPIC "cmms/limble/work-request/decline/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/work-requests/work-request-submissions/{value.json.id}/decline"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # Spare parts and stock (partID, number, name, generalStock, minQtyStatus, minQtyThreshold); filters parts, locations, name, numbers
    ADD EVENT ListParts
        WITH SOURCE_TOPIC "cmms/limble/part/list"
        WITH DESTINATION_TOPIC "cmms/limble/part/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/parts?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Parts used on tasks (partID, taskID, usedCount, usedPrice, usedOn); filters parts, tasks, start / end
    ADD EVENT ListPartsUsage
        WITH SOURCE_TOPIC "cmms/limble/part-usage/list"
        WITH DESTINATION_TOPIC "cmms/limble/part-usage/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/parts/usage?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # Registered webhooks (webhookID, endpoint, type, enabled 0 | 1); filters webhooks, endpoint, type, enabled
    ADD EVENT ListWebhooks
        WITH SOURCE_TOPIC "cmms/limble/webhook/list"
        WITH DESTINATION_TOPIC "cmms/limble/webhook/list/response"
        WITH METHOD "GET"
        WITH ENDPOINT "/v2/webhooks?{value.json.query}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
    # WRITE: Register a webhook: body {endpoint (required), type (required) asset | assetField | location | part | po | poItem | task | instruction, enabled 1 | 0}; 201 {webhookID}. One type per webhook; receive with LimbleCmmsWebhookReceiver
    ADD EVENT CreateWebhook
        WITH SOURCE_TOPIC "cmms/limble/webhook/create"
        WITH DESTINATION_TOPIC "cmms/limble/webhook/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v2/webhooks"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    # WRITE: Update a webhook, e.g. body {enabled: 1} to re-enable one Limble disabled after failed retries; also endpoint, type
    ADD EVENT UpdateWebhook
        WITH SOURCE_TOPIC "cmms/limble/webhook/update"
        WITH DESTINATION_TOPIC "cmms/limble/webhook/update/response"
        WITH METHOD "PATCH"
        WITH ENDPOINT "/v2/webhooks/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "Basic {value.json.basic_auth}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
```

Webhook receiver (optional):

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Limble CMMS webhook receiver — REST_API route (server mode, beta, companion of LimbleCmmsRestApi.lot)
#   Receives Limble webhooks (Settings > Configuration > API Settings, or CreateWebhook) and publishes the JSON body on
#   MQTT, so task, asset, asset field and part changes 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/limble-cmms
#
# Limble needs a public URL. Put a reverse proxy or tunnel with a public TLS certificate in front of SERVER_PORT.
#   Limble posts a small JSON body {<object>ID, status, category, user} (for example {"taskID": 8203, "status":
#   "COMPLETE", "category": "task", "user": "..."}); read the full object back with ListTasks / ListAssets /
#   ListAssetFields / ListParts. Limble signs every delivery with signature, timestamp and token headers:
#   signature = HMAC-SHA256(key = hex SHA-256 of the account's OLDEST Limble API key, message = timestamp + token).
#   This route does not verify that signature or authenticate callers: verify it in the proxy, or use a hard-to-guess
#   path segment. Answer within 10 s; Limble retries after 10 min, 1 h, 2 h and 4 h, then disables the webhook
#   (re-enable with UpdateWebhook {"enabled": 1}). Webhooks need at least one active Limble API key on the account.
#   One webhook has one type: register task -> /tasks, asset and assetField -> /assets, part -> /parts,
#   location / po / poItem / instruction -> /events.

DEFINE ROUTE LimbleCmmsWebhookReceiver WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH ENABLE_SERVER "true"
        WITH SERVER_PORT '9010'
        WITH DEFAULT_CONTENT_TYPE "application/json"
    # Task webhooks (type task: CREATED, COMPLETE, CHANGED DUE DATE, CHANGED ASSIGNMENT, ADDED COMMENT TO TASK, ...): body {taskID, status, category, user}
    ADD EVENT WebhookTasks
        WITH ENDPOINT "/limble/webhook/tasks"
        WITH METHOD "POST"
        WITH DESTINATION_TOPIC "cmms/limble/webhook-task/received"
    # Asset and asset field webhooks (types asset, assetField): body {assetID | valueID, status, category, user}
    ADD EVENT WebhookAssets
        WITH ENDPOINT "/limble/webhook/assets"
        WITH METHOD "POST"
        WITH DESTINATION_TOPIC "cmms/limble/webhook-asset/received"
    # Part webhooks (type part: CREATED, QUANTITY INCREASED, QUANTITY DECREASED, PRICE UPDATED, ...): body {partID, status, category, user}
    ADD EVENT WebhookParts
        WITH ENDPOINT "/limble/webhook/parts"
        WITH METHOD "POST"
        WITH DESTINATION_TOPIC "cmms/limble/webhook-part/received"
    # Any other Limble webhook (types location, po, poItem, instruction). Filter on body.category in a LoT action
    ADD EVENT WebhookEvents
        WITH ENDPOINT "/limble/webhook/events"
        WITH METHOD "POST"
        WITH DESTINATION_TOPIC "cmms/limble/webhook-event/received"
```

## Example actions

These actions check the keys every hour, open a work order when a compressor alarm is raised, push run hours into a meter field, log machine state changes on the asset, and poll open work orders.

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Example actions for the LimbleCmmsApi route (docs page). Not part of the route template.
# Secret: LIMBLE_BASIC_AUTH = base64("<client id>:<client secret>") from Limble Settings > Configuration > API Settings.
#   IDs (location, asset, asset field valueID, user) come from location/list, asset/list and asset-field/list;
#   replace the <...> placeholders below with the numeric IDs.

DEFINE ACTION LimbleAuthCheck
ON EVERY 1 HOUR DO
    SET "auth" WITH GET SECRET "LIMBLE_BASIC_AUTH"
    PUBLISH TOPIC "cmms/limble/me/get" WITH { "basic_auth": {auth} }

DEFINE ACTION LimbleWorkOrderFromAlarm
ON TOPIC "porto/line1/compressor/alarm/raised" DO
    SET "auth" WITH GET SECRET "LIMBLE_BASIC_AUTH"
    SET "alarm" WITH (GET JSON "alarm" IN PAYLOAD AS STRING)
    SET "value" WITH (GET JSON "value" IN PAYLOAD AS STRING)
    SET "now" WITH TIMESTAMP "UNIX"
    SET "due" WITH ({now} + 86400)
    SET "name" WITH "Line 1 compressor alarm " + {alarm}
    SET "desc" WITH "Coreflux alarm " + {alarm} + " (value " + {value} + ")"
    PUBLISH TOPIC "cmms/limble/task/create" WITH { "basic_auth": {auth}, "body": { "name": {name}, "description": {desc}, "locationID": "<location id>", "assetID": "<asset id>", "type": 2, "due": {due}, "priority": 3, "meta1": {alarm} } }

DEFINE ACTION LimbleRunHoursMeter
ON TOPIC "porto/line1/press01/run-hours" DO
    SET "auth" WITH GET SECRET "LIMBLE_BASIC_AUTH"
    SET "hours" WITH PAYLOAD AS DOUBLE
    PUBLISH TOPIC "cmms/limble/asset-field/update" WITH { "basic_auth": {auth}, "id": "<run hours valueID>", "body": { "value": {hours} } }

DEFINE ACTION LimbleAssetLogFromMachineState
ON TOPIC "porto/line1/press01/state" DO
    SET "auth" WITH GET SECRET "LIMBLE_BASIC_AUTH"
    SET "state" WITH PAYLOAD AS STRING
    SET "entry" WITH "Press 01 state " + {state} + " (Coreflux)"
    PUBLISH TOPIC "cmms/limble/asset-log/create" WITH { "basic_auth": {auth}, "id": "<asset id>", "body": { "logEntry": {entry} } }

DEFINE ACTION LimblePollOpenWorkOrders
ON EVERY 15 MINUTES DO
    SET "auth" WITH GET SECRET "LIMBLE_BASIC_AUTH"
    PUBLISH TOPIC "cmms/limble/task/list" WITH { "basic_auth": {auth}, "query": "status=0&type=2,4&locations=<location id>&limit=100&page=1" }
```

## Notes

* The catalog hint `PATCH assets/{id}/fields` does not exist in the Limble API. Meter values are written per field value with `PATCH /v2/assets/fields/{valueID}`; `PUT /v2/assets/{assetID}/fields` only attaches a field to an asset.
* `UpdateTask` cannot set `statusID` to `1` (Open) or `2` (Complete); use `status` `0` / `1` for that. Its `priority` accepts `0` to `6` (`6` is "On Hold").
* Limble's reference also lists `name`, `locationID`, `due`, `type` and `templateID` as query parameters of `POST /v2/tasks`. Its description and example send them in a JSON body, which is what the connector does.
* Pagination uses `limit` + `page` (the reply has `Next-Page` / `Last-Page` headers) or `orderBy` + `cursor`, never both. A `limit` above the endpoint maximum returns `422`. Limble plans to deprecate cursor paging on tasks and assets.
* Rate limits per account: GET 4000 per hour (360 per minute), PATCH 2000 per hour, POST and PUT 1000 per hour, batch requests 20 per hour. Every reply carries `X-RateLimit-*` headers; over the limit you get `429`. Poll lists every few minutes, not per message.
* `api.limblecmms.com` sits behind Cloudflare, which rejects some client signatures (the Python `urllib` default user agent gets `403 error code: 1010`). Requests with no user agent, or a normal one, pass.
* Writes start Limble's own follow-ups: assignment notifications, meter and threshold PMs, workflow automations and webhooks.
* Errors come back on the response topic as `{"error": <code>, "success": false, "msg": "..."}`: `400` validation, `401` / `403` auth, `404` unknown ID, `409` conflict, `422` limit too high, `429` rate limit.

## Not covered

Deletes, images and files, creating assets, locations, parts, vendors, users, roles and teams, task instructions and invoices, purchase orders, bills, budgets and general ledgers, suggested field definitions and dropdown options, regions, tags, units of measure, and user capacity. All are in the Limble API and can be added to a fork.

## Status

Technical preview, **blocked**. The template passes 369 of 369 smoke checks: static checks of both connectors against Limble's OpenAPI spec and Postman docs, every event against a local mock of the Limble API v2, negative controls, and calls to the real Limble hosts without valid keys. The live `/openapi.json` lists every packaged method and path, the regional hosts answer, and every packaged call with fake keys was refused at the auth check (`403 Invalid credentials.`). Limble checks keys before it connectors a request, so those calls cannot prove a path exists on their own. No call has run yet with real Limble API keys.

To finish testing, use a Limble account (ideally a test location) with API keys: set `LIMBLE_CLIENT_ID` and `LIMBLE_CLIENT_SECRET` (or `LIMBLE_BASIC_AUTH`), `LIMBLE_HOST` for a non-default region, `LIMBLE_TEST_LOCATION`, and optionally `LIMBLE_TEST_USER` and `LIMBLE_TEST_VALUE_ID`, then run `LIMBLE_ALLOW_WRITE=1 python3 smoke_limble_cmms.py`. It reads every list, creates a work order called "Coreflux smoke test (safe to delete)", comments on it, reads it back, closes it, and writes one asset field value. Then deploy the connector on a broker and do an MQTT round trip.

## Related

* [REST API connectors](/latest/lot-language/routes/rest-api-routes)
* [REST API app templates](/latest/lot-language/routes/rest-api-apps/index)
* [Limble API v2 reference](https://apidocs.limblecmms.com/)
* [Limble OpenAPI spec](https://api.limblecmms.com/openapi.json)
* [Limble: linking readings to assets](https://help.limblecmms.com/en/articles/2971834-linking-readings-to-assets)
* [Limble: PM scheduling options](https://help.limblecmms.com/en/articles/6989116-pm-scheduling-options)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.