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 13 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_documentcreate_extractionget_extractionget_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, …).
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.

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 (extractingconsolidating), 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.