> ## Documentation Index
> Fetch the complete documentation index at: https://muveya.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Management analytics

> Every management metric muveya computes, what each one means, and where to read it: console, public API, MCP or CSV export.

muveya computes a closed set of management metrics from your operational records: stock alerts, deliveries, orders, approvals, custody and consumption. This page defines each one in plain words and says where you can read it.

All of them share the same rules:

* **Read-only and live.** A metric is computed when you ask for it, from the current records. Nothing is cached or stored, except the CSV file of an export.
* **Deterministic.** The same records give the same figure. No AI computes or rewrites a figure.
* **Counts, quantities and durations only.** No metric carries a cost, an order value or a patient reference.
* **The same figure everywhere.** The console, the public API and MCP run the same calculation, so the same question gets the same number on every surface.
* **UTC.** Every date, window and bucket is in UTC.

## Where each capability lives

The console has one analytics screen today: [Consumption report](/docs/en/reports/consumption). Everything else is read through the public API with an API key or through MCP with OAuth. There is no console screen for the other metrics, for exports, or for creating API keys; to get a key, write to [team@muveya.com](mailto:team@muveya.com).

| Capability | Console | Public API | MCP tool |
| - | - | - | - |
| Operational metrics (8 ids) | No | `GET /v1/analytics/metrics/{metric}` | Not as a tool. The five snapshot counts are inside `management.briefing`. |
| Operational exceptions | No | `GET /v1/analytics/exceptions` | Inside `management.briefing` |
| Clinic comparison | No | `GET /v1/analytics/clinics/comparison` | `management.compare_clinics` |
| Consumption trend | **Reports** | `GET /v1/analytics/consumption-trend` | `analytics.consumption` |
| Approval backlog | No | `GET /v1/analytics/approval-backlog` | Not as a tool. Its bands are inside `management.briefing`. |
| Order cycle time | No | `GET /v1/analytics/cycle-time` | Not as a tool. Its stages are inside `management.briefing`. |
| Management briefing | No | `GET /v1/analytics/briefing` | `management.briefing` |
| CSV export of the briefing | No | `POST /v1/analytics/exports`, `GET /v1/analytics/exports/{exportId}` | No |

For the technical details of each surface, see [Exports](/docs/en/api-reference/exports) and [MCP tools](/docs/en/mcp/tools).

## Who can read them

| Surface | What you need |
| - | - |
| Console | The member permission `reports.read` (**View reports**). No role includes it; it is granted in **Team**. |
| Public API | An API key with the scope `analytics:read`. muveya translates it to `reports.read`. See [Scopes](/docs/en/api-reference/scopes). |
| MCP | An OAuth connection with `analytics:read`, subject to the member's `reports.read` permission. See [Connect](/docs/en/mcp/connect). |

`reports.read` is enough: a metric reads the underlying orders, deliveries and stock on your behalf only to count them, and never returns a field you could not read otherwise. An API key covers every clinic and warehouse of its dental clinic account. A console member limited to some warehouses sees consumption for those warehouses only.

## Fields the results share

Most results carry these fields. The exact shape of each operation is in the API reference.

| Field | Meaning |
| - | - |
| `metric` | The metric id, for example `LOW_STOCK` or `ORDER_CYCLE_TIME`. |
| `definition` | The definition in words, always in English. Present on every report except the exceptions list, and on the three pilot metrics. |
| `unit` | `count`, `percent`, `hours`, `orders`, or a unit of measure for consumption. |
| `asOf` | When the figure was computed. |
| `freshness` | Always `live`. |
| `timezone` | Always `UTC`. |
| `evidenceId` | A stable description of the metric and its filters, for example `CONSUMPTION_TREND:catalogItemId=...,granularity=week`. The same request always cites the same `evidenceId`, so you can reproduce and cite a figure. It never contains the time the figure was computed (`asOf`), but a `from` or `to` you send is part of it. |
| `truncated` | `true` when a scan reached its limit. The figure is then a lower bound, never a silent full result. |
| `filters` | The filters that were applied. |

A `value` of `null` means there was no sample to compute a percentage, median or average. It is never replaced by zero.

### Scan limits

| What is scanned | Limit | When the limit is reached |
| - | - | - |
| Open stock alerts | 500 alerts | `truncated: true` |
| Orders and deliveries | 10,000 records (200 per page, 50 pages), oldest first | `truncated: true`; the newest records are left out |
| Pending approvals for the backlog | 1,000 orders, oldest first | `truncated: true`; the newest pending orders are left out |
| Consumption movements | 5,000 movements, newest first | `truncated: true` and `coverage.coveredFrom` |

## Operational metrics

`GET /v1/analytics/metrics/{metric}` returns one scalar figure. `{metric}` must be one of these eight ids; any other value is refused with `400` and `common.invalid_request`.

