Spreads
Finalized figures as data: the spread lifecycle and the attribute payload.
What a spread is
A spread is the analyst’s finished read of a borrower’s documents on one loan. It lays the returns, statements and schedules out as the figures a credit decision is made on. It is worked in the SpreadSpace workspace or inside your own product through the embed.
When the analyst finalizes it, those figures are frozen as an attribute snapshot: a versioned, machine-readable statement of the loan’s numbers, keyed by name and period. That snapshot is what your backend reads. Only the finished figures, and what they were built from, cross the API. The spread’s own layout, formulas and working state never do.
The lifecycle
While the analyst works, saves create draft snapshots. A draft moves only
when the figures move: a save that leaves the payload unchanged creates nothing.
Finalize is the key step. It creates the machine and final snapshots at
consecutive per-loan versions, locks the board on the final snapshot, and sends
the spread.finalized event. A locked board refuses replace, delete and a
second finalize alike with 423 spread_locked until it is reopened.
The spread.finalized event is a pointer. It carries the loan, the board, both
snapshot ids and the version, but not the figures. Fetch those with the reads
below. See Webhooks for the envelope and delivery rules.
Reopen clears the lock so the board can be edited again, bumps its
version, and queues the spread.reopened webhook. That event points at the
final snapshot that was current, so a backend holding a copy can mark it
stale. The snapshots created by the finalize are kept as the loan’s history.
The next finalize creates a new version alongside them instead of replacing
them.
Statuses
Reading attributes
Three reads, all requiring the spreads:read scope, all in the Spreads
group of the API reference:
The versions list is capped at the 200 most recent snapshots and is not
paginated; fetch any one of them by id for its payload and manifest. Add
?status=final (or draft, machine) to filter it to one status. The first
row is then the current final, so backfilling a deal whose finalize predates
your subscription is one call rather than listing and then filtering. The
200-row cap applies after the filter.
Every snapshot and summary carries snapshot_id, version (per-loan, counting
up across every status), status, created_at, and spread_board_id when one
is known. On a final or machine snapshot, finalized_at says when the
analyst finalized and finalized_by names them; both are null on a draft,
and finalized_by is also null for a caller who may not see names, and a
plain API key never does. The join key a backend wants is
finalized_by_external_user_id: the external_user_id your own embed
session named for that analyst, returned to every caller because it is your
identifier, not a person’s name. It is null on drafts and when the finalize
happened in the SpreadSpace workspace or under a session that named no user.
A snapshot id belonging to another tenant or another loan answers 404, never
403. The read never confirms that a foreign id exists.
An API key receives values only. The source-provenance geometry our own surfaces draw click-to-source highlights from is stripped out of the payload and the manifest at every depth for a key-authenticated caller.
The payload
The payload states the figures. schema_version is attributes.v1.
unit is money, ratio, percent or count. A money attribute read from a
filed return carries a basis (tax or book) naming which side of the return
the figure came from; a ratio always carries one, saying where its operands came
from. Every value basis can take is listed under
Basis on the catalog page. An attribute keyed
by as-of date rather than fiscal year (an aging, a debt schedule) carries
period_kind: "as_of_date".
Values follow the house value conventions: money is a
string with exactly two decimals ("754144.00", "-5644.00"), ratios and
percents are strings with at most four decimals, counts are integers, and
timestamps are RFC 3339 UTC. A period a figure has no value for reads null.
Keys are never dropped, so an absent year is visible as absence rather than as
a missing key.
The manifest
The manifest states what the payload was built from.
A snapshot is content-hashed over its payload. When a new snapshot would repeat
the latest snapshot of that status byte for byte, no new version is written.
Two identical payloads from two different builds dedupe to one snapshot, and
builder_version says which build produced the one that was stored. A manifest
field does not change the hash.
The catalog
The fixed vocabulary is generated from the table the builder emits through and published as the attribute catalog. It lists every attribute and ratio name a snapshot can carry, with its label, unit, basis and the conditions it appears under. A name there is a name you can key on.
Two families cannot be listed there, because they are named after things an analyst typed:
dscr_<recipe>is one ratio per debt-service coverage recipe saved against the business on the loan. The slug is the recipe’s own name, lowercased, with each run of characters outsidea-z0-9collapsed to a single_, the edges trimmed and the result capped at 48 characters; two recipes that slug the same are suffixed_2,_3… in the order the loan holds them.inputs.dscr_configsnames every recipe saved on the loan, whether or not it had a figure to state.custom_<slug>is one entry per figure an analyst built and placed on the spread. Same slug rule, trimmed again after the cap so a key can never end in_, and an empty result readsvisual. A slug is claimed once per snapshot across both groups, with repeats suffixed_2,_3… A figure that divides is stored inratios. One that does not is an aggregate money figure and is stored inattributes. Each carriescustom_visual_id, the definition’s own opaque id, stable across renames. Key on the slug and you accept rename churn. Key on the id and you do not.
Both families appear in final and draft snapshots, because each is analyst
judgment applied on every save. Neither appears in the documents-only machine
snapshot. What an analyst built a figure out of never leaves the workspace.
Provenance
Provenance in an attribute snapshot is document-level: for each attribute, each
period’s documents entry lists the document_id and extraction_version its
figure was read from, and [] when the figure is null. That is enough to
tell a reader which filing a number came from, and to notice when a document
has been re-extracted since.
Sub-document geometry (where on the page a figure was found) is not part of this surface. It reaches the SpreadSpace UI, which draws click-to-source highlights from it, and is stripped for an API key at every depth of both the payload and the manifest.