Python
The official Python SDK, published to PyPI as spreadspace. It wraps the
API Reference with a typed error hierarchy, automatic retries,
idempotency, and lazy cursor pagination. Requires Python 3.9+.
Install
Construct the client
The constructor raises when it finds no key, from neither the argument nor the environment variable. Construct the client where a missing key should fail: at startup, or on first use, not at import time in a module every test loads.
Other constructor options: base_url (override the base URL),
api_version (pins the SpreadSpace-Version header), timeout (seconds, per
request), and max_retries.
Paginate (auto-paged borrowers)
List endpoints return a lazy iterator that walks cursors for you; it fetches the next page only as you consume it.
Upload a document and wait
The upload helper requests a presigned URL, PUTs the file bytes to that URL
with the Content-Type it was given, then returns a job handle you can wait
on. Files up to 100 MB; a ZIP’s members up to 50 MB each.
Create an extraction export and wait
Exports are asynchronous operations. create returns a handle; wait polls to
completion (raising AsyncOperationError on a failed operation, or
AsyncOperationTimeout on timeout). format is json, csv, or xlsx;
xlsx is available for bank statements only, so pass it only when every
document in the export is a bank statement.
Read a loan’s finalized attributes
When an analyst finalizes a spread, its figures freeze as an
attribute snapshot. get returns the latest of a status,
versions the history (a plain list: newest first, capped at 200 and not
paged), get_snapshot any one snapshot by id. All three are typed: the reads are
annotated AttributesEnvelope / AttributeVersions / AttributeSnapshot, and
every payload and manifest type ships as a TypedDict (AttributeSnapshot,
LoanAttributesPayload, LoanAttributesManifest) exported from spreadspace,
so a type-checker knows the keys. The receiver hands your spread.finalized
handler the same AttributeSnapshot.
versions takes a status of its own. You get the filtered history, the first
row is the loan’s current one of that status, and the 200 cap applies after the
filter.
Verify a webhook signature
Verify the SpreadSpace-Signature header against the exact raw request body
(bytes or str) you received on the wire, not re-serialized JSON, or the HMAC
won’t match. verify_and_parse_webhook raises WebhookSignatureError on any
failure and otherwise returns the parsed, typed event.
Receive webhooks
The receiver does the whole loop (verify, dedupe, dispatch, answer) and
on spread.finalized reads the snapshot the event names for you (by id, never
the loan’s latest: see Spreads). Full option list on
the Webhooks page.
Hand it the raw body. A framework that parses JSON first has already changed the bytes the signature covers.
To audit what we sent, client.webhooks.deliveries(endpoint_id, status=…, event_type=…, loan_id=…) pages the delivery log filtered. Every row carries
the loan its payload named.
Handle errors
All errors derive from SpreadSpaceError. Match on the typed subclass, never on
the message string. Every error carries request_id (from the X-Request-ID
response header). Quote it in support tickets.
All derive from APIStatusError → SpreadSpaceError. Transient failures (429,
5xx, transport errors) retry automatically up to max_retries with exponential
backoff + full jitter, honoring Retry-After.
Money is exact
Monetary values decode as decimal.Decimal, not float: the SDK reads the
literal digits off the wire, so amounts are exact with no float rounding (e.g.
Decimal("1234.56"), never 1234.5600000000001).