{
  "openapi": "3.1.0",
  "info": {
    "title": "Lexiara",
    "version": "v1",
    "summary": "Provenance-backed EU/UK legislation, addressable to the provision.",
    "description": "Authoritative legislative text with provenance on every answer. Every provision payload carries an `authoritative` flag and a `provenance` block; derived data (transposition links, concept tags) carries method and confidence. Lexiara provides legal information, not legal advice.\n\nAn MCP server for AI agents is available at POST /mcp (see /docs/MCP.md in the repository); it exposes these same routes as tools under the same keys, quotas and metering."
  },
  "servers": [
    {
      "url": "https://lexiara.org"
    }
  ],
  "security": [
    {
      "bearerKey": []
    },
    {
      "headerKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key as 'Authorization: Bearer <key>'."
      },
      "headerKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "Provenance": {
        "type": "object",
        "description": "Where this text came from and whether it may be relied on. Downstream users are contractually required to preserve this (Terms of Service §3).",
        "properties": {
          "sourceUrl": {
            "type": "string",
            "description": "The official publication this text was retrieved from."
          },
          "retrievedAt": {
            "type": "string",
            "description": "When Lexiara retrieved it."
          },
          "licence": {
            "type": "string",
            "description": "Licence the source is reused under (e.g. OGL v3.0)."
          },
          "contentSha256": {
            "type": "string",
            "description": "Hash of the retrieved content."
          },
          "isAuthoritative": {
            "type": "boolean",
            "description": "True when the text reproduces the cited official source. False text (e.g. machine translation) is a reading aid, never the operative law."
          }
        }
      }
    }
  },
  "paths": {
    "/v1/health": {
      "get": {
        "summary": "Liveness. Public; does not touch the database.",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up."
          }
        }
      }
    },
    "/v1/openapi.json": {
      "get": {
        "summary": "This document. Public.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 description."
          }
        }
      }
    },
    "/v1/stats": {
      "get": {
        "summary": "Published accuracy and provenance statistics. Public.",
        "description": "Aggregate trust figures: provenance coverage, and classifier agreement measured against the publisher's own EuroVoc descriptors, with denominators and an honest statement of what is and is not being measured. The `graph` block reports the Lexiara Nexus with every numerator named — declared work-level edges, article-level candidates awaiting a human verdict, ratified and rejected links, resolved provision-to-provision citations and national mandates, each absolute and per 1,000 current provisions — plus directive-article coverage per declared lineage, reported twice as candidate and ratified coverage.",
        "security": [],
        "responses": {
          "200": {
            "description": "Aggregate statistics; no corpus text."
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "summary": "Who the caller is, and what is left of their free preview",
        "description": "The identity behind this request, resolved the same way every other route resolves it: a presented API key, then a session cookie, then an anonymous preview. `signedIn` reports whether a session was found; `preview` carries the remaining allowance and is null for a keyed or signed-in caller. Reads no corpus table and asserts nothing about the law. Sign-in itself is a browser redirect flow (/auth/google/start), not an API call, so it is not specified here. Answerable anonymously, but NOT a public discovery route: it reports on the caller's own credential, so it inherits the global security.",
        "responses": {
          "200": {
            "description": "Caller identity and preview allowance."
          },
          "401": {
            "description": "The free preview is used up (`error: preview_exhausted`); sign in to continue."
          }
        }
      }
    },
    "/v1/me/keys": {
      "get": {
        "summary": "The signed-in person's own API keys (prefixes only)",
        "description": "Session-cookie authentication ONLY: an API key can never list or mint keys, the shared demo identity owns nothing, and a preview visitor has no identity yet. Secrets are never returned here — a key's secret exists in exactly one response, the POST that created it.",
        "responses": {
          "200": {
            "description": "Active keys: prefix, name, plan, timestamps."
          },
          "401": {
            "description": "Not signed in."
          }
        }
      },
      "post": {
        "summary": "Create an API key for use outside the browser",
        "description": "Issues a key owned by the signed-in profile and returns the secret ONCE. Body: {\"name\": string} (optional label). Capped at five active keys per profile — an abuse bound, not a product limit. Credential management only: it touches no corpus table and asserts nothing about the law — POST /v1/review/verdict remains the only route that does.",
        "responses": {
          "201": {
            "description": "The new key, secret included — shown once."
          },
          "401": {
            "description": "Not signed in."
          },
          "409": {
            "description": "Too many active keys; revoke one first."
          }
        }
      }
    },
    "/v1/me/keys/{prefix}": {
      "delete": {
        "summary": "Revoke one of your own keys",
        "description": "Scoped to the signed-in profile: anyone else's prefix is a 404 here regardless of validity, the same not-yours-means-not-found rule the follow routes use. Revoking the 'web session' key is harmless — a fresh one is issued on the next request.",
        "responses": {
          "200": {
            "description": "Revoked."
          },
          "401": {
            "description": "Not signed in."
          },
          "404": {
            "description": "No such active key on this profile."
          }
        }
      }
    },
    "/v1/me/apps": {
      "get": {
        "summary": "Applications this person has authorised over OAuth",
        "description": "Session-cookie authentication ONLY, exactly as key management is: an access token must not be able to enumerate its siblings, or a stolen one could quietly evict the others. Each entry is one grant — client name, when it was connected, when it last read.",
        "responses": {
          "200": {
            "description": "Connected applications, newest first."
          },
          "401": {
            "description": "Not signed in (an access token is not a session)."
          }
        }
      }
    },
    "/v1/me/apps/{id}": {
      "delete": {
        "summary": "Disconnect an application",
        "description": "Revokes the grant and every token issued from it, immediately — not at the next expiry. Scoped to the signed-in profile, so another person's grant id is a 404 rather than a refusal that confirms it exists. Credential management only: it touches no corpus table and asserts nothing about the law — POST /v1/review/verdict remains the only route that does.",
        "responses": {
          "200": {
            "description": "Disconnected; its tokens stopped working at once."
          },
          "401": {
            "description": "Not signed in."
          },
          "404": {
            "description": "No such live grant on this profile."
          }
        }
      }
    },
    "/v1/admin/users": {
      "get": {
        "summary": "Every profile, its plan, and this month's usage (operator only)",
        "description": "Session-cookie authentication PLUS the profile's is_admin flag, which only the grant-admin ops workflow can set — no API key and no web request opens this surface. Lists accounts with plan, effective quota, key count and usage for the current period.",
        "responses": {
          "200": {
            "description": "Profiles with plan and usage."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Signed in, but not an operator."
          }
        }
      }
    },
    "/v1/admin/users/{id}/plan": {
      "post": {
        "summary": "Set a profile's plan (operator only)",
        "description": "Body: {\"plan\": \"free\"|\"pro\"|\"enterprise\"}. Takes effect immediately for every key the profile holds, because quotas are derived from the owner's plan at request time rather than snapshotted at issue. Account bookkeeping only: it touches no corpus table and asserts nothing about the law — POST /v1/review/verdict remains the only route that does.",
        "responses": {
          "200": {
            "description": "The new plan and its derived limits."
          },
          "400": {
            "description": "Unknown or role-only plan name."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Signed in, but not an operator."
          },
          "404": {
            "description": "No such profile."
          }
        }
      }
    },
    "/v1/admin/keys/{prefix}/quota": {
      "post": {
        "summary": "Set or clear a bespoke per-key override (operator only)",
        "description": "Body: {\"quotaMonthly\": integer|null, \"rateLimitPerMinute\": integer|null} — an integer pins an override for that one key; null clears it so the key derives from its plan again; a field left out is left alone. Account bookkeeping only: it touches no corpus table and asserts nothing about the law.",
        "responses": {
          "200": {
            "description": "The override set, and the now-effective limits."
          },
          "400": {
            "description": "Malformed body."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Signed in, but not an operator."
          },
          "404": {
            "description": "No active key with that prefix."
          }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "summary": "Start a 'pro' subscription (signed-in browser only)",
        "description": "Creates a Stripe Checkout session for the signed-in profile and returns its URL; the browser completes payment on Stripe's page, and the webhook moves the plan when it succeeds. Session-cookie only — no API key can start a subscription. Billing plumbing only: it touches no corpus table and asserts nothing about the law.",
        "responses": {
          "200": {
            "description": "{url}: where to send the browser."
          },
          "401": {
            "description": "Not signed in."
          },
          "409": {
            "description": "Already subscribed — use the portal."
          },
          "502": {
            "description": "Payment provider unreachable."
          },
          "503": {
            "description": "Billing not configured; body names the missing secrets."
          }
        }
      }
    },
    "/v1/billing/portal": {
      "post": {
        "summary": "Manage an existing subscription (signed-in browser only)",
        "description": "Creates a Stripe billing-portal session — change card, cancel, download invoices — and returns its URL. Requires a customer record, which only a completed checkout creates. Billing plumbing only: it touches no corpus table and asserts nothing about the law.",
        "responses": {
          "200": {
            "description": "{url}: where to send the browser."
          },
          "401": {
            "description": "Not signed in."
          },
          "409": {
            "description": "No subscription on file yet."
          },
          "502": {
            "description": "Payment provider unreachable."
          },
          "503": {
            "description": "Billing not configured; body names the missing secrets."
          }
        }
      }
    },
    "/v1/billing/webhook": {
      "post": {
        "summary": "Stripe's event delivery — signature-authenticated",
        "description": "Authenticated by the Stripe-Signature header (HMAC over the raw body, five-minute tolerance), never by key or cookie. A verified event may move profile.plan between free and pro and record the Stripe customer/subscription ids — nothing else. Billing plumbing only: it touches no corpus table and asserts nothing about the law — POST /v1/review/verdict remains the only route that does.",
        "responses": {
          "200": {
            "description": "Received; `handled` says whether it changed anything."
          },
          "400": {
            "description": "Signature verification failed, or unparseable event."
          },
          "503": {
            "description": "Billing not configured; body names the missing secrets."
          }
        }
      }
    },
    "/v1/provisions": {
      "get": {
        "summary": "One provision's authoritative text, enriched",
        "description": "Text plus in-force window, repealed/prospective status, concept tags, transposition edges and provenance. `stand` states the date the SERVED text speaks as of and what that excludes — `kind` is `consolidated` (with a `date`), `as-adopted` (an EU act whose later amendments are not folded in) or `unknown` (the publisher gives no date) — and `sentence` is that in one line. `inForce` is unchanged and is a different fact: when this version's validity starts. `structure` carries immediate parent/child addresses, hasChildren and textRole from the same selected expression. Empty container text is not missing law: traverse children using their apiHref or pageHref. `sourceLang` selects a held three-letter source language and preserves language through those links; it never translates and cannot be combined with `lang`. `expression` selects one exact stored expression UUID, normally copied from `result_handles.expressionId` returned by an ingestion request. Use it to preserve the acquired expression and language; omit it for the normal current-expression selection. `asAt` serves point-in-time law only when a held expression has an explicit validity window covering that date; otherwise the response is `date_not_covered`. An undated current snapshot never establishes historical coverage. `asAt` and `lang` cannot be combined; omit `asAt` to request current text or a current translation. `lang` serves a translation, flagged non-authoritative, with the original attached.\n\n`lang` NEVER returns the source language dressed as the target. If we hold an authentic or official version in that language you get that, authoritative, free and to anyone. Otherwise a machine rendering is generated once and cached; generating one needs a signed-in account or a key with Nexus allowance left, though the result is then served to everyone. Where none can be produced you get 501 (no engine configured), 401 (`translation_gated`) or 502 (the engine did not answer) — each carrying the authoritative `original`, because that is the part we can always answer with.\n\n`nationalMandate` says where NATIONAL law lives under this provision, read off the act's own words: `implements` (a duty on Member States to legislate), `derogates` (an option it grants, so national law may diverge) or `exceeds` (a stricter national rule it permits or grandfathers — gold-plating). `cue` is the publisher's own sentence, so the reading is checkable rather than trusted. It never names WHICH national measure answered: for a Regulation no authority publishes that mapping.\n\n`workType` matters for reading an EMPTY `transposedBy`. A Regulation is directly applicable and never transposed (Article 288 TFEU), so zero edges is the correct answer and not a coverage gap — the two are indistinguishable without this field.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument, e.g. http://www.legislation.gov.uk/id/ukpga/1994/23 (UK VAT Act 1994) or http://data.europa.eu/eli/dir/2006/112/oj (EU VAT Directive). Where the publisher issues no ELI the official number addresses the work instead — Légifrance issues none for a code, so the Code de l'environnement is 'LEGITEXT000006074220'. Search results carry both as work.eli and work.officialNumber; either is accepted here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eId",
            "in": "query",
            "required": true,
            "description": "Provision identifier: 'sec_4' (section 4), 'art_2__para_1' (article 2(1)), 'sch_8__part_ii__grp_1__para_1' (Schedule 8, Part II, Group 1, item 1).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expression",
            "in": "query",
            "required": false,
            "description": "Optional exact expression UUID, normally from an ingestion result handle. Preserves the selected expression and language instead of resolving the current expression.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sourceLang",
            "in": "query",
            "required": false,
            "description": "Stored three-letter source language, e.g. eng or fra. Can combine with asAt; cannot combine with lang translation.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "asAt",
            "in": "query",
            "required": false,
            "description": "Real calendar date YYYY-MM-DD. Returns text only when an explicit held validity window covers the date; otherwise `date_not_covered`. Omit for current text.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "ISO 639 language code, two or three letters, e.g. 'eng' or 'fr' — a translation request. Anything else is a 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The provision, with `authoritative`, `notice`, `provenance`, `workType` and `nationalMandate`."
          },
          "400": {
            "description": "Malformed/impossible `asAt`, invalid `lang`, or the unsupported `asAt + lang` combination."
          },
          "404": {
            "description": "`not_found` for an unknown work/eId; `date_not_covered` when the address exists but no explicit held validity window covers `asAt`."
          }
        }
      }
    },
    "/v1/search": {
      "get": {
        "summary": "Ranked full-text search over provision text",
        "description": "Terms are matched conjunctively first. If NOTHING contains all of them, the terms are ORed and ranked instead, and `matchMode` says which pass answered: 'all-terms' (exact), 'any-term' (partial — the payload also carries a `matchNotice`, and results are leads rather than answers), or 'none'. Search returns the most specific provision that matches, so a section whose subsections carry the text is represented by those subsections; ask /v1/works/outline for structure. A query that is ITSELF a citation — 'L. 541-10-9-1', 'Article 6(1)', 'section 12(3)' — is also resolved by address, and the provision it names comes back under `citation` alongside the ranked `results`. That lane exists because ranking cannot answer it: Postgres splits 'L. 541-10-9-1' into 'l' & '541' & '-10' & '-9' & '-1' and the stored number 'L541-10-9-1' into 'l541' & …, so an article's own number never matches its own citation. `citation` is absent when the query is prose or when the corpus holds no such provision.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search terms, e.g. 'zero-rating of food'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Restrict to a text language, e.g. 'eng'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum hits, default 10.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "subject",
            "in": "query",
            "required": false,
            "description": "Restrict to provisions carrying this subject (a conceptId from `subjects` on an unfiltered response). Counts are exact: a subject listed with 42 returns 42.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "When the query matches nothing AS TYPED but is a known acronym (VLOP, GDPR, RGPD), the alias layer answers with the phrase the statutes actually use and the response carries `alias` + `aliasNotice` — the resolution is editorial (curated list or the instrument's own title parenthetical), never statutory text. Hits with work, eId, status, rank, a match-centred `snippet` ([[ ]] fences mark the matched terms) and provenance, plus `total` (matches beyond this page), `works` (instruments the query NAMES — by number ('2005/29', '32005L0029', 'ukpga/2024/13'), by alias or by title, strongest claim first, each carrying `matchedOn`), `concepts`, `matchMode` and — when the match was partial — `matchNotice`. Ranking prefers headings over body mentions and demotes repealed/prospective text; not-yet-in-force law never outranks law in force."
          }
        }
      }
    },
    "/v1/provisions/equivalents": {
      "get": {
        "summary": "Corresponding provisions in other jurisdictions, via the EU anchor",
        "description": "The lateral question: what is this provision, in the jurisdiction where I am qualified? Answered HUB-AND-SPOKE: a national provision is linked to the EU article it implements, and the lateral answer is the pivot through that shared anchor — never an edge asserted directly between two national laws. 'Corresponds via CRD Article 9' is a claim the corpus can stand behind; 'is the same as' is not, because transposition splits, merges and exceeds the EU floor.\n\nEach anchor carries the asking provision's OWN link (`ownLink`) and the sibling provisions on that anchor, every hop with its method, confidence, review verdict and corroboration — a lateral inference is only as strong as its weaker hop, and the two numbers are never averaged for you. Rejected links are excluded; unreviewed model links are returned and labelled as leads. Asked of an EU provision, `perspective` is 'eu' and the fan-out is direct.\n\nSIGN-IN REQUIRED. This is derived data — our own analysis, not the publisher's record — so it answers identified callers only. The free plan carries a monthly allowance, reported in `x-lexiara-graph-remaining`.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument, e.g. http://www.legislation.gov.uk/id/ukpga/1994/23 (UK VAT Act 1994) or http://data.europa.eu/eli/dir/2006/112/oj (EU VAT Directive). Where the publisher issues no ELI the official number addresses the work instead — Légifrance issues none for a code, so the Code de l'environnement is 'LEGITEXT000006074220'. Search results carry both as work.eli and work.officialNumber; either is accepted here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eId",
            "in": "query",
            "required": true,
            "description": "Provision identifier: 'sec_4' (section 4), 'art_2__para_1' (article 2(1)), 'sch_8__part_ii__grp_1__para_1' (Schedule 8, Part II, Group 1, item 1).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "text",
            "in": "query",
            "required": false,
            "description": "'1' to carry each corresponding provision's own text as `text`. Off by default: a panel that lists addresses does not want six national texts, and the screen that sets them side by side wants nothing else.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`perspective`, `anchors[]` (EU anchor + ownLink + equivalents), and a `notice` stating the correspondence doctrine."
          },
          "401": {
            "description": "Not signed in. The Lexiara Nexus is for identified callers; the legislative text stays free. The body carries `signIn`."
          },
          "404": {
            "description": "No such work/eId."
          },
          "429": {
            "description": "The monthly Lexiara Nexus allowance for this plan is used up. `x-lexiara-graph-remaining` tracks it on every successful call."
          }
        }
      }
    },
    "/v1/provisions/context": {
      "get": {
        "summary": "Why a provision matters when it does not say so itself",
        "description": "The provisions this one cites and the provisions that cite it (resolved within the instrument), plus the consolidation amendment markers on this provision AND on the ones it points at, resolved to the amending act through the document's own amendment table. This is how a duty connects to an amending directive whose subject it never mentions: 'the information referred to in Article 6(1) ... on a durable medium' → Article 6(1) → points inserted by Directive (EU) 2024/825. Every hop is the publisher's own text; nothing is inferred. ALSO carries the cross-INSTRUMENT citation layer. Internal cross-references stay within one instrument; `cites` names the acts this provision points at, and `citedBy` names the instruments in this corpus that point at THIS act. `citedBy` is ACT-level; `cites` also carries the provision named, where the publisher named one adjacently — 'Article 30(1), points (d) and (e), of Regulation (EU) 2022/2065' gives `toPath` '30(1)(d)(e)' and `toEId` resolved against the CITED act's own provisions. `toEId` is null rather than guessed, and the act-level edge stands whenever it is. `citedByArticle` is the inbound direction at ARTICLE granularity — the question `citedBy` cannot answer, since it returns the same list on every provision of the act — split into `eu` and `national` by the CITING instrument's jurisdiction.\n\nSIGN-IN REQUIRED. This is derived data — our own analysis, not the publisher's record — so it answers identified callers only. The free plan carries a monthly allowance, reported in `x-lexiara-graph-remaining`.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument, e.g. http://www.legislation.gov.uk/id/ukpga/1994/23 (UK VAT Act 1994) or http://data.europa.eu/eli/dir/2006/112/oj (EU VAT Directive). Where the publisher issues no ELI the official number addresses the work instead — Légifrance issues none for a code, so the Code de l'environnement is 'LEGITEXT000006074220'. Search results carry both as work.eli and work.officialNumber; either is accepted here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eId",
            "in": "query",
            "required": true,
            "description": "Provision identifier: 'sec_4' (section 4), 'art_2__para_1' (article 2(1)), 'sch_8__part_ii__grp_1__para_1' (Schedule 8, Part II, Group 1, item 1).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`references.out` / `references.in` with the citation as written, `citedByArticle.eu` / `citedByArticle.national` with the provisions of other instruments that name THIS article (a citation resolved to a paragraph counts for its article; the widening is one-way), and `amendedBy` with marker, amending act and whether the amendment is on this provision (`direct`) or on one it cites."
          },
          "401": {
            "description": "Not signed in. The Lexiara Nexus is for identified callers; the legislative text stays free. The body carries `signIn`."
          },
          "429": {
            "description": "The monthly Lexiara Nexus allowance for this plan is used up. `x-lexiara-graph-remaining` tracks it on every successful call."
          }
        }
      }
    },
    "/v1/works/text": {
      "get": {
        "summary": "The whole instrument, in reading order",
        "description": "An act as a document rather than as rows: every provision in document order with its text, the internal citations it makes, and the consolidation markers on it. This is what /v1/works/outline is not — the outline serves structure without text.\n\nCHUNKED: a national code runs to twelve thousand provisions, so follow `nextOffset` until it is null. Aggregate roll-up parents arrive with `text: null` because their text repeats their children; parents that own source lead-in prose retain it beside their children.\n\n`ownText` carries what a roll-up parent holds that NONE of its children carry — an article's lead-in, a second subparagraph, the wrap-up under a list. It is the parent speaking and carries no address of its own: there is no provision there to cite, and it is absent when the children carry every word, which is what a complete decomposition looks like.\n\nREAD `ownText.resolution` BEFORE RENDERING `ownText.segments`. `exact` — every child was located unambiguously and in order, so each segment's `afterChildEId` says which child it follows and null is the lead-in. `unplaced` — a child's words also occur in the parent's own prose, or a child could not be located; every `afterChildEId` is null, the runs must be shown under the parent rather than in a slot between children, and some wording may also appear below because an ambiguous child's text is left in rather than subtracted on a guess. `unresolved` — no child was located at all, so the single segment is the WHOLE aggregate and must be shown as that, behind a disclosure; inline it repeats every descendant. `childrenMatched`/`childrenTotal` and the `ambiguousChildren` and `unmatchedChildren` address lists say why. `expressionId` names the expression this answer came from. A work can hold more than one at a single version point, so which one answered is part of the answer: without it a caller comparing two responses cannot tell a data change from a selection change. The reading, the outline and the export resolve it the same way — newest version point, then newest capture, then the id — so one work is never served from two.\n\n`terms` lists THIS instrument's own defined terms (longest first, ready for alternation matching) and ships once per document: on the first chunk, or on any request naming an `eId`. A term inside an act means what that act says it means, which is why the list is never widened to the whole corpus.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI or official number of the instrument — Légifrance codes have no ELI and are addressed by their LEGITEXT id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expression",
            "in": "query",
            "required": false,
            "description": "Optional exact expression UUID, normally from `result_handles.expressionId` returned by an ingestion request. Preserves the acquired expression and language instead of selecting the current expression.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Provisions to skip, in document order. Default 0.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Provisions per chunk. Default 400, clamped to 500.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "eId",
            "in": "query",
            "required": false,
            "description": "Snap the window to the chunk containing this provision, and echo it back as `anchorEId`. How a deep link into the middle of an act works.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`provisions` in document order with `text`, `refs` and `amendments`; `total`/`offset`/`limit`/`nextOffset` for paging; `terms` on the first chunk; plus `notice`, `notices` and `provenance`."
          },
          "400": {
            "description": "`work` was not given."
          },
          "404": {
            "description": "No such instrument (or not held — see /v1/coverage)."
          }
        }
      }
    },
    "/v1/works/terms": {
      "get": {
        "summary": "Every term an instrument defines — the act's glossary",
        "description": "The act-level question the defined-terms index could not answer until 2026-09-03: /v1/terms takes a phrase and returns acts; this takes an act and returns its phrases, alphabetical, with the publisher's own wording verbatim and the provision that defines each one.\n\nA ZERO IS ABOUT OUR READER, NOT THE ACT. Extraction reads the drafting constructions it has been taught, and coverage is uneven across legal traditions — `provisions` is returned so an empty glossary can be read against the size of the instrument.\n\nA NULL `definition` MEANS ONE OF TWO THINGS: with `borrowed` true the instrument takes the definition from another act and states no meaning of its own; otherwise the term is marked as defined here and the wording could not be delimited. `count` is three populations — `defines`, `adoptsCount`, `undelimited` — never one figure standing for all three.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI or official number of the instrument.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The instrument's identity, the three counts, `provisions`, the authentic `languages` any entry carries a wording in, and `entries[]` — term, definition, method, confidence, usageCount, the defining provision (eId/pathLabel/point), `borrowed`, and `languages[]`. `borrowed` is free (the act's own text says it); the `adopts` resolution — which act, which article, whether we hold it — is derived and requires a key on a signed-in account, disclosed as `gated.adoptions` otherwise."
          },
          "400": {
            "description": "`work` was not given."
          },
          "404": {
            "description": "No such instrument (or not held — see /v1/coverage)."
          }
        }
      }
    },
    "/v1/terms": {
      "get": {
        "summary": "Where the law defines a phrase",
        "description": "The defined-terms index. Returns the provisions that DEFINE a term of art, with the publisher's own definition text verbatim, ordered best-evidence first (human verdict, then the publisher's own markup, then the quoted-'means' construction), and between instruments of equal standing by `usageCount` — how many provisions of the defining instrument use the term, measured at the last extraction run. Search returns provisions that use a phrase; this returns the law that gives it meaning. Definitions are scoped to the instrument that carries them: several instruments may define one term differently and all be right, so check the work before applying one. A row carrying `adopts` does NOT define the term — that instrument borrows the definition from the act named there and states no meaning of its own, so `definition` is null. Instruments that write a definition rank above instruments that borrow one.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "The phrase, e.g. 'durable medium'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum definitions, default 8.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Definitions with term, definition text, method, confidence, usageCount, and the defining provision's work/eId/jurisdiction. A work whose stored title is a repo-written placeholder is presented by its citation (e.g. 'Directive 85/374/EEC'). An adopting row carries `adopts` with the citation, the adopted act's CELEX and title, `eId` where the citation names a provision, and `held` — whether this corpus holds that act."
          }
        }
      }
    },
    "/v1/terms/languages": {
      "get": {
        "summary": "The same defined term in the act's other authentic languages",
        "description": "How the act itself words a defined term in each language version held. NOT A TRANSLATION: an EU instrument is equally authentic in all 24 official languages (Regulation No 1/1958, article 4), so every wording returned is operative law in that language, and `translationStatus` can never read 'machine'. Alignment is the publisher's own — same act, same eId, same point of the same definition list, matched on the marker the publisher wrote, never on similarity between two texts. English and German number an inserted point '14a' where French numbers it '14 bis'; those are one point. The phrase may be given in ANY language held, so 'gewerbliche Haltbarkeitsgarantie' and 'commercial guarantee of durability' reach the same answer. This is how the EU act words the term — NOT how a Member State's transposing legislation words it, which is an inferred question answered by /v1/provisions/equivalents.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "The phrase, in any language held.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "work",
            "in": "query",
            "required": false,
            "description": "Scope to one instrument (ELI or official number).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum terms, default 8.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Terms with the base-language wording, the defining provision, the publisher's point marker, and `languages[]` — one entry per authentic version held, each with its own term, definition, point marker, translationStatus and provenance. `sameAsBase` marks a language that writes the term with the same words, which is a fact about the drafting rather than a gap. For every term of ONE instrument, use /v1/works/terms — the wordings are a column of the glossary rather than a second answer to the same question."
          },
          "400": {
            "description": "`q` was not given."
          }
        }
      }
    },
    "/v1/topics": {
      "get": {
        "summary": "Curated obligation topics under a subject head",
        "description": "Topics ('Pre-contract information') are curated navigation nodes between a subject and its provisions: a one-paragraph orientation, per-jurisdiction status notes and anchored provisions. Descriptions with editorialStatus='draft' are UNREVIEWED model drafts and every payload says so.",
        "parameters": [
          {
            "name": "subject",
            "in": "query",
            "required": true,
            "description": "Subject head concept id, e.g. 'local:consumer'.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Topic summaries with anchor counts per jurisdiction."
          }
        }
      }
    },
    "/v1/topics/{conceptId}": {
      "get": {
        "summary": "One topic: orientation, notes, anchors, article-level links",
        "description": "The full topic page payload: description (+ draft/published editorial status), per-jurisdiction legal-status notes (GB notes carry the assimilated-law caveat), anchored provisions with anchor provenance (method/confidence/source), article-level transposition links (reviewed and unreviewed, visibly distinguished), and related Nexus instrument relationships.",
        "parameters": [
          {
            "name": "conceptId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Topic concept id, e.g. 'local:topic-pre-contract-information'."
          }
        ],
        "responses": {
          "200": {
            "description": "The topic detail payload."
          },
          "404": {
            "description": "No such topic."
          }
        }
      }
    },
    "/v1/concepts": {
      "get": {
        "summary": "Subject-matter browse tree: groups and subtopics with counts",
        "description": "Top-level subject groups (concepts with no broader concept) and their direct subtopics, each with a rolled-up count of tagged provisions, a distinct-instrument count (workCount) and the jurisdictions those instruments come from (EU first). Only subjects the corpus actually reaches are returned — this reflects the corpus, not the whole thesaurus.",
        "responses": {
          "200": {
            "description": "Groups with nested topics, provision and instrument counts, jurisdictions."
          }
        }
      }
    },
    "/v1/concepts/{conceptId}/works": {
      "get": {
        "summary": "The instruments behind a subject ('parent legislation')",
        "description": "Every Work with at least one current provision tagged with the concept or (by default) one of its narrower concepts, with tagged-provision counts. `narrower` lists the concept's direct subtopics with counts, for drill-down below the two levels the browse tree shows.",
        "parameters": [
          {
            "name": "conceptId",
            "in": "path",
            "required": true,
            "description": "Namespaced concept id, e.g. 'local:taxation' or 'eurovoc:4585'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeNarrower",
            "in": "query",
            "required": false,
            "description": "Set 'false' for exact-concept matching only.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Works with jurisdiction, type and counts."
          }
        }
      }
    },
    "/v1/works/outline": {
      "get": {
        "summary": "A Work's table of contents, plus its landscape edges",
        "description": "The current Expression's provisions in document order — structure only, no text; fetch text per provision via /v1/provisions. `related` carries the instrument Nexus in both directions (implements / implemented by), and is public in both — a declared edge is the member state's own notification. `linkCoverage` is not: for an EU act it gives one row per (article, national jurisdiction) with `confirmed` and `proposed` counts, which is our reading of where each article is answered, so it arrives empty for a caller who is not signed in and a `gated.linkCoverage` teaser states its size instead. Links curated on a paragraph count towards their article.",
        "parameters": [
          {
            "name": "eli",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Work metadata, ordered outline entries, related instruments."
          },
          "404": {
            "description": "Unknown Work, or no current Expression held (a stub)."
          }
        }
      }
    },
    "/v1/works/mentions": {
      "get": {
        "summary": "Who names this act, provision by provision",
        "description": "The act-level overview: every provision of every instrument that names this act, grouped by citing instrument, with the article of this act each citation resolved to (`targetEId`, null where the citation is act-level). `related.in` on /v1/works/outline answers the same question at instrument level and is public; this is the derived layer and needs a signed-in caller. A `provisions` row asserts that the provision NAMES this act, and nothing about whether it implements or corresponds to it. An `adoptions` row is a second, different mention: the provision BORROWS a definition from this act and states no meaning of its own. The two are counted apart and never summed.",
        "parameters": [
          {
            "name": "eli",
            "in": "query",
            "required": true,
            "description": "ELI URI or official number of the instrument.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum citing provisions to return (default 500, cap 2000).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Citing instruments, each with its citing `provisions` and the `adoptions` — definitions it borrows from this act. The top-level `citations` and `adoptions` are separate totals and are never summed."
          },
          "401": {
            "description": "Not signed in. The Lexiara Nexus is for identified callers; the legislative text stays free. The body carries `signIn`."
          },
          "404": {
            "description": "Unknown Work, or no current Expression held (a stub)."
          },
          "429": {
            "description": "The monthly Lexiara Nexus allowance for this plan is used up. `x-lexiara-graph-remaining` tracks it on every successful call."
          }
        }
      }
    },
    "/v1/concepts/{conceptId}/provisions": {
      "get": {
        "summary": "Every provision tagged with a concept, cross-jurisdiction",
        "description": "The language-neutral join: line up EU and national law on one subject. Tags carry method + confidence; they are derived data.",
        "parameters": [
          {
            "name": "conceptId",
            "in": "path",
            "required": true,
            "description": "Namespaced concept id, e.g. 'eurovoc:4585' (VAT).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minConfidence",
            "in": "query",
            "required": false,
            "description": "Drop tags below this confidence (0–1).",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tagged provisions with tag metadata."
          }
        }
      }
    },
    "/v1/works/related": {
      "get": {
        "summary": "Instrument-level Nexus relationships",
        "parameters": [
          {
            "name": "eli",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "'in' = edges pointing at this work (default); 'out' = from it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Typed edges with method, confidence, source."
          }
        }
      }
    },
    "/v1/provisions/history": {
      "get": {
        "summary": "A provision's version history across consolidations\n\nSIGN-IN REQUIRED. This is derived data — our own analysis, not the publisher's record — so it answers identified callers only. The free plan carries a monthly allowance, reported in `x-lexiara-graph-remaining`.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument, e.g. http://www.legislation.gov.uk/id/ukpga/1994/23 (UK VAT Act 1994) or http://data.europa.eu/eli/dir/2006/112/oj (EU VAT Directive). Where the publisher issues no ELI the official number addresses the work instead — Légifrance issues none for a code, so the Code de l'environnement is 'LEGITEXT000006074220'. Search results carry both as work.eli and work.officialNumber; either is accepted here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eId",
            "in": "query",
            "required": true,
            "description": "Provision identifier: 'sec_4' (section 4), 'art_2__para_1' (article 2(1)), 'sch_8__part_ii__grp_1__para_1' (Schedule 8, Part II, Group 1, item 1).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Prior and successor versions."
          },
          "401": {
            "description": "Not signed in. The Lexiara Nexus is for identified callers; the legislative text stays free. The body carries `signIn`."
          },
          "404": {
            "description": "No such provision."
          },
          "429": {
            "description": "The monthly Lexiara Nexus allowance for this plan is used up. `x-lexiara-graph-remaining` tracks it on every successful call."
          }
        }
      }
    },
    "/v1/provisions/implementations": {
      "get": {
        "summary": "Article-level national implementations of an EU provision",
        "description": "DERIVED, not published by any authority. Every link carries method and confidence; treat links not marked method='human' as leads to verify.\n\nSIGN-IN REQUIRED. This is derived data — our own analysis, not the publisher's record — so it answers identified callers only. The free plan carries a monthly allowance, reported in `x-lexiara-graph-remaining`.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument, e.g. http://www.legislation.gov.uk/id/ukpga/1994/23 (UK VAT Act 1994) or http://data.europa.eu/eli/dir/2006/112/oj (EU VAT Directive). Where the publisher issues no ELI the official number addresses the work instead — Légifrance issues none for a code, so the Code de l'environnement is 'LEGITEXT000006074220'. Search results carry both as work.eli and work.officialNumber; either is accepted here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eId",
            "in": "query",
            "required": true,
            "description": "Provision identifier: 'sec_4' (section 4), 'art_2__para_1' (article 2(1)), 'sch_8__part_ii__grp_1__para_1' (Schedule 8, Part II, Group 1, item 1).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minConfidence",
            "in": "query",
            "required": false,
            "description": "Drop links below this confidence (0–1).",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Links, with an explicit derivation notice."
          },
          "401": {
            "description": "Not signed in. The Lexiara Nexus is for identified callers; the legislative text stays free. The body carries `signIn`."
          },
          "429": {
            "description": "The monthly Lexiara Nexus allowance for this plan is used up. `x-lexiara-graph-remaining` tracks it on every successful call."
          }
        }
      }
    },
    "/v1/provisions/guidance": {
      "get": {
        "summary": "Official guidance discussing one provision",
        "description": "Sections of NON-BINDING interpretive material — Commission notices, guidelines — whose text names this provision, quoted verbatim with the section number the publisher prints.\n\nEvery row carries `binding: false`, and so does the envelope's `notice`. Guidance states the view of the body that published it: only the instrument's own text has legal force, and only a court settles what it means. A client that renders these beside the provision must keep them visibly apart from it.\n\nSIGN-IN REQUIRED. That the guidance EXISTS is public and appears on /v1/works/outline; which section discusses which article is our reading of the text, so it answers identified callers only.",
        "parameters": [
          {
            "name": "work",
            "in": "query",
            "required": true,
            "description": "ELI URI of the instrument, e.g. http://www.legislation.gov.uk/id/ukpga/1994/23 (UK VAT Act 1994) or http://data.europa.eu/eli/dir/2006/112/oj (EU VAT Directive). Where the publisher issues no ELI the official number addresses the work instead — Légifrance issues none for a code, so the Code de l'environnement is 'LEGITEXT000006074220'. Search results carry both as work.eli and work.officialNumber; either is accepted here.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eId",
            "in": "query",
            "required": true,
            "description": "Provision identifier: 'sec_4' (section 4), 'art_2__para_1' (article 2(1)), 'sch_8__part_ii__grp_1__para_1' (Schedule 8, Part II, Group 1, item 1).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeActLevel",
            "in": "query",
            "required": false,
            "description": "Also return sections naming the act but no article (default false).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum sections, default 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guidance sections, with a non-binding notice."
          },
          "401": {
            "description": "Not signed in. The Lexiara Nexus is for identified callers; the legislative text stays free. The body carries `signIn`."
          },
          "429": {
            "description": "The monthly Lexiara Nexus allowance for this plan is used up. `x-lexiara-graph-remaining` tracks it on every successful call."
          }
        }
      }
    },
    "/v1/changes": {
      "get": {
        "summary": "Change feed — what the corpus observed",
        "description": "`detectedAt` is when Lexiara observed the change; legal in-force dates come only from the provision itself.",
        "parameters": [
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "concept",
            "in": "query",
            "required": false,
            "description": "Restrict to one concept id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO date: only changes detected after this.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum items, default 50.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Observed changes, newest first."
          }
        }
      }
    },
    "/v1/review": {
      "get": {
        "summary": "The review queue — everything awaiting a human verdict",
        "description": "Every AUTO-GATED item from one place, newest first, with recorded findings and divergent links pinned above it: unreviewed article-level transposition links carrying BOTH provision texts and the stored reasoning, model and low-confidence concept tags, draft topic descriptions and per-jurisdiction notes. Each item ships ready-to-POST confirm / reject / edit payloads. Everything listed is UNREVIEWED EVIDENCE — none of it is a finding, and no automated path in this service can make it one.",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Comma-separated: transposition, tag, topic-description, jurisdiction-note, finding. Omit for all.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "vertical",
            "in": "query",
            "required": false,
            "description": "Vertical id, e.g. 'eu-consumer', 'fr-consumer', 'es-consumer'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "ISO country filter, e.g. 'UK', 'FR', 'EU'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "urgent (default — corroborated and divergent links first), oldest (longest-waiting first, for working a backlog fairly) or weakest (lowest match score first, which is where rejections live).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum items, default 20, max 100.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Skip this many items — the queue is paged, not sampled.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "chars",
            "in": "query",
            "required": false,
            "description": "Characters of provision text per side; omit for the WHOLE text (the default — a byte budget once hid two parts of a French code article and produced two false absences).",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "lowConfidence",
            "in": "query",
            "required": false,
            "description": "Also list non-model classifier tags at or below this confidence. Omit for model tags only — the keyword baseline's weak tail runs to tens of thousands of rows and would bury the links a verdict changes.",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Counts, filters and one page of the queue."
          },
          "400": {
            "description": "Unknown kind, surface or sort."
          }
        }
      }
    },
    "/v1/review/verdict": {
      "post": {
        "summary": "Record a HUMAN verdict on one queued item",
        "description": "confirm / reject / edit, one item per call. Requires an API key on the 'reviewer' plan and accepts nothing else: the shared demo key does not open it, and an instance running with authentication disabled still refuses it. That is deliberate and it is the point of the whole surface — a model verdict recorded as a human one would poison the provenance layer this product stands on. Every call is written to an append-only audit whose key column cannot be null.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "id",
                  "action"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "transposition",
                      "tag",
                      "topic-description",
                      "jurisdiction-note",
                      "finding"
                    ]
                  },
                  "id": {
                    "type": "string",
                    "description": "The queued item's id."
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "confirm",
                      "reject",
                      "edit"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "description": "The reviewer's own reasoning."
                  },
                  "text": {
                    "type": "string",
                    "description": "Replacement editorial text (edit)."
                  },
                  "publish": {
                    "type": "boolean",
                    "description": "Publish a topic description in the same call as an edit."
                  },
                  "confidence": {
                    "type": "number",
                    "description": "Replacement tag confidence (edit)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verdict, as applied."
          },
          "400": {
            "description": "Malformed body or an action the kind does not accept."
          },
          "401": {
            "description": "No key presented."
          },
          "403": {
            "description": "A key that is not on the 'reviewer' plan."
          },
          "404": {
            "description": "No such item."
          }
        }
      }
    },
    "/v1/review/requests": {
      "get": {
        "summary": "Decisions the reviewer has been asked for, against the law they are about",
        "description": "Pending `request_legal_review` asks from Taliara's queue, each resolved to the corpus rows it names so the question can be answered where the evidence is. Takes the SAME credential as a verdict — a key on the 'reviewer' plan — and nothing else: these are the questions put to the lawyer, and the answer to one is a verdict. Reading changes nothing. `addressing.source` says whether the ids were DECLARED by the round that raised the ask or INFERRED from its text; an inferred set can be incomplete, and any address matching more than one link is dropped rather than guessed at. 503 when the instance is not wired to a queue.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100
            },
            "description": "Asks to return, newest first."
          }
        ],
        "responses": {
          "200": {
            "description": "Pending asks, each with its addressing."
          },
          "401": {
            "description": "No key presented."
          },
          "403": {
            "description": "A key that is not on the 'reviewer' plan."
          },
          "503": {
            "description": "This instance is not wired to a decision queue."
          }
        }
      }
    },
    "/v1/review/requests/{id}": {
      "get": {
        "summary": "One ask, with both texts of every item it names",
        "description": "The same items the review queue renders — EU provision and national provision side by side, with provenance and the stored reasoning — but chosen by the ask rather than by the queue's ordering. Unlike the queue this does NOT filter out rows that already carry a verdict: a re-review names links that were decided once already, and hiding them would hide exactly what the ask is about. Each such item carries `verdict` and `reviewedAt`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 10,
              "maximum": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "chars",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Characters of provision text per side. 0 or absent = the whole text."
          }
        ],
        "responses": {
          "200": {
            "description": "The ask, its addressing, and a page of its items."
          },
          "401": {
            "description": "No key presented."
          },
          "403": {
            "description": "A key that is not on the 'reviewer' plan."
          },
          "404": {
            "description": "No such ask in the decision queue."
          },
          "503": {
            "description": "This instance is not wired to a decision queue."
          }
        }
      }
    },
    "/v1/review/requests/{id}/close": {
      "post": {
        "summary": "Record that this ask was answered here",
        "description": "Tells the decision queue the question has been answered in Lexiara, and with what. NOT a decision recorded there: no approval response is written, no reviewer of that system is named, and the queue refuses this on any ask that already carries a decision of its own. The verdicts reported are READ BACK from the corpus's own audit rows, never taken from the caller — so a client that claimed a verdict it never recorded would close the ask with a record saying so. THREE THINGS MUST HOLD OR IT REFUSES (409): every item the ask addresses carries a verdict — an ask half-answered is still waiting on a human; every verdict was recorded AFTER the ask was raised, so a re-review cannot be closed by the verdicts it exists to revisit; and the `items` list, if sent, matches the ask's own addressing exactly — it may restate the set, never narrow or widen it. This route touches no corpus table and asserts nothing about the law: it is NOT the verdict route. A verdict is still POST /v1/review/verdict, one row at a time, on the 'reviewer' plan — this only reports what those calls already recorded.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "description": "Which rows the ask covered. Omit for the ones this service resolved. Naming a row does NOT assert its verdict — that is read from the database either way. Where the ask resolves to rows of its own, this list must match them exactly or the call is refused (409 items_disagree); it is accepted as the addressing only for an ask that resolves to none, and the record written to the queue then says the ids were supplied.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "transposition",
                            "topic-description"
                          ]
                        },
                        "id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Closed, with the verdicts as stored."
          },
          "400": {
            "description": "The ask resolves to no corpus rows."
          },
          "401": {
            "description": "No key presented."
          },
          "403": {
            "description": "A key that is not on the 'reviewer' plan."
          },
          "409": {
            "description": "Items are still undecided, a verdict predates the ask, the `items` list disagrees with the ask's addressing, or the queue refuses."
          },
          "503": {
            "description": "This instance is not wired to a decision queue."
          }
        }
      }
    },
    "/v1/coverage": {
      "get": {
        "summary": "What the corpus holds, and how fresh",
        "description": "Call first when unsure whether Lexiara holds the law you need — absence of coverage means 'not held', never 'does not exist'.",
        "responses": {
          "200": {
            "description": "Per-source coverage and freshness."
          }
        }
      }
    },
    "/v1/follows": {
      "get": {
        "summary": "The instruments, subjects and jurisdictions you follow",
        "responses": {
          "200": {
            "description": "Your follows, oldest first."
          }
        }
      },
      "post": {
        "summary": "Follow a piece of law",
        "description": "Follow an instrument (`work`), a single provision, a EuroVoc subject, a jurisdiction or a vertical. The target is resolved against the corpus BEFORE the follow is written: a mistyped ELI is a 404 here rather than a subscription that silently matches nothing forever. Requires your OWN key — the shared demo key is refused, because every keyless visitor is served as that one key and the follows would be a mailbox shared with strangers. This route and /v1/review/verdict are the only two that accept a write, and they are unalike: a verdict asserts something about the LAW and takes a 'reviewer' key; a follow is subscriber-owned data and touches no corpus table.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "ref"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "work",
                      "provision",
                      "concept",
                      "jurisdiction",
                      "vertical"
                    ]
                  },
                  "ref": {
                    "type": "string",
                    "description": "ELI (work/provision), concept id, ISO jurisdiction code, or vertical manifest id."
                  },
                  "provisionEId": {
                    "type": "string",
                    "description": "Required for kind='provision', e.g. 'art_44'."
                  },
                  "label": {
                    "type": "string",
                    "description": "Your own name for this follow."
                  },
                  "minSignificance": {
                    "type": "string",
                    "enum": [
                      "major",
                      "minor",
                      "housekeeping"
                    ],
                    "description": "Floor for the digest. Default 'minor'. See /v1/follows/updates."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The follow."
          },
          "400": {
            "description": "Unknown kind, missing ref, or a provision follow with no eId."
          },
          "401": {
            "description": "No key — a follow must belong to somebody."
          },
          "403": {
            "description": "The shared demo key."
          },
          "404": {
            "description": "The target is not held by the corpus."
          }
        }
      }
    },
    "/v1/follows/updates": {
      "get": {
        "summary": "Developments in the law you follow, graded",
        "description": "Every change the corpus observed on the law you follow, since your last read (or `since`), each carrying a SIGNIFICANCE grade with the reasoning behind it: 'major' (a new consolidation whose version point advanced, a provision appearing, a provision now repealed), 'minor' (wording changed, or the stored text moved while the publisher's version point did not), 'housekeeping' (first ingest, and bulk churn from a version substitution). The grade is Lexiara's assertion, not the publisher's, and it says so. Items also carry `reach`: 'direct' on the instrument you named, or 'via-transposition' when the development is on the other side of a transposition edge — the directive behind the national measure you follow, or the national measures behind the directive.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO timestamp. Defaults to each follow's own last read.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minSignificance",
            "in": "query",
            "required": false,
            "description": "Floor for this call: major | minor | housekeeping.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum developments, default 100.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "markRead",
            "in": "query",
            "required": false,
            "description": "'true' advances the read watermark. Off by default: fetching a digest is not the same act as reading it, and a client that crashes mid-render must not lose a week of law.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Graded developments, newest first."
          }
        }
      }
    },
    "/v1/follows/{id}": {
      "delete": {
        "summary": "Unfollow",
        "description": "Scoped to your own key. An id belonging to another subscriber 404s rather than 403s — you are not entitled to learn that it exists.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The follow's id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unfollowed."
          },
          "404": {
            "description": "No such follow on this key."
          }
        }
      }
    },
    "/v1/ingestion/requests": {
      "get": {
        "summary": "Your ingestion request history",
        "description": "Returns the latest 50 ingestion requests for the authenticated account, newest first. Each row carries private status data including `intent`, `created_at` and `origin`; requests belonging to another account are never returned. This read does not start, repeat or alter acquisition work.",
        "responses": {
          "200": {
            "description": "An account-private `requests` array with status fields."
          },
          "401": {
            "description": "No account credential."
          },
          "403": {
            "description": "The shared demo key cannot read private request history."
          }
        }
      },
      "post": {
        "summary": "Ask Lexiara to acquire a law it does not hold",
        "description": "When a read returns a `coverage` block with `requestable: true`, the law is missing from THIS corpus rather than nonexistent, and this route asks for it. Lexiara validates the target, deduplicates it against any equivalent acquisition already running, fetches the publisher's own bytes, reads them through the same parser and the same fail-closed checks every other act goes through, and returns handles you repeat your original read with.\n\nTHE TARGET MUST RESOLVE TO A PUBLISHER IDENTIFIER THIS SERVICE DERIVES. A CELEX, or an EU ELI it can turn into one. No caller-supplied URL is ever fetched: `proposed_sources` is recorded as text for a human and is never dereferenced, and a private-network, file: or javascript: target is refused by name.\n\nEXPECTATIONS ARE HYPOTHESES. `has_article`, `has_annex`, `mentions` (three words or more), `min_provisions` and `language_is` are checked against the stored rows after publication and reported as matched, contradicted or not_verifiable. A contradicted expectation is reported and the publisher's text is left exactly as published.\n\nRequires an account credential. The shared demo key is refused, because every keyless visitor is served as that one key and both the rationale and the per-account cap would be shared with strangers.\n\nOAuth callers need the explicit `ingestion:request` scope in addition to `read`; an OAuth grant with only `read` remains read-only and cannot submit this write. Reconnect and approve `read ingestion:request` to enable it.\n\nTHIS IS NOT THE VERDICT ROUTE. /v1/review/verdict is the only write on this service that asserts anything about the law, and it takes a 'reviewer' key. What this route writes is a request and a queued acquisition, both account-owned rows: it touches no corpus table, publishes no text and records no finding. Acquiring an instrument later makes the publisher's own words addressable and states nothing about what they mean.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "request_schema",
                  "intent",
                  "target"
                ],
                "properties": {
                  "request_schema": {
                    "type": "string",
                    "enum": [
                      "lexiara.ingestion-request/1"
                    ]
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "new_work",
                      "missing_component",
                      "language_counterpart",
                      "version",
                      "repair",
                      "derived_references"
                    ],
                    "description": "The bounded acquisition or repair needed: `derived_references` refreshes derived references for a held work without asserting a legal finding."
                  },
                  "target": {
                    "type": "object",
                    "properties": {
                      "celex": {
                        "type": "string",
                        "description": "e.g. '32024L0825'."
                      },
                      "eli": {
                        "type": "string",
                        "description": "An EU ELI, if you have no CELEX."
                      },
                      "language": {
                        "type": "string",
                        "description": "ISO 639-3. Default 'eng'."
                      },
                      "version": {
                        "type": "string",
                        "description": "'as-adopted' (default), a consolidation date YYYY-MM-DD, or 'unresolved' — which returns a question rather than a job."
                      },
                      "role": {
                        "type": "string",
                        "enum": [
                          "law",
                          "guidance"
                        ]
                      }
                    }
                  },
                  "rationale": {
                    "type": "string",
                    "description": "One or two sentences. PRIVATE to your account."
                  },
                  "origin": {
                    "type": "object",
                    "description": "Held handles you started from. Private."
                  },
                  "evidence_path": {
                    "type": "array",
                    "description": "The path from held material to the gap. Private.",
                    "items": {
                      "type": "object"
                    }
                  },
                  "proposed_sources": {
                    "type": "array",
                    "description": "Recorded as text and NEVER fetched.",
                    "items": {
                      "type": "string"
                    }
                  },
                  "expectations": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "idempotency_key": {
                    "type": "string"
                  },
                  "requested_bounds": {
                    "type": "object",
                    "description": "maxBytes / maxSeconds. May only NARROW the server's budget."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Joined an equivalent acquisition, or replayed an idempotency key."
          },
          "201": {
            "description": "A new acquisition was queued (or answered immediately)."
          },
          "400": {
            "description": "The envelope, the target or an expectation was refused, by name."
          },
          "401": {
            "description": "No key — a request must belong to an account."
          },
          "403": {
            "description": "The shared demo key."
          },
          "405": {
            "description": "GET: this route takes a POST."
          },
          "429": {
            "description": "This account already holds the maximum live requests."
          }
        }
      }
    },
    "/v1/ingestion/requests/{id}": {
      "get": {
        "summary": "One ingestion request: state, stage, reason and when to ask again",
        "description": "Scoped to your own account. Another account's request 404s rather than 403s — you are not entitled to learn that it exists — and the rationale, origin and evidence path come back only to the account that wrote them, even when several accounts share one acquisition.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request_id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The request, with its shared job's state."
          },
          "404": {
            "description": "No such request on this account."
          }
        }
      }
    },
    "/v1/ingestion/requests/{id}/result": {
      "get": {
        "summary": "What an ingestion request produced",
        "description": "Published handles (expression id, work ELI, the /v1/works/text and /v1/provisions addresses), the source manifest (publisher URL, content hash, bytes, retrieval time, parser version) and the expectation results. The `expressionId` handle is accepted as the exact `expression` query parameter by `/v1/works/text` and `/v1/provisions`, preserving the acquired expression and language. `coverage: complete_for_scope` means the DECLARED SCOPE was acquired and checked — never that all law applicable to a question has been found.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request_id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Handles, manifest and expectation results."
          },
          "404": {
            "description": "No such request on this account."
          }
        }
      }
    }
  }
}