Skip to main content

Datasets

GET /api/v1/charts/:dataset returns the aggregated data behind TrackMyMachines' dashboards as rows.

There are thirteen datasets, one per underlying query — between them they cover every figure a dashboard draws. They are not charts: a chart is a way of drawing one of these, and the same rows are drawn a dozen ways in the app. So a dataset takes only the parameters that change which rows come back — the window, the bucket, the grouping, the filters — and every row carries every column that query produces. You read the column you want; there is no parameter for picking one figure out of a row.

note

The path still says /charts/ and the parameter is still called chart_type. Both are kept so existing integrations and connector definitions keep working. The value is a dataset name.

Permissions​

EndpointMethodRead-only AccessRead-write AccessDescription
/api/v1/charts/:datasetGET✅✅Retrieve the rows of a dataset

Required role: read-only or read-write.

The datasets​

datasetwhat it measures
downtimeDowntime per bucket and asset, split by reason, category or kind
downtime_paretoThe same downtime, ranked worst-first with a running total
utilisationThe machine-time ledger: uptime, downtime by kind, powered-off time, and the two utilisation percentages
energyEnergy consumption in kWh
idle_energyEnergy consumed while machines sat idle, with the idle time behind it
partsGood, scrap and total parts, with the scrap share
oeeAvailability, performance, quality and OEE, with the inputs behind each
job_oeeOne row per job run in the period, on the machine that ran it
downtime_stopsStop statistics — stops, total, mean stop, MTBF, MTTR, top reason — per reason, category or machine
downtime_eventsThe downtime event log: one row per stop
cycle_timesShortest, median and longest cycle per machine and cycle
work_ordersWork orders against target: made, scrapped, progress, status, due date
powerAverage power in kW per bucket and asset (at most 7 days)

Query parameters​

Every parameter is optional — each dataset has its own defaults, listed with it below.

  • from, to — ISO8601 start and end, read in your organisation's timezone when they carry no offset. Must be provided together. A bare date to (2026-08-16) means the whole of that day. An unparseable value, a to before from, or a window longer than five years (seven days on power) returns 422.
  • range — a named window resolved server-side in your organisation's timezone: today, yesterday, this_week, last_week, this_month, last_month, this_quarter, last_quarter, this_year, last_year, last_7_days, last_14_days, last_30_days, last_90_days, last_365_days, or rolling_<n>_<days|weeks|months|years>. Mutually exclusive with from/to; an unknown or forward-looking token returns 422 with the allowed list.
  • bucket — the time grain of a row: hour, day, week, month, quarter, or max (one bucket spanning the whole window). Each dataset lists the ones it offers; anything else is coerced and reported in coercion_notes.
  • group_by — the entity grain of a row: none (the whole organisation), factory, area, machine, shift_pattern, and job on oee. Same coercion rule.
  • grain — on downtime and downtime_pareto: reason, category or kind; on downtime_stops: reason, category or machine. Refused on a dataset that has none, and a value outside the dataset's own list is a 422 naming the ones it takes.
  • offset, limit — paging (see Paging). limit is at most 500, the default.
  • factory_names[], area_names[], machine_names[] — scope by site, area or machine name (machine names also match foreign ids). Levels intersect. Unknown or ambiguous names return 422 with the candidates, never a silent guess; a site or area with no machines is a 422, not the whole organisation.
  • machine_foreign_ids[] — machine foreign ids to filter by. A single bare value (machine_foreign_ids=CNC-01) works too; an unknown id matches nothing.
  • shift_ids[] — on downtime, downtime_pareto, downtime_stops, downtime_events and utilisation: which slices of time count, as shift pattern names plus the reserved words all (every pattern, including ones added later) and out_of_shift. Defaults to ["all"]; ["all","out_of_shift"] is 24/7. oee and job_oee refuse it — OEE is measured per job, which runs straight through a shift handover — and so do the datasets that don't slice by shift.
  • activity_reason_category_names[], activity_reason_names[] — on the four downtime datasets: only these categories / reasons.
  • downtime_types[] — on downtime and downtime_pareto: only these kinds, unproductive, productive, discounted.
  • include_discounted — on the four downtime datasets: true counts discounted time (planned maintenance, holidays, closures). Off by default.
  • search — free-text over the dataset's own identifiers, on oee (part or work-order number), job_oee, cycle_times (cycle or program name) and work_orders (work-order or part number).

