# BigBooks API — Integrator Guide

Conventions that apply across every `/v1/**` endpoint. The OpenAPI spec is served at
`/v3/api-docs` (Swagger UI at `/swagger-ui/index.html`).

## Glossary

The domain model is double-entry accounting; these map its vocabulary to what a
personal-finance developer expects:

- **Party** — a person, household, client, or organization; the tenant boundary. "Whose data
  is this" is always a party, and parties can contain member parties (a household's people, an
  advisor's clients).
- **Journal** — a transaction: one purchase, transfer, or paycheck. Its entries balance to zero.
- **Entry** — one leg of a journal: the amount posted to a single account.
- **Account** — a ledger account. Bank and credit-card accounts (usually Plaid-linked), but also
  categories — income and expense categories are accounts too, arranged in trees.
- **Balance sheet** — assets minus liabilities: net worth.
- **`unit_type`** — the currency every aggregate figure in a response is presented in. Sent as the
  `unit_type` query parameter or the `X-Unit-Type` header (the parameter wins); default `USD`.
  The header is read on every request, so setting it once on your HTTP client converts every
  aggregate without per-call work; the spec declares it on each operation that takes the parameter.
  It never filters: rows keep their account's native currency and say so.
- **Perspective** — `SINGLETON` returns the acting party's own data; `COMPOSITE` combines the
  party with its member parties.
- **Plaid item** — one linked financial-institution connection. *Rebuild* empties it and
  re-imports from the institution. *Refresh* asks Plaid to pull its transactions now rather
  than on Plaid's schedule; the data still arrives through the webhook, so nothing on the
  item changes and the call takes no `If-Match`. A `409` means the item's state blocks the
  pull — `ITEM_LOGIN_REQUIRED` means the user must relink it.
- **Clean** — removes an account's imported transaction data (async, `202`).
- **Archive** — the soft delete behind every `DELETE`; archived rows surface on the matching
  `/deleted` endpoint.
- **Tag** — a free-form label on journals.

## Authentication

Every endpoint except the heartbeat, swagger, `.well-known` MCP discovery, this guide, and the
Plaid webhook requires `Authorization: Bearer <JWT>` issued by the BigBooks authorization server.
(The Plaid webhook is not in the spec: BigBooks registers it on every Link token it mints, Plaid
calls it with a signed `Plaid-Verification` header, and clients never call or configure it.)
The token subject is either a user↔client membership (user tokens) or a registered client
(client-credential tokens); some endpoints accept only one kind — the spec notes which. OAuth2
scopes are not enforced; authorization is ownership-based inside each operation.

### Getting credentials

There is no dynamic client registration endpoint. Sign in at the authorization server and create
an OAuth client at `{issuer}/clients` — new clients are **public**
(`token_endpoint_auth_method: none`) with PKCE required by default; register your redirect URIs on
the client. Confidential clients (client-credential grant) are supported for server-to-server use.

### Bootstrap: finding your party

Access tokens carry no party claim. After obtaining a token, call `GET {issuer}/oauth2/userInfo`;
the `bigbooks:party` claim is your own party's UUID — use it as `X-Acting-Party-ID`. Note that
`GET /v1/parties` is a lookup, not a listing: it requires an `id` or `email` query parameter and
returns `400` without one.

## Tenancy: `X-Acting-Party-ID`

List and report endpoints operate on an *acting party* (the tenant being viewed) selected by the
`X-Acting-Party-ID: <party uuid>` header. The server verifies the caller's party hierarchy actually
contains that party. Item-level endpoints (`…/{uuid}`) derive tenancy from the addressed resource and do not take the header. On the account list endpoints (`/v1/accounts`, `/v1/accounts/names`,
`/v1/accounts/deleted`) the header is optional and defaults to the caller's own party.

Endpoints that accept `perspective=SINGLETON|COMPOSITE` return either the acting party's own data
or its member parties' data combined. `page_size`/`page_number` always window the returned rows;
member enumeration is not paged.

