REST reference
Nine law endpoints, one envelope.
Base URL https://api.binding.law/v1. Everything is JSON over HTTPS. Successes carry result or results; failures carry error.code and error.message, and the code is what you switch on.
Conventions
POST variants. /law/coverage, /law/lookup, /law/text, /law/search and /law/format also accept POST with the same parameters as a JSON body. The POST path is implemented as a loopback GET, so auth, metering, validation and response shape are identical by construction. Use it when a citation key carries characters you would rather not put in a query string.
The /v1/statutes alias. Every /v1/law/* path is also mounted at /v1/statutes/*, permanently, because earlier integrations hardcoded it. Same handlers, same answers. New code should use /v1/law/*.
Billing. Metered calls bill one unit. Citation resolution bills one unit per citation in the batch. /law/coverage, /law/quality and the health checks bill zero.
Coverage
What binding.law carries, for whom, how far back, and how its validity dates should be read. Public and unmetered — call it before spending a lookup, and call it to decide whether a verification is possible at all. Served from a cache refreshed behind the response; when the cached numbers can no longer be trusted as current the payload grows a stale object carrying ageSeconds, refreshFailures and lastError, so a silent refresh failure cannot masquerade as a healthy endpoint.
curl "https://api.binding.law/v1/law/coverage?jurisdiction=fl&corpus=statutes"{
"coverage": [
{
"jurisdiction": "fl",
"corpus": "statutes",
"name": "Florida",
"cadenceClass": "edition",
"temporalBasis": "compiled-edition",
"coverageFrom": "2010-07-01",
"latestVersion": "2025",
"sections": 26776,
"versions": 48076,
"lastRetrievedAt": "2026-07-22 05:14:30.429+00"
}
],
"note": "asOf dates earlier than coverageFrom return no_edition_on_file; ...",
"generatedAt": "2026-09-20T19:51:41.148Z"
}note is truncated here for width; the live endpoint sends it in full.
- 500internal_errorThe coverage query failed and no cached value was available.
- 503statutes_unavailableThe corpus store is not configured on this deployment.
Corpus quality
Our own audit of our own data — granularity below section level, bodies missing without a stated disposition, citations whose source URL names a different section. Published deliberately: if you are grounding an agent on this corpus you should be able to see where we say it is thin, rather than discovering it in an answer. Only findings that did not pass are returned. This audits our data, not the law. Public and unmetered.
curl "https://api.binding.law/v1/law/quality?jurisdiction=co&history=true"{
"run": null,
"findings": [],
"note": "no audit has been recorded yet"
}Production has no audit run recorded yet, so this is the live answer today. When a run exists, run carries ranAt, jurisdiction, trigger, totalChecks, passed, warned and failed, and findings carries one object per non-passing check (scope, check_name, status, detail, rows, avg_chars, max_chars, unexplained_empty, misattributed).
- 503statutes_unavailableThe corpus store is not reachable. This endpoint answers 503 rather than 500 on a query failure.
Lookup a section
One section version — the one in force on asOf — with its full provenance, bitemporal bounds and permalink. Never a nearest guess: if nothing was in force on that date under that key, the answer is a 404 with a code that tells you which kind of nothing it was.
curl -G "https://api.binding.law/v1/law/lookup" \
-H "Authorization: Bearer $BINDING_API_KEY" \
--data-urlencode "jurisdiction=fl" \
--data-urlencode "citation=768.28" \
--data-urlencode "asOf=2019-03-01"{
"result": {
"citation": {
"jurisdiction": "fl",
"corpus": "statutes",
"key": "768.28",
"display": "§ 768.28, Fla. Stat.",
"parts": { "title": "XLV", "chapter": "768" }
},
"catchline": "Waiver of sovereign immunity in tort actions; ...",
"bodyText": "... full section text ...",
"historyNote": "s. 1, ch. 73-313; ...",
"note": null,
"status": "active",
"validFrom": "2019-07-01T00:00:00+00:00",
"validTo": "2020-07-01T00:00:00+00:00",
"ingestedAt": "2026-07-22T05:14:30.429+00:00",
"temporalBasis": "compiled-edition",
"permalink": "https://binding.law/law/fl/statutes/768.28",
"provenance": {
"sourceUrl": "https://www.flsenate.gov/Laws/Statutes/2019/768.28",
"retrievedAt": "2026-07-22T05:14:30.429+00:00",
"contentHash": "9f2c...",
"sourceSystem": "flsenate.gov",
"versionLabel": "2019"
},
"disclaimer": "Unofficial copy of government-published law, ..."
}
}Transcribed from the handler that builds it. Every key shown is one the API emits; bodyText, hashes and structure values are abbreviated. Rows in corpus 'rules' that the filing-format registry has read additionally carry topics: ["format"].
- 400missing_fieldsjurisdiction or citation absent.
- 400bad_as_ofasOf is not YYYY-MM-DD.
- 400bad_corpuscorpus is not one of the six.
- 401missing_bearer · invalid_keyNo token, or an unknown one.
- 403missing_scopeThe key lacks the lookup scope.
- 404not_carriedNothing of that corpus for that jurisdiction is held. Stop.
- 404no_edition_on_fileCarried, but not that key or not on that date. May carry error.didYouMean.
- 429rate_limited · daily_limit_exceeded · monthly_limit_exceededA ceiling on the key was reached; read Retry-After.
- 500internal_errorThe query failed.
- 503statutes_unavailableThe corpus store is not reachable from this deployment.
Section text
The same resolution as /law/lookup, returning only what you need to quote or compare a quote: citation, catchline, body, history note, our own note, status, temporal basis, version label, permalink and disclaimer. The permalink is included on purpose — an agent quoting a section needs somewhere to send the person who asks where it came from, and for whole-document sources a raw sourceUrl drops them into a multi-megabyte page with no anchor.
{
"result": {
"citation": { "jurisdiction": "fl", "corpus": "statutes", "key": "768.28", "display": "...", "parts": {} },
"catchline": "Waiver of sovereign immunity in tort actions; ...",
"bodyText": "... full section text ...",
"historyNote": "s. 1, ch. 73-313; ...",
"note": null,
"status": "active",
"temporalBasis": "compiled-edition",
"versionLabel": "2019",
"permalink": "https://binding.law/law/fl/statutes/768.28",
"disclaimer": "Unofficial copy of government-published law, ..."
}
}Same parameters, same miss semantics, and the same error codes as /law/lookup.
Search
Ranked full-text search over catchlines and body text, restricted to versions in force on asOf so results respect the same point-in-time model as a lookup. Snippets are highlighted. The search is lexical and ANDs its terms, so reformulate a natural-language question into statutory keywords rather than asking it whole.
An empty result over material we do not carry is a 404 not_carried, not {"results": []} — the two mean opposite things to an agent. That check only runs for a single named jurisdiction; a comma list or all always answers 200, because those can legitimately match nothing.
curl -G "https://api.binding.law/v1/law/search" \
-H "Authorization: Bearer $BINDING_API_KEY" \
--data-urlencode "q=sovereign immunity tort" \
--data-urlencode "jurisdiction=fl" \
--data-urlencode "limit=5"{
"results": [
{
"citation": {
"jurisdiction": "fl",
"corpus": "statutes",
"key": "768.28",
"display": "§ 768.28, Fla. Stat.",
"parts": { "chapter": "768" }
},
"catchline": "Waiver of sovereign immunity in tort actions; ...",
"snippet": "... <b>sovereign immunity</b> in <b>tort</b> actions ...",
"status": "active",
"temporalBasis": "compiled-edition",
"versionLabel": "2025",
"provenance": {
"sourceUrl": "https://www.flsenate.gov/Laws/Statutes/2025/768.28",
"retrievedAt": "2026-07-22T05:14:30.429+00:00",
"contentHash": "9f2c...",
"sourceSystem": "flsenate.gov",
"versionLabel": "2025"
}
}
],
"disclaimer": "Unofficial copy of state statutes served at compiled-edition granularity ..."
}Each hit carries its own provenance object. snippet is a Postgres ts_headline excerpt and contains <b> markers around the matched terms.
- 400missing_fieldsq is absent or empty.
- 400bad_corpuscorpus is not one of the six.
- 400bad_as_ofasOf is not YYYY-MM-DD.
- 404not_carriedA single named jurisdiction holds nothing of that corpus.
- 401 · 403 · 429see AuthenticationToken, scope and limit failures.
- 500internal_errorThe search query failed.
- 503statutes_unavailableCorpus store unreachable.
Court filing formats
The filing-format registry: per court, the typeface, minimum type size, line spacing, margins, paper size, length limits and certificate of compliance the rules require — as data, each value quoting the rule text it was read from, each record pinned to the content hash of the rule row it was read from.
A record is a reading of the rules, not the rules. Check stale before relying on one: it is true when a cited rule’s in-force text no longer matches the pin, or when no row is in force, and staleDetail says which. Verify against rules[].citationKey through /v1/law/lookup?corpus= the rule’s rules[].corpus before certifying compliance — rules for a court rule, statuteswhere a statute sets the format (Wisconsin’s appellate courts, keyed statutes:809.81). A court whose carried rules set no statewide typography is still a record, with coverage: "no-statewide-typography-rule", so that “no statewide rule” is an answer rather than a gap.
curl -G "https://api.binding.law/v1/law/format" \
-H "Authorization: Bearer $BINDING_API_KEY" \
--data-urlencode "jurisdiction=il" \
--data-urlencode "level=intermediate-appellate"{
"result": [
{
"id": "il-appellate",
"jurisdiction": "il",
"court": "Illinois Appellate Court",
"level": "intermediate-appellate",
"coverage": "specified",
"ruleKeys": ["sct-341"],
"rulePins": { "sct-341": "<contentHash at reading time>" },
"governingRuleKey": "sct-341",
"typeface": { "allowed": ["..."], "prohibited": [] },
"minSizePt": 12,
"maxSizePt": null,
"footnoteMinSizePt": null,
"lineSpacing": "double",
"margins": { "all": 1.5 },
"paperSize": "letter",
"limits": [
{ "document": "brief of appellant", "pages": 50, "basis": "pages", "quote": "..." }
],
"certificateOfCompliance": { "required": true, "ruleKey": "sct-341", "contents": "..." },
"effectiveFrom": "2020-10-01",
"basis": { "typeface": "...", "margins": "..." },
"note": "...",
"unresolved": ["local rules of the individual districts"],
"stale": false,
"staleDetail": null,
"loadedAt": "2026-09-19T00:00:00+00:00",
"rules": [
{
"key": "sct-341",
"corpus": "rules",
"citationKey": "sct-341",
"display": "Ill. S. Ct. R. 341",
"effectiveFrom": "2020-10-01",
"sourceUrl": "https://...",
"note": null,
"permalink": "https://binding.law/law/il/rules/sct-341",
"changedSincePinned": false
}
]
}
],
"note": "Each record is binding.law's reading of the cited court rules, ...",
"disclaimer": "Unofficial copy of state statutes served at compiled-edition granularity ..."
}Records come back highest court first. typeface.allowed is either an exhaustive list of named faces or one of the tokens any, any-conventional, any-proportional, any-serif, court-published-list — and is null where the rules set no typeface requirement. Where a rule's minimum depends on the face, typeface.faceMinimums carries one entry per face and minSizePt alone is not the rule.
- 400missing_fieldsjurisdiction absent.
- 400bad_levellevel is not one of the four.
- 404not_carriedNo record for that jurisdiction; or none matching the level/court filter, in which case the message lists the ids that do exist.
- 500internal_errorThe lookup failed.
- 503statutes_unavailableCorpus store unreachable.
Resolve case citations
Resolve case citations to the cases they name, against a local replica of CourtListener’s citation index. Resolution only — whether the case is still good law is not claimed here. The result vocabulary is CourtListener’s own, so a client written against their citation-lookup endpoint needs the base URL changed and results unwrapped.
curl -X POST "https://api.binding.law/v1/law/citations/lookup" \
-H "Authorization: Bearer $BINDING_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "347 U.S. 483\n999 F.3d 1", "fallback": "courtlistener"}'{
"results": [
{
"citation": "347 U.S. 483",
"normalized_citations": ["347 U.S. 483"],
"start_index": 0,
"end_index": 12,
"status": 200,
"error_message": "",
"clusters": [
{
"id": 105221,
"absolute_url": "/opinion/105221/brown-v-board-of-education/",
"courtlistener_url": "https://www.courtlistener.com/opinion/105221/brown-v-board-of-education/",
"case_name": "Brown v. Board of Education",
"case_name_full": "...",
"case_name_short": "...",
"date_filed": "1954-05-17",
"court": "scotus",
"docket_id": null,
"docket": null,
"precedential_status": "Published",
"citation_count": 0,
"citations": [{ "volume": "347", "reporter": "U.S.", "page": "483", "type": 1 }]
}
]
}
],
"provenance": {
"source": "courtlistener-bulk",
"dataAsOf": "2026-09-11",
"resolution": "volume/reporter/page against search_citation"
},
"fallback": { "attempted": 0, "resolved": 0, "capped": 0, "backfilled": 0, "status": "none" },
"disclaimer": "..."
}status uses CourtListener's vocabulary: 200 found, 300 ambiguous (up to eight candidates), 404 not found, 400 unknown reporter. The fallback object is present only when you asked for one; its status is one of ok, unavailable, rate_limited, skipped, none. The response also carries an x-binding-fallback-remaining header with CourtListener's remaining daily requests on the shared token.
- 400missing_fieldsBody is neither { text } nor { citations }.
- 400bad_fallbackfallback is not "none" or "courtlistener".
- 400too_manyMore than 250 citations parsed; batch the request.
- 413too_largetext exceeds 64,000 characters.
- 500internal_errorThe resolution query failed.
- 503caselaw_unavailableThe case-law replica is not configured on this deployment.
A case, and what cites it
One case (a CourtListener cluster) with its parallel citations, court, filing date and cited_by_count, the number of distinct cases that cite it. clusterId must be a positive integer; anything else is a 400 bad_id, and an unknown id is a 404 not_found.
The cases that cite this one, most-cited first, from CourtListener’s citation graph. Each result carries depth, how many times the citing opinion cites this one. Treatment — followed, distinguished, overruled — is deliberately absent: it needs a classifier over opinion text, and a citing list mistaken for a good-law signal is worse than none.
- 400bad_idclusterId is not a positive integer.
- 404not_foundNo cluster with that id. (/cases/{id} only.)
- 500internal_errorThe query failed.
- 503caselaw_unavailableReplica not configured.
Health and the spec
Is the process alive. Never touches the corpus, answers in microseconds, and is what the load balancer polls. It stays green through a corpus outage — correctly, because the process is fine. Do not point uptime monitoring at it.
Can law actually be served. This one touches the corpus and answers 503 when it is unreachable, which is precisely the condition /v1/law/coverage hides by design when it falls back to its cache. This is the endpoint to monitor. The underlying probe runs at most every five minutes however often you call it, and checkedSecondsAgo tells you how old the verdict is; a verdict older than fifteen minutes is reported as 503 unknown / no_recent_probe rather than passed off as healthy.
{
"ok": true,
"status": "healthy",
"reason": "ok",
"checkedSecondsAgo": 41,
"service": "lawfirm.dev/v1",
"timestamp": "2026-09-20T19:53:48.552Z"
}The machine-readable spec, also served as /v1/openapi.json. Where it and this page disagree, the handlers are the truth and we would like to know.