### Snapshot counts

These describe the situation now. `catalogItemId` narrows the two stock counts to one supply.

| Id | Definition | Unit |
| - | - | - |
| `LOW_STOCK` | Number of low-stock alerts that are open right now (a supply below its minimum). | `count` |
| `EXPIRING_STOCK` | Number of expiry alerts that are open right now (boxes getting close to their expiration date). | `count` |
| `DELIVERY_EXCEPTIONS` | Number of deliveries currently in status `exception` (a delivery confirmed as failed). | `count` |
| `RECEIPT_DISCREPANCIES` | Number of deliveries currently in status `partially_fulfilled` (at least one box was disputed at receipt and the delivery is not closed yet). | `count` |
| `PENDING_ORDERS` | Number of orders currently in status `pending_approval`. | `count` |

### Pilot signals

These three add `numerator`, `denominator` and `sampleCount` where they apply, a `definition`, and machine-readable `caveats`.

| Id | Definition | Unit | Window | Caveats |
| - | - | - | - | - |
| `BOX_TRACEABILITY` | The share of active boxes whose location is identifiable: a known warehouse, a status and a latest ledger movement. `numerator` is the traceable boxes, `denominator` the active boxes. | `percent` | None. A snapshot of now; past weeks are not rebuilt. `catalogItemId` is ignored. | `snapshot_of_now_not_reconstructed_for_past_windows` |
| `APPROVAL_DECISION_MEDIAN_HOURS` | The median time from an order's submission to its first approval decision, over orders submitted in the window that received a human decision. Orders still waiting and orders approved automatically are not in the sample. | `hours` | `from` (inclusive) and `to` (exclusive), both optional ISO 8601 | `wall_clock_hours_no_business_calendar`, `orders_without_a_human_decision_excluded` |
| `DISTINCT_CUSTODY_ACTORS` | The share of handoffs received in the window whose delivery and receipt were confirmed by two different people. A delivery without both confirmations is not in the sample. | `percent` | `from` and `to`, as above, applied to the receipt time | `handoffs_without_both_confirmations_excluded` |

<Note>
  `wall_clock_hours_no_business_calendar` means nights, weekends and holidays count as hours. muveya does not apply a business-hours calendar yet.
</Note>

A window whose `from` is not before `to`, or a date that cannot be read, is refused with `400`. The result includes `period` only when you send both `from` and `to`.

## Operational exceptions

`GET /v1/analytics/exceptions` lists the problems a manager should act on, most urgent first. `catalogItemId` narrows the stock exceptions to one supply.

| Priority | Category (`metric`) | One entry per | Drill-down (`drillDownType`, `drillDownId`) | `reference` fields |
| - | - | - | - | - |
| 0 | `EXPIRING_STOCK` | open expiry alert | `movement` and the movement id, or `item` and the supply id when no movement triggered the alert | `alertId`, `catalogItemId`, and `boxId`, `warehouseId` when known |
| 1 | `DELIVERY_EXCEPTIONS` | delivery in `exception` | `order` and the order id | `orderId` |
| 2 | `LOW_STOCK` | open low-stock alert | as for `EXPIRING_STOCK` | as for `EXPIRING_STOCK` |
| 3 | `RECEIPT_DISCREPANCIES` | delivery in `partially_fulfilled` | `order` and the order id | `orderId` |
| 4 | `PENDING_ORDERS` | order in `pending_approval` | `order` and the order id | `orderId`, `clinicId` |

Each entry also has `priority`, a per-entry `evidenceId`, and, for stock alerts, `detectedAt`. Entries are sorted by priority, then by drill-down id. The report adds `total`.

To follow a drill-down with the same key:

* `order`: `GET /v1/orders/{orderId}` and `GET /v1/fulfillments/{orderId}`.
* `movement`: the movement appears in `GET /v1/inventory/boxes/{boxId}/movements`, using the `boxId` in `reference`.
* `item`: `GET /v1/catalog/items/{itemId}`.

Each of those operations needs its own scope. See [Scopes](/docs/en/api-reference/scopes).

## Clinic comparison

`GET /v1/analytics/clinics/comparison` (metric `ORDERS_BY_CLINIC`, unit `orders`) ranks the clinics of your account by order volume.

| Field per clinic | Meaning |
| - | - |
| `clinicId`, `clinicName`, `clinicStatus` | The clinic. Every clinic appears, even with zero orders. |
| `totalOrders` | Every order of the clinic, whatever its status, drafts included. |
| `pendingApprovalOrders` | The part of those orders in `pending_approval`. |

Clinics are ranked by `totalOrders`, most first, then by clinic id. Order values are never included.

## Consumption trend