Investment holdings (`GET /v1/investing/holdings`) take `perspective` too. A brokerage linked through
Plaid keeps its accounts on a hidden institution party beneath the party that linked it, and
`SINGLETON` counts those holdings as the acting party's own. Each holding carries `accountId` and
`partyId` (the account's party, which is that institution party for a linked brokerage), so a
`COMPOSITE` answer can be grouped by account or by party.

## Concurrency: ETag / If-Match

GET (and create) responses for versioned resources carry the entity version as a standard quoted
ETag header (for example `ETag: "3"`), also mirrored in the body's `version` field (for a journal
response, the first journal's). Every mutation
of a versioned resource requires `If-Match` echoing that value — quoted (`"3"`), weak (`W/"3"`), or
bare (`3`) forms are all accepted. Missing header → `400` (`missing_header`); stale version →
`409` (`conflict`): re-GET the resource and retry. The spec declares the `ETag` response header on
every operation that sends one, and says when it is absent. (The image download's ETag is the image id,
a cache validator, not a version.)

Stopping a conversation's turn (`POST /v1/ai/conversations/{uuid}/stop`) takes no `If-Match` although
it bumps the version: it signals a turn already in flight, whose own writes keep moving the version, and
repeating it is harmless. Use the ETag it returns for the next send. The spec declares `If-Match` on
exactly the operations that require it.

A party's planning assumptions (`PUT /v1/planning/assumptions`) have no version until the party
first saves its own, so that first save takes no `If-Match`. Every later save echoes the ETag the
last one returned; a save without it once the party has assumptions of its own is a `409`, since
the client last saw none.

## Errors

Error bodies have one shape:

```json
{ "errors": ["human-readable message", "..."], "code": "not_found" }
```

Every operation in the spec declares this shape as its `default` response. Switch on `code`, not
the message text. Codes in use: `bad_request`, `validation_failed`, `type_mismatch`,
`missing_parameter`, `missing_header`, `missing_credentials`, `malformed_request`,
`forbidden_property`, `payload_too_large`, `method_not_allowed`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `in_use`,
`last_manager`, `app_connection`, `payment_required`, `too_many_requests`, `timeout`, `upstream_error`,
`internal_error`. The spec lists them as the enum of `ExceptionResponse.code`; new codes may be added, so
handle an unknown one by its HTTP status. `missing_credentials` is a `400`
from a Plaid operation when the app has no Plaid credentials stored and sent none. Four codes are `409`s:
`conflict` is a stale `If-Match` that a re-read cures; `in_use` a resource something still refers to (a
planning person a scenario is dated by, a party with its own user, Plaid items or a member it alone
manages), which must be changed first; `last_manager` an edge whose cut would orphan the member party,
which is archived instead; `app_connection` a membership an app connection made, which the app ends with
`DELETE /v1/party/{uuid}/connection` (or the user disconnects the app). Two exceptions: a missing/invalid bearer token is rejected by the
security filter with an empty-body `401` + `WWW-Authenticate` header, and unknown routes return a
body-less `404`.

`401` means your token is missing/expired; `403` means the token is fine but the resource belongs
to a party outside your hierarchy.

## Pagination

Query params `page_size` (1–2048) and `page_number` (**zero-based**, despite the name) are required
on paged lists. Responses carry a `paging` object: `pageSize`, `pageIndex`, `totalPages`,
`totalElements`, `nextPage`, `previousPage`. The `/deleted` sync endpoints historically used
`page`/`list` field names; they now also emit the canonical `paging`/`deletions` — prefer those.

## Parameters and types

- Query params are `snake_case`; JSON bodies and responses are `camelCase`.
- Multi-value params accept both repetition (`?include=a&include=b`) and commas (`?include=a,b`).
- Enum values are case-insensitive in query params and bodies.
- Business dates are ISO dates (`2026-08-05`); audit/sync timestamps are ISO offset date-times (`2026-08-05T12:00:00Z`). `afterDate`/`beforeDate` on `/updated` and `/deleted` endpoints are
  timestamps; on report endpoints they are dates — the spec marks each.
- Money is a JSON number (decimal). Per-row amounts (entries, accounts, holdings) are in the
  account's own currency, labelled by its `unitType`. Aggregates (balance sheets, totals, cash
  flows) are converted into the request's `unit_type` and carry `unitType`, the `rates` applied
  (rate, date, source), and `unconvertedUnitTypes` for currencies with no stored rate — those are
  reported in their native subtotals, never guessed or dropped. An unknown code is a `400`.
  Two carve-outs: `/v1/entries*` returns register legs, and a journal posts a leg to two accounts,
  so those responses leave `convertedTotal` and `entrySetTotals` null rather than publish a
  double-counted sum — take totals from `/v1/journals` or `/v1/accounts/{uuid}/register`. And
  budget rows are per account: add them up with `budgetPeriodTotals` on
  `/v1/budgeting/actuals/{time_period}` and `/v1/budgeting/estimates/{time_period}`, never by
  summing `budgetPeriods[].amount` yourself.
