# Case Repository API — Complete Guide Case Repository is a REST API over the complete corpus of published U.S. court opinions (~10 million cases from ~3,400 courts, 1600s–present), including full opinion text and citation counts. Data is public domain, sourced from the Free Law Project's CourtListener bulk data. Base URL: https://caserepository.com/api/v1 All responses are JSON. All endpoints require an API key. ## Authentication Send your API key (starts with `ck_`) on every request, either way: Authorization: Bearer ck_yourkeyhere (preferred) ?api_key=ck_yourkeyhere (query param fallback) Test your key: GET /api/v1/whoami → {"name": "your-key-name", "requests_count": 123, "key_active_since": "..."} A missing or invalid key returns 401 with instructions. ## Core concepts (read this first) - A **case** is a court decision: name, court, date, citation count. One case can contain multiple **opinions** (majority, concurrence, dissent). - Cases may include BOTH a `010combined` opinion (the full document with everything in it) AND separate individual opinions (`020lead`, `030concurrence`, `040dissent`). The combined version usually CONTAINS the individual ones — do not concatenate all opinions or you will double-count text. Prefer the combined document when present; otherwise use the individual opinions. - Opinion `type` codes sort in reading order as strings: `010combined` < `020lead` < `030concurrence` < `040dissent`. - **Courts** are identified by lowercase slugs: `scotus` (U.S. Supreme Court), `ca1`–`ca11` + `cadc` + `cafc` (federal circuits), state slugs like `mo`, `cal`, `ny`, `tex`. GET /api/v1/courts lists all valid IDs — call it instead of guessing. - **Citation counts** measure how often other cases cite this one — a good proxy for importance. (Highest in the corpus: Ashcroft v. Iqbal at ~150k.) ## Choosing between /cases and /exports — IMPORTANT - Interactive lookup, search, browsing, displaying cases → GET /cases - Collecting MORE THAN ~500 cases, or full text for more than a handful of cases → POST /exports (an async bulk job that produces one file) NEVER write a loop that follows `next_url` through many pages to collect bulk data. You will hit rate limits and it is much slower than one export. Rule of thumb: if your code follows next_url more than a few times, use an export instead. ## GET /api/v1/courts — list courts Optional filters: `?jurisdiction=F` (F=federal appellate, S=state supreme, SA=state appellate, FD=federal district), `?in_use=true`. GET /api/v1/courts?jurisdiction=S → {"count": 55, "courts": [ {"id": "mo", "short_name": "Supreme Court of Missouri", "full_name": "Supreme Court of Missouri", "jurisdiction": "S", "citation_string": "Mo.", "in_use": true, "start_date": "1821-01-01", "end_date": null}, ...]} ## GET /api/v1/courts/:id — one court GET /api/v1/courts/scotus → {"id": "scotus", "short_name": "Supreme Court", "full_name": "Supreme Court of the United States", ...} Court IDs are case-insensitive (SCOTUS works) but canonical form is lowercase. ## GET /api/v1/cases — list/search cases Filters (all optional, combine freely): - `court=scotus` — court slug - `filed_after=2020-01-01` / `filed_before=2023-12-31` — date range - `min_citations=100` — only cases cited at least this many times - `per_page=20` — page size, max 100 - `after_id=12345` — pagination cursor (see below) GET /api/v1/cases?court=scotus&filed_after=2020-01-01&per_page=5 → { "cases": [ {"id": 4695147, "case_name": "Ritzen Group, Inc. v. Jackson Masonry, LLC", "date_filed": "2020-01-14", "citation_count": 354, "precedential_status": "Published"}, ... ], "per_page": 5, "next_url": "https://caserepository.com/api/v1/cases?after_id=4695151&court=scotus&filed_after=2020-01-01&per_page=5" } Pagination: results are ordered by `id` ascending. To get the next page, request the `next_url` from the response verbatim — it preserves your filters and sets the cursor. When `next_url` is null, you have all results. There are no page numbers and no total count. Results are NOT sorted by citation_count or date; if you need sorted output, collect the matching set (small sets via pagination, large via export) and sort client-side. ## GET /api/v1/cases/:id — case detail GET /api/v1/cases/1723 → { "id": 1723, "case_name": "Padilla v. Kentucky", "date_filed": "2010-03-31", "citation_count": 5017, "precedential_status": "Published", "case_name_full": "Padilla v. Kentucky", "judges": "Stevens, Kennedy, Ginsburg, ...", "docket_id": 127914, "court": "scotus", "opinions": [ {"id": 1723, "type": "010combined", "author": "", "page_count": 40}, {"id": 9413157, "type": "020lead", "author": "Stevens", "page_count": null}, {"id": 9413158, "type": "030concurrence", "author": "Alito", "page_count": null}, {"id": 9413159, "type": "040dissent", "author": "Scalia", "page_count": null} ], "citations": ["559 U.S. 356", "130 S. Ct. 1473", ...] } The `citations` array holds this case's reporter citations (how lawyers cite it), not the cases it cites. ## GET /api/v1/cases/:id/text — full opinion text Returns the complete text of every opinion in the case. Responses can be large (a Supreme Court case is typically 50–200KB). GET /api/v1/cases/1723/text → { "case_id": 1723, "case_name": "Padilla v. Kentucky", "opinions": [ {"id": 1723, "type": "010combined", "author": "", "text_source": "plain_text", "text_length": 84441, "text": "(Slip Opinion) OCTOBER TERM, 2009 ..."}, ... ] } - `text_source` tells you which underlying column supplied the text (plain_text, html_with_citations, etc. — HTML sources are stripped to plain text for you). `xml_harvard`-sourced text may contain minor OCR artifacts. - `text` can be null for rare cases with no digitized text. - Text may contain page markers like `*359` — these are U.S. Reports page numbers, useful for pinpoint citations. - Remember the combined-vs-individual overlap rule from Core Concepts. ## POST /api/v1/exports — bulk export (async job) Creates a background job that writes matching cases to a JSONL file (one JSON object per line). Same filters as /cases, plus: - `include_text=true` — include full opinion text per case. Text exports are limited to 10,000 cases per job (metadata-only exports have no size limit). Max 3 queued/running jobs at once, max 10 export jobs per hour. Workflow (three steps — implement all three): 1. POST /api/v1/exports?court=scotus&filed_after=2020-01-01 → 202 {"id": 2, "status": "queued", "download_url": null, ...} 2. Poll until complete (every 2–5 seconds): GET /api/v1/exports/2 → {"id": 2, "status": "completed", "record_count": 3994, "file_size_bytes": 537374, "download_url": "https://caserepository.com/api/v1/exports/2/download", ...} Statuses: queued → running → completed (or failed, with an "error" field explaining why). 3. GET the download_url → the JSONL file. JSONL parsing: read line by line; each line is one case object. With include_text=true each case object also has an "opinions" array with full text (same shape as /cases/:id/text). Python example: import requests, time, json H = {"Authorization": "Bearer ck_yourkey"} BASE = "https://caserepository.com/api/v1" r = requests.post(f"{BASE}/exports", params={"court": "scotus", "filed_after": "2020-01-01"}, headers=H).json() while r["status"] not in ("completed", "failed"): time.sleep(3) r = requests.get(f"{BASE}/exports/{r['id']}", headers=H).json() if r["status"] == "failed": raise RuntimeError(r["error"]) data = requests.get(r["download_url"], headers=H) cases = [json.loads(line) for line in data.text.splitlines()] Large text collections (>10,000 cases): split into multiple export jobs by date range, e.g. filed_after/filed_before slices of a few years each, and run them sequentially (max 3 concurrent). Each job's file contains complete, untruncated text. GET /api/v1/exports lists your recent export jobs. Export files expire after 7 days — download promptly. ## Rate limits Per API key: 300 requests per 5 minutes (general), 10 export creations per hour, 3 concurrent export jobs. Exceeding a limit returns 429 with a `Retry-After` header and a JSON body stating how long to wait. If you hit the general limit while collecting data, you should be using an export instead. ## Errors All errors are JSON with this shape: {"error": "machine_readable_code", "message": "Human/LLM-readable explanation with the fix", "docs": "link"} Codes you may see: `unauthorized` (401, bad/missing key), `unknown_court` (422, invalid court slug — GET /courts for valid IDs), `case_not_found` / `export_not_found` (404), `export_too_large` (422, narrow your filters or drop include_text), `export_expired` (410, file past its 7-day expiry — create a new export with the same filters), `too_many_active_exports` (429, wait for jobs to finish), `rate_limited` (429, wait Retry-After seconds). Error messages are written to be self-explanatory — read the `message` field and follow its instructions. ## Recipes **"Most important cases on X court since year Y":** GET /cases?court=Y&filed_after=YYYY-01-01&min_citations=500, paginate via next_url (small result sets), sort by citation_count client-side. **"Full text of one case I know by name":** GET /cases?court=...&filed_after=... with filters to find it, take the id, then GET /cases/:id/text. **"Dataset of all cases from court X, 2015–2025, with text":** POST /exports?court=X&filed_after=2015-01-01&filed_before=2025-12-31&include_text=true (if export_too_large, split the date range into multiple jobs), poll, download, parse JSONL. **"Chart cases per year for a court":** Metadata-only export (no include_text, no size limit), then group the JSONL rows by date_filed year client-side. ## Notes and limitations - Data snapshot: quarterly from CourtListener bulk data (currently 2026-06-30). Very recent decisions may be absent. - Coverage is published opinions; docket-only federal litigation (PACER records without opinions) is not exposed through this API. - All data is public domain. No attribution required, but crediting the Free Law Project / CourtListener as the underlying source is good practice.