schema_version with
advance notice and an overlap window — your integration always knows which
shape it’s reading, because every result declares its version.
2026-11
- Read and change finished underwritings from your own assistant. New
MCP tools and endpoints let your AI read a report piece by piece
(
get_underwriting_outline,get_underwriting_section), list its assumptions, preview a what-if, and — with your user’s confirmation — start a new version with changed assumptions. Underwritings now carryversionandprevious_underwriting_id. SendX-Cactus-End-Userto limit a connection to one of your users. See MCP Server.
2026-10
-
Sage and the full workspace in private embeds. An embedded report now
opens the full report workspace: your customer can download the Excel
model, ask Sage about the deal, and approve assumption changes that create
a new version. Choose a
light,dark, orsystemtheme with thethemeURL parameter or acactus:set-thememessage. See Private embed. -
A real example underwriting in sandbox. Register documents as
example-underwriting-rent-roll.xlsxandexample-underwriting-t12.xlsxand a sandbox underwriting completes instantly with a real report link, Excel model, and results. See Sandbox. -
Simpler underwriting delivery.
notification_emailis now optional for public-link delivery; omit it to receivereport.urlwithout an email. An opened embedded report now lasts 12 hours with no renewal, so your backend only creates the launch URL. The embed-session renew and revoke endpoints are removed. To cut off access, disable the API key or ask Cactus to remove the parent origin. -
Private underwriting embeds. Request a single-use iframe launch URL
with
POST /v1/underwritings/{id}/embed-sessions, supplying the owningend_user_refand a registeredparent_origin. Launch within 60 seconds; the report-only access lease lasts 15 minutes, renews through the authenticated host, and can be revoked byDELETE.POST /v1/underwritings/{id}/embed-sessions/{session_id}/renewkeeps an authorized viewer connected without reloading the iframe. Start runs withoptions.delivery: "private"to avoid public share links and completion emails. Private delivery requiresend_user_refand does not requirenotification_email; the defaultpublic_linkbehavior remains available. See private embedding. -
Underwriting setup matches the available app controls. Send
options.perspective: "broker"and optionaloptions.notes(up to 10,000 characters), alongsideoptions.modeand role-assigneddocuments. Notes reach the same Analyst input as the app’s Notes field. Broker is the only available perspective; Investor and return targets are not exposed. See the underwriting walkthrough.
2026-09
-
The completion email now arrives.
notification_emailonPOST /v1/underwritingshas always been documented as receiving the report link, and since the pipeline change below it did not. A completed live run now emails that address a link to the same public report asreport.url. Sandbox runs still send nothing. -
Underwriting now returns the whole report, not just a link. A
completed
GET /v1/underwritings/{id}carries three things in one object:report.url, a public link anyone can open with no login;report.excel_url, pointing at the newGET /v1/underwritings/{id}/modelfor the populated Excel workbook; andresults, the full report as structured data (broker-public-underwriting-delivery-v1) — property, eight chapters covering valuation, income, revenue, costs, comparables, buyer returns and market risk, the ranked critical assumptions, and the readiness disclosures. A flatsummarylifts the headline metrics out of it. Every figure is a{state, value, reason}triple, so a value the run could not establish says so instead of arriving as a zero.resultsis returned only when you fetch a single run; list responses and webhook payloads omit it. Behind that, the endpoint now runs the same underwriting pipeline the Cactus app runs, which changes the request:documentsis required and replacesdocument_ids, because a run reads the property’s own documents and needs to know what each one is. Send[{"document_id": "doc_…", "role": "rent_roll"}]; omitroleand it is taken from the document’sdeclared_type. At most one document per role (rent_roll,t12,financial_model,offering_memorandum,supporting), and each role accepts particular formats — an offering memorandum must be a PDF, a financial model a spreadsheet. Mismatches are rejected at acceptance, unbilled, rather than failing a run half an hour in.options.investment_strategyis replaced byoptions.mode(light/medium/heavy), andprogress.stagenow names the pipeline step behind the coarsestatus. -
/v1/underwriting-inputsis removed. It returned the underwriting model’s populated input fields, which is whatresultsnow gives you as part of a finished underwriting — with the analysis and the workbook alongside it, rather than a field set on its own. Theunderwriting_inputs.completedandunderwriting_inputs.failedevents are gone with it. No account had it in production.
2026-08
-
Extractions are more accurate and take longer; the documented durations
now match. We moved rent-roll extraction to a stronger model after
finding it was misreading columns — square footage landing in market rent,
market rent landing in rent, unit status dropped entirely. On the
documents where we could measure the same file under both models, that
cost 1.4x to 5.5x in run time. The published figures had said “1-5
minutes” for everything, which was no longer true for rent rolls. Measured
over 79 live extractions: a T-12, offering memorandum or other document
usually finishes in 2-5 minutes (91% within five); a rent roll runs a
second consolidation phase and is the slow path, with most within 10
minutes and a large one reaching 30. The 60-minute ceiling is unchanged,
and no refund, polling or webhook behaviour changed. If your integration
hard-codes a timeout under ten minutes, raise it —
poll_after_secondsand thewaitlong-poll are there so you never have to guess. -
asset_classis now required onPOST /v1/extractions. It was optional, which read as a hint but behaved as a cliff. Without a class, rent-roll consolidation fell back to the cross-class column subset — 14 standard columns instead of multifamily’s 29 — somarket_rent,area,bedrooms,bathrooms,bed_bath,move_in,move_out,deposit,concession,other_income,floor,furnished,escalation,subsidizedandsubsidy_amountstopped being standard fields and came back undercustom__*keys named after whatever the source document’s header happened to say. Two rent rolls of the same asset class could therefore disagree —custom__sqftin one,custom__sq_feetin the next — and nothing in the response explained why. Requests without the field now return400 invalid_requestnamingasset_class, rather than quietly returning the narrower shape. Accepted values are unchanged:multifamily,self-storage,industrial-outdoor-storage,commercial,industrial,office,retail,hotel. Theasset_classargument on thecreate_extractionMCP tool is required to match. Adding the field is the whole migration; callers already sending it are unaffected.
2026-07
- Per-product account access, and an errors reference. Accounts are now
enabled per product, so an endpoint your account hasn’t bought returns
403with the newproduct_not_enabledcode and a message naming what to do about it, rather than working. The existingmissing_scopecode now means something narrower: the account has the product, but the key you used was issued without it. Both are documented alongside every other code the API returns on the new Errors page. Two related changes:/v1/underwriting-inputsnow belongs to the underwriting product rather than extraction, and the MCP server advertises only the tools your key can use. No change for accounts enabled for everything. - T-12 month headers: Spanish abbreviations, and a coverage check.
Column headers written in Spanish (
Dic-25,Ene-25,Abr-25,Ago-25,Set-25) now map to months like their English equivalents. Previously such a column was dropped fromheadersand from everyline_itemsentry, so a statement’s monthly figures could silently under-report the year by a month. A newt12_column_coveragevalidation check reportswarningwhenever a column that carried data was dropped because its header could not be interpreted —t12_month_mappingonly describes the columns that were read, so it could not surface this. - Long polling and richer job status.
GET /v1/extractions/{id}andGET /v1/underwritings/{id}accept?wait=(0-45 seconds), holding the request until the job finishes so you don’t need a sleep loop; a wait that elapses returns the in-flight state as a normal200. In-flight responses now includepoll_after_seconds(mirrored in aRetry-Afterheader) andelapsed_seconds, and rent-roll documents reportphase(extracting→consolidating) so two-phase jobs are visibly progressing. The samewaitargument is available on theget_extractionandget_underwritingMCP tools. All additive — existing polling code is unaffected. - MCP server.
https://api.trycactus.com/mcpexposes the API as tools for Claude, Cursor, and any other MCP client, authenticated with your existing API key. See MCP Server. - Typed T-12 and offering memorandum results. T-12 extractions now
return
t12.v1: report metadata, the statement’s line items with every monthly value and its source cell, and aheadersarray typing each spreadsheet column as a calendarmonth, a namedtotal, orunknown. Filter totype: monthfor the trailing-twelve series — statements routinely carry extra columns, and a column is only called a total when the document names it one. Offering memorandum extractions now returnoffering_memorandum.v1: every extracted fact organized into the document’s own sections, plus apropertysummary of the key property attributes. Extractions that produce no data at all now fail and are refunded in full instead of returning an empty result. - Typed rent roll results. Rent-roll extractions now return
rent_roll.v1: a normalized unit table (typed headers, one entry per unit) and the as-of date. If the structured result cannot be produced, the extraction fails and the charge is refunded in full — you are never billed for output you didn’t receive. - Initial release. Documents, extractions, usage, and webhook endpoints
are live. Other document types return the
facts.v1schema — extracted values with confidence scores and source locations.