Skip to main content
The Cactus Partner API ships a built-in MCP server at https://api.trycactus.com/mcp. Point an MCP-capable agent at it with your API key and the agent can upload documents, run extractions, start underwritings, and check usage — the same operations as the REST API, with the same authentication, billing, and rate limits. Use a sandbox key (ck_sandbox_...) while building: every tool works identically, results are instant fixtures, and nothing is billed. See Sandbox.
Connection details — URL https://api.trycactus.com/mcp, transport streamable HTTP, auth header Authorization: Bearer ck_.... There is no package to install and nothing to run locally; it’s a remote server.

Install

Add --scope project to write the server into the repo’s .mcp.json and share it with your team — keep the key out of a committed file by passing --header "Authorization: Bearer ${CACTUS_API_KEY}", which Claude Code expands from your environment.Verify with claude mcp list, or run /mcp inside Claude Code: cactus should show as connected with 21 tools.
Your API key authorizes real, billable work. Only configure it in agents you control, and prefer sandbox keys anywhere an agent runs unattended.

Your first extraction

With a sandbox key connected, no file upload is needed — the example documents are extractable by filename alone. Ask the agent:
Using the Cactus tools, register example-rent-roll-1.xlsx as a rent roll, extract it, and summarize the unit mix.
It will call upload_document → create_extraction → get_extraction → get_extraction_result on its own. The server describes those flows to the client on connect, so you don’t have to spell out the sequence. Moving to a live key changes two things: the agent must PUT the real file bytes to the presigned URL from upload_document before starting a job, and extractions take minutes instead of returning instantly.

Tools

Every tool maps onto a documented REST endpoint and returns the same JSON. Two ergonomic differences from the raw API:
  • create_extraction takes the three bundle document ids as flat parameters (offering_memorandum_document_id, rent_roll_document_id, t12_document_id) instead of a nested bundle object. Its asset_class parameter is required, exactly as on the endpoint - the tool description tells the agent to ask you rather than guess.
  • create_underwriting takes the property address as flat parameters (address_line1, city, state, postal_code, …). Its documents parameter is the same list of {document_id, role} objects the endpoint takes, and is required.
Each write tool also accepts an optional idempotency_key, with the same semantics as the Idempotency-Key header on the REST endpoints. get_extraction and get_underwriting also take wait — see below. With a live key, upload_document returns a presigned upload.url; the agent (or you) must HTTP PUT the raw file bytes to it before starting a job — the MCP server never handles file contents itself. Sandbox documents skip the upload entirely.

Answering questions about a report

Use these when your own assistant answers your users’ questions about a finished underwriting. Every tool returns data; your model writes the answer.
  1. get_underwriting_outline first: the headline numbers, the property, the critical assumptions with their impact, readiness, and a map of the report’s sections. Small enough to read on every question — unlike get_underwriting, which returns the whole report.
  2. get_underwriting_section for the one section a question needs: answer, income, revenue, cost, value, buyer, risk, next, critical_assumptions, readiness or property.
  3. get_underwriting_assumptions for the assumptions behind the numbers: value, unit, whether it can be changed, its options, confidence, support and impact. Percent values are fractions (0.058 = 5.8%).
  4. preview_underwriting_change for any “what if”: the saved model is recalculated with the changed assumptions and the figures come back before and after. Nothing is saved or charged.
Every figure is {state, value, reason}. known and zero are established values (zero is a real zero). unavailable means the figure couldn’t be established and reason says why — it is not zero. not_applicable means it doesn’t apply to the deal. The tool descriptions tell the model this, and to use previews rather than its own arithmetic.

Changing a report

A change is two calls with your user’s yes in between:
  1. request_underwriting_change records the change and its before/after preview and returns a chg_… id. Nothing runs. Show it to the user.
  2. confirm_underwriting_change, only after they agree, starts a new version: a new underwriting (new_underwriting_id) that re-runs the report with the changed assumptions. Poll it with get_underwriting; it fires underwriting.completed like any run.
The previous version stays exactly as it was. Each underwriting carries version and previous_underwriting_id; get_underwriting_versions lists the whole chain and latest_completed_id. A pending change expires after 30 minutes, a change to a version that already has a newer one is refused with underwriting_superseded, and confirming twice returns the same version. Changes are not charged today.

