> ## 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.

# MachineMetrics

> Client-mode REST API connector for MachineMetrics: read machine state, alarms and open downtime over GraphQL, pull OEE and production or quality rollups with CSV export, check scheduled time, and manage work schedule breaks and holidays

Technical Preview

## Overview

One client-mode connector, **MachineMetrics**, for the MachineMetrics cloud APIs at `https://api.machinemetrics.com`. MachineMetrics collects data from CNC and other shop-floor machines and turns it into machine state, alarms, downtime, part counts and OEE. This connector brings that data into your Unified Namespace and keeps planned breaks and holidays in sync.

The API has two parts:

* **GraphQL** (`POST /graphql`, read-only): live machine data such as current execution state, active alarms, open downtime annotations, part counts and any other object in the published schema.
* **REST**: production and quality report rollups (OEE, good and rejected parts, cycle time, downtime) with optional CSV download, scheduled time per machine, and the work schedule events API for breaks and holidays.

| Event | Request | Notes |
| - | - | - |
| GraphqlQuery | `POST /graphql` | Any GraphQL query; body `{"query": "...", "variables": {...}}` |
| MachinesCurrentState | `POST /graphql` | Topic preset for a `machines { currentStates }` query |
| MachinesCurrentAlarms | `POST /graphql` | Topic preset for a `machines { currentAlarms }` query |
| MachinesActiveDowntime | `POST /graphql` | Topic preset for machines with an open downtime annotation |
| CreateProductionReport | `POST /reports/production` | Metric rollups (OEE, parts, downtime, ...) for a day range, grouped by machine, shift, operator, ... |
| CreateQualityReport | `POST /reports/quality` | Reject count rollups for a time range |
| GetCsvToken | `GET /user-token` | Short-lived JWT for the CSV downloads |
| GetProductionCsv | `GET /csv/production/{resource_id}?{query}` | CSV of a production report run with `"csv": true`; `query` = `token=<jwt>` |
| GetQualityCsv | `GET /csv/quality/{resource_id}?{query}` | CSV of a quality report; `query` = `token=<jwt>` |
| GetMachineScheduledTime | `GET /machines/{machine_ref}/scheduled-time?{query}` | Scheduled time in a window; `startAt` and `endAt` required |
| ListWorkScheduleEvents | `GET /work-schedules/events?{query}` | Break and holiday definitions |
| GetWorkScheduleEvent | `GET /work-schedules/events/{event_ref}?{query}` | One definition |
| CreateWorkScheduleEvent | `POST /work-schedules/events` | New break, holiday or custom holiday |
| UpdateWorkScheduleEvent | `PUT /work-schedules/events/{event_ref}` | Partial update |
| ArchiveWorkScheduleEvent | `DELETE /work-schedules/events/{event_ref}` | Soft delete; also archives its assignments |
| BulkCreateWorkScheduleEventAssignments | `POST /work-schedules/events/{event_ref}/assignments` | Apply one event to many work schedules |
| ListWorkScheduleEventAssignments | `GET /work-schedules/{work_schedule_ref}/events?{query}` | Assignments on one work schedule |
| GetWorkScheduleEventAssignment | `GET /work-schedules/{work_schedule_ref}/events/{assignment_ref}?{query}` | One assignment |
| CreateWorkScheduleEventAssignment | `POST /work-schedules/{work_schedule_ref}/events` | Assign an event with recurrence rules |
| UpdateWorkScheduleEventAssignment | `PUT /work-schedules/{work_schedule_ref}/events/{assignment_ref}` | Change recurrence |
| ArchiveWorkScheduleEventAssignment | `DELETE /work-schedules/{work_schedule_ref}/events/{assignment_ref}` | Soft delete |
| CreateWorkScheduleEventInstances | `POST /work-schedules/{work_schedule_ref}/event-instances` | One-off instances, for one machine or all machines on the schedule |
| ListMachinesEventInstances | `GET /work-schedules/event-instances/machines?{query}` | Instances for many machines; window up to 31 days |
| ListMachineEventInstances | `GET /work-schedules/event-instances/machines/{machine_ref}?{query}` | Instances for one machine |