A parameter a dataset does not take is refused with 422, naming it — whether it is unknown, a filter that dataset's query cannot honour, or a misspelling. Nothing is silently dropped: a 200 with the whole organisation in it, labelled as your selection, is the failure this rule exists to prevent. The GET /api/v1/hierarchy, /api/v1/shift_patterns and /api/v1/reason_categories endpoints list the names the filters take.

Internal database ids (machine_ids, activity_reason_category_ids, …) are refused — only names and foreign / external identifiers cross the API boundary.

Parameters that are refused​

These are chart settings, and sending one returns 422 with the list of columns the rows carry instead. They are refused rather than ignored, because a request that silently returned the default variant is how a caller ends up reading 0–1 fractions as second-durations.

parameterwhat to do instead
measure, basis, columnRead the column you want — every row carries all of them
orientation, templatePresentation only; the API returns rows
compare, compare_to, previous_from, previous_toIssue two requests with different windows

Response shape​

Every dataset returns the same envelope. filter_applied echoes the resolved filter — defaults and coercions included — so you always see the window and grain the data actually covers, and columns describes what is in each row.

{
"dataset": "utilisation",
"title": "Utilisation",
"columns": [
{ "name": "entity_name" },
{ "name": "uptime_seconds", "label": "Spindle Uptime", "format": "duration", "unit": "seconds" },
{ "name": "percentage", "label": "Share of available time", "format": "percent", "unit": "fraction (0-1)" }
],
"filter_applied": {
"from": "2025-05-20T00:00:00+00:00",
"to": "2025-05-27T00:00:00+00:00",
"bucket": "day",
"group_by": "machine",
"shift_ids": ["all"]
},
"coercion_notes": [],
"warnings": [],
"row_count": 1,
"total_row_count": 1,
"offset": 0,
"limit": 500,
"next_offset": null,
"truncated": false,
"rows": [
{
"entity_name": "CNC-01",
"from": "2025-05-20T00:00:00+00:00",
"to": "2025-05-21T00:00:00+00:00",
"uptime_seconds": 21600,
"downtime_seconds": 5400,
"offtime_seconds": 1800,
"productive_downtime_seconds": 3600,
"unproductive_downtime_seconds": 1800,
"discounted_downtime_seconds": 0,
"unaccounted_downtime_seconds": 0,
"total_seconds": 28800,
"percentage": 0.875,
"spindle_percentage": 0.75,
"shift_type": "in_shift",
"machine_foreign_id": "CNC-01"
}
]
}

Three things to read every time:

  • coercion_notes — a bucket or grouping the dataset does not offer is corrected, not refused, and each correction is listed here with the reason. A caller that asked for hourly data and silently got daily would report the wrong thing.
  • columns — the label, format and unit of every column, from the aggregator's own catalog. Percent values are 0–1 fractions everywhere in this API — every dataset, every column — and durations are integer seconds.
  • next_offset — responses carry at most 500 rows. When there are more, truncated is true, next_offset says where the next page starts and a warnings entry says how many rows there are in all.

When the request is grouped by machine, each row carries machine_foreign_id so results can be joined back to the ids you filtered by. Rows never contain internal database ids.

Columns with an organisation target (utilisation and OEE percentages) carry it as target in columns on the MCP describe_chart answer and in every envelope — the figure the dashboard's KPI cards colour against, as a fraction.

Paging​

Rows come back in a fixed order per dataset (each dataset lists it below), so offset N is the same row on every call over the same data. Pass the previous response's next_offset as offset until it is null:

curl "https://your-org.trackmymachines.app/api/v1/charts/downtime_events?range=last_month&offset=500" \
-H "Authorization: Bearer YOUR_API_KEY"

The array endpoints (/events, /machine_activities, /jobs) take the same offset and limit; their body stays an array, so the next page is in the X-Next-Offset response header (absent on the last page) beside X-Total-Count.

Rate limit​

