# Data model

import { Mermaid } from "zudoku/mermaid";

A subscription is three layers: the **timeline** records what was agreed, the
**engine** computes what to bill, and the **ledger** records what was charged.

<Mermaid chart={`flowchart TD
  T["Timeline — segments<br/>(what was agreed, immutable)"] --> E["Billing engine<br/>(pure computation)"]
  E --> L["Ledger<br/>(append-only — what was charged)"]
`} />

## One chain, many versions

The `id` returned at creation is the **chain id** — fixed for the life of the
subscription. Each structural change (plan replacement, direction or cadence
change, scheduled cancellation) appends a **new version** to the chain rather
than mutating the current one, so the billing history stays immutable.

<Mermaid chart={`flowchart LR
  id["stable id (chain)"]
  id --> v1["v1 · Starter"]
  v1 --> v2["v2 · Pro (replace plan)"]
  v2 --> v3["v3 · Pro, arrears (direction change)"]
`} />

You never address a version directly. `GET /v1/subscriptions/{id}` takes the
chain id and returns whatever version runs today; its `planId` and `status`
reflect that version, even after several changes.

## Timeline segments

Within a version, the timeline is a sequence of **segments** — immutable rows,
each covering a date range and the products billed in it. A mid-cycle change
doesn't edit a segment; it **splits** the timeline into a new segment, which is
how [proration](/guides/subscriptions/proration) is expressed (full new charge
minus what was already billed), not as a delta calculation.

Each segment's products feed a **billing schedule** — one per `(subscription,
priceId)` — which the engine reads to decide when the next charge lands (see
[Billing & cadence](/guides/subscriptions/billing-schedules)).

## `versionId` is for diagnostics only

Webhook payloads carry a `versionId` alongside the chain `subscriptionId`. It
identifies the internal version and is useful for support, but it is **not** a
reconciliation key — it changes on every structural change. Always reconcile on
`subscriptionId` (or `partnerReferenceId`), never `versionId`.
