Skip to main content
Breaking changes to result schemas ship as a new 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-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_seconds and the wait long-poll are there so you never have to guess.
  • asset_class is now required on POST /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 — so market_rent, area, bedrooms, bathrooms, bed_bath, move_in, move_out, deposit, concession, other_income, floor, furnished, escalation, subsidized and subsidy_amount stopped being standard fields and came back under custom__* keys named after whatever the source document’s header happened to say. Two rent rolls of the same asset class could therefore disagree — custom__sqft in one, custom__sq_feet in the next — and nothing in the response explained why. Requests without the field now return 400 invalid_request naming asset_class, rather than quietly returning the narrower shape. Accepted values are unchanged: multifamily, self-storage, industrial-outdoor-storage, commercial, industrial, office, retail, hotel. The asset_class argument on the create_extraction MCP 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 403 with the new product_not_enabled code and a message naming what to do about it, rather than working. The existing missing_scope code 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-inputs now 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 from headers and from every line_items entry, so a statement’s monthly figures could silently under-report the year by a month. A new t12_column_coverage validation check reports warning whenever a column that carried data was dropped because its header could not be interpreted — t12_month_mapping only describes the columns that were read, so it could not surface this.
  • Long polling and richer job status. GET /v1/extractions/{id} and GET /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 normal 200. In-flight responses now include poll_after_seconds (mirrored in a Retry-After header) and elapsed_seconds, and rent-roll documents report phase (extractingconsolidating) so two-phase jobs are visibly progressing. The same wait argument is available on the get_extraction and get_underwriting MCP tools. All additive — existing polling code is unaffected.
  • MCP server. https://api.trycactus.com/mcp exposes 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 a headers array typing each spreadsheet column as a calendar month, a named total, or unknown. Filter to type: month for 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 return offering_memorandum.v1: every extracted fact organized into the document’s own sections, plus a property summary 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.v1 schema — extracted values with confidence scores and source locations.