Every API key or OAuth token may make 60 requests a minute across /api/v1/* and /mcp. The 61st returns 429 Too Many Requests with a Retry-After header (seconds). Separately, one IP address may make at most 300 requests a minute across the same paths, whatever credentials they carry. The limit is counted per server instance, so it is a floor, not a guarantee of throughput.

Comparing two periods​

There is no compare parameter: request the two windows separately. To compare against an average of N periods, request the N periods as one window and divide durations, counts and kWh by N; percentages and other rates need no divisor — the pooled figure is already the time-weighted mean, which weights a quiet week less than a busy one, as it should.

Example request​

curl -X GET "https://your-org.trackmymachines.app/api/v1/charts/utilisation?range=last_week&bucket=day&group_by=machine" \
-H "Authorization: Bearer YOUR_API_KEY"

Each dataset​

The column tables below are generated from the aggregators' own column catalogs. A blank label/format/unit means the column is an identity rather than a measured figure — a name, a timestamp, or the bucket a row covers.

downtime​

Downtime per bucket and asset, split by individual reason, reason category, or kind of downtime. Every row carries both the hours and the share of that asset's own scheduled time — the "% of scheduled time" view of the dashboards is a column here, not a mode.

  • Defaults: range=this_week, bucket=max, group_by=machine
  • Buckets: hour, day, week, month, quarter, max
  • Groupings: none, factory, area, machine, shift_pattern
  • grain: reason (default) · category · kind
columnwhat it isformatunit
entity_name
bucket
duration_secondsDowntimedurationseconds
activity_reason_namepresent at grain=reason
activity_reason_category_namepresent at grain=category and grain=kind
percentageDowntime as a share of the asset's scheduled timepercentfraction (0-1)
downtime_costCost of downtime at your organisation's hourly rate; null when no rate is setcurrency

Rows are ordered by bucket, entity_name, category, reason. percentage is each row's downtime over that entity's own denominator — scheduled seconds in shift, elapsed minus scheduled out of shift, elapsed calendar otherwise, picked by the shift filter. Assets with no downtime appear at 0, and assets with no usable denominator appear with percentage: null rather than at 0 — a machine that cannot be measured must not read as a clean one.

grain=kind includes discounted time (planned maintenance, holidays, closures), because the kinds are its subject. Every other grain excludes it unless you pass include_discounted=true.

downtime_pareto​

The same downtime, ranked worst-first with a running total: the few causes behind most of the loss.

  • Defaults: range=this_week, bucket=max, group_by=none
  • Buckets: max only — a Pareto ranks the whole period
  • Groupings: none only — a Pareto ranks across the organisation
  • grain: reason (default) · category · kind
columnwhat it isformatunit
activity_reason_name
duration_secondsDowntimedurationseconds
percentShare of downtimepercentfraction (0-1)
cumulative_percentRunning sharepercentfraction (0-1)
downtime_costCost of downtime; null without a ratecurrency

Rows are ranked worst-first, ties by name.

utilisation​

The machine-time ledger per bucket and asset: spindle uptime, productive and unproductive downtime, discounted and unaccounted time, powered-off time, and the two utilisation percentages computed from them.

  • Defaults: range=last_7_days, bucket=day, group_by=none
  • Buckets: hour, day, week, month, quarter, max
  • Groupings: none, factory, area, machine, shift_pattern
columnwhat it isformatunit
entity_name
from
to
uptime_secondsSpindle Uptimedurationseconds
downtime_secondsDowntimedurationseconds
offtime_secondsPowered Offdurationseconds
productive_downtime_secondsProductive Downtimedurationseconds
unproductive_downtime_secondsUnproductive Downtimedurationseconds
discounted_downtime_secondsDiscounted Downtimedurationseconds
unaccounted_downtime_secondsUnaccounted Downtimedurationseconds
total_secondsTotal Timedurationseconds
percentageShare of available timepercentfraction (0-1)
spindle_percentageSpindle Utilisationpercentfraction (0-1)
shift_type

The two percentages are two definitions of the same question. spindle_percentage is spindle uptime over total time; percentage is uptime plus productive downtime over non-discounted time. uptime_seconds is the numerator of the first and uptime_seconds + productive_downtime_seconds of the second.

energy​

Energy consumption in kWh per bucket and asset.

  • Defaults: range=this_week, bucket=week, group_by=machine
  • Buckets: hour, day, week, month, quarter, max
  • Groupings: none, factory, area, machine — energy is not tied to shift patterns
columnwhat it isformatunit
entity_name
bucket
energy_kwhEnergyfloatkWh

idle_energy​

Energy consumed while machines sat idle — powered, not cutting — with the idle time behind it.

  • Defaults: range=this_week, bucket=day, group_by=machine
  • Buckets: hour, day, week, month, quarter, max
  • Groupings: none, factory, area, machine — idle energy is not tied to shift patterns
columnwhat it isformatunit
entity_name
bucket
idle_secondsIdle Timedurationseconds
idle_energy_kwhIdle EnergyfloatkWh

parts​

Good, scrap and total parts over the whole window, per asset, with the scrap share. Operator-confirmed quantities where a job was confirmed, live counts everywhere else.

  • Defaults: range=this_week, bucket=max, group_by=none
  • Buckets: max only — part counts are totalled over the whole window
  • Groupings: none, factory, area, machine
columnwhat it isformatunit
entity_name
good_partsGood Partsinteger
total_partsTotal Partsinteger
scrap_partsScrap Partsinteger
scrap_rateScrap Ratepercentfraction (0-1)

scrap_rate is null when nothing was produced.

oee​

Availability, performance, quality and their product, per bucket and entity, with the raw inputs behind each factor. All four factors ride every row: there is no factor parameter, you read the column you want.

  • Defaults: range=this_week, bucket=max, group_by=none
  • Buckets: hour, day, week, month, quarter, max
  • Groupings: none, factory, area, machine, shift_pattern, job
columnwhat it isformatunit
entity_name
bucket
from
to
availabilityAvailabilitypercentfraction (0-1)
performancePerformancepercentfraction (0-1)
qualityQualitypercentfraction (0-1)
oeeOEEpercentfraction (0-1)
operating_secondsOperating Time — availability's numeratordurationseconds
scored_operating_secondsOperating Time (Performance) — performance's denominator: operating time on jobs with a computable idealdurationseconds
planned_secondsPlanned Production Time — availability's denominatordurationseconds
ideal_secondsIdeal Run Time — performance's numeratordurationseconds
good_partsGood Partsinteger
total_partsTotal Partsinteger
scrap_partsScrap Partsinteger
quantities_confirmedIncludes Confirmed Quantities — true when any slice used operator-confirmed countsboolean

search narrows the runs by part number or work-order identifier. total_parts and good_parts are finished parts, prorated across sub-operations, so they can differ from parts by less than one per job. A window with no production returns no rows, not a row of nulls.

job_oee​

One row per job run in the period, on the machine that ran it: the three OEE factors, the part counts behind them and the cycle times.

  • Defaults: range=this_week, bucket=max, group_by=machine
  • Buckets: max only — Job OEE covers each job in full, the period is never sub-divided
  • Groupings: machine only — always one row per job, on the machine that ran it
  • search matches: part number, work-order identifier or operation name
columnwhat it isformatunit
external_id
eventable_name
machine_name
started_at
ended_at
part_countTotal Parts — confirmed once the job is signed off, measured until theninteger
scrap_countScrap Partsinteger
expected_part_countExpected Partsinteger
machine_part_countMeasured Partsinteger
confirmed_part_countConfirmed Partsinteger
availabilityAvailabilitypercentfraction (0-1)
performancePerformancepercentfraction (0-1)
qualityQualitypercentfraction (0-1)
oeeOEEpercentfraction (0-1)
ideal_secondsIdeal Run Timedurationseconds
expected_cycle_timeExpected Cycle Timedurationseconds
median_button_to_buttonMedian Button to Buttondurationseconds
quantities_confirmedQuantities Confirmedboolean

downtime_stops​

Stop statistics over the whole window — whole stops, which downtime (clipped, merged hours) cannot count.

  • Defaults: range=this_week; bucket=max and group_by=none only
  • grain: reason (default) · category · machine — what one row is
  • Ordered by total_seconds descending, then entity_name
columnwhat it isformatunit
entity_namethe reason, category or machine
category_namethe reason's category (grain=reason)
stopsStopsinteger
total_secondsDowntimedurationseconds
mean_stop_secondsMean stopdurationseconds
shareShare of the downtime in these rowspercentfraction (0-1)
mtbf_secondsMTBF: running time between stops (grain=machine)durationseconds
mttr_secondsMTTR: the mean stopdurationseconds
share_of_plannedDowntime as a share of the machine's planned time (grain=machine)percentfraction (0-1)
top_reason_namethe reason it stopped for most (grain=machine)

downtime_events​

The downtime event log: one row per stop touching the window, newest first, with the seconds of it inside the window and the shift selection. Paged in the query — read next_offset.

  • Defaults: range=this_week
  • Ordered by started_at descending, then machine and reason
columnwhat it isformatunit
machine_name
started_at
secondsDowntime inside the window and the shift selectiondurationseconds
reason_name
category_name

cycle_times​

Cycle time per machine and cycle: a toleranced cycle timed by its own events, a machine program by the gap between consecutive part counts inside one run of it.

  • Defaults: range=this_week; one row per machine and cycle
  • search matches the cycle or program name
columnwhat it isformatunit
machine_name
cycle_name
cyclesCycles timedinteger
minimum_secondsShortest cycledurationseconds
median_secondsMedian cycledurationseconds
maximum_secondsLongest cycledurationseconds

work_orders​

One row per work order due in the window or worked in it. Work orders belong to the organisation, not to a machine, so there is no machine filter.

  • Defaults: range=this_week; ordered by due_date, then work_order
  • search matches the work-order or part number
columnwhat it isformatunit
work_order
external_id
part_name
quantity_expectedTarget quantityinteger
quantity_madeQuantity madeinteger
quantity_scrappedQuantity scrappedinteger
progressMade as a share of target (not capped at 1)percentfraction (0-1)
statusplanned, released, in_progress, completed, cancelled
due_date
overduePast due and not finishedboolean
machine_namesthe machines that ran it

power​

Average electrical power per bucket and asset: the bucket's kWh over its hours, offline minutes counting as 0 kW. The one-minute source is kept for a week, so a window may be at most 7 days (named ranges included).

  • Defaults: range=today, bucket=hour, group_by=machine
  • Buckets: hour, day, max; groupings: none, factory, area, machine
columnwhat it isformatunit
entity_name
bucket
avg_powerAverage PowerfloatkW

Renamed chart types​

Until 2026 this endpoint published nineteen chart types — nineteen names for eight of these queries, six of them the same utilisation query drawn differently and four the same OEE row with a different figure lifted out. They now answer with a 404 that names the dataset and the parameters that replace them:

old chart_typedatasetparameters
downtime_by_assetdowntimegroup_by=machine (or factory / area)
downtime_by_reasondowntimegroup_by=none
downtime_analysisutilisationthe five-way split is columns of every row
downtime_hoursutilisationread unproductive_downtime_seconds
utilisation_over_timeutilisationbucket=day (or week / month)
utilisation_rankingutilisationbucket=max&group_by=machine, then sort by percentage
shift_breakdownutilisationbucket=max&group_by=shift_pattern
calendarutilisationbucket=day&group_by=factory, then read percentage
total_partspartsread total_parts
good_partspartsread good_parts
scrap_ratepartsread scrap_rate
oee_availabilityoeeread the availability column
oee_performanceoeeread the performance column
oee_qualityoeeread the quality column

downtime_pareto, energy, idle_energy, oee and job_oee are dataset names already and keep working unchanged — though oee now returns every factor on the row, and no value field.

Older names still in circulation — machine_utilisation, effective_utilisation, machine_hours, effective_hours, downtime_reason_hours, downtime_reason_percent, the _category_ and _horizontal variants, downtime_reason_pareto — were retired earlier and return a plain 404. machine_utilisation and effective_utilisation are utilisation (read spindle_percentage and percentage); the downtime reason family is downtime at the matching grain; downtime_reason_pareto is downtime_pareto.

What else changed​

  • There is no value field, and no returns. Every dataset returns rows. The four OEE factors and the three part counts were single-figure chart types; they are columns.
  • format and unit moved from the envelope to columns. A dataset row carries several formats at once — hours and a share, four factors and their inputs — which one envelope-level format could never describe.
  • chart_type in the envelope is now dataset.

Error responses​

  • 401 Unauthorized — missing, invalid, expired, or wrong-organisation API key.
  • 404 Not Found — the name is not a dataset. The body carries datasets (every name) and, for a retired chart type, the dataset and parameters that replace it.
  • 422 Unprocessable Entity — an unknown parameter (with parameters, the ones this dataset takes); a filter the dataset cannot honour (the message names the datasets that take it); from/to unparseable, inverted, too long or provided without the other; range unknown or sent alongside from/to; an unknown or ambiguous site / area / machine / reason / category name (with candidates); a refused presentation or comparison parameter (with columns); limit above 500.
  • 429 Too Many Requests — over 60 requests a minute on this credential; wait Retry-After seconds.