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'
| endpoint | what |
|---|---|
| POST /verify | text (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=1 | the same, then a Server-Sent Events stream: one event per citation as the deep check lands. |
| POST /render?format=pdf|md|docx | the 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 /coverage | the coverage statement and the storage notice, as JSON. |
| GET /health | register dump date, refresh date, deep-check budget. |
| GET /me | the plan, this month's usage and the keys on the account behind the key. |
| /v1/briefs | saved 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. |
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.
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.
| client | how |
|---|---|
| Claude Desktop | download proofread-mcp-<version>.mcpb and open it (Settings, Extensions). The only setting is the optional key. |
| Claude Code | claude 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 client | npx 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.
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
| endpoint | what |
|---|---|
| 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/resolve | up to 500 citations in one call, the same object each, in order. |
| POST /v1/extract | the 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/coverage | the 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 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.
| tier | meaning |
|---|---|
| red | check this: not in the register, a different case at that citation, the named case at another citation, or quoted language not in the opinion. |
| orange | cannot verify: Westlaw or Lexis id, a volume newer than the register, a recent case the register may lack. |
| green | found: register name, court, date, and the quotation if there was one. |
| white | deep check ran: passage found, likely, not confirmed, or may state the opposite, with a confidence. |
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.