Stats & Cost API
The stats reads say how each record your workspace holds is doing now: its health, and on request its metrics
and what it costs. They change nothing. For what the cost figures mean on screen, see
What your workspace costs; over MCP the same read is
get_stats.
Workspace stats
GET /ox-api/stats
Call it like any other /ox-api route (see Auth & Tokens). kinds,
lookups and metrics take a comma list, and a key sent more than once keeps every value.
| Query | Meaning |
|---|---|
kinds | Only these record kinds, by collection (for example IoT,DataStores). Absent: every active kind |
lookups | Only these record lookups. Absent: every record of each kind asked |
metrics | true adds every kind's named metrics; a list of kinds adds them for those kinds only |
cost | true adds each record's Cost beside its Health. Any other value, or no key, reads no cost |
The answer is { Kinds: { [Kind]: { [Lookup]: record } } }. A record is { Health, Metrics?, OldCopyHealth?, Cost? },
or { Known: false, Reason: "NO_STATS_SOURCE" } for a kind with no health source.
| Status | Code | When |
|---|---|---|
| 400 | STATS_KIND_UNKNOWN | A kind asked is not active here; the Remedy names the kinds that are |
| 503 | STATS_UNAVAILABLE | The stats could not be read just now |
| 503 | WORKSPACE_EAC_UNAVAILABLE | The workspace itself could not be read |
Asking for cost
GET /ox-api/stats?cost=true
cost=true is read the same way on both stats reads: true in any case and with spaces around it counts
(TRUE counts too); yes does not. Cost is read only when asked, so the canvas's regular health refresh never reads it.
- Which records carry a
Cost. Only a record whose kind says it can tell what it costs: todayIoT,DataStores,CALZ(the landing zone),ProvenanceandDNSZones. Every other record has noCostkey. NoCostkey means "this kind reads no cost", never "it costs nothing". - A record that could not be read still carries a
Cost, with both figuresunavailableand a reason from the table below. It is never a zero. - Kept for an hour. An answer that holds at least one figure that was read is kept for an hour, separately for each caller's set of rights. An answer with no figure read is not kept, so the next call asks again.
- One bound. The read of every asked record waits at most 15 seconds before it answers
COST_REASONS.TooSlow.
{
"Kinds": {
"DataStores": {
"telemetry": {
"Health": { "Status": "up", "CheckedAt": "2026-10-31T15:00:00.000Z" },
"Cost": {
"Estimate": {
"Status": "read",
"Amount": 25,
"Currency": "USD",
"ReadAt": "2026-10-31T15:00:00.000Z",
"Source": "retail-prices"
},
"Actual": {
"Status": "unavailable",
"Reason": "This kind has no actual cost to read.",
"Source": "cost-management"
}
}
}
}
}
}
One record's stats
GET /api/stats/{type}/{lookup}?cost=true
The per-node read the workspace's own windows and inspector use, on the api origin. It is not an /ox-api
route: the /ox-api door forwards /ox-api/stats only, so a key, a job or a connected AI reads one record's
cost with GET /ox-api/stats?kinds=DataStores&lookups=telemetry&cost=true instead.
{type}is the kind's stats type:iot,data-store,calz,provenanceordns-zonesfor the kinds that read a cost.cost=trueadds the record'sCostbeside itsHealth, read alongside the stats, the health and, when asked, the inventory. Asked the same way as on the workspace read.health=only&cost=trueanswers the health and the cost without reading the record's stats, which is how the Environment windows read their Cost section.- A kind that reads no cost answers no
Costkey.
The Cost shape
type Cost = {
Estimate: Figure; // a monthly estimate, at the provider's list price
Actual: ActualFigure; // what the record has cost so far
Projected?: Figure; // where the period is heading, when the actuator says
Suggestions?: Suggestion[]; // cheaper ways; absent never means "nothing to save"
RefusedSuggestions?: { Key: string; Code: string; Reason: string }[];
};
type Figure =
| {
Status: 'read';
Amount: number;
Currency: string;
ReadAt: string; // ISO time the figure (or its cached price) was read
Source: string; // where it came from, in the actuator's own words
Basis?: string | Basis;
}
| { Status: 'unavailable'; Reason: string; Source?: string };
type ActualFigure = Figure & {
UsageThrough?: string; // the last day the provider has usage for
Period?: { From: string; To: string; State: 'open' | 'closed'; ClosedReadAt?: string };
};
readorunavailable, nothing between. Areadfigure carries the number the provider gave; a real 0 stays aread0. Anunavailablefigure carries a plain reason and noAmountat all, so it can never be mistaken for a zero.Sourcenames where a figure came from. The Azure actuator saysretail-pricesfor an estimate andcost-managementfor an actual. Nothing in the shape names one provider.Basisis either plain words or what the estimate rests on:Lines(each priced line, below),FixedandUsage(the two parts of the total),Assumptions,NotCounted(sentences naming what the figure leaves out),Region,CurrencyNoteandPricesFrom(the oldest date among the prices used). A key is present only when the actuator said it: an absentNotCountednever means "nothing left out".- A line is
{ Label, Quantity, Unit, Tiers, Monthly, Hourly?, Fixed?, Amount?, FreeGrant? }: unit price (by tier) times quantity gives the monthly figure.Amount.Saidisyour amountorassumed. One malformed line drops the whole list, so a list is never shown short of its total. - An assumption is
{ Key, Label, Amount, Unit, Source }, whereSourceisdefault,person,measuredorhistory. One with any other source is dropped, never guessed. - A suggestion is
{ Key, Node?, Change, Saving?, Evidence, Proposal? }.Nodeis{ Kind, Lookup }; without it the suggestion is for the whole workspace.Savingis{ Amount, Currency, Basis }, and a saving without itsBasisis refused, never shown.Proposalis the change that applies it, when there is one. - A figure that comes back in a shape the api does not know reads as
unavailablewith "This figure was not read.", and the other figure still reads.
Why a figure is unavailable
An unavailable figure's Reason is one of these sentences, word for word. A reason about one priced line of
an estimate starts with that line's label (for example Hub: ...).
| Constant | Where it comes from | Sentence |
|---|---|---|
COST_REASONS.NotConnected | The stats reads: the workspace has no reader | Its cost could not be read: this workspace is not connected to the service that reads it. |
COST_REASONS.NotOffered | The stats reads: the reader predates cost | Its cost can't be estimated on this workspace yet. |
COST_REASONS.TooSlow | The stats reads: past the 15 second bound | Its cost took too long to read. Try again in a minute. |
COST_REASONS.Unreachable | The stats reads: any other failure | Its cost could not be read just now. Try again in a minute. |
COST_REASONS.Unreadable | The stats reads: the answer held no usable cost | Its cost could not be worked out from what came back. Try again in a minute. |
COST_NO_ESTIMATE | A kind with no estimate reader | This kind has no estimate to give. |
COST_NO_ACTUAL | A kind with no actual reader | This kind has no actual cost to read. |
COST_READ_FAILED | A figure's read failed | The cost could not be read just now. |
COST_NOT_LOCATED | The record's resources could not be found | The record's resources could not be located just now. |
COST_STILL_READING | A first read past 4.5 seconds; it keeps reading | Still reading this figure, ask again in a moment. |
COST_NO_MODEL | An estimate: the kind has no cost model | This kind has no cost model yet. |
COST_MODEL_EMPTY | An estimate: the model has no lines | This kind's cost model has no lines to price. |
COST_PRICE_NOT_READ | An estimate: a price could not be read | The price could not be read just now. |
COST_CURRENCIES_MIXED | An estimate: lines priced in two currencies | The lines are listed in different currencies, so they are not added together. |
The first five replace both figures at once; the rest are one figure's. An error's own text never reaches a reason.
Codes
| Code | Meaning |
|---|---|
ACT_NOT_SUPPORTED | The kind's actuator does not serve the cost read. The record gets no Cost key, as if its kind read none |
SAVING_MALFORMED | In RefusedSuggestions: the suggestion states a saving with no amount or currency |
SAVING_WITHOUT_BASIS | In RefusedSuggestions: the suggestion states a saving without the price it rests on |
Any other code an actuator answers is logged for an operator and the record's figures read unavailable with
COST_REASONS.Unreadable. The code is never shown.
Rights
- No right of its own today. Anyone who can read the stats can add
cost=true; the/ox-api/statsroute lists no required right. The workspace read keeps its answers per set of rights, so two callers with different rights never share one there. - Placeholder: a "See cost" right, and what an app's own identity may see, are planned and not built yet. This page will name them when they are.
Not built yet
- Placeholder: an estimate for a proposed node, before it is saved. Today a record is estimated once it is stored in the workspace (saved or deployed).
- Placeholder: actual costs. No kind reads one yet, so every
ActualisunavailablewithCOST_NO_ACTUAL's sentence.
The actuator cost contract
For anyone writing a kind. Cost is an optional actuator read, beside health and inventory. Any provider's actuator answers it in the same shape.
- Say it. The kind's capability manager answers
Cost: truefromDescribeActuatorReads()and has a record kind (its collection). That flag is the only list of kinds that read a cost. - Describe the model. For an estimate, the manager's
DescribeCostModelturns the stored record's settings into priced lines. A saved record that is not deployed yet is estimated from its settings, with no location and no credential. A paid meter is never defaulted to 0. - Serve the route. The actuator serves
<kind>/costbeside<kind>/health. The steward posts it once per collection, asking{ [Collection]: { [lookup]: {} } }, and the actuator answers each record as{ Known: true, Model: Cost }, or{ Known: false, Reason: CODE }(ACT_NOT_SUPPORTEDwhen it does not serve the read). - Answer in time. Each record has 6 seconds. The Azure actuator answers a figure it has kept at once, and
a first read past 4.5 seconds as
COST_STILL_READINGwhile the read finishes. - Never a zero for a figure not read. A figure that could not be read is
unavailablewith a fixed sentence; an error's text never leaves the actuator. - Suggestions are optional. Leave
Suggestionsout when the actuator has none to offer; an empty list says the provider itself found none. Each suggestion carries itsEvidence, and aSavingonly with itsBasis.