Docs menu

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.

QueryMeaning
kindsOnly these record kinds, by collection (for example IoT,DataStores). Absent: every active kind
lookupsOnly these record lookups. Absent: every record of each kind asked
metricstrue adds every kind's named metrics; a list of kinds adds them for those kinds only
costtrue 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.

StatusCodeWhen
400STATS_KIND_UNKNOWNA kind asked is not active here; the Remedy names the kinds that are
503STATS_UNAVAILABLEThe stats could not be read just now
503WORKSPACE_EAC_UNAVAILABLEThe 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: today IoT, DataStores, CALZ (the landing zone), Provenance and DNSZones. Every other record has no Cost key. No Cost key means "this kind reads no cost", never "it costs nothing".
  • A record that could not be read still carries a Cost, with both figures unavailable and 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, provenance or dns-zones for the kinds that read a cost.
  • cost=true adds the record's Cost beside its Health, read alongside the stats, the health and, when asked, the inventory. Asked the same way as on the workspace read.
  • health=only&cost=true answers 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 Cost key.

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 };
};
  • read or unavailable, nothing between. A read figure carries the number the provider gave; a real 0 stays a read 0. An unavailable figure carries a plain reason and no Amount at all, so it can never be mistaken for a zero.
  • Source names where a figure came from. The Azure actuator says retail-prices for an estimate and cost-management for an actual. Nothing in the shape names one provider.
  • Basis is either plain words or what the estimate rests on: Lines (each priced line, below), Fixed and Usage (the two parts of the total), Assumptions, NotCounted (sentences naming what the figure leaves out), Region, CurrencyNote and PricesFrom (the oldest date among the prices used). A key is present only when the actuator said it: an absent NotCounted never 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.Said is your amount or assumed. 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 }, where Source is default, person, measured or history. One with any other source is dropped, never guessed.
  • A suggestion is { Key, Node?, Change, Saving?, Evidence, Proposal? }. Node is { Kind, Lookup }; without it the suggestion is for the whole workspace. Saving is { Amount, Currency, Basis }, and a saving without its Basis is refused, never shown. Proposal is the change that applies it, when there is one.
  • A figure that comes back in a shape the api does not know reads as unavailable with "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: ...).

ConstantWhere it comes fromSentence
COST_REASONS.NotConnectedThe stats reads: the workspace has no readerIts cost could not be read: this workspace is not connected to the service that reads it.
COST_REASONS.NotOfferedThe stats reads: the reader predates costIts cost can't be estimated on this workspace yet.
COST_REASONS.TooSlowThe stats reads: past the 15 second boundIts cost took too long to read. Try again in a minute.
COST_REASONS.UnreachableThe stats reads: any other failureIts cost could not be read just now. Try again in a minute.
COST_REASONS.UnreadableThe stats reads: the answer held no usable costIts cost could not be worked out from what came back. Try again in a minute.
COST_NO_ESTIMATEA kind with no estimate readerThis kind has no estimate to give.
COST_NO_ACTUALA kind with no actual readerThis kind has no actual cost to read.
COST_READ_FAILEDA figure's read failedThe cost could not be read just now.
COST_NOT_LOCATEDThe record's resources could not be foundThe record's resources could not be located just now.
COST_STILL_READINGA first read past 4.5 seconds; it keeps readingStill reading this figure, ask again in a moment.
COST_NO_MODELAn estimate: the kind has no cost modelThis kind has no cost model yet.
COST_MODEL_EMPTYAn estimate: the model has no linesThis kind's cost model has no lines to price.
COST_PRICE_NOT_READAn estimate: a price could not be readThe price could not be read just now.
COST_CURRENCIES_MIXEDAn estimate: lines priced in two currenciesThe 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

CodeMeaning
ACT_NOT_SUPPORTEDThe kind's actuator does not serve the cost read. The record gets no Cost key, as if its kind read none
SAVING_MALFORMEDIn RefusedSuggestions: the suggestion states a saving with no amount or currency
SAVING_WITHOUT_BASISIn 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/stats route 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 Actual is unavailable with COST_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.

  1. Say it. The kind's capability manager answers Cost: true from DescribeActuatorReads() and has a record kind (its collection). That flag is the only list of kinds that read a cost.
  2. Describe the model. For an estimate, the manager's DescribeCostModel turns 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.
  3. Serve the route. The actuator serves <kind>/cost beside <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_SUPPORTED when it does not serve the read).
  4. 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_READING while the read finishes.
  5. Never a zero for a figure not read. A figure that could not be read is unavailable with a fixed sentence; an error's text never leaves the actuator.
  6. Suggestions are optional. Leave Suggestions out when the actuator has none to offer; an empty list says the provider itself found none. Each suggestion carries its Evidence, and a Saving only with its Basis.
On this page