Ezel API Reference
Build on Ezel: list matters, ask cited questions over your own documents, search U.S. case law, upload files, and run review tables, all over a simple REST API. The same account also works as a remote MCP server for Claude, Cursor, and Claude Code.
The API is organised around your matters (cases or projects) and the documents inside them. Every response is JSON. All requests are made over HTTPS to:
Base URL
https://app.ezel.ai/api/v1
Anything you can do in the Ezel app, you can do with the API, gated by the same plan and permissions on your account. Two things you get beyond a typical assistant API: real, citable U.S. case law (not a model's memory), and keyword or regex search across your own uploaded documents.
/llms.txt for a
concise index, or /llms-full.txt for this whole
reference as one plain-markdown file.# Verify your key
curl https://app.ezel.ai/api/v1/whoami \
-H "Authorization: Bearer ezel_live_..."
# Ask a question, grounded in a matter
curl https://app.ezel.ai/api/v1/matters/MATTER_ID/ask \
-H "Authorization: Bearer ezel_live_..." \
-H "Content-Type: application/json" \
-d '{"question":"What is the effective date?"}'
Authentication
Authenticate every request with a personal API key, sent as a bearer token. Keys look like ezel_live_... and are created (and revoked) in Settings → Connector. The full key is shown once at creation; Ezel only ever stores a hash, so keep it somewhere safe.
Send it in the Authorization header. For tools that cannot set that header, X-API-Key is also accepted.
curl https://app.ezel.ai/api/v1/whoami \
-H "Authorization: Bearer ezel_live_..."
{
"user_id": "5d6bbf5e-...",
"full_name": "Alex Morgan",
"plan": "Pro",
"scopes": ["read", "write"],
"enabled_features": ["matters", "chat", "caselaw"]
}
Scopes
Each key carries one or more scopes, so you can hand out keys that can read but not change anything.
| read | Every query endpoint: whoami, usage, listing and reading matters and files, ask, search, case law, and reading review tables. |
| write | Mutations: create or delete a matter, upload or delete a file, and create or run a review table. |
A request that needs a scope the key lacks returns 403 with type insufficient_scope. The MCP connector only ever uses read.
{
"error": {
"type": "insufficient_scope",
"message": "This key lacks the 'write' scope required for this endpoint."
}
}
Rate limits
Requests are limited per key, per category, over a rolling 60 second window. Every response carries RateLimit-* headers; exceeding a limit returns 429 with a Retry-After header.
| Ask | 20 / min | The completion endpoints (/ask). |
| Search | 60 / min | Document search and case law. |
| Write | 60 / min | Creating, uploading, deleting, running tables. |
| Default | 150 / min | Everything else (reads). |
Token usage limits from your plan are enforced separately, on top of these.
RateLimit-Limit: 20
RateLimit-Remaining: 18
RateLimit-Reset: 47
Errors
Errors use standard HTTP status codes and a consistent JSON body with a stable type and a human message.
| 400 | Invalid request (bad_request). |
| 401 | Missing or invalid key (unauthorized). |
| 403 | Missing scope or feature not enabled. |
| 404 | Resource not found. |
| 429 | Rate limit or usage limit exceeded. |
{
"error": {
"type": "not_found",
"message": "Matter not found."
}
}
Whoami
Returns the account behind the key: name, plan, the key's scopes, and which features are enabled. The quickest way to verify a key works.
Scopes
read
curl https://app.ezel.ai/api/v1/whoami \
-H "Authorization: Bearer ezel_live_..."
Usage
Returns the account's storage usage and current message-limit status.
Scopes
read
{
"plan": "Pro",
"storage": { "used_bytes": 13250667, "limit_bytes": 53687091200, "used_percent": 0.02 },
"messages": { "within_limits": true, "status": "...", "reset_at": null }
}
List matters
Lists the caller's matters, pinned first, each with a document count.
Scopes
read
curl https://app.ezel.ai/api/v1/matters \
-H "Authorization: Bearer ezel_live_..."
{
"matters": [{
"id": "2a951d7c-...",
"name": "Acme acquisition",
"client_name": "Acme Corp",
"document_count": 6
}],
"total": 1
}
Create matter
Creates a new matter. The name must be unique within your account.
Scopes
write
Body
| name | string required |
The matter name. |
| client_name | string | Optional client label. |
| description | string | Optional description. |
curl https://app.ezel.ai/api/v1/matters \
-H "Authorization: Bearer ezel_live_..." \
-H "Content-Type: application/json" \
-d '{"name":"Acme acquisition","client_name":"Acme Corp"}'
{ "matter": { "id": "7d51cbad-...", "name": "Acme acquisition", "document_count": 0 } }
Get matter
Returns a single matter with its document count.
Scopes
read
Path parameters
| matter_id | uuid required |
The matter's id. |
curl https://app.ezel.ai/api/v1/matters/MATTER_ID \
-H "Authorization: Bearer ezel_live_..."
Delete matter
Deletes a matter. Its files and chats are not deleted; they are released back to your drive (their matter_id is cleared).
Scopes
write
curl -X DELETE https://app.ezel.ai/api/v1/matters/MATTER_ID \
-H "Authorization: Bearer ezel_live_..."
{ "deleted": true, "released_documents": 6, "released_chats": 2 }
List files
Lists the documents in a matter, newest first.
Scopes
read
curl https://app.ezel.ai/api/v1/matters/MATTER_ID/files \
-H "Authorization: Bearer ezel_live_..."
Upload file
Uploads a document into a matter as multipart/form-data. The file is stored, its text indexed for search, and scanned PDFs are OCR'd in the background so they become searchable and answerable. Max 20 MB per file.
Scopes
write
Form fields
| file | binary required |
The document to upload. |
curl https://app.ezel.ai/api/v1/matters/MATTER_ID/files \
-H "Authorization: Bearer ezel_live_..." \
-F "[email protected]"
{ "file": { "id": "b5733b49-...", "filename": "contract.pdf", "file_size": 109223 } }
Delete file
Removes a file from a matter and deletes it from storage.
Scopes
write
curl -X DELETE https://app.ezel.ai/api/v1/matters/MATTER_ID/files/FILE_ID \
-H "Authorization: Bearer ezel_live_..."
Ask
Ask a general U.S. legal question. Ezel's research mode is on, so the agent can reach real case law on its own. The answer comes back with any citations and sources. Not scoped to your documents; for that, use Ask a matter.
Scopes
read
Body
| question | string required |
The question to answer. |
| stream | boolean | When true, the response is a text/event-stream of SSE frames instead of one JSON body. |
curl https://app.ezel.ai/api/v1/ask \
-H "Authorization: Bearer ezel_live_..." \
-H "Content-Type: application/json" \
-d '{"question":"What is the holding of Gideon v. Wainwright?"}'
{
"answer": "Gideon v. Wainwright held that...",
"citations": [],
"sources": [],
"conversation_id": "87a96194-..."
}
Ask a matter
Ask a question answered from the documents in a specific matter. The answer is grounded only in that matter's files, with citations back to them. Supports the same stream flag as Ask.
Scopes
read
Body
| question | string required |
The question to answer. |
| stream | boolean | Stream the answer as SSE. |
curl -N https://app.ezel.ai/api/v1/matters/MATTER_ID/ask \
-H "Authorization: Bearer ezel_live_..." \
-H "Content-Type: application/json" \
-d '{"question":"List the parties.","stream":true}'
data: {"type":"delta","text":"The "}
data: {"type":"delta","text":"parties "}
data: {"type":"done","answer":"...","citations":[]}
data: [DONE]
Search documents
Keyword or regex search across a matter's documents, returning page-level snippets. Useful when you want exact matches rather than an AI answer. Pass terms (literal) or regex.
Scopes
read
Body
| terms | string | string[] | Literal terms. OR by default. |
| regex | string | A POSIX regular expression, case-insensitive. |
| match_all | boolean | Require all terms (AND) instead of any. |
| count_only | boolean | Return per-document match counts instead of snippets. |
curl https://app.ezel.ai/api/v1/matters/MATTER_ID/search \
-H "Authorization: Bearer ezel_live_..." \
-H "Content-Type: application/json" \
-d '{"terms":["indemnify","indemnification"],"match_all":false}'
{
"results": [{
"document_id": "3ff8...",
"filename": "contract.pdf",
"page": 4,
"snippet": "...shall indemnify and hold harmless..."
}]
}
Search case law
Search real, citable U.S. case law (via CourtListener), ranked by relevance. Returns case name, court, date, citation, and a cluster_id you can pass to Get a case for the full opinion.
Scopes
read
Query parameters
| query | string required |
Case name, topic, or legal principle. |
| jurisdiction | string | Optional court or jurisdiction filter. |
| year_start, year_end | integer | Optional decision-year range. |
| published_only | boolean | Only precedential opinions. |
curl "https://app.ezel.ai/api/v1/caselaw/search?query=right+to+counsel" \
-H "Authorization: Bearer ezel_live_..."
{
"cases": [{
"case_name": "Gideon v. Wainwright",
"court": "Supreme Court of the United States",
"date_filed": "1963-03-18",
"citation": "372 U.S. 335",
"cluster_id": 8954562
}],
"count": 7
}
Get a case
Fetches the full opinion text and metadata for a case, using a cluster_id from case law search. Long opinions are truncated with truncated: true.
Scopes
read
curl https://app.ezel.ai/api/v1/cases/110254 \
-H "Authorization: Bearer ezel_live_..."
{
"case_name": "Rhode Island v. Innis",
"court": "Supreme Court of the United States",
"citation": "446 U.S. 291",
"plain_text": "Mr. Justice Stewart delivered...",
"truncated": false
}
Create review table
Creates a review table: a grid of documents (rows) by questions (columns), where each cell is an AI-extracted answer with a cited quote and page. Define the columns and the documents in one call; the table starts filling immediately unless you pass run: false. Poll Get table for cell status.
Scopes
write
Body
| columns | object[] required |
Each { header, prompt, type }. The prompt is the question asked of every document. |
| all_documents | boolean | Add every document in the matter as a row. |
| document_ids | uuid[] | Or add specific documents. |
| name | string | Optional table name. |
curl https://app.ezel.ai/api/v1/matters/MATTER_ID/review-tables \
-H "Authorization: Bearer ezel_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Key terms",
"columns": [
{"header":"Parties","prompt":"Who are the parties?","type":"text"},
{"header":"Date","prompt":"Effective date?","type":"date"}
],
"all_documents": true
}'
Get review table
Returns the table with its columns, rows, and cells. Each cell has a status (pending, running, done, not_found, or error), and when done, the extracted value plus a cited quote and page. Poll this while a table fills.
Scopes
read
{
"table": { "id": "bd85...", "status": "ready" },
"columns": [{ "header": "Parties" }],
"rows": [{ "filename": "contract.pdf" }],
"cells": [{ "status": "done", "value": "Acme Corp; Beta LLC" }]
}
Run review table
Fills any pending cells and retries errored ones. Cells normally fill automatically as columns and documents are added, so this is mostly for retries.
Scopes
write
curl -X POST https://app.ezel.ai/api/v1/review-tables/TABLE_ID/run \
-H "Authorization: Bearer ezel_live_..."
Connect over MCP
Ezel is also a remote Model Context Protocol server, so you can use it directly from Claude (claude.ai and Desktop), Cursor, Claude Code, and ChatGPT. It exposes a read-only slice of this API as MCP tools.
Server URL
https://app.ezel.ai/mcp
Claude connects with OAuth: add a custom connector with the Server URL, sign in, and click Allow. No API key needed. Cursor, Claude Code, and others use an API key as a bearer token, the same key as the REST API.
claude mcp add ezel \
--transport http https://app.ezel.ai/mcp \
--header "Authorization: Bearer ezel_live_..."