A quarter of the citations in filed briefs are Westlaw or Lexis ids. We list them, we do not guess.Coverage ›

The same report,
as JSON

One request, one report. Every flag carries its evidence; every response carries the coverage statement.

curl -H 'Authorization: Bearer pl_…' -F file=@brief.pdf https://proofread.law/verify
curl -H 'Authorization: Bearer pl_…' -H 'Content-Type: application/json' -d '{"text": "…"}' 'https://proofread.law/verify?doc=1'
endpointwhat
POST /verifytext (JSON), PDF, DOCX or TXT (multipart). Returns the report: summary, rows with tier, headline, detail, evidence and span. ?doc=1 echoes your text so a client can render the marks.
POST /verify?deep=1the same, then a Server-Sent Events stream: one event per citation as the deep check lands.
POST /render?format=pdf|md|docxthe report back as the PDF record, Markdown, or a plain .docx copy of your draft with a comment on every flagged citation (formatting is not preserved).
GET /coveragethe coverage statement and the storage notice, as JSON.
GET /healthregister dump date, refresh date, deep-check budget.
GET /methe plan, this month's usage and the keys on the account behind the key.
/v1/briefssaved briefs, opt-in: POST {text, title} checks and keeps the text and its report encrypted in the account (one check); GET lists them; GET, PUT {text} (re-check, a new version) and DELETE on /v1/briefs/{id}; GET /v1/briefs/{id}/versions/{v}. Nothing else is stored.

For agents

An agent can open its own account, get a key, check a draft and hand its owner a billing link, in three requests, about ten seconds end to end. Paste the onboarding prompt into any agent, or point it at /agent/onboarding.md and /llms.txt.

POST /agent/signup          {"email": "owner@firm.com", "agent_name": "brief-bot"}     -> 201 {"api_key": "pl_…"}   shown once
POST /verify                Authorization: Bearer pl_…   {"text": "…"}                    -> the report
POST /agent/checkout-link   Authorization: Bearer pl_…   {"plan": "payg"}                 -> {"checkout_url": "https://checkout.stripe.com/…"}

The free tier works before any payment (20 checks, 3 deep checks, 1,000 resolves a month, one key). The owner gets one confirmation email; an unconfirmed account cannot buy anything, and signing up again with the same address rotates a lost key until it is confirmed.

MCP server

Works with Claude and with OpenAI agents. The same check as a tool your agent can call: proofread-mcp (open source, MIT) on npm and in the MCP Registry. Eight tools: check_citations, check_document (PDF, DOCX, TXT), resolve_citation, resolve_citations, coverage, render_report, sign_up and billing_link. Needs Node 20; no install step, npx fetches it. A key is optional: without one the free tier applies per address.

clienthow
Claude Desktopdownload proofread-mcp-<version>.mcpb and open it (Settings, Extensions). The only setting is the optional key.
Claude Codeclaude mcp add proofread -- npx -y proofread-mcp (add -e PROOFREAD_API_KEY=pl_… for a key)
Cursor, any stdio client{"mcpServers": {"proofread": {"command": "npx", "args": ["-y", "proofread-mcp"], "env": {"PROOFREAD_API_KEY": "pl_…"}}}}
OpenAI Agents SDK, any HTTP clientnpx proofread-mcp --http --port 3333, then point the client at http://127.0.0.1:3333/mcp (give the session up to 15 minutes; a deep check takes 1 to 2 s per citation)

What leaves the machine: the text or file being checked, over HTTPS to proofread.law, in the request that checks it; nothing is stored. sign_up sends the owner's email and the agent's name. No telemetry.

The register itself

curl 'https://proofread.law/v1/resolve?cite=590+U.S.+644'
curl -H 'Content-Type: application/json' -d '{"cites": ["590 U.S. 644", "925 F.3d 1339"]}' https://proofread.law/v1/resolve
curl -H 'Content-Type: application/json' -d '{"text": "…"}' https://proofread.law/v1/extract
endpointwhat
GET /v1/resolve?cite=one citation: found, ambiguous, not_found, unverifiable (recent; the register may lack it), beyond_register (volume newer than the dump), known_cite (other opinions cite it), unresolvable (Westlaw or Lexis id), unparsed; the case, the coverage of that volume, the freshness date.
POST /v1/resolveup to 500 citations in one call, the same object each, in order.
POST /v1/extractthe citations in a text, each resolved, with character spans. Nothing is stored.
GET /v1/case/{id}one case; ?text=1 returns the opinion text on Solo and Firm.
GET /v1/coveragethe coverage statement, dump and refresh dates, and the highest volume held per reporter; ?jurisdiction=ch gives the Swiss register per court with the share of the live index held.

Free: 1,000 resolved citations a month. Solo: 5,000. Firm: 50,000. An absence is never a verdict: not_found comes with the volume's coverage, and a recent citation the register lacks is unverifiable.

Keys

Keys come with the Firm plan: up to five, created and revoked on the account page, shown once. Send one as Authorization: Bearer pl_…. Without a key the request counts against the free tier of the calling address.

Tiers

tiermeaning
redcheck this: not in the register, a different case at that citation, the named case at another citation, or quoted language not in the opinion.
orangecannot verify: Westlaw or Lexis id, a volume newer than the register, a recent case the register may lack.
greenfound: register name, court, date, and the quotation if there was one.
whitedeep check ran: passage found, likely, not confirmed, or may state the opposite, with a confidence.

Limits and errors

Free: 20 checks and 3 deep checks a month, 20 requests an hour. Firm: 600 requests an hour, 15,000 deep-checked citations a month. 10 MB per request. Errors are {"error": {"code", "message"}}: 402 plan_required when a feature is on another plan, 429 quota_exceeded or rate_limited (with retry_after), 413 too_large, 422 unreadable. The full schema is in the interactive reference.