- Request bodies: a property the spec does not mark `required` may be left out, and takes the default
  its description gives.
- Public identifiers are UUIDs, returned as `id` in responses.
- `include` expands optional response content. Values by resource: accounts — `balance`,
  `parent_accounts`, `children_accounts`; parties — `member_parties`, `manager_parties`;
  conversations — `conversation_messages`.

## Signs

Every per-account amount — a balance, a projected point, an upcoming item, a ledger event, an
attribution total — is in the **account's own sign convention**: positive grows the account's normal
balance. An asset's balance is what it holds and a liability's is what is owed, so both are positive
in the ordinary case; a negative asset balance is an overdraft, a negative liability balance a credit.
Likewise an expense category's spend and a revenue category's income are positive. So a paycheck into
checking is `+`, a bill paid from checking `-`, a charge on a card `+` (more owed), and a loan
payment `-` on the loan by its principal alone (interest and escrow are reported beside it). Net
positions are assets minus liabilities. Where a figure is negative by nature — a scenario funding
account's `shortfall`, its lowest balance below zero — its description says so.

## Scenarios

A scenario is a small document the client sends whole: `PUT /v1/scenarios/{uuid}` replaces its flows,
stream overrides, excluded accounts, members and hypothetical accounts, and every flow, override and
hypothetical account is saved again **under a new id**. The scenario's own id survives, as do the
book-account ids it names.

To edit a saved scenario, rebuild the body from its GET:

