# Ledgers and metrics

> Read any number in Continual from its ledger, list the records behind it, and define your own metrics through the API.

Every number in Continual is a sum of ledger entries. This page shows how to read a metric, read a
ledger directly, list the records behind a number, and define your own metrics. The examples use
`CONTINUAL_URL` and `CONTINUAL_API_KEY`, as [REST API](/docs/developers/api) sets up.

## Read a metric

List your organization's metrics, then read one over a period:

<CodeGroup>

```bash curl
curl "$CONTINUAL_URL/api/v1/metrics" -H "Authorization: Bearer $CONTINUAL_API_KEY"

curl -G "$CONTINUAL_URL/api/v1/metrics/<metric ID>:values" \
  -H "Authorization: Bearer $CONTINUAL_API_KEY" \
  --data-urlencode 'fromDate=2026-10-01' \
  --data-urlencode 'toDate=2026-10-31' \
  --data-urlencode 'grain=week' \
  --data-urlencode 'compareTo=previous'
```

```ts TypeScript
const query = new URLSearchParams({
  fromDate: "2026-10-01",
  toDate: "2026-10-31",
  grain: "week",
  compareTo: "previous",
})
const response = await fetch(
  `${process.env.CONTINUAL_URL}/api/v1/metrics/${metricId}:values?${query}`,
  { headers: { Authorization: `Bearer ${process.env.CONTINUAL_API_KEY}` } }
)
const { value, compared, series, groups } = await response.json()
```

</CodeGroup>

The answer for **Pipeline**, the first week of its series shown:

```json
{
  "currency": "USD",
  "value": "1203601.600000",
  "compared": "762658.800000",
  "sum": "1203601.600000",
  "per": null,
  "series": [
    {
      "startDate": "2026-09-28",
      "value": "837062.400000",
      "compared": "0",
      "sum": "837062.400000",
      "per": null
    }
  ],
  "groups": []
}
```

| Field      | What it holds                                                                          |
| ---------- | -------------------------------------------------------------------------------------- |
| `currency` | The currency money is in: your organization's, unless you name another; null otherwise |
| `value`    | The value for the period: a flow's total over it, or a balance at its end              |
| `compared` | The value of what you compared it with                                                 |
| `sum`      | What the value is made of: the sum, which a ratio divides                              |
| `per`      | What a ratio's sum is divided by; null for a plain sum                                 |
| `series`   | One value per day, week, or month, each with its `compared`, `sum`, and `per`          |
| `groups`   | One value per group, when you name `groupBy`                                           |

A sum of nothing is `"0"`; a ratio over nothing is `null`. Numbers are decimal strings, so they keep
every digit.

## What a read takes

`metrics.values` and a ledger's `balances` take the same inputs.

| Input       | What it says                                                                                    |
| ----------- | ----------------------------------------------------------------------------------------------- |
| `fromDate`  | The period's first day                                                                          |
| `toDate`    | The period's last day                                                                           |
| `scenario`  | `actual` unless named                                                                           |
| `asKnownAt` | Only what was recorded by this moment, to read a number as it was reported then                 |
| `where`     | Which entries, in the [filter language](/docs/developers/api#lists-filters-and-paging), as JSON |
| `groupBy`   | What to group by, as a JSON array                                                               |

A metric's values also take `grain` (`day`, `week`, or `month`), `compareTo` (`previous`,
`lastYear`, or a scenario such as `budget`), and `currency`.

`where` and `groupBy` name a ledger's dimensions, such as `stage` or `owner`, or a field of the
record a dimension names, such as `customer.name` or `account.type`. A record's field is read as the
record is now. `groupBy` also takes `book`, `currency`, `unit`, `actor`, `record`, `day`, `week`, and
`month`. A flow can be grouped by `actor`; a balance cannot.

Pipeline by customer name:

```bash
curl -G "$CONTINUAL_URL/api/v1/metrics/<metric ID>:values" \
  -H "Authorization: Bearer $CONTINUAL_API_KEY" \
  --data-urlencode 'fromDate=2026-10-01' \
  --data-urlencode 'toDate=2026-10-31' \
  --data-urlencode 'groupBy=["customer.name"]'
```

## List the records behind a number

Group by `record` to list the records a value is made of, each with its share. The deals won in
October, by **Bookings**:

```bash
curl -G "$CONTINUAL_URL/api/v1/metrics/<metric ID>:values" \
  -H "Authorization: Bearer $CONTINUAL_API_KEY" \
  --data-urlencode 'fromDate=2026-10-01' \
  --data-urlencode 'toDate=2026-10-31' \
  --data-urlencode 'groupBy=["record"]'
```

