# MapleJuris API - full reference > Verify Canadian legal citations against structured graphs of Canadian legislation - federal (Justice Laws Canada) plus Ontario (e-Laws); citations from every other Canadian jurisdiction are recognized and return `unsupported` - and of Canadian case law (Supreme Court of Canada, 1970 onward), where a resolved decision also comes back with a deterministic good-law verdict. REST + MCP. This file is the complete reference, written for language models and agents. Base URL: `https://api.maplejuris.com` MCP endpoint: `https://api.maplejuris.com/mcp` Sign-up: https://maplejuris.com/signup · API keys: https://maplejuris.com/dashboard/api-keys ## Authentication Every request needs an API key created at https://maplejuris.com/dashboard/api-keys. The secret is shown once, at creation. Two headers are accepted, on every `/v1` route and on `/mcp`, and both are checked by the same code - send one, not both: ``` Authorization: Bearer ak_your_key_here # preferred X-API-Key: ak_your_key_here # equally valid ``` Prefer `Authorization: Bearer`. It is the header every HTTP client already handles, it is what MCP clients send, and the 24-hour tokens from `POST /v1/token` are presented exactly the same way. `X-API-Key` exists because it is the scheme the OpenAPI document declares (`APIKeyHeader`), so the Swagger UI "Authorize" box at https://api.maplejuris.com/docs and any SDK generated from `/openapi.json` will send that one. The published spec advertises only `X-API-Key`; the server accepts either, and if both headers are present `X-API-Key` is the one read. A missing, revoked, or expired key returns `401`. Keys are scoped to one account and count against that account's monthly allowance; an organization's keys count against its owner's (see "Organizations" below). Never embed a key in client-side code. ### Token exchange (optional, recommended for high-volume callers) `POST /v1/token` trades your API key for a self-signed bearer token valid for 24 hours. Requests made with the token verify locally on our servers (no auth-provider round trip), which shaves latency and means key revocation propagates at the next mint. Tokens draw down the same monthly allowance as the key that minted them. Minting is free (unmetered). ```bash curl -X POST https://api.maplejuris.com/v1/token -H "X-API-Key: $MAPLEJURIS_KEY" # {"access_token": "eyJ...", "token_type": "Bearer", "expires_in": 86400, "plan": "non_commercial_free"} ``` Use the returned `access_token` exactly like a key: `Authorization: Bearer ` or `X-API-Key: `. When it expires (`401`), mint a new one. ### Usage introspection `GET /v1/usage` (any key or token) returns the account's month-to-date consumption - checking usage never burns quota: ```json {"scope": "key", "subject": "user_...", "plan": "non_commercial_free", "quota": 1000, "month": "202607", "used": 42, "remaining": 958} ``` The response also carries `by_endpoint` and `by_key`: this month's requests and credits per endpoint and per API key. `by_key` lists every key drawing on the account — its personal keys and the keys of organizations it owns. An organization's key carries `org_id` and `created_by` (the member who created it); fields that are null are omitted. The dashboard at https://maplejuris.com/dashboard/api-keys shows the same numbers. ## Organizations On the Commercial and Enterprise plans you can create an organization and invite teammates by email; they share your credits. Organizations are managed from the account switcher on the dashboard (https://maplejuris.com/dashboard/api-keys). - A key created while an organization is selected belongs to the organization, not to the member who created it. - Every organization key uses the organization owner's plan and monthly credits — by default the owner is the member who created the organization — so the owner and all members draw on one shared pool. `by_key` on the owner's `GET /v1/usage` shows each key and the member who created it. - Organization keys work only while the owner is on Commercial or Enterprise; otherwise every call answers `403`, and the message names the owner's plan. If the owner's last payment failed, the organization's keys pause with `402` along with the owner's own. - Removing a member from the organization revokes the organization keys that member created. - Keys created with the personal account selected stay personal: they use that user's own plan. ## POST /v1/validate-citation Validate a single citation. Returns `200` whenever the key is valid - a citation that does not resolve is a result, not an error. Body (send exactly one of `query` or `structured`): | Field | Type | Notes | |---|---|---| | `query` | string | The citation as free text, 1-500 characters. | | `structured` | object | Pre-parsed citation (`short_title`, `chapter`, `jurisdiction`, `id`, `section_label`, `subsection_label`, `paragraph_label`, `subparagraph_label`, `schedule_label`, `defined_term`, `target_node_type`). Skips the language-model parse for a fully deterministic lookup. Requires one of `short_title` / `chapter` / `id`. | | `jurisdiction` | string | Optional. Scopes the lookup to one jurisdiction. Accepted at the top level (alongside `query` or `structured`) and inside `structured`; when both are set the top-level value wins. | `jurisdiction` takes a code - `CA` (federal), `ON`, `BC`, `AB`, `MB`, `SK`, `NS`, `NB`, `PE`, `NL`, `YT`, `NT`, `NU`, `QC` - or a full name (`Ontario`, `British Columbia`, `Québec`). An unrecognised value is rejected with `422` and the message lists the accepted codes. Every code is a valid input, but only `CA` and `ON` are in the corpus today - a citation scoped to any other jurisdiction returns `status: "unsupported"` (see "Coverage" below). **Supply it whenever you know it.** Statute short titles repeat across Canada: *Business Corporations Act*, *Employment Standards Act*, and *Occupational Health and Safety Act* each exist federally and in several provinces. Without a jurisdiction the lookup cannot tell which one is meant, and a same-titled statute from the other in-corpus jurisdiction becomes eligible. Omit it and the jurisdiction is inferred from the citation itself - a reporter token (`R.S.B.C. 1996`, `CQLR c C-11`) or a title qualifier (`Ontario Securities Act`) - and whatever was concluded comes back in the top-level `jurisdiction` object. `jurisdiction` works identically on both input paths. `{"query": "Business Corporations Act, s. 108", "jurisdiction": "ON"}` resolves to Ontario's *Business Corporations Act*, R.S.O. 1990, c. B.16 - the bare title names three different statutes across Canada, and the supplied jurisdiction is what settles it. Writing the jurisdiction into the citation itself (`{"query": "Ontario Business Corporations Act, s. 108"}`) resolves the same statute; supplying the field is more reliable, because it does not depend on the parser preserving the qualifier. Example: ```bash curl -X POST https://api.maplejuris.com/v1/validate-citation \ -H "Authorization: Bearer $MAPLEJURIS_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Criminal Code s. 718"}' ``` Response (a real payload; only `target.text`, `data_license`, and `disclaimer` are shortened here): ```json { "query": "Criminal Code s. 718", "valid": true, "validity_score": 1.0, "score_breakdown": { "graph_match": true, "statute_title_verified": true, "jurisdiction_verified": null, "identity_conflicts": [], "unique_match": true, "target_type_matches": true, "has_current_text": true, "text_in_corpus": true, "statute_in_corpus": true, "pinpoint_supplied": true }, "status": "resolved", "resolution_level": "provision", "pinpoint_supplied": true, "confidence": 0.95, "jurisdiction": null, "statute": { "short_title": "Criminal Code", "long_title": "An Act respecting the Criminal Law", "chapter": "C-46", "kind": "act", "jurisdiction": "CA", "jurisdiction_name": "Canada (Federal)", "source_url": "https://laws-lois.justice.gc.ca/eng/XML/C-46.xml", "slug": "ca/act/rsc-1985-c-c-46" }, "target": { "node_type": "Section", "label": "718", "title_text": "Purpose", "text": "The fundamental purpose of sentencing is to protect society and to contribute ...", "valid_from": "2015-07-23", "last_amended": "2015-07-23", "display_label": "Criminal Code, RSC 1985, c C-46, s 718", "submitted_label": "Criminal Code, s. 718", "tree": [ { "node_type": "Statute", "label": "C-46", "title_text": "Criminal Code" }, { "node_type": "Section", "label": "718", "title_text": "Purpose" } ] }, "alternatives": [], "request": { "jurisdiction": null, "query": "Criminal Code s. 718", "structured": null }, "notes": ["Verified against the MapleJuris graph."], "data_license": "...", "disclaimer": "..." } ``` `"jurisdiction": null` at the top level is meaningful here: *Criminal Code* carries no reporter and no qualifier, so nothing scoped the search, and `jurisdiction_verified` is therefore null rather than true. The match landed federally (`statute.jurisdiction: "CA"`) but that was not checked against an expectation. Passing `"jurisdiction": "CA"` turns both into a positive verification. ### Field reference | Field | Meaning | |---|---| | `valid` | Boolean. True only when the citation was verified against the graph **at the depth it was written to** - a provision citation needs its provision matched; a whole-Act citation needs the Act found. Never true when the matched statute's text is missing, never true on a jurisdiction mismatch, and never true when the matched section is not the one written or the citation names levels below the section that were not read (`s. 118(1)99`) - the first note says which. A repealed provision stays `valid: true`; see `target.repealed`. | | `validity_score` | Float 0-1. `>= 0.75` verified (`0.9` for a corroborated whole-Act citation) · `0.4-0.75` understood but unmatched, ambiguous, or matched without corroboration · `0.1-0.4` a provision **was** cited and is not in a statute we hold · `< 0.1` nothing found. | | `status` | `resolved` \| `ambiguous` \| `not_found` \| `unsupported`. Recomputed from graph facts, not from the parser's opinion. `resolved` covers both depths - read `resolution_level`. | | `resolution_level` | `provision` \| `statute` \| null. How deep the resolution goes. `statute` means the Act was found and the citation named nothing inside it, so `target` is null **and that is the complete answer**. | | `pinpoint_supplied` | Boolean or null. Did the citation name a location *inside* the statute - a section, subsection, paragraph, schedule, or defined term? `false` for `Criminal Code, R.S.C. 1985, c. C-46`. Null only when the input was never read as a citation. | | `pinpoint_verified` | Boolean or null. Was the match made at the depth the citation named? `false` means a **shallower** provision came back - typically an Ontario subsection kept inline in its section's text, where the section is returned as `target` with the subsection's marker verified inside that text. The citation is located, not disproved; the returned text contains more than the cited pinpoint (for an Ontario `s X(1)`, subsection (1) with its paragraphs; a paragraph pinpoint under it is checked against the paragraphs that exist). Also `false` when the citation names levels below the section that were not read - that result is not valid. Null when there was no pinpoint to verify or nothing matched. | | `score_breakdown` | `graph_match`, `statute_title_verified`, `jurisdiction_verified`, `identity_conflicts`, `unique_match`, `target_type_matches`, `has_current_text`, `text_in_corpus`, `statute_in_corpus`, `pinpoint_supplied`, `pinpoint_verified`. See below. | | `confidence` | Model's interpretation confidence, or null when there is no candidate. Not a verification signal - use `validity_score`. | | `jurisdiction` | What the lookup was **scoped to**: `{code, name, determined_by, in_corpus}`. `determined_by` is `caller` (you sent it), `reporter` (read from `R.S.B.C. 1996`-style tokens), or `title_qualifier` (read from `Ontario …`). Null when nothing established a jurisdiction - the search then spanned every jurisdiction. | | `statute` | Identified statute, populated even on partial resolves: `short_title`, `long_title`, `chapter`, `kind`, plus `jurisdiction` / `jurisdiction_name` (where the match actually **landed**), `source_url` (the official publisher page it was ingested from, for checking against the primary source), and `slug` - a canonical, stable, human-readable reference for the matched instrument (act or regulation) of the form `{jurisdiction}/{act\|reg}/{citation-token}`, e.g. `ca/act/rsc-1985-c-c-46`, `ca/reg/sor-2002-227`, `on/act/rso-1990-c-b16`. A slug is stable - it is not re-derived on re-ingest, and a change of citation mints a new instrument rather than rewriting this one - is always a graph fact (never an echo of your input), and is null when the instrument has no slug yet. | | `target` | Matched provision: `node_type`, `label`, `title_text`, `text`, `valid_from`, `last_amended`, `repealed`, `repealed_by`, a `tree` breadcrumb from the statute root, `display_label`, and `submitted_label`. Present whenever a node matched, **including when `valid` is false**, so you can see what was found. | | `target.text` | The whole provision: its own words followed by everything below it (subsections, paragraphs, clauses, definitions) in document order, each with its marker (`(2)`, `(a)`). Headings and amendment notes printed beside the provision are left out. Cut at 8,000 characters, ending `…[truncated]`. Since 3.5.2; before, a provision with parts below it returned only its lead-in. | | `target.repealed` | Boolean or null. `true` when the provision's current text is a repeal note (`Repealed: 2021, c. 4, Sched. 9, s. 3.`, `[Repealed, 2019, c. 16, s. 9]`): the citation points at a real provision that is **no longer in force**. `valid` stays true - it confirms the citation, not that the law still applies - so check this before relying on the provision. `false` when the text is not a repeal note; null when no text is held. Also set on each entry in `alternatives`. Since 3.5.0. | | `target.repealed_by` | The repealing authority as printed in the note (`2021, c. 4, Sched. 9, s. 3`), when `repealed` is true and the note names one. | | `target.display_label` | Canonical citation for the provision that was **matched**, built from the graph record. Safe to render as the authoritative form. | | `target.submitted_label` | The citation as interpreted from your input, before lookup. Compare the two: a difference means the citation you sent and the provision found are not the same thing. | | `alternatives` | Up to five other candidates. Populated when `status` is `ambiguous`. | | `request` | Echo of the request body (`jurisdiction`, `query`, `structured`), so a stored response traces back to the call that produced it. The same object appears per item in a batch. | | `notes` | Human-readable diagnostics: which jurisdiction was applied and why, identity conflicts, coverage gaps, why a citation failed. | Gating advice: check `valid`, then `score_breakdown.jurisdiction_verified is not false` and `score_breakdown.identity_conflicts == []` before recording a citation as good, and `target.repealed is not true` before relying on the provision as law in force. To tell a user a cited provision **does not exist**, require both flags: ```python provision_does_not_exist = ( r["pinpoint_supplied"] is True and r["score_breakdown"]["statute_in_corpus"] is True and not r["valid"] ) ``` Gating on `statute_in_corpus` alone also fires on correct whole-Act citations, which have no provision to be missing. | `score_breakdown` field | Meaning | |---|---| | `graph_match` | A node was deterministically matched. | | `statute_title_verified` | The statute identity in the citation was positively corroborated by the graph record - something agreed and nothing contradicted. A chapter that matches while the title does not is a contradiction, not verification. | | `jurisdiction_verified` | True when a jurisdiction was known (supplied or detected) and the match belongs to it. **False means the match is in a different jurisdiction than the citation indicated** - the flag to gate on for cross-jurisdiction errors. Null when nothing established a jurisdiction. | | `identity_conflicts` | Fields where the citation and the matched statute positively disagree: `title`, `chapter`, `jurisdiction`, `id`. Empty is the good case; non-empty means the match is approximate and needs review. | | `unique_match` | Exactly one candidate matched the pinpoint. | | `target_type_matches` | The matched node's type equals the type the citation implied. | | `has_current_text` | An in-force text expression is attached to the matched node. | | `text_in_corpus` | False when the matched provision's wording is not held in the corpus - the pinpoint exists but no text can be quoted back. Such results are capped below the validity threshold and never reported `valid: true`. | | `statute_in_corpus` | Set when no provision matched. True: the statute is indexed. False: the statute is not ingested yet (a coverage gap - the citation may be perfectly good). **True on its own is not evidence of a bad citation** - read it with `pinpoint_supplied`. | | `pinpoint_supplied` | Whether the citation named anything inside the statute. Mirrors the top-level field. `statute_in_corpus: true` **and** `pinpoint_supplied: true` is the only combination that means the cited provision does not exist. | ### Pinpoints and statute-level citations A **pinpoint** is the part of a citation naming a location *inside* the statute - a section, subsection, paragraph, schedule, or defined term. ``` Criminal Code, R.S.C. 1985, c. C-46 <- no pinpoint (statute-level) Criminal Code, R.S.C. 1985, c. C-46, s. 718(2) <- pinpoint: s. 718(2) ``` Citing an Act with no pinpoint is common and correct. Such a citation returns: ```json { "status": "resolved", "resolution_level": "statute", "pinpoint_supplied": false, "valid": true, "validity_score": 0.9, "target": null, "statute": { "short_title": "Criminal Code", "chapter": "C-46", "slug": "ca/act/rsc-1985-c-c-46" } } ``` **A null `target` here is the complete answer, not a partial one.** Do not treat it as a failure, and do not read `statute_in_corpus: true` on its own as "the cited section does not exist" - a citation that named no section has no section to be missing. Contrast the two: | Citation | `pinpoint_supplied` | `status` | `valid` | Meaning | |---|---|---|---|---| | `Criminal Code, R.S.C. 1985, c. C-46` | `false` | `resolved` | `true` | Act verified | | `Criminal Code, R.S.C. 1985, c. C-46, s. 999` | `true` | `not_found` | `false` | Section does not exist | Both find the Act, both return a null `target`, and both carry `statute_in_corpus: true`. `pinpoint_supplied` is what separates them. ### Status values - `resolved` - verified in the graph, at one of two depths. `resolution_level: "provision"` means a single provision matched and `target` is populated; `resolution_level: "statute"` means the citation named only an Act, that Act was found, and `target` is null. Check `valid` as well: a match can resolve and still fail corroboration. - `ambiguous` - two or more provisions matched the pinpoint; `alternatives` populated. Also returned for a whole-Act citation whose title exists in several jurisdictions when the citation named none - send `jurisdiction` to resolve it. - `not_found` - nothing matched. `score_breakdown.statute_in_corpus` separates "we hold that Act" (true) from "we have not ingested that statute" (false); with `pinpoint_supplied: true`, the former means the cited provision does not exist. - `unsupported` - the citation belongs to a jurisdiction outside the corpus (any Canadian jurisdiction other than federal `CA` and Ontario `ON`, e.g. British Columbia or Québec), or is outside Canadian legislation entirely - a case citation, for instance, which belongs on `POST /v1/validate-case-citation`; `target` null, and a note names the gap. A `status` is only ever a conclusion about the citation. Infrastructure failures do not appear here - they are `503` (see below). ### Coverage: which jurisdictions are indexed 8,900 acts and regulations as of 2026-08-20, both jurisdictions in full text. Indexed means every provision, its labels, its place in the hierarchy, and the legislative wording itself. | Jurisdiction | Acts + regulations | Provision text | |---|---|---| | `CA` federal - Justice Laws | 5,832 | yes | | `ON` Ontario - e-Laws | 3,068 | yes | | All other provinces and territories | out of corpus | returns `unsupported` | Every other Canadian jurisdiction - `BC`, `AB`, `MB`, `SK`, `NS`, `NB`, `PE`, `NL`, `QC`, `YT`, `NT`, `NU` - is **out of corpus**. The codes stay valid as `jurisdiction` inputs, but a citation scoped or resolved there returns `status: "unsupported"` with `jurisdiction.in_corpus: false` and a note naming the gap (e.g. "British Columbia legislation is not currently in the MapleJuris corpus; coverage focuses on federal and Ontario law") - it is never matched against another jurisdiction's statute and never guessed at. ### Transient failures A temporary upstream failure (model timeout, graph unavailable) returns **`503`** with a `detail` telling you to retry. It is *not* reported as a citation verdict. Earlier builds surfaced these as `status: "unsupported"`, which a checker would have stored as a finding that the citation was out of scope; retry on `503` instead. In a batch the equivalent is a per-item `error` with `retryable: true` - the batch itself still returns `200`. ## POST /v1/validate-citation/batch Validate up to 100 citations in one call. Each citation in the batch costs one credit - the same rate as the single endpoint. Batching saves round trips and latency, not credits. A malformed envelope (empty list, more than 100 items) is rejected with `422` before any credit is spent. **The body is an object with a `citations` array - not a bare array.** Each element has exactly the shape `POST /v1/validate-citation` takes, `jurisdiction` included. A bare array is rejected with `422` (`"Input should be a valid dictionary or object to extract fields from"`). ```bash curl -X POST https://api.maplejuris.com/v1/validate-citation/batch \ -H "Authorization: Bearer $MAPLEJURIS_KEY" \ -H "Content-Type: application/json" \ -d '{"citations": [ {"query": "Criminal Code s. 718"}, {"query": "Business Corporations Act, s. 108", "jurisdiction": "ON"} ]}' ``` **The response is an object with `results` and `summary` - not an array.** Each entry in `results` is `{index, request, response, error, retryable}`: ```json { "results": [ { "index": 0, "request": { "jurisdiction": null, "query": "Criminal Code s. 718", "structured": null }, "response": { "query": "Criminal Code s. 718", "valid": true, "validity_score": 1.0, "...": "full single-citation response" }, "error": null, "retryable": false }, { "index": 1, "request": { "jurisdiction": "ON", "query": "Business Corporations Act, s. 108", "structured": null }, "response": null, "error": "upstream timeout while interpreting the citation", "retryable": true } ], "summary": { "total": 2, "succeeded": 1, "failed": 1, "valid": 1, "avg_validity_score": 1.0, "elapsed_ms": 2143 } } ``` | Per-item field | Meaning | |---|---| | `index` | Zero-based position of this citation in the `citations` array you sent. **Always present, always unique, never null.** It is the durable way to place a result - key your own records off `index`, not off the position of the entry in `results`. | | `request` | Echo of the citation object you sent at that index, so results can be correlated without keeping a separate record. | | `response` | The full single-citation response, identical in shape to `POST /v1/validate-citation` (it carries its own `request` echo too). Null when `error` is set. | | `error` | Message string when that one item raised; null otherwise. Per-item errors are isolated - one bad citation never fails the batch, and the HTTP status is still `200`. **Check `error` on every item**: an item with `error` set has no verdict at all, which is different from a citation that resolved to `valid: false`. | | `retryable` | True when `error` was a temporary upstream failure rather than a conclusion about the citation. Re-send just that item; do not record it as a finding. | Items are returned in input order today, and `index` matches that order, but `index` is the contract - order is not. `summary` reports `{total, succeeded, failed, valid, avg_validity_score, elapsed_ms}`, where `succeeded` counts items that produced a response and `valid` counts how many of those actually verified; `avg_validity_score` is the mean over succeeded items (0 when none succeeded). A quick triage is `summary.failed > 0 || summary.valid < summary.succeeded`. ## POST /v1/validate-case-citation Does a case citation resolve to a real decision in the corpus, and **is that decision still good law**. Both answers come back in one response: a citation that resolves but has been overruled reads as an endorsement if the second question is not asked, so a caller cannot get the first answer without the second. Returns `200` whenever the key is valid - a citation that does not resolve is a result, not an error. **One endpoint, one or many.** Send `query` (or `structured`) at the top level for a single citation, or `citations` (up to 100 items, each with the single-citation fields) for many. The response shape does not change with the count - always `results` in request order plus a `summary`. `POST /v1/validate-case-citation/batch` is an alias with the identical body and response. **50 credits per citation**, whichever shape or route. | Field | Type | Notes | |---|---|---| | `query` | string | The citation as written: `R. v. Jordan, 2016 SCC 27, at para 46`, `Roncarelli v. Duplessis, [1959] S.C.R. 121`, or a slug `ca/case/2016-scc-27`. Neutral citations, Supreme Court Reports citations (with or without a volume - `[1970] S.C.R. 337` is a real form), and paragraph pinpoints (`at para 46`, `¶ 46`) are read. | | `structured` | object | Pre-parsed: `neutral_citation` (preferred), `style_of_cause`, `court`, `year`, `decision_number`, `pinpoint_paragraph`. Skips the parse; fully deterministic. | | `citations` | array | Up to 100 items of the fields above. Mutually exclusive with the top-level shorthand. Each item resolves independently: a bad item yields a per-item `error` and never fails the others. | | `as_of` | date | Replay the good-law verdict at a date. A case overruled in 2019 was good law in 2018, and a brief about 2018 conduct needs the 2018 answer. | | `include_treatments` | boolean | Also return `treatments`: the full dated ledger, worst-first. Off by default; `authority` carries the counts either way. | | `strict` | boolean | When the deterministic parser cannot read the citation, the service asks a small language model for a second reading. `strict: true` refuses that fallback so identical input always yields identical output. The deterministic parse always wins when it succeeds, and the model never mints a graph id. | ``` curl -X POST https://api.maplejuris.com/v1/validate-case-citation \ -H "Authorization: Bearer $MAPLEJURIS_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "R. v. Jordan, 2016 SCC 27, at para 46", "include_treatments": true}' ``` ```json { "results": [{ "index": 0, "request": {"query": "R. v. Jordan, 2016 SCC 27, at para 46", "include_treatments": true}, "response": { "query": "R. v. Jordan, 2016 SCC 27, at para 46", "valid": true, "validity_score": 0.95, "status": "resolved", "case": { "slug": "ca/case/2016-scc-27", "neutral_citation": "2016 SCC 27", "reporter_citation": null, "title": "R. v. Jordan", "court": "SCC", "jurisdiction": "CA", "decision_date": "2016-07-08", "docket": "36068", "source_url": "https://decisions.scc-csc.ca/scc-csc/scc-csc/en/item/16057/index.do", "in_corpus": true }, "pinpoint": {"paragraph_number": "46", "anchor": "par46", "text": "At the heart of the new framework is a ceiling beyond which delay is presumptively unreasonable. ..."}, "score_breakdown": {"graph_match": true, "neutral_citation_verified": true, "unique_match": true, "decision_ingested": true, "pinpoint_verified": true, "case_in_corpus": null}, "authority": { "status": "positively_treated", "as_of": null, "negative_events": 0, "cautionary_events": 0, "positive_events": 8, "neutral_events": 2, "unreviewed_excluded": 0, "coverage": {"ingested_decisions": 4908, "corpus_description": "Supreme Court of Canada decisions ingested from the Court's official judgments site, 1970-01-27 to 2026-07-31", "complete": false}, "note": "Status reflects only treatments recorded in the ingested corpus. Absence of negative treatment is not confirmation the case is good law; the coverage boundary states exactly what this claim is computed against.", "events": [{"relationship": "applies", "direction": "positive", "effective_date": "2016-07-08", "treating_case_citation": "2016 SCC 28", "treating_case_title": "R. v. Williamson", "evidence_class": "court_printed", "evidence_text": "Applied: R. v. Jordan, 2016 SCC 27, [2016] 1 S.C.R. 631", "pinpoint_paragraphs": ["2", "18", "19"], "target_pinpoint_paragraphs": []}] }, "treatments": {"target_citation": "2016 SCC 27", "events": [ … ]}, "alternatives": [], "notes": [] }, "error": null }], "summary": {"total": 1, "succeeded": 1, "failed": 0, "valid": 1, "avg_validity_score": 0.95} } ``` ### Field reference (each `results[].response`) | Field | Meaning | |---|---| | `valid` | True iff the citation deterministically resolved to a fully ingested decision and the cited paragraph, if any, exists. A graph fact, not a threshold. | | `validity_score` | 0-1. 0.95 a full match with the pinpoint verified · 0.5 a `coverage_gap` (the case is known but its text is not ingested) · 0.15 nothing matched. | | `status` | `resolved` (full decision matched) · `coverage_gap` (the case is a known citation target - cited by an ingested decision - but its own decision is not ingested) · `not_found` (no case in the graph matched) · `unsupported` (nothing parseable as a case citation). | | `case` | `title`, `neutral_citation`, `reporter_citation` (the S.C.R. form - how everything the Court decided before neutral citations began in 2000 is cited), `court`, `jurisdiction`, `decision_date`, `docket`, `source_url` (the Court's own page), `slug` (`ca/case/2016-scc-27`, `ca/case/1959-scr-121`; also accepted as a `query`), `in_corpus`. | | `pinpoint` | The cited paragraph: `paragraph_number`, `anchor`, verbatim `text`. Null when no paragraph was cited. | | `authority` | The good-law fold. `status` ∈ `overruled` · `overruled_in_part` · `negatively_treated` · `cautionary` · `positively_treated` · `no_treatment_found`. Event counts by direction (`negative_events`, `cautionary_events`, `positive_events`, `neutral_events`), `unreviewed_excluded`, `as_of`, the full `events` ledger worst-first (each: `relationship`, `direction`, `effective_date`, `treating_case_citation`, `treating_case_title`, `evidence_text` - the Court's printed words -, `pinpoint_paragraphs` in the treating decision, `target_pinpoint_paragraphs` in the treated one), and `coverage` - an explicit statement of what the verdict was computed against, so "nothing found" never reads as "nothing exists". **Null when the citation did not resolve, or when the case-law graph holds no ledger for the case** - the citation still validates; only the verdict is missing. | | `treatments` | The same ledger as `authority.events`, returned only when `include_treatments` was set. | | `score_breakdown` | `graph_match`, `neutral_citation_verified`, `unique_match`, `decision_ingested`, `pinpoint_verified` (null when no paragraph was cited - nothing verified, nothing claimed), `case_in_corpus` (set only when NOT a full match). | | `alternatives` | Other candidate decisions when the match was not unique. | | `notes` | Plain-language caveats: "Case is a known citation target but its decision is not yet ingested.", etc. | `summary`: `total`, `succeeded`, `failed`, `valid`, `avg_validity_score` - across the whole request, one citation or a hundred. ### Coverage: which courts are indexed Supreme Court of Canada decisions from 1970-01-27 onward, ingested in full from the Court's official judgments site (4,908 decisions as of 2026-09-04; `authority.coverage` carries the live number and date range on every response). The Court withdrew its unilingual 1877-1969 decisions from public access in November 2024 pending translation, so those exist in the graph only as **citation targets** - a case an ingested decision cites - and resolve as `coverage_gap` with `case.in_corpus: false` (Roncarelli v Duplessis, [1959] S.C.R. 121 is one). Lower courts and tribunals are not yet ingested: their decisions, when cited by the SCC, also surface as `coverage_gap`; otherwise `not_found`. A `not_found` is a statement about the corpus, never a statement that the case does not exist. ## POST /v1/discover-cases A legal situation in, ranked decisions out, at **paragraph level**. Discovery extracts doctrinal concepts from the situation, then runs deterministic structural retrieval over the case-law graph - paragraph text, issues, and legislation anchors - and re-ranks by how heavily the corpus itself relies on each decision. No vector similarity. Every returned case carries `good_law` and its `treatments` inline: a discovered case that has since been overruled is worse than no result. | Field | Type | Notes | |---|---|---| | `situation` | string | Natural-language description of the issue. Optional when `concepts` is supplied. | | `concepts` | array of strings | Explicit doctrinal terms, e.g. `["unreasonable delay", "trial within a reasonable time"]`. Overrides extraction. The concepts-only form runs the whole path with **no model in it** - deterministic, replayable, auditable. | | `provision` | string | Legislation anchor, e.g. `Criminal Code s 718` - find cases citing it. | | `jurisdiction` | string | Optional filter (`CA`, `ON`). The corpus today is the Supreme Court of Canada. | | `top_k` | integer | 1-25, default 10. Also sets the credit reservation (below). | | `clarification_history` | array | Prior `{question, answer}` pairs from a `needs_clarification` response. Stateless: re-POST the same `situation` with the history extended. | **Pricing: 100 credits per result returned.** Because the count is not known until the pipeline has run, the call reserves `top_k × 100` up front and refunds the difference once the real count is known; `needs_clarification` and `no_results` cost nothing. Expect several seconds of latency when `situation` is supplied (an interpretation step runs); the concepts-only form is faster. ``` curl -X POST https://api.maplejuris.com/v1/discover-cases \ -H "Authorization: Bearer $MAPLEJURIS_KEY" \ -H "Content-Type: application/json" \ -d '{"situation": "Criminal trial delayed 40 months between charge and trial; the accused says the delay is unreasonable under s. 11(b) of the Charter", "top_k": 5}' ``` ```json { "status": "results", "clarifying_question": null, "applied_concepts": ["trial within a reasonable time", "Charter s. 11(b) delay", "presumptive ceiling", "waiver of delay", "defence and institutional delay"], "applied_anchors": [], "cases": [ { "title": "R. v. Cody", "neutral_citation": "2017 SCC 31", "reporter_citation": null, "court": "SCC", "decision_date": "2017-06-16", "score": 0.365, "good_law": "caution", "good_law_note": "Good-law status reflects only treatments found in the ingested corpus — absence of negative treatment is not confirmation the case is still good law. Verify against a full citator.", "authority_weight": 41, "matched_by": ["concept:paragraph"], "why": ["Paragraph text matches: presumptive ceiling, trial within a reasonable time, waiver of delay"], "on_point": [{"paragraph_number": null, "anchor": null, "text": "…"}], "treatments": [{"by_citation": "2019 SCC 55", "by_title": "R. v. K.J.M.", "by_date": "2019-11-15", "relationship": "distinguishes", "direction": "cautionary", "pinpoint": "…", "quote": "…", "source_url": "…", "review_status": "reported", "confidence": 0.9}], "source_url": "https://decisions.scc-csc.ca/…" }, { "title": "R. v. Jordan", "neutral_citation": "2016 SCC 27", "court": "SCC", "decision_date": "2016-07-08", "score": 0.358, "good_law": "good", "authority_weight": 104, "matched_by": ["concept:paragraph"], "why": ["Paragraph text matches: presumptive ceiling, trial within a reasonable time", "Cited 104 time(s) within the corpus.", "Relied on by other results here (applies x7, considers x2, refers_to x1) - doctrine-origin authority 1.00."] } ], "notes": ["Concepts extracted by the LLM analyze stage.", "Results are limited to fully-ingested decisions in the case-law corpus; absence of a case is not evidence it does not exist."] } ``` | Field | Meaning | |---|---| | `status` | `results` · `needs_clarification` (one `clarifying_question`; answer it and re-POST) · `no_results`. | | `applied_concepts` / `applied_anchors` | Exactly what was executed - the concepts (extracted or supplied) and the legislation anchors that resolved. | | `cases[].good_law` | `good` (no negative treatment recorded) · `caution` (distinguished/criticised) · `negative` (overruled/reversed by a later decision) · `unknown` (no treatment data). `good_law_note` states the coverage caveat every time. | | `cases[].treatments` | Each later treatment, fully lineaged: `by_citation`, `by_title`, `by_date`, `relationship` (overrules, reverses, distinguishes, follows, applies, considers…), `direction`, `pinpoint` in the treating case, the Court's verbatim `quote`, `source_url`, `review_status`, `confidence`. | | `cases[].on_point` | The matched paragraphs with verbatim text. | | `cases[].why` | Human-readable explanation: which concepts hit, how often the corpus cites the case, and whether other results here rely on it. | | `cases[].score` · `authority_weight` · `matched_by` | Rank; in-corpus citation count; the signals that fired (`concept:paragraph`, `concept:issue`, `legislation_anchor`). | ## Accepted citation styles The resolver reads free-form text, so citations can be passed as a lawyer would write them: - Short title + section - `Criminal Code s. 718` - Subsection - `Access to Information Act, s. 2(1)` - Paragraph / subparagraph - `s. 718.2(a)(i) of the Criminal Code` - Chapter form - `R.S.C. 1985, c. C-46, s. 2` - Common abbreviation - `PIPEDA s. 5`, `CDSA s. 4`, `ITA s. 18` - Defined term - `definition of "officer" in s. 2 of the Customs Act` - Full statute title - `Income Tax Act s. 18` - Jurisdiction-qualified title - `Ontario Business Corporations Act, s. 108` (the qualifier scopes the lookup: `determined_by: "title_qualifier"`) - Clause / subclause - `Income Tax Act, s. 110(1)(d)(i)(A)` - Regulation number - `SOR/2002-227, s. 10` · `C.R.C., c. 870, s. A.01.001` · `O. Reg. 4/00, s. 1` · `R.R.O. 1990, Reg. 194, r. 1.04`. The number is an exact identity: it derives the instrument's canonical slug and is looked up as an id, so a generic title like "General" (the actual title of hundreds of Ontario regulations) can never pull the match onto a different regulation. - Range / list of sections - `Criminal Code, ss. 265-267`, `ss. 12 and 13` (every member is looked up; see below) - French reporters - `Code criminel, LRC 1985, c C-46, art 718`, `DORS/2002-227` (LRC = RSC, LC = SC, DORS = SOR, TR = SI - the same instruments under their French titles) - Provincial reporter - `Employment Standards Act, R.S.B.C. 1996, c. 113, s. 40` (`determined_by: "reporter"`; British Columbia is out of corpus, so this returns `unsupported` with `jurisdiction.in_corpus: false` and a note - never a same-titled statute from another jurisdiction) Pinpoints are read however they are punctuated - `s. 718`, `Section 718`, `718.`, `§ 718`, and a compound `2(1)` written into a single `section_label` all normalise to the same lookup, six levels deep (`s 110(1)(d)(i)(A)(I)` - section, subsection, paragraph, subparagraph, clause, subclause). A range or list of sections (`ss 265-267`, `ss 12 and 13`) is expanded and **every member is looked up**: all found → `resolved` with the first member as `target` and a note naming the rest; any missing → `provision_not_in_statute` for that member, named in a note. A span whose endpoints are not plain integers (`718-718.2`) cannot be enumerated and verifies its head only, with a note saying so. Scope of this route is Canadian legislation - federal and Ontario, per "Coverage: which jurisdictions are indexed" above. Case citations belong on `POST /v1/validate-case-citation`; sent here they return `unsupported`. Foreign law and every other Canadian jurisdiction return `unsupported` rather than a wrong match; a federal or Ontario statute we simply have not ingested returns `not_found` with `statute_in_corpus: false`. One deliberate exclusion, reported honestly rather than guessed at: constitutional instruments - the Constitution Acts, including the *Canadian Charter of Rights and Freedoms* - are outside the corpus permanently (`statute_in_corpus: false` plus a note saying the exclusion is permanent, so there is nothing to re-check as coverage grows). ## Slugs and provision ids Every response carries two stable identifiers alongside the human citation. `statute.slug` is a canonical, human-readable reference for the matched instrument: `{jurisdiction}/{act|reg}/{citation-token}`, lowercase, where the token is the instrument's own citation. Reading examples: | Slug | Citation it encodes | |---|---| | `ca/act/rsc-1985-c-c-46` | Criminal Code, RSC 1985, c C-46 | | `ca/act/rsc-1985-c-1-5th-supp` | Income Tax Act, RSC 1985, c 1 (5th Supp) | | `ca/act/rsc-1985-c-41-4th-supp-part-i` | ACOA Act - Part tails split Acts that share one chapter | | `on/act/rso-1990-c-b16` | Business Corporations Act, RSO 1990, c B.16 | | `ca/reg/sor-2002-227` | SOR/2002-227 (IRPR) | | `ca/reg/crc-c-870` | CRC, c 870 (Food and Drug Regulations) | | `on/reg/o-reg-4-00` | O Reg 4/00 | | `on/reg/rro-1990-reg-194` | RRO 1990, Reg 194 (Rules of Civil Procedure) | Reading rules: lowercase; punctuation becomes dashes, including periods between digits (chapter `A-10.7` → `a-10-7`; Ontario's `B.16` → `b16`). A slug never changes once minted - a changed citation mints a new instrument rather than rewriting the slug. `statute.id` is the slug with `/` replaced by `-`, then `--`, then the publisher's own document id: `ca-act-rsc-1985-c-c-46--114997`. `target.id` extends it with the provision's **type-tagged** structural path: `…--s-718-sub-2` is section 718, subsection (2). Type tags: `s` section · `sub` subsection · `p` paragraph · `sp` subparagraph · `cl` clause · `scl` subclause · `sch` schedule · `def` definition · `pt` part. The tags keep different law apart: `s-16-1` is section 16.1 while `s-16-sub-1` is section 16(1) - different provisions with opposite rules in the Access to Information Act. Treat both identifiers as opaque - match them, don't parse them - and send either back as `structured.id` to fetch exactly that instrument or provision again with no other field. Provision ids minted before 2026-08-24 used `-ss-` for subsections; they keep resolving. ## MCP server Human-readable setup guide (public, no account needed to read): https://maplejuris.com/mcp Endpoint: `https://api.maplejuris.com/mcp` Availability: the Commercial and Enterprise plans (and Custom agreements). Free and Research are REST only. Transport: streamable HTTP (JSON-RPC 2.0 over POST, SSE responses). Auth: the same credentials as REST, and the same two headers - `Authorization: Bearer` (what MCP clients send) or `X-API-Key`. ### Tools **`validate_citation`** - `query` (string, optional) - citation as written, natural language. - `structured` (object, optional) - pre-parsed citation; skips the model parse. Takes its own `jurisdiction`. - `jurisdiction` (string, optional) - code or full name, same values as the REST field; pass it whenever you know it. - Exactly one of `query` or `structured` must be supplied. - Returns the same object as `POST /v1/validate-citation`. **`validate_citations_batch`** - `queries` (array of strings, 1-100, required). - `jurisdiction` (string, optional) - applied to **every** citation in the list, so send one batch per jurisdiction when a document mixes them. - Returns `{results: [{index, query, response, error}], summary: {total, succeeded, failed, valid, avg_validity_score}}`. Note this differs from the REST batch shape: the MCP items carry the `query` string rather than a `request` object and have no `retryable` flag. `index` is present and unique here too. ### Client configuration Claude Code: ```bash claude mcp add maplejuris \ --transport http https://api.maplejuris.com/mcp \ --header "Authorization: Bearer $MAPLEJURIS_KEY" ``` Codex - `~/.codex/config.toml`: ```toml experimental_use_rmcp_client = true [mcp_servers.maplejuris] url = "https://api.maplejuris.com/mcp" bearer_token_env_var = "MAPLEJURIS_KEY" startup_timeout_sec = 20 ``` Then `export MAPLEJURIS_KEY=ak_your_key_here` and verify with `codex mcp list`. Codex rejects an inline `bearer_token` for streamable HTTP - it must be an environment variable reference. Cursor and other clients accepting an HTTP MCP entry: ```json { "mcpServers": { "maplejuris": { "type": "http", "url": "https://api.maplejuris.com/mcp", "headers": { "Authorization": "Bearer ak_your_key_here" } } } } ``` Claude Desktop and any client that only launches local processes, via the `mcp-remote` bridge: ```json { "mcpServers": { "maplejuris": { "command": "npx", "args": [ "mcp-remote@latest", "https://api.maplejuris.com/mcp", "--header", "Authorization: Bearer ${MAPLEJURIS_KEY}" ], "env": { "MAPLEJURIS_KEY": "ak_your_key_here" } } } } ``` ## API versions Each account uses one API version and keeps it: requests from its keys get that version's request and response shapes until the account changes it, also after a later version changes them. New fields and routes still appear. Every response from a versioned route names the version that answered in a `MapleJuris-Version` response header. Choose the account's version on the API keys page of the dashboard (https://maplejuris.com/dashboard/api-keys): any supported version, from 3.0.0 to the latest. A change applies to new requests within a minute. An organization's version applies to the organization's keys and is chosen by an organization admin. Until a version is chosen, the account uses the version current at its first API call, saved automatically. The `MapleJuris-Version` request header overrides the account's version for a single request (for example, to try a newer version before switching the account): ``` MapleJuris-Version: 3 # the newest 3.x release MapleJuris-Version: 3.8 # the newest 3.8.x release MapleJuris-Version: 3.8.0 # exactly that release ``` A version that was never released, or one older than 3.0.0, answers `400` with `code: "unsupported_api_version"`, `supported_versions`, `latest` and `min_supported`, before anything runs or is charged. A blank header counts as no header. `GET /v1/versions` (no key needed) returns `latest`, `min_supported`, `supported` (newest first), `header`, the breaking `changes` older versions are translated across, and `effective` / `source`: the version this request ran at and why (`header`, `pin` - the account's version, when the request carries an API key - or `latest`). `X-MapleJuris-Version`, on every response, is the newest version the API runs. When it is newer than `MapleJuris-Version`, a newer version is available; https://maplejuris.com/changelog says what changed. Breaking changes are announced at least 30 days before they ship, and support for an older version ends only with at least 30 days' notice. Versions apply to the routes called with an API key (`/v1/validate-citation`, `/v1/validate-case-citation`, `/v1/discover`, `/v1/discover-cases`, their `/batch` routes, `/v1/token`, `/v1/usage`, `/v1/versions`). The MCP server always runs the latest version. ## Plans, quotas, and errors | Plan | Licence | Monthly credits | Access | |---|---|---|---| | Free | Non-commercial | 1,000 | REST | | Research | Non-commercial | 10,000 | REST | | Commercial | Commercial | 100,000 | REST and MCP | | Enterprise | Commercial | 1,000,000 | REST and MCP | Current prices (monthly and billed-annually) are listed at https://maplejuris.com/pricing. Usage is counted in credits per account (summed across all the account's keys and tokens), per calendar month, and resets on the first of the month. Endpoints are priced for what they cost to serve: | Endpoint | Credits | Unit | |---|---|---| | `POST /v1/validate-citation` and `/batch` | 1 | per citation - a 100-citation batch costs 100, over REST or MCP | | `POST /v1/discover` | 10 | per call | | `POST /v1/validate-case-citation` and `/batch` | 50 | per citation - never per request | | `POST /v1/discover-cases` | 100 | per result returned - `top_k × 100` is reserved, then refunded down to the real count; `needs_clarification` and `no_results` cost nothing | `GET /v1/usage` returns this table as `pricing` (each entry `endpoint`, `credits`, `unit`), read from the same constants the server bills with. Over MCP only `tools/call` is metered - `initialize`, `tools/list`, and reconnecting are free (they still require a valid key). Quota exhaustion returns 429 after the handshake succeeds, so an MCP client connects and reports the error rather than failing to start. | Code | Meaning | |---|---| | `400` | `code: "unsupported_api_version"`: the `MapleJuris-Version` header names a version that was never released or is no longer supported. The body lists `supported_versions` and `latest`. | | `401` | Missing, revoked, or expired API key. | | `402` | The account's last payment failed. The plan is kept, but API and MCP calls are paused until a payment succeeds; the account owner updates the payment method at https://maplejuris.com/dashboard/billing. | | `403` | The plan behind the key does not include what was asked for: the MCP server on Free or Research, or an organization's key while the organization owner is not on Commercial or Enterprise. The message names the plan. | | `422` | Malformed body; the response names the offending field. Common causes: sending the batch as a bare array instead of `{"citations": [...]}`, sending both `query` and `structured` (or neither), and an unrecognised `jurisdiction`. | | `429` | Monthly allowance spent; the message states the plan and its limit. | | `500` | Unexpected server error; safe to retry. | | `503` | Temporary upstream failure - retry the request. Never a verdict about the citation. | ## Legal MapleJuris is a citation-verification tool, not a law firm. It does not provide legal advice and does not create a solicitor-client relationship. All output must be independently verified by a qualified legal professional. Statutory text is public law (Crown copyright, reproduced under the applicable federal and provincial reproduction terms); MapleJuris's proprietary rights are in the graph structure, embeddings, scoring, and validation methodology. Contact: info@maplejuris.com