Exports

Line items from many documents, bundled in the background and handed back as one download.

An export takes a set of documents on one loan and bundles their extracted line items into a single file. It runs in the background, so the request that starts it answers immediately with an export to poll rather than with the file.

Three steps, in the Extractions group of the API reference. Creating an export needs the extractions:export scope; reading and downloading need extractions:read.

StepOperation
1. Start itPOST /api/extractions/exports
2. Poll itGET /api/extractions/exports/{exportId}
3. Download itGET /api/extractions/exports/{exportId}/download

Create answers 202 with the export in queued. Poll the same export until status is terminal. When it reaches succeeded, the response carries the bundle and a download link.

$curl -X POST https://api.spreadspace.app/api/extractions/exports \
> -H "Authorization: Bearer ss_live_..." \
> -H "Content-Type: application/json" \
> -H "Idempotency-Key: 3f7c31c9-3a06-4a5f-9f6b-8d34ef9f2b41" \
> -d '{"borrower_id": "brw_4a2c8e1f6b3d5a7c9e0f", "loan_id": "loan_7d1e3b5a9c2f4e6b8d0a", "document_ids": ["doc_1f2e3d4c5b6a7908", "doc_9a8b7c6d5e4f3021"], "format": "xlsx"}'
$curl https://api.spreadspace.app/api/extractions/exports/exp_9f8b7c6d5e4a3b2c1d0e9f8a \
> -H "Authorization: Bearer ss_live_..."

Poll every few seconds. progress carries completed and total, counting documents processed against documents requested, so a long export can be shown as it moves. GET /api/extractions/exports lists the exports you started, newest first, filtered with status and paged with cursor.

Statuses

StatusWhat it is
queuedAccepted and waiting for a worker. Nothing has been read yet.
runningA worker is reading the documents and building the bundle.
cancellingA cancel landed mid-bundle. The worker stops at its next document boundary and the export then reads cancelled.
succeededThe bundle is built and can be downloaded.
failedThe export stopped without a bundle. error_code and error_message say why.
cancelledStopped on request. No bundle.

succeeded, failed and cancelled are terminal. cancelling is not: keep polling and it settles on cancelled.

error_code on a failed export is one of no_documents_resolved (none of the ids named a document on that borrower and loan), export_ceiling_exceeded (more documents than one export may carry), stale_running_exceeded (the worker stopped answering and the export was abandoned), or internal_error.

Documents and counts

borrower_id, loan_id and document_ids are all required. document_ids names between one and one thousand documents, and a request missing any of the three is rejected with 400. Ids are resolved inside the borrower and loan you named, and an id that does not resolve there is skipped rather than failing the export.

FieldWhat it says
document_counts.requestedHow many ids you sent.
document_counts.exportedHow many of them are in the bundle.
document_counts.skippedHow many did not resolve on that borrower and loan.
skipped_document_idsThe ids counted in skipped. Empty until the export resolves them.
bundlename, size_bytes and line_item_count for the built file. null until the export succeeds.

Read skipped_document_ids before you trust a bundle: an export of ten documents that skipped four still succeeds, and the counts are the only place that says so.

Formats

format is xlsx, csv or json, and defaults to xlsx. xlsx covers bank statements only, so pass it only when every document in the export is a bank statement; use csv or json for a mixed set. The values inside a bundle follow the house value conventions: money is a string with exactly two decimals, counts are integers, and timestamps are RFC 3339 UTC.

Downloading

A succeeded export carries download_url and download_expires_at. The link is minted fresh on every read, and it is good for minutes rather than hours. Do not store it: read the export again when you need another one. The link and its expiry roll over at each five-minute signing boundary, so two reads inside the same window return the same link and the next window returns a new one. A link that has run out is not an error to retry against, it is a link to replace.

links.download points at GET /api/extractions/exports/{exportId}/download, an authenticated door to the same file. It answers 302 with a freshly minted link in Location, so a client that follows redirects can pull the bundle in one call. It answers 404 until the export has succeeded, and again once the export has expired.

Cancelling

POST /api/extractions/exports/{exportId}/cancel stops an export that has not finished.

  • A queued export is cancelled at once and reads cancelled.
  • A running export moves to cancelling. The worker acknowledges at its next document boundary and the export then reads cancelled. links.cancel is null from that point, and a second cancel answers 200 with the export unchanged.
  • An export that already succeeded or failed answers 409.
  • An export already cancelled answers 200, unchanged.

Retention

expires_at is seven days after the export was created. At that time the bundle and the export record are both deleted, and every operation on that id answers 404 from then on. Pull what you need inside the window; there is no way to revive an expired export, so start a new one.

Idempotency

Send an Idempotency-Key on create and a retry returns 200 with the same export instead of starting a second one. Reusing a key with a different body is a 409. See Idempotency for the full rules.

Rate limits

Creating an export rides the export lane at 120 per minute. Get, list and cancel ride the general lane. See Rate limits.

Test mode

A test-mode key can create exports. It exports the sandbox workspace’s own documents, the response shape is the same as in live, and the events it generates carry livemode: false.

Webhooks

Three events report the end of an export: export.succeeded, export.failed and export.cancelled. Each carries identifiers, the format, and a timestamp saying when the export reached that state; export.succeeded also carries document_count, and export.failed carries reason, which is the export’s error_code. No event carries a download link. Read the export when one arrives, and use the download_url on that response. See Webhooks for the envelope, signing and delivery rules.