Research CLI · ocl
Check citations and keep evidence from the command line
ocl is a small, dependency-free client for the OpenCaseLaw research API. It serves a lawyer at a terminal and an AI agent in a script alike: readable text on screen, JSON when piped. Everything it shows comes from the service unchanged: identifiers, citation strings, passage text.
Install
Python 3.10 or newer. Install it from PyPI as a tool with pipx or uv; both create the isolated environment that current Python installations require. ocl doctor checks the connection, the server and the client.
pipx install opencaselaw-cli # or: uv tool install opencaselaw-cli ocl --help
Without pipx or uv, use a virtual environment: python3 -m venv .venv && .venv/bin/pip install opencaselaw-cli, then run .venv/bin/ocl. Upgrade with pipx upgrade opencaselaw-cli (or uv tool upgrade opencaselaw-cli).
1. Check the citations in a draft
Put the references from your draft in a file, one per line, or as JSON lines when a reference carries a pinpoint. Each comes back as resolved, missing or ambiguous with the decision it found; a pinpoint counts as resolved only if that Erwägung exists in the index.
cat > references.jsonl <<'JSONL'
{"reference":"BGE 136 III 513","pinpoint":"2.3"}
{"reference":"4A_747/2012"}
{"reference":"BGE 999 III 1"}
{"reference":"BGE 140 III 86","pinpoint":"2.3"}
JSONL
ocl citations resolve --input references.jsonl
At a terminal this prints:
resolved BGE 136 III 513 bge_BGE_136_III_513 E. 2.3 retrieved resolved 4A_747/2012 bger_4A_747_2012 BGer 4A_747/2012 vom 5. April 2013 missing BGE 999 III 1 not in the corpus pinpoint_unavailable BGE 140 III 86 bge_BGE_140_III_86 E. 2.3 not in the index partial: 2 resolved, 1 missing, 1 pinpoint_unavailable. Existence and pinpoints only; no assessment of legal support.
The exit code is 4 because not everything resolved. What the check does not do: it never says that a decision supports your proposition, is still good law, or fits your facts. That reading is yours.
2. Keep the evidence behind a memo
One command runs the search and saves the decisions it selects, the Erwägungen you name and the statute articles you name into a folder: full texts as served, a plain-language INDEX.md, and a manifest.json with every request, timestamp, source link and file hash. Saved files are never overwritten; --resume finishes an interrupted run.
ocl bundle create 'Rachekündigung Art. 336 OR' --max-results 10 --passage 2 \ --law OR:336 --out rachekuendigung-2026-09
The folder is readable without any tool:
rachekuendigung-2026-09/ INDEX.md what was saved, in plain language manifest.json every request, timestamp, hash, source link decisions/bge_BGE_136_III_513-5b3e22ef.txt full text as served (+ .json metadata) passages/bge_BGE_136_III_513_2-ff487655.txt E. 2, verbatim laws/OR_336-812a5382.json Art. 336 OR, consolidation date, Fedlex link search/page-0000-1850ce82.json the search page the selection came from
A bundle is complete when every requested item was saved, otherwise partial with each failed item listed, for example a cantonal decision without numbered Erwägungen. It preserves what the service returned on that day; the corpus is rebuilt nightly, so the same query next month can select other decisions.
What the results mean
resolved: the decision exists in the corpus. With a pinpoint, the named Erwägung exists and its text is in the row.pinpoint_unavailable: the decision exists; the numbered passage is not in the structure index. For a lettered sub-number (E. 2a, E. 3c/aa) the parent number is returned (parent_retrieved); locate the letter inside it.missing: no decision with that citation or docket. A wrong citation or a gap in coverage, never proof that a citation was invented.ambiguous: more than one decision carries that label (dockets are reused across courts). Pick adecision_id.unrecognized: the decision the service proposed carries no label written in the reference; the proposal is underservice_candidate, never indecision_id.resolution_incomplete,error: identity could not be established, or the request failed. Nothing is guessed.total_is_lower_bound: truemeans “at least this many”. A text query is ranked over a bounded candidate pool, sohas_more: falsenever proves that every relevant decision was seen.- Exit codes: 0 complete, 2 invalid input, 3 service or network failure, 4 partial or unresolved (including a decision or passage the service does not have). Scripts branch on them without parsing prose.
discrepancy: the decision was identified, but the date or the docket written next to the BGE label contradicts the record;discrepancieslists each one.
Recipes
Search, then fetch the full decisions, in one pipeline:
ocl decisions search 'Rachekündigung Art. 336 OR' --max-results 5 --format jsonl | ocl decisions get --stdin --format jsonl > decisions.jsonl
Everything a court decided in a period. Without query text the filters enumerate an exact set, page by page:
ocl decisions search --court bge --date-from 2026-01-01 --date-to 2026-06-30 \ --sort date_desc --max-results 500 --format jsonl > bge-2026-h1.jsonl
Quote one Erwägung verbatim, and get the citation to go with it:
ocl decisions passage bge_BGE_136_III_513 2.3 ocl cite 'BGE 136 III 513' --pinpoint 2.3 --language fr
A statute as it stood on a date:
ocl laws get OR --article 41 --as-of 2015-01-01
Who cites a leading case:
ocl citations list bge_BGE_140_III_86 --direction incoming --limit 50
Coding agents such as Claude Code or Codex run the same commands from a shell and read the JSON, which keeps the evidence in files you can check rather than in the model's memory.
For agents
An agent needs four things: an install without prompts, output it can parse, verdicts it can branch on, and rules it cannot talk itself out of. ocl provides all four: JSON as soon as output is piped; exit codes that carry the verdict (0 all resolved, 2 invalid input, 3 service or network, 4 something did not resolve); --cache so repeats within a database generation cost nothing; and three bundled skills (citation check, research, evidence bundle). ocl tool call reaches every research tool of the service.
ocl agent-guide # the contract on one page: JSON, exit codes, statuses, rules ocl skills install --claude # citation-check, research, evidence-bundle into ~/.claude/skills ocl tool list # every research tool of the service ocl tool call find_leading_cases query='Rachekündigung Art. 336 OR' limit=5 ocl quotes check --input quotes.jsonl --format jsonl ocl citations resolve --input refs.jsonl --format jsonl --cache ~/.cache/ocl
The rules are not negotiable: citation strings and quotations come from the service unchanged; a close_match or service_candidate is information for the author, never a substitute; the tool establishes existence and wording, not legal weight. opencaselaw_cli.api offers the same as a library.
Where it fits
- Conversation: connect Claude, ChatGPT or another MCP client to
mcp.opencaselaw.ch(setup per client). - Scripts, notebooks and agents:
ocl, or the REST API directly. Same records, same limits. - Corpus-scale analysis: the Parquet dataset.
Limits
A text query is sent as one ranked request of at most 800 results; filter-only searches are paged. Broad queries can exceed the timeout and are reported as a failure, not as an empty result. Cantonal statutes are addressed as --law ZH/StG:1. Queries go to the hosted service and are subject to the privacy notice; --base-url points at a separately operated server.
The full guide with the reference sections (search and retrieval, bundles, citation resolution, the research API contract) is on GitHub: docs/research-cli.md.