Operational data
Figures that live beside the ledger: headcount, volume, revenue per seat. Sent as operational data and read back dataset by dataset, they feed the dashboard and the report blocks that read a non-financial number.
Writing operational data
The endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /api/v1/organisations/{organisation}/operation-data |
Writes the figures |
| POST | /api/v1/organisations/{organisation}/operation-data/validate |
The same checks; nothing is written |
Needs a bearer token, a credential issued for that company, and the credential's operation_data.write
scope.
The body
{
"declarations": {
"source_reference": "hr-export",
"period": "2026-09",
"value_basis": "company_base",
"rate_direction": "document_to_base",
"base_currency": "SGD",
"mode": "replace_period"
},
"records": [
{ "period": "2026-09", "dimension_1": "Singapore", "dimension_2": "Operations", "value": 128 },
{ "period": "2026-09", "dimension_1": "Kuala Lumpur", "dimension_2": "Operations", "value": 76 },
{ "period": "2026-09", "account_code": "4000", "dimension_1": "Singapore", "value": 12500.00 }
]
}
Declarations
| Declaration | Required | Value |
|---|---|---|
source_reference | yes | Your own id for the feed, 1–64 characters. |
period | yes | The month, YYYY-MM. Each record states its own
period as well, so one payload may span months. |
value_basis | yes | The envelope asks for it on every kind but accounts; state the company's own. |
rate_direction | yes | As for any kind that is not accounts. |
base_currency | yes | The company's reporting currency. |
scenario | no | Actual (default), Budget or
Forecast; a record may override it. |
mode | no | replace_period (default), replace_all
or merge. |
webhook_url | no | Called once when the request finishes or fails. |
The fields
| Field | Type | Required | What it means |
|---|---|---|---|
period | date | yes | The month the figure belongs to, as
YYYY-MM. Part of the record's identity. |
scenario | string | no | The request's scenario when absent. Part of the identity. |
dimension_1, dimension_2, dimension_3 | string | no | What the figure is broken down by — entity, department, channel. Part of the identity. |
account_code | string | no | The account the figure belongs to, when the dataset is account-based. Part of the identity. |
value | number | yes | The figure. |
Identity: period, scenario, the three dimensions and the account together. Two records that
agree on all of them are the same figure, and stating it twice in one payload is refused with
duplicate_record_identity.
What it does
- A figure replaces a figure. Under
replace_periodthe declared month is restated for that source, so a dimension absent from the payload has no figure afterwards. - No report figure moves. Operational data feeds the dashboard and the report blocks that read it; it never changes a statement.
- Text is stored as sent. A dimension value is kept exactly as given — no lookup, no renamed label.
What it answers
{
"id": 97, "kind": "operation_data", "source_reference": "hr-export",
"period": "2026-09", "status": "written", "records_received": 3,
"counts": { "created": 3, "updated": 0, "unchanged": 0, "rejected": 0 },
"recalculation": null,
"written_at": "2026-10-23T06:44:31+00:00",
"links": { "self": "…/requests/97", "results": "…/requests/97/results" }
}
Refusals
| Code | When |
|---|---|
required_field_missing | A record states no period or no value |
invalid_type | value is not a number |
duplicate_record_identity | Two records state the same period, scenario, dimensions and account |
unknown_field | A field outside the six above was sent |
Reading operational data
The figures that live beside the ledger - headcount, volume, revenue per seat - stored in datasets and read by the dashboard and the report blocks.
The endpoints
| Method | Path | Answers |
|---|---|---|
| GET | /api/v1/organisations/{organisation}/operation-data | The datasets the company holds |
| GET | …/operation-data/{dataset} | One dataset's figures for a month |
Both need a bearer token, a credential issued for that company, and the
operation_data.read scope.
The datasets
GET …/operation-data
{
"data": [
{ "id": 23, "name": "Sales by Customer and Product", "description": null,
"row_count": 978, "scenario": "Actual", "scenario_mode": "column",
"translation_mode": "none", "scenarios": ["Actual", "Forecast", "Budget"],
"period_range": { "from": "2024-10", "to": "2026-09" },
"dimensions": ["Customer", "Product", null] }
],
"page": 1, "per_page": 50, "total": 1, "has_more": false
}
| Field | What it says |
|---|---|
row_count | How many rows the dataset holds. |
dimensions | What each of the three dimension columns means in this dataset, in
order, so a caller can label a figure. null where the dataset does not name one. |
scenario, scenario_mode | The scenario the dataset keeps and how it
keeps it: dataset when the whole dataset is one scenario, column when each row
states its own. |
scenarios | Which scenarios this dataset can be asked for - the one it holds, or whichever of the three its uploads stored. |
translation_mode | Whether its values are converted to another currency, and on what basis. |
period_range | Its first and last month, so a caller knows what to ask for before asking. |
A dataset is listed under the name it is stored with, so a feed written through this API appears as
API: <source reference>.
One dataset
| Parameter | What it does |
|---|---|
period | The month, YYYY-MM. Required. |
scenario | Actual, Budget or Forecast. |
dimension | One value of the first dimension. |
GET …/operation-data/23?period=2026-09
{
"dataset_name": "Sales by Customer and Product", "period": "2026-09", "scenario": "Actual",
"rows": [
{ "dimension": "Acme Manufacturing", "dimension_2": "Enterprise Licence", "dimension_3": null,
"account": "SA-4000 - Sales Revenue", "value": 6279.61 }
]
}
A row is one figure: the three dimension values, the account it belongs to when the dataset is account-based,
and the value. A dataset the company does not hold answers 404.