Acting for one of your users

If your server uses one Cactus key for many of your users, send X-Cactus-End-User: <end_user_ref> on the MCP connection (or any REST call). Every underwriting tool is then limited to that user’s underwritings — the same end_user_ref you passed to create_underwriting — and anything else is 404. Documents and extractions created on such a connection belong to that user too, and it only sees its own (documents your server uploaded without the header can’t be used from it — upload them with the header). Account-wide tools (get_usage, webhook endpoints) are refused with 403 not_available_for_end_user. It is a header your server sets, never a tool argument, so the model can’t widen its own access. Once your account has underwritings for more than your own company (any end_user_ref), MCP connections must send this header: underwriting tools called without it are refused with 403 end_user_required, so a connection that forgot it can never show one user another’s work. Server-to-server REST calls are unaffected.
Sandbox keys run the whole flow over the example report: assumptions, illustrative previews, and a confirmed change that completes instantly.

Waiting for results

With a live key, extraction and underwriting tools return immediately with status: processing — the job runs in the background. Getting the waiting part right is the main thing that separates a smooth agent run from a noisy one, so the server does most of it for you. Use wait instead of sleeping. get_extraction and get_underwriting accept wait (0-45 seconds). The API holds the request open and answers the moment the job reaches a terminal state, so a typical extraction takes a couple of tool calls and no timer:
This matters more for agents than for ordinary HTTP clients. Agent runtimes differ in how — and whether — they can sleep between tool calls, and one that sleeps wrong turns into a tight polling loop that reads the same processing back dozens of times. wait removes that failure mode entirely. A wait that elapses is not a timeout. You get a normal response with the job still in flight. Call again; nothing is lost and nothing is double-billed. In-flight responses tell you when to come back. While a job is running the body carries poll_after_seconds (the recommended interval, mirrored in the Retry-After header) and elapsed_seconds (how long it has been running). Prefer those to a hard-coded interval — status changes no faster than poll_after_seconds, so polling harder just burns rate limit. Expected durations and hard ceilings: Past the ceiling the job fails, and any billed lines are refunded in full. A job still reporting processing has not silently died. Rent rolls run in two phases, which is why they are the slow path. They extract, then consolidate into the typed rent_roll.v1 output, and both phases report status: processing. documents[].phase distinguishes them (extracting → consolidating), so a job moving between phases is visibly progressing rather than stuck. An agent that gives up after a few minutes will abandon rent rolls that were going to succeed. Sandbox keys complete everything instantly, so none of this applies there — which is also why an agent flow that looks instant in sandbox needs testing against a live key before you trust its waiting behaviour.

Billing and limits

Tool calls are ordinary API calls. Live extractions debit your usage pool at acceptance and refund automatically on failure; underwriting runs are not metered. The same rate limits apply either way. Ask the agent to call get_usage for the current balance. Underwritings are the long path and run for ~20-30 minutes. Keep unattended agents on a sandbox key.

Troubleshooting

The client connected but isn’t sending the header. Check that the value includes the Bearer prefix and that it’s configured as a header — not a URL query parameter or an OAuth setting.
The key itself was rejected. 401 means it’s unknown or revoked; 403 means it lacks the scope for that operation. See Authentication.
Use the exact URL https://api.trycactus.com/mcp — no trailing slash — with streamable HTTP transport (not SSE, not stdio). A plain browser GET of that URL returning 405 is expected; it’s a POST endpoint.
Requests are capped at 60 seconds. Extractions and underwritings are asynchronous by design: create_* returns immediately and the agent polls get_*. Retry a timed-out poll — no work is lost. wait is capped at 45 seconds so a long poll always answers inside the cap.
It’s guessing an interval. Have it pass wait to get_extraction / get_underwriting and follow poll_after_seconds from the response rather than sleeping on its own schedule — see Waiting for results.
Check elapsed_seconds against the ceilings above, and documents[].phase for rent rolls — consolidating means the second phase is running normally. Jobs cannot exceed their ceiling; past it they fail and refund in full, so processing always means still working.
This documentation site also exposes its own MCP server at https://docs.trycactus.com/mcp (search and read the docs — no API access). Connect both to give an agent the reference material and the ability to act on it.