Topics are `mes/machinemetrics/<object>/<verb>`; every reply goes to `<topic>/response`.

The three `Machines...` events send the same request as `GraphqlQuery`. They exist so state, alarms and downtime replies land on their own topics; the query you put in `body` decides what comes back.

## Authentication

Every event except the two CSV downloads sends `Authorization: Bearer {api_token}`. Create the key in MachineMetrics under **Settings → API Keys** (`https://app.machinemetrics.com/app/settings/api-keys`). A key belongs to the company it was created in and is shown only once.

Choose the scopes when you create the key:

| Scope | Needed for |
| - | - |
| `reporting` | All GraphQL queries, the production and quality reports, work schedule reads |
| `user` | Work schedule writes (create, update, archive) |

Use a separate key for each integration and give it only the scopes it needs. Keep it in a broker secret (suggested `MACHINEMETRICS_API_TOKEN`) and map it in with `GET SECRET`. A missing or wrong key returns `401` with `AuthenticationError`, and a key without the scope returns `403`.

The CSV downloads authenticate differently: call `GetCsvToken` for a JWT, then pass it as `token=<jwt>` in `query`.

## Basic Syntax

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE MachineMetrics WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://api.machinemetrics.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '60'
        WITH DEFAULT_CONTENT_TYPE "application/json"
    ADD EVENT MachinesCurrentState
        WITH SOURCE_TOPIC "mes/machinemetrics/machine-state/get"
        WITH DESTINATION_TOPIC "mes/machinemetrics/machine-state/get/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/graphql"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.api_token}"
        WITH BODY "{value.json.body}"
    ADD EVENT CreateProductionReport
        WITH SOURCE_TOPIC "mes/machinemetrics/production-report/create"
        WITH DESTINATION_TOPIC "mes/machinemetrics/production-report/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/reports/production"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/json"
            ADD "Authorization" "Bearer {value.json.api_token}"
        WITH BODY "{value.json.body}"
```

The full template, with all 24 events, is `MachinemetricsRestApi.lot` in the REST API app. GovCloud tenants set `BASE_ADDRESS` to `https://api.machinemetrics-us-gov.com`.

## MQTT examples

Request bodies go under `body`, so control fields like `api_token`, `machine_ref` and `query` are not sent to MachineMetrics.

**Current execution state of every machine**: publish to `mes/machinemetrics/machine-state/get`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "<MachineMetrics API key>",
  "body": {
    "query": "{ machines { machineRef name currentStates(where: {metricKey: {_eq: \"MasterExecution\"}}) { metricKey value startAt } } }"
  }
}
```

The reply is `{"data": {"machines": [...]}}`, one entry per machine with its `MasterExecution` state (for example `ACTIVE`) and when that state started. Leave out the `where` filter to get every current state metric.

**Active alarms**: publish to `mes/machinemetrics/machine-alarm/get`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "<MachineMetrics API key>",
  "body": {
    "query": "{ machines(where: {currentAlarms: {metricKey: {_eq: \"system\"}, label: {_nin: [\"NORMAL\", \"UNAVAILABLE\"]}}}) { name currentAlarms(where: {metricKey: {_eq: \"system\"}, label: {_nin: [\"NORMAL\", \"UNAVAILABLE\"]}}) { nativeCode label startAt } } }"
  }
}
```

**Machines in downtime right now**: publish to `mes/machinemetrics/machine-downtime/get`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "<MachineMetrics API key>",
  "body": {
    "query": "{ machines(where: {annotations: {endAt: {_is_null: true}}}) { name annotations(where: {endAt: {_is_null: true}}) { startAt annotationType { name } } } }"
  }
}
```

**Yesterday's OEE and good parts per machine**: publish to `mes/machinemetrics/production-report/create`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "<MachineMetrics API key>",
  "body": {
    "startDay": "2026-10-04",
    "endDay": "2026-10-04",
    "data": [{ "metric": "oee" }, { "metric": "goodParts" }, { "metric": "downtime" }],
    "groupBy": [{ "group": "machine" }],
    "flatten": true
  }
}
```