`GET /v1/analytics/consumption-trend` (metric `CONSUMPTION_TREND`) is the calculation behind the console's [Consumption report](/docs/en/reports/consumption).

| Parameter | Default | Rule |
| - | - | - |
| `from` | 30 days before `to` | ISO 8601, inclusive |
| `to` | now | ISO 8601, exclusive |
| `granularity` | `day` | `day` or `week` (weeks start on Monday) |
| `catalogItemId` | all supplies | one supply |

The window must have `from` before `to` and span at most 366 days; otherwise `400`.

The result has one entry in `series` per supply and unit (`seriesKey` is `catalogItemId:unitOfMeasure`), with `totalConsumed`, `usedQuantity` (observed use), `issuedQuantity` (delivered to rooms that do not count their stock, an estimated consumption) and `movementCount`, plus `buckets` per day or week. `usedQuantity + issuedQuantity = totalConsumed`. There is never a total across supplies or units.

`coverage` states what the figures stand for: `measuredMovementCount`, `unverifiedMovementCount` (records from before the unit was verified, counted but not quantified, listed per supply in `unverifiedItems`), `warehouseScope` (`all` or `restricted`) and `coveredFrom` when the scan was cut. A `narration` is included only when it passes the check that every number in it equals the table.

## Approval backlog

`GET /v1/analytics/approval-backlog` (metric `APPROVAL_BACKLOG`, unit `orders`) counts the orders waiting for approval across the account, grouped by how long they have waited since submission.

| Band (`label`) | Waiting time | `maxAgeHours` |
| - | - | - |
| `within_24h` | less than 24 hours | `24` |
| `h24_to_72h` | 24 hours to less than 72 hours | `72` |
| `d3_to_7d` | 72 hours to less than 7 days | `168` |
| `over_7d` | 7 days or more | `null` |

It also returns `total` and `oldestSubmittedAt`. For the approver's own inbox, see [Order approvals](/docs/en/orders/approvals).

## Order cycle time

`GET /v1/analytics/cycle-time` (metric `ORDER_CYCLE_TIME`, unit `hours`) gives the average duration of each stage of an order's life, over the orders that reached that stage.

| Stage | From | To |
| - | - | - |
| `submit_to_dispatch` | order submitted | delivery dispatched |
| `dispatch_to_deliver` | dispatched | delivery confirmed |
| `deliver_to_receive` | delivery confirmed | receipt confirmed |
| `receive_to_close` | receipt confirmed | delivery closed |

Each stage has `averageHours` (two decimals, `null` when no order reached it) and `sampleCount`. `observedFulfillments` is the number of deliveries read. The average is not limited to a window. See [Delivery and receipt](/docs/en/deliveries/delivery-and-receipt).

## Management briefing

`GET /v1/analytics/briefing` (metric `MANAGEMENT_BRIEFING`) is a reproducible digest that puts the metrics above in one flat list of figures. `period` is `daily` (default) or `weekly`; any other value is `400`.

Each figure has `metric` (its source), `dimension` (the sub-key, absent for a single number), `label`, `value`, `unit` and the `evidenceId` of its source. The figures come in this order:

| Figures | `metric` | `dimension` | Label (English) | `unit` |
| - | - | - | - | - |
| Five snapshot counts | `LOW_STOCK`, `EXPIRING_STOCK`, `DELIVERY_EXCEPTIONS`, `RECEIPT_DISCREPANCIES`, `PENDING_ORDERS` | none | Low stock alerts, Expiring stock alerts, Delivery exceptions, Receipt discrepancies, Pending approvals | `count` |
| Four backlog bands | `APPROVAL_BACKLOG` | the band | Approval backlog: under 24 hours, 24 to 72 hours, 3 to 7 days, over 7 days | `orders` |
| Consumption records | `CONSUMPTION_TREND` | `movements` | Consumption movements | `movements` |
| Records without a verified unit | `CONSUMPTION_TREND` | `unverified_movements` | Consumption movements without a verified unit | `movements` |
| Up to five supplies, three figures each | `CONSUMPTION_TREND` | `seriesKey`, `seriesKey:use`, `seriesKey:issue` | the supply name; `name: observed use`; `name: delivered to rooms (estimated consumption)` | the supply's unit (for example `box`) |
| Four cycle stages | `ORDER_CYCLE_TIME` | the stage | Cycle time: submission to dispatch, dispatch to delivery, delivery to receipt, receipt to close | `hours` |

What `period` changes:

* The consumption part always covers the consumption metric's default window, the last 30 days. `daily` groups it by day and `weekly` by week. Because the briefing lists totals, the figures of a daily and a weekly briefing are currently the same; only the consumption `evidenceId` differs.
* The snapshot counts, the backlog and the cycle time are not narrowed to a day or a week.

