DeedRead API reference
Last updated: September 2, 2026
For adding DeedRead to Claude or ChatGPT, use the connector setup guide. This page is the REST, CLI, and MCP contract for people writing code.
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 REST request needs an Authorization: Bearer dr_live_... header (an x-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.
The hosted MCP server at https://deedread.ca/api/mcp accepts that same API key or an OAuth 2.1 access token from this site. ChatGPT uses OAuth. Claude can use either.
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. Set DEEDREAD_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 tools
The hosted server at https://deedread.ca/api/mcp publishes three tools: start_analysis, get_analysis and check_entitlement. Realtors should add it from the connector guide, not from a CLI.
A report takes a few minutes, so start_analysis returns a job id straight away and get_analysis collects a short summary plus a download link to the client-facing report. Reports stay collectable for 60 minutes. On this REST collect path they also stay readable for 5 minutes after first collect. We do not keep report content beyond that.
Claude Code can add the same URL with a header if you are working in a terminal:
claude mcp add --transport http deedread https://deedread.ca/api/mcp \
--header "Authorization: Bearer dr_live_your_key_here"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 local-stdio 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.