OpenCase Logo
v1 · Live

Public API

Integrate citation resolution, citation verification, and legal Q&A into your applications.

Copies the full guide as Markdown for Claude, ChatGPT, Cursor, or similar — uses $OC_API_KEY, not your live key.

BASE URLhttps://public-api.opencase.com/v1

01Authentication

All requests require a bearer token. Include your API key in the Authorization header on every call.

AuthorizationBearer $OC_API_KEY
Content-Typeapplication/json

Keep your API key out of client-side or browser code. Always store it in server-side secure storage and rotate keys regularly.

02Request / response contract

Request tracing

Pass a unique X-Request-Id header on every request. The same ID is echoed back as requestId in the response.

Success

JSON
{
  "data": { /* endpoint-specific payload */ },
  "requestId": "req_abc123"
}

Error

JSON
{
  "error": {
    "code": "payment_required",
    "message": "Insufficient balance",
    "requestId": "req_abc123"
  }
}

All JSON field names use camelCase.

03Billing & wallet

Billing is tracked against a prepaid USD wallet. Each billable endpoint maps to a SKU.

citations_resolve

$15

per 1,000 units

citations_verify

$15

per 1,000 units

answers

$125

per 1,000 units

case_brief

$10

per 1,000 units

documents_review

$100

per 1,000 reviews

The case law library reads are unbilled, apart from the AI case brief. They share the `citations/resolve` rate-limit bucket rather than having their own.

Enforcement

  • If wallet balance is <= 0 before work begins, the request fails immediately with 402 payment_required
  • If balance is positive but lower than the computed charge, the request may still complete — the full charge then applies
  • Rate-limited requests (429) are never billed

04Rate limits

When throttled, the API returns HTTP 429 with a Retry-After header indicating how many seconds to wait.

429 rate_limitedRead Retry-AfterExponential backoff + jitterRetry

Limit total retry attempts and alert on sustained throttling — it signals you need to review throughput or upgrade your plan.

05Health

GET/healthLiveness check
curl
curl -sS "https://public-api.opencase.com/v1/health"
GET/health/readyReadiness check

Returns 200 when the API and its dependencies are healthy.

curl
curl -sS "https://public-api.opencase.com/v1/health/ready"

06Resolve citations

POST/citations/resolveResolve citation strings
citations_resolve — $15 / 1,000 units

Body: `{ citations: string[], searchMode: string }`

curl
curl -sS -X POST "https://public-api.opencase.com/v1/citations/resolve" \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: resolve-001" \
  -d '{
    "citations": [
      "Brown v. Board of Education, 347 U.S. 483 (1954)"
    ],
    "searchMode": "keyword"
  }'

Each result carries `caseStatus.caseId`. Pass it to any `/cases/{caseId}` read below.

07Verify citations

POST/citations/verifyValidate citations in plain text
citations_verify — $15 / 1,000 units

Body: exactly one of `{ text: string }` or `{ url: string }`, plus `country`. Text of 100,000 characters or fewer is verified inline and returns `200`; a `url`, or longer text up to the async ceiling, is queued and returns `202` with a `jobId`. Text past that ceiling returns `413 document_too_large` naming the limit that rejected it.

curl
curl -sS -X POST "https://public-api.opencase.com/v1/citations/verify" \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: verify-001" \
  -d '{
    "text": "In Brown v. Board of Education, 347 U.S. 483 (1954), ...",
    "country": "US"
  }'

# A url is always queued: we cannot know the document's size until we fetch it.
curl -sS -X POST "https://public-api.opencase.com/v1/citations/verify" \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: verify-002" \
  -d '{
    "url": "https://example.com/appellate-brief.pdf",
    "country": "US"
  }'
# -> 202 { "data": { "jobId": "...", "status": "queued" }, "requestId": "..." }

Extraction reads US citation forms only — send a non-US `country` to `/citations/resolve` with the citation strings instead. Every response carries `extractedCount`, the number of citations found, so zero found is not mistaken for a clean document; one request verifies up to 50 and sets `truncated: true` beyond that. A `url` must point at a PDF or DOCX — a web page is not a document.

