Build on CaseCanon
A REST API and an MCP server for published US opinions, structured briefs and the Legal Graph. Use them from Python, Node.js or any HTTP client.
Your first request in two minutes
Get an API key, run a search, fetch a brief. Everything is plain REST with JSON responses.
Get your API key
Log in, open Account settings, then API keys. Keys start with
sk-cc-.Run a search
bashcurl -X POST https://api.casecanon.ai/v1/search \ -H "X-API-Key: sk-cc-your-key" \ -H "Content-Type: application/json" \ -d '{"query": "non-compete enforceability Florida", "limit": 5}'Fetch a brief
bashcurl https://api.casecanon.ai/v1/briefs/608-F.3d-724 \ -H "X-API-Key: sk-cc-your-key"
Three ways to send your key
Send your key in a header, a query parameter or a Bearer token. If you send more than one, they're checked in this order.
# 1. X-API-Key header (recommended)
curl -H "X-API-Key: sk-cc-your-key" ...
# 2. Query parameter (Claude custom connector)
https://mcp.casecanon.ai/mcp?api_key=sk-cc-your-key
# 3. Bearer token
curl -H "Authorization: Bearer sk-cc-your-key" ...?api_key=. It's the simplest setup.Connect a Python agent
Use the official MCP SDK. It works with LangChain, LlamaIndex, smolagents and any framework that speaks MCP.
pip install mcpimport asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
MCP_URL = "https://mcp.casecanon.ai/mcp?api_key=sk-cc-your-key"
async def main():
async with streamablehttp_client(MCP_URL) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
for tool in tools.tools:
print(f"- {tool.name}: {tool.description[:60]}")
result = await session.call_tool(
"search_opinions",
arguments={"query": "qualified immunity false arrest", "limit": 5},
)
print(result.content[0].text)
asyncio.run(main())Connect a Node.js app
Use the official TypeScript SDK. It runs on Node.js, Deno and Bun.
npm install @modelcontextprotocol/sdkimport { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const MCP_URL = "https://mcp.casecanon.ai/mcp?api_key=sk-cc-your-key";
const client = new Client({ name: "my-app", version: "1.0.0" });
await client.connect(new StreamableHTTPClientTransport(new URL(MCP_URL)));
const result = await client.callTool({
name: "search_opinions",
arguments: { query: "ADA disability employment", court: "ca11", limit: 5 },
});
const data = JSON.parse(result.content[0].text);
data.results.forEach(o => console.log(`${o.citation} — ${o.name}`));
await client.close();Semantic search
POST https://api.casecanon.ai/v1/search
import httpx
resp = httpx.post(
"https://api.casecanon.ai/v1/search",
headers={"X-API-Key": "sk-cc-your-key"},
json={
"query": "tortious interference identified customers",
"court": "fladistctapp",
"date_from": "1990-01-01",
"limit": 10,
"include_graph": True,
},
)
data = resp.json()
print(f"{data['total']} opinions")| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | required | Your question in plain English |
court | string | optional | Court code: fla, fladistctapp, ca11, flsd, flmd, flnd |
date_from | string | optional | ISO date, e.g. 1990-01-01 |
date_to | string | optional | ISO date, e.g. 2026-12-31 |
include_graph | boolean | optional | Adds citing and cited opinions to each result |
limit | integer | optional | Up to 20 (default 10) |
Briefs and citations
Fetch a brief by official citation or CaseCanon ID, then follow the Legal Graph to the opinions that cite it.
# Full brief by citation
brief = httpx.get(
"https://api.casecanon.ai/v1/briefs/608-F.3d-724",
headers={"X-API-Key": "sk-cc-your-key"},
).json()
print(brief["issues"]) # Questions the court answered
print(brief["disposition"]) # Affirmed / Reversed / Remanded
# Who cites this opinion?
cited = httpx.get(
"https://api.casecanon.ai/v1/briefs/608-F.3d-724/cited-by?limit=20",
headers={"X-API-Key": "sk-cc-your-key"},
).json()
print(f"Cited by {cited['total']} opinions")Error codes
Every error returns JSON with an error field.
| Code | Meaning | What to do |
|---|---|---|
401 | API key missing | Add the X-API-Key header |
403 | API key invalid or expired | Create a new key in Account settings |
404 | Opinion not found | Check the citation or ID |
422 | Invalid parameter | Check the parameter values above |
429 | Daily limit reached | Wait for the reset or upgrade to Pro |
500 | Server error | Try again, or contact us |
import httpx
try:
resp = httpx.get(
"https://api.casecanon.ai/v1/briefs/not-a-citation",
headers={"X-API-Key": "sk-cc-your-key"},
)
resp.raise_for_status()
except httpx.HTTPStatusError as e:
print(e.response.status_code, e.response.json().get("error"))Limits by plan
Limits apply per API key and reset every day at midnight Eastern.
| Plan | Searches a day | Results per request | MCP |
|---|---|---|---|
| No account | 3 | 10 | No |
| Free | 5 | 10 | No |
| Pro ($29.99/month) | Unlimited | 20 | Yes |