API documentation
Run DeedRead analyses programmatically from your own code, a workflow tool, or an AI agent. The API returns the same scored, structured report as the web app. DeedRead is a screening aid, not legal or appraisal advice.
Quick start
1. Create an account at deedread.ca/login.
2. Create an API key at deedread.ca/developer. Copy it; it is shown only once.
3. Send it as a bearer token on every request.
curl https://deedread.ca/api/v1/me \
-H "Authorization: Bearer dr_live_your_key_here"Authentication
Every request needs an Authorization: Bearer dr_live_... header (anx-api-key header also works). Keys are tied to your account and its plan or credits. Revoke a key any time on the developer page; it stops working immediately.
Endpoints
GET /api/v1/me
Verify a key and read the account's remaining entitlement.
{
"ok": true,
"key": { "id": "…", "name": "my agent" },
"account": { "email": "you@brokerage.ca" },
"billingEnabled": false,
"entitlement": { "canRun": true, "reason": "unmetered", "credits": 8, ... }
}POST /api/v1/analyze
Run one analysis. Provide at least one of listingUrl,listingText,documents, or manualData. Documents are passed as already-extracted text: extract your PDFs to text first, so original files never leave your side (this is the core of the DeedRead privacy model).
The optional context object is worth sending. The hold period (horizon) decides which dated capital projects are counted against the buyer and the period the appreciation scenarios compound over; use decides whether a rental bylaw is a headline finding or a footnote; stage sets how urgent the questions are. Every field defaults to unspecified, which is treated as genuinely unknown rather than guessed, and the report says so.
curl -X POST https://deedread.ca/api/v1/analyze \
-H "Authorization: Bearer dr_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"propertyType": "strata",
"listingUrl": "https://www.realtor.ca/real-estate/12345/...",
"listingText": "Asking price $748,000\n2 Bed 2 Bath\n876 sqft\nBright corner unit.",
"market": "Vancouver, BC",
"buyerProfile": "First-time buyer",
"notes": "Client is nervous about special levies",
"context": { "horizon": "long", "use": "owner_occupied", "stage": "writing_offer" },
"manualData": { "address": "101-123 Main St", "askingPrice": "899000" },
"documents": [
{ "name": "Depreciation Report", "text": "…extracted text…" },
{ "name": "Strata Minutes", "text": "…extracted text…" }
]
}'Response:
{
"ok": true,
"report": {
"property_type": "strata",
"strata_assess": { "score": 72, "band": "Solid", ... },
"property": { "address": "…", "asking_price": 899000 },
"flags": [ { "severity": "high", "text": "…", "citation": "Page 14" } ],
...
},
"documents_missing": ["Depreciation report"],
"charged": true
}documents_missing is present only when a document a confident score depends on (Form B and depreciation report for strata, title search for freehold) could not be found in the text you submitted. It is a heads-up, not an error: the analysis still runs, but the score is held conservative and the report says so. Send the missing document and re-run to lift it.
charged tells you whether this analysis consumed a report from the account. It is false when billing is off, when the property was already paid for (re-runs are free), or when every document you submitted arrived with no usable text. That last case also sets documents_unreadable to the number of documents involved: the analysis ran on the listing details alone, so it is not charged.
Analysis usually takes a few minutes and can occasionally run longer. Set a client timeout of at least five minutes. Rate limit is 30 analyses per key per hour. When billing is enabled, each successful analysis is charged to your plan or credits; a402 means you are out of both.
CLI
A dependency-free CLI ships in the repo at scripts/deedread-cli.mjs. SetDEEDREAD_API_KEY and run:
export DEEDREAD_API_KEY=dr_live_your_key_here
node scripts/deedread-cli.mjs whoami
node scripts/deedread-cli.mjs analyze \
--type strata \
--url "https://www.realtor.ca/real-estate/12345/..." \
--doc depreciation.txt --doc minutes.txt \
--notes "rush deal" --out report.jsonMCP (for AI agents)
DeedRead runs a hosted MCP server at https://deedread.ca/api/mcp. Point an MCP client at it with your API key and the assistant can run reports itself. It publishes three tools:start_analysis, get_analysis andcheck_entitlement.
Claude Code:
claude mcp add --transport http deedread https://deedread.ca/api/mcp \
--header "Authorization: Bearer dr_live_your_key_here"Claude Desktop and claude.ai take the same URL and header as a custom connector. ChatGPT connects with OAuth instead of a key: add the same URL as a connector and it walks you through signing in to DeedRead and approving access. Claude also supports the OAuth flow if you prefer it over pasting a key.
A report takes a few minutes, so start_analysis returns a job id straight away and get_analysis collects the finished report. Reports stay collectable for 60 minutes, and for 5 minutes after they are first collected. We do not keep report content beyond that.
One thing worth telling your clients: when you upload documents at deedread.ca they are read in your own browser and only the extracted text reaches us. Through an assistant it is different, because the assistant reads the documents first. The text passes through that assistant under its terms before it reaches DeedRead.
Async reports over REST
The same drop-off and collect flow is available without MCP. Add?async=1 and poll the job.
curl -sX POST "https://deedread.ca/api/v1/analyze?async=1" \
-H "Authorization: Bearer $DEEDREAD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"propertyType":"strata","listingUrl":"https://www.realtor.ca/real-estate/12345/..."}'
# => 202 {"ok":true,"job_id":"...","status":"running"}
curl -s "https://deedread.ca/api/v1/jobs/JOB_ID" \
-H "Authorization: Bearer $DEEDREAD_API_KEY"
# => {"status":"running"} ... then the full reportRunning MCP locally instead
If you would rather not use the hosted endpoint, a small stdio server wrapping the API works too. It needs only the official MCP SDK (@modelcontextprotocol/sdk) and your key.
// deedread-mcp.mjs (npm i @modelcontextprotocol/sdk)
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
const KEY = process.env.DEEDREAD_API_KEY
const BASE = process.env.DEEDREAD_API_BASE || 'https://deedread.ca'
const server = new McpServer({ name: 'deedread', version: '1.0.0' })
server.tool(
'analyze_property',
{
propertyType: z.enum(['strata', 'freehold']),
listingUrl: z.string().optional(),
address: z.string().optional(),
askingPrice: z.string().optional(),
documents: z.array(z.object({ name: z.string(), text: z.string() })).optional(),
notes: z.string().optional(),
},
async (args) => {
const res = await fetch(BASE + '/api/v1/analyze', {
method: 'POST',
headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
propertyType: args.propertyType,
listingUrl: args.listingUrl,
notes: args.notes,
manualData: { address: args.address, askingPrice: args.askingPrice },
documents: args.documents,
}),
})
const json = await res.json()
return { content: [{ type: 'text', text: JSON.stringify(json.report ?? json) }] }
}
)
await server.connect(new StdioServerTransport())Point your MCP client at it (Claude Desktop example):
{
"mcpServers": {
"deedread": {
"command": "node",
"args": ["/path/to/deedread-mcp.mjs"],
"env": { "DEEDREAD_API_KEY": "dr_live_your_key_here" }
}
}
}Discovery
Agents can read deedread.ca/llms.txt for a machine-readable summary of this API.
Questions or higher limits? Email info@deedread.ca.