GET/jobs/:jobIdRead an async verify job

Returns `queued`, `running`, `completed` or `failed`. A completed job carries `result`, the same payload the inline response would have.

curl
curl -sS "https://public-api.opencase.com/v1/jobs/$JOB_ID" \
  -H "Authorization: Bearer $OC_API_KEY"

Never billed. Poll every 2s for the first 30s, then every 10s — `Retry-After` carries the same advice. Polling has its own rate-limit bucket of 600 requests a minute, so waiting on a job does not spend the quota you need for resolve or verify.

08Generate answers

POST/answersGenerate legal answers
answers — $125 / 1,000 units

Body: `{ question: string, country?: string }`. Optional `states`, `circuits` and `subdivisions` arrays narrow the jurisdiction the answer is written for. Omitting `country` searches every supported country.

curl
curl -sS -X POST "https://public-api.opencase.com/v1/answers" \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: answers-001" \
  -d '{
    "question": "What is the current federal standard for summary judgment?",
    "country": "US",
    "states": ["California"]
  }'
JSON
{
  "data": {
    "answer": "## Short answer\nThe movant must satisfy [[case: Winter v. NRDC, 555 U.S. 7 (2008)]] ...[1]",
    "sources": [
      { "title": "Winter v. NRDC", "url": "https://...", "published": "2008-11-12" }
    ],
    "citations": [
      { "citation": "Winter v. NRDC, 555 U.S. 7 (2008)", "type": "case", "status": "verified", "url": "https://..." }
    ],
    "stats": { "total": 1, "verified": 1, "unverified": 0 }
  },
  "requestId": "req_abc123"
}

09Find a case

POST/cases/lookupFind a case id from a citation, or from an id you already hold

Body: `{ citation }` or `{ caseId }`. `caseId` takes either shape — a `caseStatus.caseId` from resolve, or an `authority.legalContentId`. Unbilled.

curl
curl -sS -X POST "https://public-api.opencase.com/v1/cases/lookup" \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "citation": "Brown v. Board of Education, 347 U.S. 483 (1954)"
  }'

Always 200. `status` is `found` (use `caseId`), `not_in_library` (the decision exists but is not indexed here — a `courtListenerUrl` is returned), or `unresolved`. POST rather than GET so your citations never appear in a request URL or a server access log.

10Case law library

GET/cases/:caseId/treatmentsCiting decisions and how they treated this case

Query: `limit`, `cursor`, `filter` (`all` | `negative`), `sort`.

curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/treatments?filter=negative" \
  -H "Authorization: Bearer $OC_API_KEY"
GET/cases/:caseId/headnotesCurated headnotes extracted for this case
curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/headnotes" \
  -H "Authorization: Bearer $OC_API_KEY"
GET/cases/:caseId/headnotes/:headnoteId/citationsThe citing decisions supporting one headnote
curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/headnotes/$OC_HEADNOTE_ID/citations" \
  -H "Authorization: Bearer $OC_API_KEY"
GET/cases/:caseId/direct-historyReversals or vacaturs on direct appeal
curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/direct-history" \
  -H "Authorization: Bearer $OC_API_KEY"
GET/cases/:caseId/authoritiesThe cases this opinion relies on, and their current validity

Each item carries `citedCaseFlag` — the reason to read this list.

curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/authorities" \
  -H "Authorization: Bearer $OC_API_KEY"
GET/cases/:caseId/summariesHow other courts have described this case

Grouped, with `caseCount` = distinct citing cases that used that description.

curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/summaries" \
  -H "Authorization: Bearer $OC_API_KEY"
GET/cases/:caseId/summaries/:groupId/citationsThe citing decisions behind one summary group
curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/summaries/$OC_GROUP_ID/citations" \
  -H "Authorization: Bearer $OC_API_KEY"
GET/cases/:caseId/briefAI case brief
case_brief — $10 / 1,000 units