Each group's `record` is a record ID. Read the records with `records.batchGet`
(`POST /api/v1/records:batchGet`).

## Read a ledger

Each ledger has two Queries at its name: `balances`, which sums its entries for each group with
every measure, and `entries`, which lists the entries of one record.

| Ledger                   | What it keeps                                            |
| ------------------------ | -------------------------------------------------------- |
| `finance.generalLedger`  | What invoices, payments, and journal entries post        |
| `sales.dealLifecycle`    | Deals moving between stages, with their amounts          |
| `projects.taskLifecycle` | Tasks moving between statuses                            |
| `<record type>Lifecycle` | Every record type with a status, such as `todoLifecycle` |

Deals by stage at the end of October:

```bash
curl -G "$CONTINUAL_URL/api/v1/sales.dealLifecycle:balances" \
  -H "Authorization: Bearer $CONTINUAL_API_KEY" \
  --data-urlencode 'toDate=2026-10-31' \
  --data-urlencode 'groupBy=["stage"]'
```

```json
{
  "rows": [
    {
      "group": { "stage": "lost" },
      "bookAmount": "0",
      "amount": "45600.00",
      "quantity": null,
      "measures": {
        "count": "3",
        "arrivals": "3",
        "entered": "5372890200.000"
      },
      "entries": 3
    }
  ]
}
```

A lifecycle ledger's measures:

| Measure      | What it is                                                                          |
| ------------ | ----------------------------------------------------------------------------------- |
| `count`      | How many records are in each state                                                  |
| `arrivals`   | How many entered a state                                                            |
| `departures` | How many left a state                                                               |
| `duration`   | The seconds each spent in a state, posted as it leaves                              |
| `entered`    | The sum of the times, in seconds since 1970, that the records in a state entered it |

A metric reads `age`, how long the records in a state have been there, from `count` and `entered`.

The entries of one record, such as a deal:

```bash
curl -G "$CONTINUAL_URL/api/v1/sales.dealLifecycle:entries" \
  -H "Authorization: Bearer $CONTINUAL_API_KEY" \
  --data-urlencode 'record=<deal ID>'
```

Each entry has its `accountingDate`, its `effectiveAt` and `recordedAt` times, its `actor`, its
`dimensions`, and its amounts and measures. Entries are only ever added: a correction posts an entry
that reverses another, named in its `corrects`.

## Define your own metric

A metric is a record. Create one with `metrics.create`. This one counts the deals lost in a period:

```bash
curl -X POST "$CONTINUAL_URL/api/v1/metrics" \
  -H "Authorization: Bearer $CONTINUAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Deals lost",
    "unit": "count",
    "direction": "lower",
    "sum": {
      "kind": "flow",
      "ledger": "sales.dealLifecycle",
      "measure": "arrivals",
      "where": { "field": "stage", "operator": "eq", "value": "lost" }
    },
    "requestId": "create-deals-lost"
  }'
```

| Field       | What it says                                                               |
| ----------- | -------------------------------------------------------------------------- |
| `sum`       | What it adds up: a ledger, a measure, and which entries                    |
| `per`       | What the sum is divided by, for a ratio; leave it out for a plain sum      |
| `unit`      | `count`, `money`, `percent` (the ratio times 100), or `duration` (seconds) |
| `direction` | `higher` or `lower`: which way is good                                     |

A sum's `kind` is `flow`, for the entries dated in a period, or `balance`, for every entry through
its end. Its `measure` is `amount`, `bookAmount`, `quantity`, or one of the ledger's measures. Set
`normal` to `credit` to read credits as positive, as revenue and liabilities are. A new metric reads
its ledger's whole history at once.

Revenue, from every account whose type is revenue in the general ledger:

```json
{
  "kind": "flow",
  "ledger": "finance.generalLedger",
  "measure": "bookAmount",
  "where": { "field": "account.type", "operator": "eq", "value": "revenue" },
  "normal": "credit"
}
```

The metrics your apps ship are records too, with a `key` such as `sales.winRate`. Installing a new
version of an app updates them.

<Cards>
  <Card title="Metrics and ledgers" href="/docs/concepts/metrics" icon="book">
    How Continual's numbers work, in the app.
  </Card>
  <Card title="REST API" href="/docs/developers/api" icon="code">
    Authentication, routes, filters, and errors.
  </Card>
</Cards>
