> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runledger.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runledger.co/_mcp/server.

# Catalog, cost models and overrides

This guide covers the first half of pricing from cost: where provider prices come from, how a cost model records what you pay, and how your own rates win over the catalog.

> **Note**
>
> Every amount in the examples is illustrative, and so are the providers (Vidlab, Textco) and the catalog entries shown: none is a real provider price.

## Before you start

* You know the [parts of pricing from cost](/concepts/cost-reflective-pricing#the-pieces) and who controls each variable.
* You have a [sandbox key](/quickstart#get-a-sandbox-key) with `config:read` to browse the catalog and `config:write` to create cost models.

## The provider price catalog

The catalog is a list of provider models and their prices that Runledger maintains. It is read-only: you can't add to it or change it. It is the same in the sandbox and in live. Reading it needs `config:read`.

| To                                                                                    | Call                                                        |
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| List the providers                                                                    | `GET /v1/catalog/providers`                                 |
| Find a model by provider, kind or unit                                                | `GET /v1/catalog/models?provider=vidlab&unit=video_seconds` |
| Read one model with the price version in force                                        | `GET /v1/catalog/models/{catalog_model}`                    |
| Read price versions, including scheduled ones, and what changed since you last looked | `GET /v1/catalog/prices?changed_since=2026-10-01T00:00:00Z` |

A catalog model declares what it is metered in, with who controls each unit and dimension, as a cost model does. This is an invented entry:

```json
{
  "id": "catm_5xT7qD2mKa",
  "object": "catalog_model",
  "provider": "vidlab",
  "model": "vidlab-v2",
  "name": "Vidlab v2 video",
  "kind": "video",
  "status": "active",
  "units": [{ "unit": "video_seconds", "controlled_by": "customer" }],
  "dimensions": [
    { "key": "resolution", "controlled_by": "customer", "values": ["720p", "1080p"] }
  ],
  "current_price": "catp_4dK2mZ8vQw",
  "current_price_version": 1,
  "last_verified_at": "2026-10-24T09:00:00Z",
  "updated_at": "2026-10-20T04:12:00Z",
  "price": {
    "id": "catp_4dK2mZ8vQw",
    "object": "catalog_price",
    "catalog_model": "catm_5xT7qD2mKa",
    "version": 1,
    "status": "active",
    "currency": "usd",
    "rates": [
      { "unit": "video_seconds", "unit_amount_decimal": "6", "dimensions": { "resolution": "720p" } },
      { "unit": "video_seconds", "unit_amount_decimal": "12", "dimensions": { "resolution": "1080p" } }
    ],
    "effective_from": "2026-10-20T04:12:00Z",
    "as_of": "2026-10-20",
    "source_url": "https://vidlab.example/pricing",
    "source_reference": "Vidlab pricing page, read 20 October 2026",
    "published_at": "2026-10-20T04:12:00Z",
    "last_verified_at": "2026-10-24T09:00:00Z"
  }
}
```

Amounts are decimal strings in minor units of the price's `currency`, as everywhere else in the API: `"12"` is 12 US cents for each second of 1080p video. The units, dimensions, per and tiers work as [described below](#cost-models-and-rate-cards).

**What every entry carries.** A source (`source_url` and `source_reference`), the date it takes effect (`effective_from`), the date Runledger read it (`as_of`), and when Runledger last checked it against its source (`last_verified_at`). A published price version never changes. A price change, or a correction, is a new version with its own `effective_from`, never earlier than the day it is published, and a `changes` list of what differs from the version before. A model the provider has retired becomes `deprecated`: cost models that use it keep working with the last version, and no new versions are published.

**Freshness target.** Runledger commits to these targets for the providers and models the catalog covers:

| What happens                        | Target                                                                                                                                                                                                                                                                                                                                                                                     |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| A provider publishes a price change | The catalog has a new price version for it within 1 business day of the provider publishing it. The version carries the provider's own `effective_from` when that date is still ahead, so a change announced for next month is in the catalog, scheduled, long before it applies. A change already in effect takes effect when Runledger publishes the version, never from an earlier date |
| A covered provider adds a model     | The model is in the catalog within 5 business days                                                                                                                                                                                                                                                                                                                                         |
| Any entry                           | Carries `source_url`, `effective_from` and `last_verified_at`, so you can see where a price came from and how recently it was checked; [Data sources and licences](/reference/data-sources-and-licences) says how the checking works                                                                                                                                                       |

A business day is Monday to Friday in Sydney time (Australia/Sydney). There is no public holiday calendar, so a public holiday counts as a business day. The clock starts when the provider publishes the change or the model, not when Runledger notices it, and `published_at` on a catalog price shows when Runledger published the version.

These are freshness targets, a commitment about how quickly the catalog follows providers. They are not a guarantee that a price is correct, and there is no service-credit SLA: missing a target gives no automatic credit or refund. Three things stay true however fresh the catalog is:

* **Your overrides always take precedence.** An [override](#your-own-rates-override-the-catalog) beats the catalog price on every line it matches, so a negotiated rate is never replaced by a catalog update.
* **Recorded usage is never repriced.** A new version applies from its `effective_from` onward, and a correction takes effect from the date it is published. Costs and events already recorded keep the version that priced them. A rate card you add to a custom cost model must start after every earlier version and can't start in the past.
* **The invoice and the contract are what you pay.** The catalog says what the source said on the `as_of` date. Where your bill differs, fix your numbers with an override.

**What it covers.** A stated set of providers and models, not every provider. `GET /v1/catalog/providers` lists the providers covered and how many models each has, and `GET /v1/catalog/models` lists the models. Those lists are the statement of coverage: the targets above apply to what is listed, and a provider or model that isn't listed isn't covered and needs a custom cost model. Coverage includes units other than tokens (images, video seconds, audio seconds, GPU or session time) where providers publish a price. When `trace()` names a catalog model you haven't set up, the call is priced from the catalog's list price (`priced_by: price_table`), without overrides, and carries the warning `catalog_model_available`, which names the model to adopt so your own rates apply.

## Cost models and rate cards

A cost model names a provider and model the way `trace()` does, declares its units and dimensions, and carries rate cards. It gets all of that in one of two ways, shown by its `source`:

* **`catalog`.** You send `catalog_model`, and Runledger takes the provider, model, kind, units, dimensions and rates from the catalog. The model follows the catalog's price versions: each one becomes a rate card of the cost model, with the same rates and `effective_from`.
* **`custom`.** You declare the model and enter the rate cards yourself, for a model the catalog doesn't have or rates the catalog can't know.

```json
{
  "key": "vidlab-v2",
  "name": "Vidlab v2 video",
  "catalog_model": "catm_5xT7qD2mKa"
}
```

That is the whole setup for a catalog model. The response has `source: "catalog"`, the provider and model, and the active rate card. A custom cost model looks like the catalog entry above with a `rate_card` of its own, here for a self-hosted model that is billed by GPU second:

```json
{
  "key": "selfhost-llm",
  "name": "Self-hosted LLM, GPU time",
  "provider": "selfhost",
  "model": "llm-70b",
  "kind": "compute",
  "units": [{ "unit": "gpu_seconds", "controlled_by": "provider" }],
  "rate_card": {
    "currency": "usd",
    "as_of": "2026-10-20",
    "source_reference": "Cluster cost for October 2026 divided by GPU seconds used",
    "rates": [
      { "unit": "gpu_seconds", "unit_amount_decimal": "0.35" }
    ]
  }
}
```

`"0.35"` is 0.35 of a cent for each GPU second. A custom cost model's rate cards are yours to add (`POST /v1/rate_cards`). A catalog cost model refuses them with `409`: its rates come from the catalog, and your changes go in `overrides`.

**Units.** A unit is a plural name for what you count. The names to use are `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens`, `reasoning_tokens`, `images`, `video_seconds`, `audio_seconds`, `gpu_seconds`, `session_hours`, `requests` and `searches`. Any other plural works, such as `tool_calls`.

**Choosing a rate.** A usage line has a unit, a quantity and dimension values. Runledger picks the rate for that unit whose dimensions all match the line. A rate that leaves a dimension out matches any value, and the most specific match wins. A line with no matching rate is recorded without a cost and with the warning `rate_not_found`, so a gap in the catalog shows up instead of a zero.

**Per and tiers.** Providers quote token prices per million, so a rate has a `per`: `"per": "1000000"` with `"unit_amount_decimal": "300"` is 300 cents per million tokens. A rate can have `tiers` instead of one amount. In `graduated` mode each band's price covers the quantity inside it. In `volume` mode the band the whole quantity falls in prices all of it, which fits a provider that charges more for every token of a long prompt:

```json
{
  "unit": "input_tokens",
  "per": "1000000",
  "tier_mode": "volume",
  "tiers": [
    { "up_to": "200000", "unit_amount_decimal": "300" },
    { "unit_amount_decimal": "600" }
  ]
}
```

A tier applies to one call's quantity. Tiers across a customer's usage over a month aren't supported.

## Your own rates override the catalog

Some customers, especially larger ones, don't pay list price. An override is your own rate for a catalog cost model, and it always wins over the catalog:

```json
{
  "key": "textco-large",
  "name": "Textco Large",
  "catalog_model": "catm_2hK8vP5nRc",
  "overrides": [
    {
      "key": "textco_negotiated",
      "discount_percent": "0.2",
      "category": "negotiated_discount",
      "effective_to": "2026-12-31T23:59:59Z",
      "reason": "Negotiated 20% off to 31 December 2026"
    },
    {
      "key": "textco_private_output",
      "unit": "output_tokens",
      "unit_amount_decimal": "1100",
      "per": "1000000",
      "category": "private_deployment",
      "effective_from": "2026-11-01T00:00:00Z",
      "reason": "Provisioned deployment, order ref PO-1187"
    }
  ]
}
```

* **`discount_percent`** takes that share off the catalog rate of every matching line, tiers included. `"0.2"` is 20% off.
* **`unit_amount_decimal`** replaces the catalog rate for a unit, so it covers a private or provisioned deployment, a committed-spend rate you already know, and a unit the catalog model doesn't list, which is how you extend a catalog model.
* **`unit`, `dimensions`, `effective_from` and `effective_to`** say where and when it applies. With none of them it applies to every line of the model from the moment it is saved. `category` is a label for reporting, and `reason` goes in the audit log.

**Precedence.** For each usage line, at the line's own timestamp:

1. An override in force that matches the line's unit and dimensions. If several match, the most specific wins: one that names a unit and dimensions beats one that names only a unit, which beats one that names neither. Two equally specific overrides whose dates overlap are a validation error, refused with `400` and `config_invalid`, so there is never a tie to guess at.
2. Otherwise the catalog price version in force.
3. Otherwise no rate: the line is recorded without a cost and with `rate_not_found`.

A custom cost model has no catalog layer: its own rate card in force is used, and it takes no overrides.

The cost on a line says which one priced it, so a cost can be traced to the rate behind it (see [cost attribution](/guides/cost-attribution-and-margin#cost-attribution-with-trace-and-track)). Say the catalog lists Textco Large at 300 cents per million input tokens (up to 200,000 tokens) and 1,500 per million output tokens. A call with 1,840 input tokens and 512 output tokens costs 0.552 + 0.768 = 1.32 at list price, and 1.32 × 0.8 = 1.056 with the first override above.

Overrides record the rate you pay. They don't track spend against a commitment, so a rate that changes when you pass a spend threshold is entered as the new rate from the date you pass it. Overrides change only costs from their `effective_from`: nothing already recorded is repriced.

## Next steps

* [Price rules and margin guardrails](/guides/price-rules-and-guardrails) turn a cost model into a price and a margin floor.
* [Cost attribution and margin](/guides/cost-attribution-and-margin) records what each call costs, so the margin is real.
* [Provider price changes and simulation](/guides/price-changes-and-simulation) covers what happens when a catalog price moves.