Skip to main content
Run financial spreads programmatically: upload a business’s financial documents, start a spread, track it to completion, and download the finished report and Excel workbook. This guide walks the full loop end to end. Every step is a copy-pasteable curl. All requests go to https://api-v2.getomni.ai and authenticate with your workspace API key in the x-api-key header. Export both once so the examples run as-is:

Before you start

Three things to have in place before writing integration code:
  1. An API key. Generate one in workspace settings. Keys are scoped to your workspace: every lead, document, spread, and artifact in this guide is only visible to keys from the same workspace. Requests without a valid x-api-key header return 401.
  2. Leads and documents flowing into Omni. A spread always runs against a lead’s documents, so decide where those come from first. If you already create leads via the API or a CRM sync and collect documents through Omni, you’re set — skip straight to step 4. If not, steps 2 and 3 show the minimal API calls to create a lead and attach files.
  3. A webhook endpoint (recommended). To be notified when spreads finish rather than polling, you’ll need a public HTTPS endpoint that accepts POST requests and responds 2xx within 5 seconds. Step 6 covers registering it and authenticating deliveries.

1. Overview

Financial spreading takes a set of financial documents for one business — tax returns, income statements, balance sheets — and turns them into a standardized, multi-period spread. Omni classifies each document, extracts the line items, consolidates everything onto a fixed underwriting template, and computes the derived statements (income statement, balance sheet, cash flow) plus summary ratios. A completed spread produces two downloadable artifacts: A spread runs in the background and typically takes a few minutes, driven mostly by document count and page count. You start a run, then either poll its status or wait for a webhook. Spreads are always scoped to a lead — the business you are underwriting — so the flow is: create a lead, upload its documents, start the spread.

2. Create a lead

A lead represents the business you are spreading. If you already create leads through the API or a CRM sync, reuse the existing lead’s id and skip to step 3.
The 201 response wraps the created lead under lead; keep lead.id:

3. Upload financial documents

Attach the business’s financial documents to the lead. You can either upload the files directly (multipart) or pass a list of URLs Omni will fetch. Only PDF documents are spreadable — other file types are stored on the lead but ignored by the spread. Upload local files (multipart form-data):
Or reference files by URL (JSON):
The 201 response lists the created documents with their ids:
These document ids are the ids you’ll see everywhere else in this guide: in the spread’s documents array, in webhook payloads, and as the documentIds filter when starting a spread.

4. Start the spread

Start a run for the lead with Create Spread. By default it spreads every PDF document on the lead; pass documentIds to restrict it to a subset.

Request body

Response — 201 Created

The run is created and queued immediately; the response returns before any processing happens.

Errors

5. Track progress

Poll the run with Get Spread for its status, per-document progress, and (once finished) its artifacts.

Run lifecycle

A run’s status is IN_PROGRESS from the moment it’s created, then resolves to COMPLETE or FAILED. Each input document also has its own status: PROCESSING while the run works on it, ending at EXTRACTED (financial data captured), SKIPPED (nothing spreadable found), or FAILED (that document couldn’t be processed). A single failed document doesn’t necessarily fail the whole run — the other documents still spread. The run resolves to FAILED when it can’t produce a spread — most often because every document failed to process or none of them contains a recognizable financial statement, though a processing stage can also fail outright. In every case error carries a short, human-readable reason (for example, “No financial statements were found in the uploaded documents…”). Poll until status is COMPLETE or FAILED. A COMPLETE status always includes the artifacts array with the report and spreadsheet. Polling works, but webhooks let you react the moment a spread finishes without a poll loop. Subscribe once, then receive an HTTP POST for each event. Register a subscription for the three spread events with Create Webhook (or do the same in the Omni app under Settings → Webhooks):
Deliveries are not signed, so headers is how you authenticate them: any headers you set on the subscription are sent with every delivery. Set a secret header here and reject requests to your endpoint that don’t carry it.

Events

Every delivery has the same envelope. actor is always null for spread events; data carries the run, with documents keyed by the same document ids as the rest of the API:
spread.started carries the same shape at status: "IN_PROGRESS" with no artifacts. spread.failed omits artifacts and adds error, a short, human-readable reason for the failure.

Things to know

  • spread.started fires for every run, not just yours. If a teammate starts a spread from the Omni app, your endpoint still receives spread.started with the new spreadId — record the id, then poll or await the terminal event.
  • Respond within 5 seconds. A delivery that doesn’t get a 2xx within 5 seconds counts as failed and is retried with backoff (roughly 1, 2, 5, 10, then 20 minutes between attempts) for up to 6 total attempts, after which it’s marked failed. Acknowledge fast and do heavy work (like downloading artifacts) asynchronously.
  • Handle repeat deliveries. The same event can arrive more than once for a spread: a retry repeats an identical payload, and a regeneration (step 8) legitimately re-emits spread.completed with new artifact ids. Don’t drop repeats by event name alone — treat each delivery as the run’s latest state, keyed by data.spreadId, and process it idempotently.
  • Test leads don’t fire. Spreads on a test lead emit no webhooks.

7. Download the artifacts

When a run is COMPLETE, its artifacts array (from the status poll or the spread.completed webhook) holds the ids you download. A REPORT downloads as a PDF and a SPREADSHEET as an xlsx workbook. Fetch metadata with Get Artifact (optional — gives you the content type and a suggested filename):
Download the bytes with Download Artifact. Use -o to save the file:
To list every spread on a lead (for example to find a past run’s artifacts), use List Lead Spreads. Each entry is the same shape as the single-spread response, including its artifacts array:
The same report and spreadsheet also appear in the Omni app on the lead’s detail page — useful for eyeballing output while you build, without wiring up downloads first.

8. Regenerate a spread

Regenerate Spread rebuilds a finished run from its already-extracted data: the documents are not re-processed; only the consolidation and report run again. Any analyst adjustments recorded on the run inside Omni are applied, so regenerating is how an integration picks up corrections made in the app.
The 202 response returns the run back at IN_PROGRESS; on completion the run has new artifact ids. A regenerated run re-emits spread.started and spread.completed — the second completion carries the new artifact ids, which is why deliveries are processed as state updates rather than dropped as duplicates. Regenerating a run that is still in flight returns 409, and an unknown spread id returns 404.

9. End-to-end script

This bash script chains the whole loop: create a lead, upload a PDF, start the spread, poll until it finishes, then download both artifacts. It needs curl and jq.