Labels follow the reader's language: the `Accept-Language` header on the API (`en`, `es` or `pt`, English otherwise) and the `Accept-Language` header on MCP, with English as the fallback. Ids, dimensions, units, values and `evidenceId` never change with the language. A supply the catalog cannot name is labelled `Unknown supply`.

The briefing's own `evidenceId` is `MANAGEMENT_BRIEFING:period=daily` or `MANAGEMENT_BRIEFING:period=weekly`, and `truncated` is `true` if any of its sources was truncated. On MCP, `management.briefing` returns this briefing together with the operational exceptions.

## CSV exports

The briefing can be exported as a CSV file. Exports are requested and collected only through the public API. The full request and response shapes are in [Exports](/docs/en/api-reference/exports).

<Steps>
  <Step title="Request the export">
    `POST /v1/analytics/exports` with an optional `period` (`daily` by default, or `weekly`). The `Accept-Language` header of this request fixes the language of the CSV labels. The answer is `202` with an `exportId` and the job `status`.
  </Step>

  <Step title="Poll until it is ready">
    `GET /v1/analytics/exports/{exportId}` returns the current state. The file is built in the background, so the first answer is usually `pending`, although it can already be `ready`.
  </Step>

  <Step title="Download">
    When `status` is `ready`, the answer carries `downloadUrl` and `downloadExpiresAt`. The link works for 5 minutes. Each poll creates a fresh link, so poll again if it expired. The file is named `management-briefing-daily.csv` or `management-briefing-weekly.csv`.
  </Step>

  <Step title="Verify">
    Compare the SHA-256 of the downloaded bytes with `checksum`, and the size with `byteSize`.
  </Step>
</Steps>

### Export states

| `status` | Meaning |
| - | - |
| `pending` | Accepted, not built yet. |
| `building` | The file is being composed. |
| `ready` | The file is stored. `byteSize`, `checksum`, `downloadUrl` and `downloadExpiresAt` are present. |
| `failed` | The build failed. `error` holds a reason code with no sensitive data: `schedule_failed` (the job could not be queued; the request itself then fails with `500`, so request a new export), `processing_error` (a temporary fault; the build is retried automatically). |

### Rules

* **Expiry.** An export job is kept for 7 days. After that, `GET /v1/analytics/exports/{exportId}` answers `404`. A download link lasts 5 minutes and is never permanent.
* **Isolation.** An `exportId` only works with a key of the same dental clinic account. Any other id answers `404`, without saying whether it exists elsewhere.
* **Redaction.** The file holds counts, quantities, durations, labels and evidence ids only. No cost, order value or patient reference can be in it.
* **Format.** UTF-8 with a byte-order mark (so spreadsheets read accents correctly), CRLF line endings, and the columns `metric`, `dimension`, `label`, `value`, `unit`, `evidenceId`. A figure without a sample has an empty `value` cell, never `0`. A cell that starts with `=`, `+`, `-`, `@`, a tab or a carriage return is prefixed with a single quote, so a spreadsheet never runs it as a formula.
* **Audit.** Each request writes an `analytics.export` audit fact. A finished file writes `analytics.export_built` with its checksum and size, which outlives the 7-day job. A key without `analytics:read` is refused with `403` before the export is requested, and that refusal writes no audit fact.

## What can go wrong

| HTTP | `code` | Cause | What to do |
| - | - | - | - |
| `400` | `common.invalid_request` | Unknown metric id, unreadable or backwards window, consumption window over 366 days, unknown `granularity` or `period`. | Fix the parameter. |
| `401` | `api_keys.invalid` | Missing, invalid or revoked API key. | See [Authentication](/docs/en/api-reference/authentication). |
| `403` | `api_keys.scope_missing` | The key lacks `analytics:read`. | Ask [team@muveya.com](mailto:team@muveya.com) for a key with that scope. |
| `404` | `common.not_found` | Unknown or expired `exportId`. | Request a new export. |
| `429` | `common.too_many_requests` | Too many requests for the key. | See [Rate limits](/docs/en/api-reference/rate-limits). |

The full error format is in [Errors](/docs/en/api-reference/errors).

## Related pages

<CardGroup cols={2}>
  <Card title="Consumption report" icon="chart-column" href="/docs/en/reports/consumption">
    The console screen for consumption by supply.
  </Card>

  <Card title="Exports" icon="file-csv" href="/docs/en/api-reference/exports">
    Request and poll a briefing export.
  </Card>

  <Card title="MCP tools" icon="plug" href="/docs/en/mcp/tools">
    `analytics.consumption`, `management.briefing`, `management.compare_clinics`.
  </Card>

  <Card title="Replenishment and alerts" icon="bell" href="/docs/en/inventory/replenishment-and-alerts">
    Where the low-stock and expiry alerts come from.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.