Skip to content

Publishing metrics over MQTT ​

A connected device (see Connecting a device) records measurements by publishing JSON to one topic:

text
s/metrics

This is a relative topic: the broker places it in the device's own namespace, so never add the device ID. s/metrics is the only topic the platform records data from. Messages on other topics are accepted by the broker but not stored; they can still start a workflow (see Automating on device data).

Payload ​

json
{
  "points": [
    {
      "name": "temperature",
      "value": 22.5,
      "kind": "GAUGE",
      "unit": "CELSIUS",
      "displayName": "Oven temperature",
      "tags": { "zone": "front" },
      "timestamp": "2026-09-24T10:15:00Z"
    },
    {
      "name": "door_open",
      "value": 0,
      "kind": "STATUS",
      "unit": "BOOLEAN"
    }
  ]
}
FieldTypeRequiredNotes
namestringyesIdentifies the metric on this device. Used as sent (no case folding).
valuenumberyesAlways a number. Send booleans as 0 / 1.
kindstringnoMetricKind. Default GAUGE. Only used when the metric is created.
unitstringnoMetricUnit. Default NONE. Only used when the metric is created.
customUnitstringnoYour own unit label, for use with "unit": "CUSTOM" (for example "Sheets"). Only used when the metric is created.
displayNamestringnoHuman-readable label. Only used when the metric is created.
tagsobjectnoString keys and string values, stored with this data point.
timestampstringnoISO 8601 date-time with a time zone, with or without fractional seconds (2026-09-24T10:15:00Z, 2026-09-24T12:15:00.250+02:00). Defaults to the time the platform processed the message.

One message can carry up to 10 000 points, for one or many metrics.

Metric kinds ​

KindUse it for
GAUGEA value that goes up and down: temperature, speed, ink level.
COUNTERA cumulative total that only increases: total impressions, error count. Send the current total, not the increment.
STATUSA discrete state as a number, such as 0/1 for a door closed/open.
RATEA rate of change you have computed: pages per minute, waste per hour.

Units ​

Units are UPPERCASE:

GroupValues
DimensionlessNONE, PERCENT, RATIO, BOOLEAN
CountCOUNT, PIECES
LengthMILLIMETERS, METERS, INCHES, FEET
SpeedMETERS_PER_MINUTE, FEET_PER_MINUTE, UNITS_PER_MINUTE, UNITS_PER_HOUR
TemperatureCELSIUS, FAHRENHEIT, KELVIN
PressureBAR, PSI, KILOPASCAL
WeightGRAMS, KILOGRAMS, POUNDS
VolumeMILLILITERS, LITERS
AreaSQUARE_METERS, SQUARE_FEET
TimeMILLISECONDS, SECONDS, MINUTES, HOURS
ElectricalVOLTS, AMPS, WATTS, KILOWATT_HOURS
DataBYTES, KILOBYTES, MEGABYTES
OtherCUSTOM together with customUnit

The first point creates the metric ​

The first time a device sends a new name, CoCoCo creates the metric using that point's kind, unit, customUnit and displayName. After that, those fields are ignored on every later message. If the first message had the wrong kind or unit, correct the metric with the updateMetric mutation; changing what the device sends will not fix it.

When a message is dropped ​

The device gets no error back. The whole message is discarded when:

  • it is not valid JSON, or has no points array;
  • any point has a missing name or value, a non-numeric value, a lowercase or unknown kind or unit, or a non-string tag value;
  • points is empty or has more than 10 000 entries;
  • the client ID is not a valid, unrevoked, unexpired device token ID (dtk_…).

A timestamp that cannot be parsed does not drop the point: it is recorded at the time the message arrived.

Checking that data arrived ​

Open IoT → Metrics in the console, or list the device's metrics:

graphql
query DeviceMetrics($deviceId: ID!) {
  listMetrics(first: 50, filter: { deviceId: { eq: $deviceId } }) {
    totalCount
    edges {
      node {
        id
        name
        kind
        unit
        customUnit
        createdAt
      }
    }
  }
}

Use getMetricPoints with a metric's id to read its data points.

Connect machines, automate workflows, and build custom apps for print production.