Other metrics include `totalParts`, `rejectedParts`, `timeInCycle`, `timeInCut`, `setupTime` and `utilizationRate`; other groups include `day`, `shift`, `operator` and `jobOperation`. Add `"csv": true` and the reply carries `csv.url`; download it with `GetCsvToken` and `GetProductionCsv`, using the id at the end of that URL as `resource_id`.

**Scheduled time for one machine**: publish to `mes/machinemetrics/machine-scheduled-time/get`. The query string must be URL-encoded:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "<MachineMetrics API key>",
  "machine_ref": 1234,
  "query": "startAt=2026-10-05T00:00:00Z&endAt=2026-10-05T08:00:00Z"
}
```

Send `"query": ""` on GET events when you do not need options.

**Add a plant holiday**: publish to `mes/machinemetrics/work-schedule-event/create` with a key that has the `user` scope:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "api_token": "<MachineMetrics API key>",
  "body": { "name": "Plant shutdown", "eventType": "custom-holiday", "customHolidayDate": "2026-12-24" }
}
```

Then assign it to work schedules with `work-schedule-event/assign` (`event_ref` plus `{"workScheduleRefs": [...], "startRRule": {...}}`).

## Helper actions

Keep the key in a broker secret, poll machine state every minute, and request a day's OEE from any topic:

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION MachineMetricsPollMachineState
ON EVERY 1 MINUTE DO
    SET "token" WITH GET SECRET "MACHINEMETRICS_API_TOKEN"
    PUBLISH TOPIC "mes/machinemetrics/machine-state/get" WITH { "api_token": {token}, "body": { "query": "{ machines { machineRef name currentStates { metricKey value startAt } } }" } }

DEFINE ACTION MachineMetricsOeeForDay
ON TOPIC "porto/reports/machinemetrics/oee" DO
    SET "token" WITH GET SECRET "MACHINEMETRICS_API_TOKEN"
    SET "day" WITH GET JSON "day" IN PAYLOAD AS STRING
    PUBLISH TOPIC "mes/machinemetrics/production-report/create" WITH { "api_token": {token}, "body": { "startDay": {day}, "endDay": {day}, "data": [ { "metric": "oee" }, { "metric": "goodParts" }, { "metric": "downtime" } ], "groupBy": [ { "group": "machine" } ], "flatten": true } }
```

Restrict who can publish to `mes/machinemetrics/#` with a rule: its payloads carry the API key, and the work schedule events change planned production time.

## Notes

* The GraphQL API is read-only. Limits: 1000 operations per minute per company, query depth 5, at most 5 object nodes, 10 seconds per query. GraphQL errors come back as `200` with an `errors` array, so check the reply body.
* Times are UTC. Report `startDay` / `endDay` are dates (`YYYY-MM-DD`); `startAt` / `endAt` are ISO 8601 date-times.
* `machineRef`, `workScheduleRef` and the other refs are MachineMetrics integer ids. Get machine refs from a `machines { machineRef name }` query.
* Not covered by this connector: `POST /jobs/import` (multipart CSV upload), MachineMetrics webhooks (MachineMetrics calls your URL when a workflow fires, for example on categorized downtime; receive them with a server-mode connector or another HTTP endpoint), and edge data collection (MTConnect, MQTT and adapter scripts run on the MachineMetrics edge device, not through this API).

## Related

* [REST API Connectors](/latest/lot-language/routes/rest-api-routes)
* [REST API app templates](/latest/lot-language/routes/rest-api-apps/index)
* [Rockwell Plex Smart Manufacturing Platform](/latest/lot-language/routes/rest-api-apps/rockwell-plex-smart-manufacturing-platform)
* [Siemens Opcenter Execution](/latest/lot-language/routes/rest-api-apps/siemens-opcenter-execution)


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