Export value conventions
One set of value rules for every export payload. Decimals are strings; integers are numbers.
Every SpreadSpace export payload (tax return spreads, financial statement
spreads, bank statement analytics) uses one set of value conventions,
declared by schema_version at the payload root. Parse against these rules
and you never need to special-case a field.
The wire rule, inside these payloads: Decimals are strings; integers are numbers.
Numbers
Rounding is banker’s rounding (round half to even), applied once at
serialization. There is no field named rate: a field is either *_percent
or *_bps, so its scale is always explicit.
Signs
Negative means outflow or contra: "-8200.55". There are no
parentheses-negatives and no separate sign flags. Where a classification
exists (for example flow_direction: "INFLOW" | "OUTFLOW" on bank lines), it
is carried alongside the signed amount, so you never have to infer meaning
from the sign alone.
Null versus zero
null means the value is absent from the source document. "0.00" means the
document affirmatively shows zero. These are different facts and both are
always emitted: keys are never dropped.
Dates and timestamps
- Business dates are ISO 8601 date-only strings:
"2023-12-31". No time, no timezone. Do not parse them withnew Date("2023-12-31")in JavaScript (that yields UTC midnight and renders off by one day in US timezones); split the string or use a date-only parser. - Timestamps (
generated_at, audit fields) are RFC 3339 UTC with aZsuffix, seconds precision:"2026-07-20T13:30:05Z".
Fiscal periods
Never a bare label like "FY23". Always a struct:
period_type is ANNUAL, INTERIM, or TTM. A short-year return stays
ANNUAL with months_covered under 12. Non-calendar fiscal year ends are
just what the dates say.
Identifiers
EINs, account numbers, and routing numbers are always strings, and leading
zeros are preserved: "041234567". Where a display context needs a masked
variant, it is a separate field (for example account_last4).
Record identifiers (borrower_id, loan_id, job_id, document ids) are
opaque strings. Store and compare them byte-for-byte; never parse meaning
from an id or validate its shape, which can change. Where an identifier
carries a stable prefix (evt_ events, whsec_ webhook secrets, API keys’
ss_live_ / ss_test_ / ss_embed_), the prefix is part of the value.
Key casing
Envelope fields are snake_case (borrower_id, extracted_document_id,
report_data). Keys inside a document’s stored report_data body keep the
casing they were extracted and stored with: bank statement bodies, for
example, carry camelCase keys such as accountNumberLast4 and
dailyBalances. Match keys exactly as each endpoint’s example shows them.
Enums
Enums are closed, versioned sets serialized as string tokens (numeric tokens
are rejected). Wherever a value is mapped from an open source set, the enum
includes an OTHER escape and a sibling *_raw string carries the source
value verbatim, so nothing is silently coerced (statement_type +
statement_type_raw). Two enums have no OTHER by design: period_type is
a fixed set (ANNUAL | INTERIM | TTM) defined by the fiscal period contract,
and flow_direction is derived from the amount with the source rail carried
verbatim in transaction_type_raw.
Provenance
Values may carry an optional provenance object pointing back at the source
document: { "source_doc_id": "...", "page": 3, "field_ref": "line_7_officer_compensation" }. page is 1-based; field_ref is the
stable field handle: the form-line key for scalar tax lines, a dotted path
for columnar schedule values (schedule_l.line_1_cash.ending, where the
line’s period object distinguishes the beginning and ending balance
columns). Map by field_ref, never by row position: line sets can grow
additively.
Currency
currency is declared once at the envelope root as an ISO 4217 code
(currently always "USD"). There are no per-value currency objects.
Versioning
schema_version (semver) sits at the payload root. Within a major version,
changes are additive only: new optional fields and new enum values may
appear; existing fields never change name, type, scale, or meaning. Build
your parser to ignore unknown fields.