---
title: "The metric dictionary"
method: GET
path: "/api/v1/metrics"
tags: ["metrics"]
---

# The metric dictionary

`GET /api/v1/metrics`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/metrics`
- Auth: HTTP bearer

Every metric and dimension the platform defines, each with one sentence saying what it is, how it rolls up, the grain it is held at, the record it is read from, and the day it entered the dictionary.

One dictionary, four readers: the pages, the trade reports, the warehouse feed and Ask Tightly all resolve a figure through it, so a definition cannot mean one thing on a report head and another in a model. A tenant may rename a metric and may define its own; no tenant may redefine a platform one, which is why this read is the same for every organisation.

A key that cannot be served yet is LISTED with the reason rather than left out. A reader who cannot find a key concludes Tightly does not hold the idea; a reader who finds it with "landed cost needs a cost layer per receipt, which nothing has written yet" knows what they are waiting for.

Feed a `key` from here into `run_metric_query` to get the figure itself.

Scope: `metrics:read`.

## Headers

- `Tightly-Version` string

## Response `200`

The dictionary

- object
  - `data` object
    - `domains` object[]
      - `data_source` string
      - `domain` string
      - `grain` string
      - `grains` string[]
      - `label` string
      - `supports_time_range` boolean
    - `metrics` object[]
      - `data_source` string[]
      - `definition` string
      - `domains` object[]
        - `data_source` string
        - `domain` string
        - `grain` string
        - `label` string
        - `served` boolean
        - `unsupported_reason` string, nullable
      - `format` string
      - `formula` string: How the figure combines when rows are added together. Measures only.
      - `key` string
      - `kind` 'measure' | 'dimension'
      - `label` string
      - `polarity` string: Which way is good. Measures only.
      - `served` boolean
      - `since` string
      - `synonyms` string[]

## Other responses

- `401`: No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
- `403`: The key may not reach this operation. `scope_missing` when the key does not hold Metrics; `organization_mismatch` when the path names an organisation that is not the key's; `ip_not_allowed` when the caller's address is outside the key's allowlist.
- `429`: The key has spent its allowance for this minute. 600 reads and 120 writes a minute, counted per key, refused calls included. `Retry-After` says how long to wait.
- `503`: The database is briefly out of reach, which is what a failover or a full connection pool looks like from outside. Wait the seconds `Retry-After` names and send the request again. A read is safe to repeat. A write is not, because the connection was lost in the middle of it, so read the object back before sending it a second time. Branch on the status rather than on `code`, which is `POSTGRES_UNAVAILABLE` from one of the two seams that raise this and `SERVICE_UNAVAILABLE` from the other.

---

[API](https://skmtc.dev/tightly/apis/tightly-api.md) · [All operations](https://skmtc.dev/tightly/apis/tightly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tightly/apis/tightly-api/revisions/199f7eb35afb?raw)
