Skip to main content
You’ll need an API key - your Cactus contact provides one at kickoff. Use your sandbox key (ck_sandbox_...) for this walkthrough; it returns instant example results and is never billed. The same requests work unchanged with your live key.

1. Register the document

Tell us the filename, type, and exact size in bytes. The response includes a short-lived upload URL.
Save id (the document id) and upload.url from the response.
In sandbox, register the document as example-rent-roll-1.xlsx to get the real extracted output of a full reference rent roll — see example documents for the list.
Why the exact byte size? It’s built into the upload URL’s signature, so storage rejects any upload that doesn’t match what was registered - and files over the size limit are rejected here, before you spend time uploading. Your HTTP client already knows the size (it sends it as Content-Length). The filename’s extension tells us how to process the file (.xlsx/.xls are handled natively as spreadsheets). content_type is optional; when provided it’s also pinned into the upload signature.

2. Upload the file

PUT the raw bytes to the upload URL. The upload must match the registered byte size exactly.

3. Start the extraction

This validates the document and charges its fixed rate before processing - pricing is always known up front. Rejected documents are never charged.
asset_class is required. It decides which standard columns a rent roll is consolidated onto - market_rent, bedrooms and bathrooms exist for multifamily; unit size and climate control for self-storage. Send the property’s real class: one of multifamily, self-storage, industrial-outdoor-storage, commercial, industrial, office, retail or hotel. The response is 202 Accepted with an extraction id and the billed amount.

4. Wait for completion

Poll the extraction until status is completed — instant in sandbox, and for live documents usually 2-5 minutes for a T-12 or an offering memorandum. Rent rolls run a second consolidation phase and take longer: most finish within 10 minutes, a large one can run to 30. Add wait to have the API hold the request until the job finishes instead of sleeping between calls:
wait accepts 0-45 seconds. If the job is still running when the wait elapses you get a normal 200 with status: processing — that’s not a timeout, just call again. While a job is in flight the response carries poll_after_seconds and a matching Retry-After header; use those rather than a fixed interval. Nothing runs longer than 60 minutes: past that the extraction fails and every charge is refunded in full. Prefer push? Register a webhook endpoint and receive extraction.completed instead of polling.

5. Fetch the result

Every result declares its schema_version and returns extracted values with confidence scores and source locations (sheet cell or page), so you always know where a number came from.

Check your usage anytime

Returns your pool balance and period-to-date usage by document type.