| `ScenarioResponse`                  | `ScenarioBody`                          |
|-------------------------------------|-----------------------------------------|
| `flows[].id`                        | `flows[].key` (any string unique in the body) |
| `flows[].matchOfFlowId`             | `flows[].matchOf` (the matched flow's key) |
| `flows[].hypotheticalAccountId`     | `flows[].hypotheticalAccount` (the account's key) |
| `flows[].hypotheticalTargetAccountId` | `flows[].hypotheticalTargetAccount` (the account's key) |
| `flows[].fallbackAccounts[].id`     | `flows[].fallbackAccountIds`            |
| `accounts[].id`                     | `accounts[].key`                        |
| `accounts[].collateralId`           | `accounts[].collateral` (the asset's key) |
| `excludedAccounts[].id`             | `excludedAccountIds`                    |
| `members[].id`                      | `memberScenarioIds`                     |

Using the saved ids as keys is exactly how `POST /v1/scenarios/{uuid}/copy` rebuilds a scenario.
A flow with `complete: false` has lost an account it needs to one since removed, or matches a flow
that has, and moves nothing: leave it out of the body, as the copy does.
Dates are returned as stated: a flow or stream override dated by a person's age has `startAge`/`endAge`
and no `startDate`/`endDate`, and `resolvedStartDate`/`resolvedEndDate` (read-only) show the days the ages
fall on — for a match, the matched flow's days; a one-time flow, shock or sale whose day has passed
happens on a run's first day instead. Send a date or an age, never both — both is a `400`. A stream
override is in force from its start — the run's first day without one — so a raise in March is an
override with `amount` and `startDate`, a second raise a year on is another override on the same stream
with a later start, and a paycheck that stops at 62 keeps stopping; a stream takes several overrides with
different starts and at most one without. A flow names a book account by id, or one of the
scenario's hypothetical accounts by its key (`hypotheticalAccount`, `hypotheticalTargetAccount`): extra
principal paid into the mortgage of a house not yet bought, saving into an account opened later. Money
moves through a hypothetical account only while it exists, so such a flow starts no earlier than the
account's open date and stops when it is disposed of; a planned loan paid down faster is paid off sooner
and then paid nothing. SPENDING and DISPOSE take book accounts only.

A scenario run (`/v1/scenarios/{uuid}/forecast`, `/forecast/ledger`) covers the books of the scenario's
party and each member scenario's party, so it takes no `perspective`. On the forecasts
(`/v1/scenarios/{uuid}/forecast`, `/v1/scenarios/forecast`, `/v1/scenarios/compare` and
`POST /v1/forecast`), `unit_type` converts the net position into `convertedNetPosition`; the ledger
takes none, its amounts staying in the account's own unit type. `GET /v1/scenarios/compare` runs every scenario once as it is and once
per variant rate, each rate a variant of its own: `return_rate=4&inflation_rate=4` is two variants,
not one with both. The variants run return rates first, then inflation rates, then income growth rates, then tax rates,
each kind in the order its parameters were given.

Runs are nominal and pre-tax until the household states its income tax rate: `taxRate` on `/v1/planning/assumptions`,
or on a scenario for that scenario alone, is the effective rate on money leaving a tax-deferred account. The asset type
says which — a book account's, or the `assetType` a hypothetical account states. Tax-deferred is exactly `IRA`, `SEP_IRA`,
`SIMPLE_IRA`, `SARSEP`, `KEOGH`, `_401A`, `_401K`, `_403B`, `_457B`, `PROFIT_SHARING_PLAN`, `PENSION`, `RETIREMENT`,
`RRSP`, `RRIF`, `LIRA`, `LRSP`, `LIF`, `LRIF`, `RLIF`, `PRIF`, `SIPP`, `FIXED_ANNUITY`, `VARIABLE_ANNUITY` and
`OTHER_ANNUITY`. Nothing leaving any other asset is taxed: not a tax-free one (`ROTH`, `ROTH_401K`, an HSA, a 529, a TFSA,
an ISA), nor a taxable one (a brokerage, a depository account). From then on a
WITHDRAWAL or TRANSFER from such an account lands what it states and draws the tax on top — a Roth conversion is a
TRANSFER from the IRA to the Roth — a percent withdrawal and an RMD are received less the tax, and the attribution
lists each tax as a `TAX` source beside its flow. An RMD flow names the tax-deferred account (`accountId` or
`hypotheticalAccount`) and, as `personId` (required), the owner whose age it follows. Where the money is received is
optional: `targetAccountId` or `hypotheticalTargetAccount`, an asset or a liability — usually checking; without one the
distribution leaves the household's accounts. The run takes the IRS minimum each January 1 from the year the owner reaches
the required age (72 if born before 1951, 73 before 1960, 75 after): the balance as the year opens over the uniform
lifetime divisor at their age that year, received less the tax. An RMD states no `amount`, `percent`, `rate`,
`cadenceMonths`, dates or ages; sending any is a 400, as is an account that is not tax-deferred. The `assumptions` in
every run say where the rate came from, so a default of zero is never mistaken for a choice.

The early-withdrawal penalty follows the owner's age, whatever the tax rate. A WITHDRAWAL drawing on a US retirement
account — `IRA`, `SEP_IRA`, `SIMPLE_IRA`, `SARSEP`, `KEOGH`, `_401A`, `_401K`, `_403B`, `PROFIT_SHARING_PLAN`, `PENSION`,
`RETIREMENT`, the annuities, `ROTH` or `ROTH_401K`, as its own account or a fallback — names the account's owner as
`personId` (a 400 without one), and a draw before the owner is 59½ carries a 10% penalty beside the income tax: on the
whole draw from a tax-deferred account, on the earnings alone from a Roth, whose contributions come out first (a Roth
401(k)'s pro rata). `purpose: RULE_OF_55` waives it on a `_401K`, `_401A`, `_403B`, `PROFIT_SHARING_PLAN` or `ROTH_401K`
from the year the owner turns 55 (never on an IRA); a `_457B`, a non-US account, a TRANSFER (a rollover or a conversion),
an RMD and a DISPOSE are never penalized. A WITHDRAWAL from a `_529` or an `EDUCATION_SAVINGS_ACCOUNT` is qualified
education unless its `purpose` says `NON_QUALIFIED` — its earnings taxed as income and penalized 10%, pro rata — or
`SCHOLARSHIP` — its earnings taxed, no penalty. The run tells an account's basis from its earnings itself: the balance
when the run begins and everything paid in after count as basis, and what the run grows the account by as earnings. A
purpose belongs to a WITHDRAWAL and names a kind of account the withdrawal must draw on; otherwise it is a 400. Each
penalty is a `PENALTY` source beside its flow in the attribution and the ledger, as each tax is a `TAX`, and every
funding entry totals both as `tax` and `penalty` (absent in a stress run's entry). Not modelled: the Roth 5-year rule, a
SIMPLE IRA's 25% in its first two years, SEPP, a first home, medical costs and disability.

A simulation (`GET /v1/scenarios/{uuid}/forecast/simulation`, `GET /v1/scenarios/simulation`) runs the same scenario
over many paths the market might take: each month every investment account's growth takes a random move around its
stated rate, one draw a month for all of them, as wide as the household's `volatility` (the standard deviation of the
return, percent a year, on `/v1/planning/assumptions` or on a scenario; 12 by default). Every path is judged as the run
is, so the response gives `successPercent` (every funding account funded), each funding account's `fundedPercent` with
the typical short date and shortfall, each target's `onTrackPercent`, and for every account and the net position the
10th, 50th and 90th percentile balance on every sampled day. `paths` picks how many (10 to 2000, default 500); `seed`
repeats an earlier simulation's paths, and the response returns the seed it used. The single-valued `volatility`,
`return_rate`, `inflation_rate`, `income_growth_rate` and `tax_rate` run the paths under that rate instead of the assumed
one. A simulation searches for no contribution or delay and applies no stress: the deterministic run gives the
former, and the paths are the latter.

## Paths

Plural paths are canonical (`/v1/accounts/{uuid}`, `/v1/parties/{uuid}`, `/v1/journals/{uuid}`,
`/v1/entries/{id}/match`, `/v1/plaid/items/{uuid}`, `/v1/ai/conversations/{uuid}`); the older
singular forms remain as aliases. Tag search is `GET /v1/tags?prefix=…` (the path form `GET /v1/tag/{name}` is deprecated —
it cannot express names needing URL encoding). Category bootstrapping is
`POST /v1/plaid/categories` / `POST /v1/mint/categories` (the GET aliases are deprecated).
One exception to the alias rule: `/v1/balance_sheet` (snapshot as of a date) and
`/v1/balance_sheets` (series per `time_period`) are different resources — semantic
singular/plural, both canonical.

## Status codes

- Creates return `201 Created` with a `Location` header and the resource (with ETag).
- Fire-and-forget operations (account clean/archive, party archive, Plaid item rebuild/refresh/delete,
  webhook processing, transaction imports, embedding) return `202 Accepted` with a message; poll
  the corresponding GET for state where one exists.
- `DELETE` on journals, accounts, parties, and items is a **soft delete** (archive); archived rows
  appear under the matching `/deleted` endpoint.

## Streaming (SSE)

`POST /v1/ai/conversations/start/stream` and `PUT /v1/ai/conversations/{uuid}/stream` produce
`text/event-stream`:

1. `event: conversation` — `{"id": "<uuid>", "version": <n>}`, first event; the id/version of the
   conversation (new or continued).
2. `event: thinking` — the model's reasoning, one chunk per event; render or ignore.
3. unnamed events — the response text, one chunk per event (arrives via `onmessage`).
4. `event: done` — `{"id": "<uuid>"}` on normal completion.
5. `event: error` — `{"code": "...", "message": "..."}` if the turn fails after the stream opens;
   the HTTP status is already 200 by then. Failures detectable before the stream opens return real
   statuses with a JSON body instead: a stale If-Match is 409, an unknown conversation 404, a
   rejected attachment 400, fair use 429.

Every `data:` line is padded with one leading space, which a spec-compliant SSE parser strips —
browser EventSource and standard SSE libraries do this automatically. Chunks then concatenate into
the exact response text; do not additionally trim them.

`:ping` comment lines flow every 15 seconds to hold idle connections open — EventSource ignores
them automatically. After a stream completes, `GET /v1/ai/conversations/{uuid}` returns the full
transcript and the ETag to use for the next turn.

## Digests and other proactive content

BigBooks never sends email or push to your users; it provides what you need to. A weekly digest is one stateless
call, on whatever schedule your app keeps:

`POST /v1/ai/response` with `"persona": "ADVISOR"`, `enableTools: true`, and a prompt such as *"Prepare my weekly
financial digest: bills due in the next 14 days, where cash is lowest over the next 30 days, subscription price
changes, lapsed subscriptions and duplicates, and bank connections needing attention. Lead with what needs
action."* The model calls `get_upcoming_bills`, `get_cash_forecast`, `audit_subscriptions` and
`get_problematic_bank_connections` and returns markdown.

`persona` names one of the system prompts BigBooks maintains — `BOOKKEEPER` (factual; the default when you send
neither field) or `ADVISOR` (personal-finance advice) — rendered for your user at request time. The same field is
accepted when starting a conversation. Don't write your own `systemContent` unless you must: the rendered prompts
carry the coded-label rules the aliasing layer depends on, and a prompt without them produces mangled names. A request
that sends both fields is a 400.

The same facts are available without the model at `/v1/upcoming`, `/v1/forecast`, `/v1/recurrences/audit` and
`/v1/plaid/items`, for a digest you render yourself. What the user knows is coming and the books cannot see — a tax
bill, a bonus, a transfer on the first — goes in at `/v1/planning/transactions`: the calendar and every forecast
carry each occurrence from today on, one item per account it names (`kind` PLANNED), at the amount stated.
`GET /v1/forecast` pays each book loan down by what pays it, as a scenario run does: a loan without terms is paid to
nothing and its payments stop there, and `/v1/upcoming` lists no payment on it, or paying it, past that day.
`GET /v1/forecast` also carries each cash account's everyday baseline — its trailing mean of spending and income
outside its recurring streams, listed under `baselines` — a slice each day of the month, so the low point is the
one the household will actually see; `baseline=false` projects the known items alone. A charge the books hold but
detection cannot yet call — an annual premium seen once, a summer-camp bill — is declared at
`/v1/recurrences/declarations`: the merchant on its payment account, an amount when only one of the merchant's
charges recurs, and a frequency. From the save the stream is listed by `/v1/recurrences` with `source` DECLARED and
its `declarationId`, projected from its last charge at the declared cadence by the calendar and every forecast, and
left out of the everyday baseline; deleting the declaration takes the word back. Each call is metered to your client
like a chat turn.

## Budgets

`/v1/budgeting/budget` and `/v1/budgeting/budgets` distinguish by method: **POST** strictly creates
(`amount` required), **PUT** is a full upsert where an item with a **null `amount` deletes** the
budgets in that account/date range, and **DELETE `/v1/budgeting/budgets`**
(`?account=&after_date=&before_date=`) is the explicit range delete. Serializers that emit null
fields will trigger the PUT delete branch — send POST if you never intend deletion.

`GET /v1/budgeting/projection?date=` says where each category ends the month holding `date` (today by default):
`actualToDate` is what has posted through `postedThrough`, `remainingKnown` what the recurring streams dated in the
`daysRemaining` land on the category — each at the share of it the stream's journals over the trailing year put
there, so a transfer lands nothing — and `remainingEveryday` the category's share of the everyday run-rate for those
days, the same everyday `GET /v1/forecast` replays as the baseline. `projected` is their sum, beside `budgeted` and
`variance` when the month has a budget. A past month is its actuals alone; a future month is all projection. Rows
are in their category's own currency, added up per unit type and account type under `totals`. Every amount is signed
as the budget reports are: spending positive on an expense, income positive on a revenue (a refund larger than the
month's spending makes an expense negative). `variance` is `budgeted` less `projected`: on an expense, positive is money
left unspent and negative is overspending; on a revenue, positive is income still short of the budget and negative is
income beyond it.

## Transaction enrichment flow

Enrichment is a deliberate two-step: `POST …/transactions/enrich` sends raw transactions out for
enrichment and returns the enriched rows *without persisting them*; the user may review and edit
them; `POST …/transactions/enriched` then submits the (possibly edited) rows for import. The pair
exists under `/v1/plaid` (Plaid-sourced) and `/v1/banking/file` (file-sourced) with
source-specific body shapes.

## File uploads

`POST /v1/file/image/{type}` (multipart, part name `file`, image content types only; `{type}` is a
lowercase slug such as `logo` or `avatar`) stores the image and returns
`201 { id, name, url, width, height, contentType }`. `GET /v1/file/image/{id}` streams it back.

## CORS

Browser origins are allow-listed per environment. `ETag` and `Last-Modified` are exposed to
cross-origin JavaScript; all methods including preflight `OPTIONS` are handled inside the security
chain.