Returns `{ summary, judges }`. Billed 1 unit per request on success. A case with no brief returns 404 rather than an empty one, so you are never charged for a blank result.

curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/brief" \
  -H "Authorization: Bearer $OC_API_KEY"

The only billed read in this group — the summary was written by a model, not extracted.

GET/cases/:caseId/bodyOpinion text

Returns `{ format, content }`. `format` is `html`, `markdown`, or `text` when the stored provenance is unrecorded.

curl
curl -sS "https://public-api.opencase.com/v1/cases/$OC_CASE_ID/body" \
  -H "Authorization: Bearer $OC_API_KEY"

Requires opinion content to be enabled on your account; otherwise `403 content_not_enabled`. `html` is stored as CourtListener supplied it — sanitize before rendering. `text` means escape it, do not parse it as markup.

11Review a contract

POST/documents/reviewRun a contract-review playbook
documents_review — $100 / 1,000 reviews

Body: `{ text: string, playbookId?: string }`

curl
curl -sS -X POST "https://public-api.opencase.com/v1/documents/review" \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: review-001" \
  -d '{
    "text": "MUTUAL NONDISCLOSURE AGREEMENT\n\nThis Agreement is entered into ...",
    "playbookId": "nda-standard"
  }'

Without `playbookId` the contract type is detected and only the validated types are reviewed — anything else returns `reviewed: false` and is not billed, as is a review that fails. Max 500,000 characters (413 above that), and the request body is capped at 1 MiB, so text outside the Latin alphabet may reach that first. The first 200,000 characters are analysed; every response reports `analyzedChars` against `totalChars`.

GET/documents/playbooksList the review playbooks

Returns `{ id, name, contractType, checks, autoDetected }` per playbook.

curl
curl -sS "https://public-api.opencase.com/v1/documents/playbooks" \
  -H "Authorization: Bearer $OC_API_KEY"

Not billed. `autoDetected: false` means detection will not select that playbook on its own — it has not been validated against production documents and runs only when you name it.

12Error reference

All errors follow the same shape. Match on error.code for programmatic handling.

Statuserror.codeMeaningAction
401unauthorizedMissing or invalid API keySend a valid bearer token
402payment_requiredWallet balance is zero or negativeAdd funds, review billing settings
413document_too_largeDocument exceeds the maximum size a verify job will acceptSplit the document and submit each part
503async_unavailableOversized or url input was sent where the job queue is not enabledSend text of 100,000 characters or fewer
400invalid_document_urlThe url is not public https, or points at a non-public addressHost the document at a publicly reachable https URL
400invalid_request_idX-Request-Id starts with `job:`, a prefix reserved for internal useSend any other X-Request-Id
400document_unreadableThe document was fetched but cannot be read — an unsupported format, too many pages, or a scan with no text layerSend a text-bearing PDF or DOCX, or post the text directly
503document_service_unavailableThe document service could not be reached while fetching a urlRetry; a queued job retries this on its own
400unsupported_jurisdictionVerify cannot extract citations for the requested country (US only)Send the citation strings to /citations/resolve instead
429rate_limitedToo many requestsRespect Retry-After, exponential backoff
409request_id_reusedThis X-Request-Id was already billed on this endpointRetry with a new X-Request-Id
502review_failedThe model answered, but not with a usable review (POST /documents/review)Retry the request
504gateway_timeoutThe model did not finish in time (POST /answers, POST /documents/review)Retry once; shorten the question or the document
400bad_requestRequest failed schema validation, or the answer model refused the questionCheck request body shape and fields

13Operational best practices

  • Use separate API keys per logical service boundary and per environment (dev, staging, prod)
  • Rotate keys regularly — revoke any keys you no longer use
  • Never expose keys in client-side or browser code; use secure server-side storage
  • Monitor error volume grouped by error.code and HTTP status
  • Alert on sustained 402 (insufficient balance) and 429 (throttling) activity
  • Always capture and log requestId — include it in every support ticket and incident report