Skip to main content
The Partner API starts the same underwriting workflow as Cactus’s automated underwriting setup page. You supply the property address and asset class instead of choosing an existing app deal.

1. Register and upload each document

For each file, call POST /v1/documents with its filename, declared_type, and exact byte_size. Save the returned id, then PUT the file’s raw bytes to upload.url, using the returned upload headers. Registration alone does not upload the file. See the upload walkthrough. Repeat for the rent roll, T-12, and any other documents. You do not need to call /v1/extractions first; underwriting reads the documents itself. Already uploaded documents can be reused in later runs.

2. Start the underwriting

Send documents as objects containing a document_id and its role. There is no document_ids field. Each role corresponds to a document slot on the app’s setup page.
At least one document is required, with at most one file per role and no file selected twice. Rent rolls, T-12s, and supporting documents accept PDF, XLS, XLSX, and XLSM; financial models accept spreadsheets only; offering memoranda accept PDF only. Occupancy Report and Management Summary remain coming-soon slots in the app; they are not separate API roles. Prefer explicit roles. If omitted, the API infers a role from the uploaded document’s declared_type. To upload a financial model, register it as other_standard or other_complex, then select role: "financial_model" here. The document’s declared type and its role in a particular run are separate fields. Return targets are not exposed by this API. Use options.delivery: "private" for an embedded integration: end_user_ref is required, notification_email is optional, and completion creates no public link and sends no email. The default, public_link, requires notification_email and creates and emails a public report link. Supply a stable opaque end_user_ref to isolate each customer’s analyses. Reuse the same idempotency key when retrying the same underwriting request; change it for a new run.

3. Wait for the result

The response is 202 Accepted with an uw_... ID. Poll GET /v1/underwritings/{id} using poll_after_seconds, or receive underwriting.completed / underwriting.failed through a webhook. Check both terminal statuses. On completion, the response includes structured results and report.excel_url; report.url is included only for public-link delivery. Call GET /v1/underwritings/{id}/model for a fresh workbook download URL. On failure, inspect error for the reason. Sandbox keys return example results without running the AI workflow or sending email. Use a live key to validate actual document processing.

Customer ownership and access

Reuse the same end_user_ref for every underwriting belonging to a customer. Each run receives a separate uw_... ID. Store both in your backend: References are scoped to your partner account, so another partner using the same reference does not share your customer’s organization. If a customer is a company with several users sharing analyses, use a stable customer account identifier and enforce individual users’ permissions in your app. end_user_ref and the underwriting ID are identifiers, not credentials. Your API key authorizes access to your partner’s underwritings, including those belonging to different customers. Keep it on your server. Derive the customer reference from your authenticated session and verify a run’s ownership before returning it to a browser. The list filter is a convenience; it does not authenticate an end user. The single-run GET endpoint currently checks partner ownership, not a caller-supplied end-user reference. For integrations serving multiple customers, ask Cactus to enable requires_end_user_ref on your partner account. This rejects starts without a customer reference; it does not replace authorization when viewing a run.

Embed a private report

Your backend authenticates the customer and checks their permission to view this underwriting. It then requests a session with your server-side API key:
Cactus checks that the underwriting belongs to your partner account and the exact customer reference, that its results are complete, and that the parent origin is registered on your account. Ask Cactus to register each exact HTTPS origin before integrating; wildcards and arbitrary origins are rejected. Use an API key with the underwriting scope. Sandbox example underwritings have no underlying app report and cannot be embedded. The 201 response contains id, underwriting_id, url, launch_expires_at, and session_ttl_seconds (900). Return only the launch URL and session ID to your authorized browser, with Cache-Control: no-store. Set the iframe’s src to that URL immediately:
The fragment contains a single-use launch ticket, valid for 60 seconds. The frame removes it from its URL and exchanges it for a report-only session held in memory with a renewable 15-minute access lease. No Cactus login or third-party cookies are required. Never log or persist the launch URL, put your API key in the browser, or use either customer/run identifier as a credential. Derive end_user_ref from your authenticated session rather than trusting browser input. The registered origin restricts framing; it does not authenticate a customer. This is the app’s eight-chapter report in a read-only presentation. Editing, AI follow-up, and file downloads are not exposed in the iframe. Your backend can still use the authenticated model endpoint if you want to provide a separate download. A new session reads the current report, following any completed revisions in the same report family; an already open frame keeps its loaded snapshot. A page reload requires a new launch URL because the browser credential is held only in memory. Session creation deliberately does not replay an idempotent response: each successful request creates a new single-use ticket.

Keep an authorized viewer’s report open

The report can stay open indefinitely while your application continues to validate the viewer. Your backend renews its access lease every five minutes:
The response is 200 with id and expires_at. The same iframe and token keep working; no navigation, report reload, or scroll reset occurs. The frame observes the renewed deadline on its normal access checks. Renewal cannot revive a revoked session or extend an unused launch ticket. Implement a small authenticated route in your backend which:
  1. Reads the currently signed-in customer and rechecks their permission to view the underwriting. Never trust a browser-supplied customer reference.
  2. Verifies that the requested embed session belongs to that customer/run.
  3. Calls the renewal endpoint using your server-side Cactus key and the configured parent origin.
Call that route from your host page every five minutes and when the tab returns to the foreground. For example (the route below is yours to implement, not a Cactus endpoint):
Keep your normal CSRF protection on this host route. Retry transient failures; the console retries once a minute after a failed renewal. A successful fresh backend authorization can resume a lease that expired while a laptop was asleep. The frame reconnects automatically using its in-memory credential. It hides the report while the lease is expired, and never extends its own permission just because its tab remains open. The lease limits access when the partner stops confirming authorization (for example after logout, permission removal, or an abandoned tab). A transient network error or 5xx response does not immediately close a report whose last confirmed lease is still valid. To revoke a launch ticket and its session (for example on logout), call:
The response is 204. Every subsequent access is rejected. The frame checks access every 15 seconds and clears the report on revocation or expiry (browser background throttling can delay the visual update). Disabling the partner, revoking/expiring the originating key, removing its scope or the registered origin also invalidates access. Revocation cannot erase data a viewer has already copied or captured. Existing report.url links remain public bearer links, readable by anyone holding them until sharing is revoked. Creating or revoking an embed session does not disable an existing public link. Use private delivery when starting new underwritings to avoid creating one.

Test in the admin console

Open API Console → Embedded report, choose a completed underwriting with an end-user reference, and select Open embedded report. The console calls the public session API from its server and loads the returned URL in a real iframe. Revoke session leaves the frame open so you can see its access check clear the report. Sessions renew through the console’s authenticated server action while the page remains open, without reloading the iframe. Changing the customer reference demonstrates the ownership check (404). New underwritings in the console default to private delivery.

Shared results components

The embed composes BrokerSharedReport / SharedDealJourneyWorkspace from the same eight production chapter components used by the signed-in result workspace. Chapter layouts, charts, tables, and presentation fixes therefore carry through after deployment. It does not copy a separate set of chapters. Owner-only controls (editing, Sage, sharing, history) remain outside this composition. New data fields need to be explicitly added to the safe shared report projection before the embed can display them.