{"openapi": "3.1.0", "info": {"title": "Lenz \u2014 AI Fact-Checking API", "version": "1.0.0", "description": "# Four API primitives for AI product teams\n\nA research-depth ladder: find claims, judge them fast, prove them deep, follow up.\n\n- `POST /extract` \u2014 pull verifiable claims out of any text. Free, 1000/key/day.\n- `POST /assess` \u2014 fast 3-model frontier panel verdict in ~5-10s. Sync, paid.\n- `POST /verify` \u2014 full 8-model pipeline with citations in ~90s. Async, paid.\n- `POST /ask/{id}` \u2014 ask follow-up questions grounded on a verification.\n\n## Built for teams whose AI output is async or document-shaped\n\nLegal-memo generators, AI deep-research, due-diligence platforms, vertical agents producing structured deliverables, \"report-as-a-service\" companies. `/assess` opens the door to sync UX too \u2014 fast enough to gate a chat completion or a UI submit.\n\n## Install the SDK\n\n```bash\npip install lenz-io      # Python\nnpm install lenz-io      # Node / TypeScript\n```\n\nSource on GitHub: [lenz-io-python](https://github.com/lenzhq/lenz-io-python) \u00b7 [lenz-io-node](https://github.com/lenzhq/lenz-io-node).\n\n## The canonical integration: extract \u2192 assess \u2192 conditional verify\n\n```python\nfrom lenz_io import Lenz\nclient = Lenz(api_key=\"lenz_...\")\n\n# 1. Pull factual claims out of your model output (free, ~1s)\nclaims = client.extract(text=llm_output).identified_claims\n\n# 2. Fast verdict on all of them via /assess (~5-10s, 3-model panel)\nquick = client.assess(text=llm_output)\n\n# 3. Escalate low-confidence claims to /verify (~90s, 8-model pipeline)\nfor c in quick.claims:\n    if c.confidence == \"low\":\n        deep = client.verify_and_wait(claim=c.claim).verdict\n        print(deep.verdict, deep.lenz_score)\n```\n\n## How /verify works under the hood\n\nFrame \u2192 Collect Evidence \u2192 Debate (2 models, 2 rounds) \u2192 Adjudicate (3 models: sources, logic, precision) \u2192 Conclude. ~90 seconds wall-clock per claim.\n\n## Common patterns\n\n- **Runtime sync UX**: `POST /assess` on the user-facing path; escalate `confidence == \"low\"` claims to `/verify` in the background.\n- **Runtime webhook**: `POST /verify` with `webhook_url`. Lenz POSTs the typed payload when done. Best for document pipelines.\n- **CI / pre-release**: `/extract` + batch `/verify` your golden set on every deploy. The free `/extract` tier covers most of this.\n- **Incident triage**: paste the offending output into `/extract`, then `/verify` the wrong-looking claims. Get a citation trail to send back to the customer.\n\n## What's in the response\n\nUnified vocabulary across every claim-shaped response:\n\n- **`claim`** \u2014 the framed claim text.\n- **`verdict`** \u2014 `\"True\" | \"Mostly True\" | \"Mixed\" | \"Mostly False\" | \"False\" | \"Error\"`.\n- **`confidence`** \u2014 `\"high\" | \"medium\" | \"low\"`.\n- **`lenz_score`** \u2014 0\u201310 numeric score (deep payloads).\n- **`sources`** \u2014 evidence with title, URL, snippet (`/verify` only).\n- **`audit`** \u2014 adjudication reasoning, debate transcript, panel agreement (`/verify` only).\n\n## How the API is organized\n\n- **Extract** \u2014 pull verifiable claims out of text.\n- **Assess** \u2014 fast 3-model verdict.\n- **Verify** \u2014 deep 8-model pipeline, submit and poll status.\n- **Ask** \u2014 follow-up questions on a verification.\n- **Verifications** \u2014 list, fetch, delete, and toggle visibility on stored verifications.\n- **Library** \u2014 browse the public catalog. No API key needed.\n- **Account** \u2014 credits and rate-limit usage.\n\n## Authentication\n\n1. [Create a Lenz account](https://lenz.io/auth).\n2. Generate an API key on the [API Integration](https://lenz.io/api-integration) page.\n3. Send it on every request:\n   ```\n   Authorization: Bearer lenz_...\n   ```\n\n## Idempotency\n\nSend an `Idempotency-Key` header on `POST /extract`, `POST /assess`, `POST /verify`, and `POST /verify/batch` so retries after a network drop do not spawn duplicate tasks or double-debit quota. The server caches the response body and returns it on replay; a re-used key with a different body returns 422. The SDKs auto-generate a key per call by default.\n\n## Webhooks\n\nOn `POST /verify` you may supply a `webhook_url`. We POST an HMAC-SHA256-signed JSON payload to it when the pipeline terminates. Signature header is `X-Lenz-Signature: sha256=<hex>`. Verify with your webhook secret (generate + rotate on [API Integration](https://lenz.io/api-integration)). Retries: 10s, 60s, 600s (3 retries after the initial delivery, 4 attempts total). The SDKs ship a `LenzWebhooks` helper that verifies signatures and parses the payload into typed events.\n\nFor commercial use, volume pricing, or onboarding support, [get in touch](https://lenz.io/contact).", "termsOfService": "https://lenz.io/api-terms"}, "paths": {"/api/v1/": {"get": {"operationId": "apiRoot", "summary": "Api Root", "parameters": [], "responses": {"200": {"description": "OK"}}, "description": "Service discovery root \u2014 links to docs, OpenAPI spec, and endpoints.\n\nReturned at ``GET /api/v1/`` so curious humans and probing clients\nget a useful JSON payload instead of a 404.", "tags": ["Discovery"]}}, "/api/v1/library": {"get": {"operationId": "listLibrary", "summary": "List Library", "parameters": [{"in": "query", "name": "domain", "schema": {"default": "", "title": "Domain", "type": "string"}, "required": false}, {"in": "query", "name": "entity", "schema": {"default": "", "title": "Entity", "type": "string"}, "required": false}, {"in": "query", "name": "search", "schema": {"default": "", "title": "Search", "type": "string"}, "required": false}, {"in": "query", "name": "sort", "schema": {"default": "recent", "title": "Sort", "type": "string"}, "required": false}, {"in": "query", "name": "page", "schema": {"default": 1, "title": "Page", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VerificationListOut"}}}}}, "description": "Search and browse the public catalog of verified claims.\n\nReturns paginated results from the Lenz Library. Only publicly\npublished verifications are included.\n\n**Sort options:** `recent`, `popular`, `most_true`, `most_untrue`,\n`relevance` (only when `search` is provided).", "tags": ["Library"], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz()  # no api_key needed for the public library\npage = client.library.list(page=1, sort=\"recent\")\nfor v in page.items:\n    print(v.claim, \"\u2192\", v.verdict)\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz();  // no api_key needed\nconst page = await client.library.listLibrary({ page: 1, sort: \"recent\" });\npage.items.forEach(v => console.log(v.claim, \"\u2192\", v.verdict));\n"}]}}, "/api/v1/verify": {"post": {"operationId": "submit", "summary": "Verify", "parameters": [], "responses": {"200": {"description": "OK"}}, "description": "Submit a claim for verification.\n\nReturns a ``task_id`` immediately. Poll ``GET /verify/status/{task_id}``\nor supply ``webhook_url`` in the body for an asynchronous callback when\nthe pipeline terminates.", "tags": ["Verify"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/VerifyIn"}}}, "required": true}, "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nv = client.verify_and_wait(claim=\"Sharks don't get cancer\")\n# Add language=\"es\" for Spanish output (12 languages supported).\nprint(v.verdict, v.lenz_score)\n# false 2.0\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst v = await client.verifyAndWait({ claim: \"Sharks don't get cancer\" });\nconsole.log(v.verdict, v.lenz_score);\n// false 2.0\n"}]}}, "/api/v1/verify/batch": {"post": {"operationId": "batch", "summary": "Verify Batch", "parameters": [], "responses": {"200": {"description": "OK"}}, "description": "Fan-out wrapper around /verify for multi-claim LLM responses.\n\nBody: ``{claims: [{text, source_url?, webhook_url?, visibility?}, ...]}``\ncapped at 20 items. Server pre-checks total credits, then fans out N\nindependent pipelines \u2014 each with its own ``task_id`` and lifecycle.\nEach claim's webhook (per-item override, or batch ``webhook_url``, or\nper-key default) fires when its pipeline terminates. ``batch_id`` is\npurely for client-side correlation; the server doesn't gate anything\non it.\n\nOn a mid-fan-out enqueue error, the response includes the partial\n``items`` list and a ``partial: true`` flag. Per-claim credit spend\nhappens at each claim's normal persist time \u2014 claims that never\nenqueue are never charged.", "tags": ["Verify"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/VerifyBatchIn"}}}, "required": true}, "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nbatch = client.verify_batch(claims=[\n    {\"text\": \"The Eiffel Tower is in Berlin.\"},\n    {\"text\": \"Water boils at 100\u00b0C at sea level.\"},\n])\nfor item in batch.items:\n    print(item.task_id, item.claim_text)\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst batch = await client.verifyBatch({\n  claims: [\n    { text: \"The Eiffel Tower is in Berlin.\" },\n    { text: \"Water boils at 100\u00b0C at sea level.\" },\n  ],\n});\nbatch.items.forEach(i => console.log(i.taskId, i.claimText));\n"}]}}, "/api/v1/assess": {"post": {"operationId": "assess", "summary": "Assess", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AssessOut"}}}}}, "description": "Submit text for fast verdict via 3-model panel (sync).\n\nReturns a per-atomic-claim verdict array in ~5-10 seconds. Cheaper\nand faster than ``POST /verify``; uses 3 frontier models in\nparallel and skips the research/debate/adjudication pipeline.\n\nSmart-routed: time-sensitive claims (events from the past ~6\nmonths) get a retrieval-grounded panel; evergreen claims get a\nparametric panel.\n\nCache lookup chain: existing Claim row first (deep /verify\nresult), then Assessment row (prior /assess verdict). Both honor a\n6h staleness rule for time-sensitive claims.", "tags": ["Assess"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AssessIn"}}}, "required": true}, "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nout = client.assess(text=\"Coffee causes cancer and the earth is flat.\")\n# Add language=\"es\" for Spanish verdict text; verdict labels stay English.\nfor c in out.claims:\n    print(c.claim, \"\u2192\", c.verdict, c.confidence)\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst out = await client.assess({\n  text: \"Coffee causes cancer and the earth is flat.\",\n});\nout.claims.forEach(c => console.log(c.claim, \"\u2192\", c.verdict, c.confidence));\n"}]}}, "/api/v1/extract": {"post": {"operationId": "extract", "summary": "Extract", "parameters": [], "responses": {"200": {"description": "OK"}}, "description": "Extract verifiable claims from arbitrary text.\n\nRuns only the framing step of the pipeline \u2014 no research, no debate,\nno credit charge. Returns the list of verifiable claims, candidate\ndisambiguations (if the input was ambiguous), domain, key entities,\nand presumed intent.\n\nFree for API-key holders, capped at 1000 calls per key per day\n(resets at 00:00 UTC). Keys with admin-set ``unlimited_extract=True``\nbypass the cap.", "tags": ["Extract"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExtractIn"}}}, "required": true}, "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nclaims = client.extract(text=\"Coffee causes cancer and the earth is flat.\")\n# Add language=\"es\" to return extracted claims in Spanish.\nfor c in claims.identified_claims:\n    print(c)\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst out = await client.extract({\n  text: \"Coffee causes cancer and the earth is flat.\",\n});\nout.identifiedClaims.forEach(c => console.log(c));\n"}]}}, "/api/v1/verify/{task_id}/select": {"post": {"operationId": "select", "summary": "Select Claim", "parameters": [{"in": "path", "name": "task_id", "schema": {"title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Resolve a multi-claim / clarification interrupt by selecting one or more\nof the offered claims.\n\nEach selected claim fans out into its own independent pipeline (like\n``/verify/batch``), so the response is batch-shaped:\n``{batch_id, items: [{task_id, claim_text}], partial?}``. Poll each\n``task_id`` via ``GET /verify/status/{task_id}``.\n\nEvery selected text must match a claim that was offered in the prior\n``needs_input`` response (server-validated against the recorded offer set);\narbitrary client-asserted text is rejected.", "tags": ["Verify"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SelectIn"}}}, "required": true}, "security": [{"APIKeyBearerAuth": []}]}}, "/api/v1/verify/status/{task_id}": {"get": {"operationId": "getStatus", "summary": "Get Status", "parameters": [{"in": "path", "name": "task_id", "schema": {"title": "Task Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Poll processing status for a submitted verification.", "tags": ["Verify"], "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nstatus = client.get_status(\"tsk_abc123\")\nprint(status.status)  # \"processing\" | \"needs_input\" | \"completed\" | \"failed\"\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst s = await client.getStatus(\"tsk_abc123\");\nconsole.log(s.status);\n"}]}}, "/api/v1/me/usage": {"get": {"operationId": "getUsage", "summary": "Me Usage", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MeUsageOut"}}}}}, "description": "Return the calling key's remaining capacity, per capability.\n\nOne block per capability \u2014 ``verify``, ``ask``, ``assess``, ``extract``.\nFor the quota-backed capabilities the response separates two buckets:\n\n- ``quota_*`` \u2014 the recurring monthly allowance for the current ``plan``.\n  Resets every period (``quota_resets_at``).\n- ``credits`` \u2014 one-off top-up credits that do NOT reset monthly. They are\n  spent only after the monthly quota runs out. ``assess`` has no one-off\n  credit pool, so its ``credits`` is always 0 and ``remaining`` equals its\n  ``quota_remaining``.\n\n``remaining`` is the true usable capacity (``quota_remaining + credits``) \u2014\ne.g. the number of ``/verify`` calls the key can still make. ``extract`` is\na per-day rate limit (not credit-based), so it is reported separately.", "tags": ["Account"], "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nu = client.usage()\n# Monthly quota vs one-off top-up credits, separated per capability.\nprint(u.verify.quota_used, \"/\", u.verify.quota_total, \"quota\")\nprint(u.verify.credits, \"top-up credits\")\nprint(u.verify.remaining, \"verify calls left (quota + credits)\")\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst u = await client.account.getUsage();\nconsole.log(`${u.verify.quotaUsed} / ${u.verify.quotaTotal} quota`);\nconsole.log(`${u.verify.credits} top-up credits`);\nconsole.log(`${u.verify.remaining} verify calls left`);\n"}]}}, "/api/v1/verifications": {"get": {"operationId": "listVerifications", "summary": "List Verifications", "parameters": [{"in": "query", "name": "page", "schema": {"default": 1, "title": "Page", "type": "integer"}, "required": false}, {"in": "query", "name": "page_size", "schema": {"default": 20, "title": "Page Size", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VerificationListOut"}}}}}, "description": "List the authenticated key's verifications (all visibilities).", "tags": ["Verifications"], "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\npage = client.verifications.list(page=1)\nfor v in page.items:\n    print(v.verification_id, v.verdict)\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst page = await client.verifications.listVerifications({ page: 1 });\npage.items.forEach(v => console.log(v.verificationId, v.verdict));\n"}]}}, "/api/v1/verifications/{verification_id}": {"get": {"operationId": "getVerification", "summary": "Get Verification", "parameters": [{"in": "path", "name": "verification_id", "schema": {"title": "Verification Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Retrieve the full verification report for a single claim.\n\nReturns the verdict, citations, and (under ``audit``) the panel\nreasoning, debate transcript, and assessments. Same shape as the\n/verify webhook payload and the deprecated /library/{id} endpoint\nthis merger replaces.\n\nAuthorization model (optional Bearer):\n- Anon callers see any claim whose ``visibility != 'private'`` AND\n  ``status != 'hidden'`` \u2014 i.e. ``visibility='public'`` AND\n  ``status`` in {pending, published}.\n- Authed callers additionally see their own claims regardless of\n  visibility or status.\n- Private / hidden claims owned by someone else return 404.", "tags": ["Verifications"]}, "delete": {"operationId": "deleteVerification", "summary": "Delete Verification", "parameters": [{"in": "path", "name": "verification_id", "schema": {"title": "Verification Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Delete an owned verification.", "tags": ["Verifications"], "security": [{"APIKeyBearerAuth": []}]}}, "/api/v1/verifications/{verification_id}/related": {"get": {"operationId": "listRelated", "summary": "List Related", "parameters": [{"in": "path", "name": "verification_id", "schema": {"title": "Verification Id", "type": "string"}, "required": true}, {"in": "query", "name": "limit", "schema": {"default": 5, "title": "Limit", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RelatedVerificationsOut"}}}}}, "description": "List public verifications semantically related to this one.\n\nReturns the closest public-library claims by pgvector ANN distance\n(cosine, 0 = identical). Excludes the verification itself, near-\nduplicates (distance < 0.2), and editorially-hidden claims. Empty\nlist when the verification has no embedding yet (early pipeline) or\nwhen no claim is close enough.\n\nAccess: any verification the caller owns OR any public library item.\n``limit`` is clamped to [1, 10].", "tags": ["Verifications"], "security": [{"APIKeyBearerAuth": []}], "x-codeSamples": [{"lang": "Python", "source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nrelated = client.verifications.related(\"vid_abc123\", limit=5)\nfor r in related.items:\n    print(r.verification_id, r.claim, r.verdict)\n"}, {"lang": "TypeScript", "source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst related = await client.verifications.related(\"vid_abc123\", { limit: 5 });\nrelated.items.forEach(r => console.log(r.verification_id, r.claim, r.verdict));\n"}]}}, "/api/v1/ask/{verification_id}": {"get": {"operationId": "getAskHistory", "summary": "Get Ask History", "parameters": [{"in": "path", "name": "verification_id", "schema": {"title": "Verification Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Get ask history and remaining quota for a verification.", "tags": ["Ask"], "security": [{"APIKeyBearerAuth": []}]}, "delete": {"operationId": "resetAsk", "summary": "Reset Ask", "parameters": [{"in": "path", "name": "verification_id", "schema": {"title": "Verification Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Delete all ask messages for this user on a verification.", "tags": ["Ask"], "security": [{"APIKeyBearerAuth": []}]}, "post": {"operationId": "sendAsk", "summary": "Send Ask", "parameters": [{"in": "path", "name": "verification_id", "schema": {"title": "Verification Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK"}}, "description": "Send a question and get an expert response.\n\nThe reply (``content`` field on the response) is plain text with a\nsmall markdown subset baked in by the chat model:\n\n- ``**bold**`` and ``*italic*``\n- ``- `` or ``* `` bullet lists\n- Blank-line paragraph breaks; single newlines inside a paragraph\n  mean line break\n\nThe model only produces these \u2014 no headings, no tables, no code\nblocks. Pass the reply through any markdown library or display it\nverbatim.\n\nDocumented at https://lenz.io/docs/quickstart#ask-reply-format.", "tags": ["Ask"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AskIn"}}}, "required": true}, "security": [{"APIKeyBearerAuth": []}]}}}, "components": {"schemas": {"EntityRef": {"description": "An entity (person, place, organization, concept) referenced in the\nsubmitted claim.\n\n``qid`` is the Wikidata Q identifier (e.g. ``Q42`` for Douglas Adams)\nwhen Lenz resolved the entity against its internal catalog; ``null``\notherwise. Customers can join on ``qid`` to their own Wikidata-indexed\ncorpus, or hit the Wikidata API directly for richer metadata.", "properties": {"name": {"title": "Name", "type": "string"}, "qid": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Qid"}}, "required": ["name"], "title": "EntityRef", "type": "object"}, "VerificationListItemOut": {"description": "Single item in a list of verifications.\n\nUsed by both ``GET /library`` (public catalog) and\n``GET /verifications`` (authed-owner listing). Slim shape \u2014 no\n``url`` (customers reference claims by ``verification_id``), no\n``visibility`` (API claims are private by default; the field had\nzero information value on the listing surface).", "properties": {"verification_id": {"title": "Verification Id", "type": "string"}, "claim": {"title": "Claim", "type": "string"}, "domain": {"title": "Domain", "type": "string"}, "entities": {"default": [], "items": {"$ref": "#/components/schemas/EntityRef"}, "title": "Entities", "type": "array"}, "verdict": {"default": "", "title": "Verdict", "type": "string"}, "confidence": {"default": "low", "title": "Confidence", "type": "string"}, "lenz_score": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lenz Score"}, "executive_summary": {"default": "", "title": "Executive Summary", "type": "string"}, "created_at": {"default": "", "title": "Created At", "type": "string"}, "modified_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Modified At"}, "language": {"title": "Language", "type": "string"}}, "required": ["verification_id", "claim", "domain", "language"], "title": "VerificationListItemOut", "type": "object"}, "VerificationListOut": {"description": "Paginated verifications response. Shared by /library + /verifications.", "properties": {"items": {"items": {"$ref": "#/components/schemas/VerificationListItemOut"}, "title": "Items", "type": "array"}, "total": {"title": "Total", "type": "integer"}, "page": {"title": "Page", "type": "integer"}, "page_size": {"title": "Page Size", "type": "integer"}}, "required": ["items", "total", "page", "page_size"], "title": "VerificationListOut", "type": "object"}, "VerifyIn": {"properties": {"text": {"title": "Text", "type": "string"}, "source_url": {"default": "", "title": "Source Url", "type": "string"}, "webhook_url": {"default": "", "title": "Webhook Url", "type": "string"}, "language": {"default": "", "title": "Language", "type": "string"}}, "required": ["text"], "title": "VerifyIn", "type": "object"}, "VerifyBatchIn": {"properties": {"claims": {"items": {"$ref": "#/components/schemas/VerifyBatchItemIn"}, "title": "Claims", "type": "array"}, "webhook_url": {"default": "", "title": "Webhook Url", "type": "string"}, "language": {"default": "", "title": "Language", "type": "string"}}, "required": ["claims"], "title": "VerifyBatchIn", "type": "object"}, "VerifyBatchItemIn": {"properties": {"text": {"title": "Text", "type": "string"}, "source_url": {"default": "", "title": "Source Url", "type": "string"}, "webhook_url": {"default": "", "title": "Webhook Url", "type": "string"}, "language": {"default": "", "title": "Language", "type": "string"}}, "required": ["text"], "title": "VerifyBatchItemIn", "type": "object"}, "AssessClaimOut": {"description": "Per-claim entry inside an ``AssessOut.claims`` list.\n\nDeliberately minimal \u2014 no model_votes, no panel identity, no\nsource/cache-provenance, no internal confidence label. The\n``verification_url`` is the SDK's signal that a deep payload exists\nfor this claim; otherwise this row is the entire answer.\n\nSame field vocabulary as ``ClaimDetailOut`` (``claim``, ``verdict``,\n``confidence``); /assess just omits ``lenz_score`` (no deep score\nis computed at this tier). ``language`` echoes the requested\noutput language so SDK consumers can route per-claim text in\nbatch responses where items may have different languages.", "properties": {"claim": {"title": "Claim", "type": "string"}, "language": {"title": "Language", "type": "string"}, "verdict": {"title": "Verdict", "type": "string"}, "confidence": {"title": "Confidence", "type": "string"}, "verification_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Verification Url"}}, "required": ["claim", "language", "verdict", "confidence"], "title": "AssessClaimOut", "type": "object"}, "AssessOut": {"description": "Response body for ``POST /assess``.\n\n``error_code`` is the machine-readable companion to ``error`` so callers\ncan distinguish a genuine non-claim (``'no_claim'``) from an ambiguous\nclaim that framing could disambiguate (``'ambiguous'``). For the ambiguous\ncase ``candidate_claims`` carries framing's specific readings so a caller\ncan pick one and re-assess \u2014 mirroring how ``/verify`` surfaces a\n``clarification_required`` interrupt. Both fields are absent/empty on the\nhappy path; existing callers that only read ``claims``/``error`` are\nunaffected.", "properties": {"claims": {"default": [], "items": {"$ref": "#/components/schemas/AssessClaimOut"}, "title": "Claims", "type": "array"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "error_code": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error Code"}, "candidate_claims": {"default": [], "items": {"type": "string"}, "title": "Candidate Claims", "type": "array"}}, "title": "AssessOut", "type": "object"}, "AssessIn": {"description": "Request body for ``POST /assess``.", "properties": {"text": {"title": "Text", "type": "string"}, "language": {"default": "", "title": "Language", "type": "string"}}, "required": ["text"], "title": "AssessIn", "type": "object"}, "ExtractIn": {"properties": {"text": {"title": "Text", "type": "string"}, "language": {"default": "", "title": "Language", "type": "string"}}, "required": ["text"], "title": "ExtractIn", "type": "object"}, "SelectIn": {"properties": {"texts": {"items": {"type": "string"}, "title": "Texts", "type": "array"}}, "required": ["texts"], "title": "SelectIn", "type": "object"}, "MeUsageExtractOut": {"description": "Daily ``/extract`` usage (rate-limited per day, not credit-based).", "properties": {"calls_today": {"default": 0, "title": "Calls Today", "type": "integer"}, "daily_limit": {"default": 0, "title": "Daily Limit", "type": "integer"}, "unlimited": {"default": false, "title": "Unlimited", "type": "boolean"}}, "title": "MeUsageExtractOut", "type": "object"}, "MeUsageOut": {"description": "Per-capability capacity for the calling API key.\n\nOne block per API capability (``verify``, ``ask``, ``assess``, ``extract``).\nFor the quota-backed capabilities, monthly quota (resets at\n``quota_resets_at``) and one-off top-up credits are reported separately so\ncallers can tell a recurring allowance from a purchased balance. ``assess``\nhas no one-off credit pool, so its ``credits`` is always 0 (not a bug).", "properties": {"plan": {"default": "free", "title": "Plan", "type": "string"}, "quota_resets_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Quota Resets At"}, "verify": {"$ref": "#/components/schemas/MeUsageQuotaOut"}, "ask": {"$ref": "#/components/schemas/MeUsageQuotaOut"}, "assess": {"$ref": "#/components/schemas/MeUsageQuotaOut"}, "extract": {"$ref": "#/components/schemas/MeUsageExtractOut"}}, "required": ["verify", "ask", "assess", "extract"], "title": "MeUsageOut", "type": "object"}, "MeUsageQuotaOut": {"description": "Per-capability allowance for the public API key.\n\nTwo distinct buckets, kept separate on purpose:\n- ``quota_*``: the recurring monthly allowance for the current plan. Resets\n  every period (see ``MeUsageOut.quota_resets_at``). ``quota_remaining`` is\n  ``quota_total - quota_used`` (never negative).\n- ``credits``: one-off top-up credits that DO NOT reset monthly. Spent only\n  after the monthly quota is exhausted. Some capabilities have no one-off\n  credit pool (e.g. ``assess``); for those ``credits`` is always 0 and\n  ``remaining == quota_remaining``.\n\n``remaining`` is the true usable capacity: ``quota_remaining + credits``.", "properties": {"quota_used": {"default": 0, "title": "Quota Used", "type": "integer"}, "quota_total": {"default": 0, "title": "Quota Total", "type": "integer"}, "quota_remaining": {"default": 0, "title": "Quota Remaining", "type": "integer"}, "credits": {"default": 0, "title": "Credits", "type": "integer"}, "remaining": {"default": 0, "title": "Remaining", "type": "integer"}}, "title": "MeUsageQuotaOut", "type": "object"}, "RelatedVerificationsOut": {"description": "Wrapper for ``GET /verifications/{id}/related``.\n\nEmpty ``items`` when the verification has no embedding yet (early\npipeline) or no public-library claim is close enough.", "properties": {"items": {"default": [], "items": {"$ref": "#/components/schemas/SimilarVerificationOut"}, "title": "Items", "type": "array"}}, "title": "RelatedVerificationsOut", "type": "object"}, "SimilarVerificationOut": {"description": "An existing public verification that semantically resembles a\ngiven claim text.\n\nSurfaced in two places:\n\n* When duplicate detection finds a close-enough match on a fresh\n  `/verify` (the ``duplicate_found`` needs-input branch).\n* As an item in ``GET /verifications/{id}/related`` for callers\n  surfacing \"see also\" references next to a verdict.", "properties": {"verification_id": {"title": "Verification Id", "type": "string"}, "claim": {"title": "Claim", "type": "string"}, "verdict": {"default": "", "title": "Verdict", "type": "string"}, "confidence": {"default": "low", "title": "Confidence", "type": "string"}, "lenz_score": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Lenz Score"}, "url": {"default": "", "title": "Url", "type": "string"}, "distance": {"title": "Distance", "type": "number"}}, "required": ["verification_id", "claim", "distance"], "title": "SimilarVerificationOut", "type": "object"}, "AskIn": {"properties": {"message": {"title": "Message", "type": "string"}, "language": {"default": "", "title": "Language", "type": "string"}}, "required": ["message"], "title": "AskIn", "type": "object"}, "AssessmentOut": {"description": "An adjudicator's individual assessment. Nested under ``audit``.\n\n``warnings`` carries the panelist-specific flags (logical fallacies for\nthe Logic Examiner, precision issues for the Precision Analyst, weakest\nsources for the Source Auditor; verifications from before 2026-06 carry\nmissing context for the retired Context Analyst). The kind is implicit\nin ``focus_area``; every panelist emits exactly one category.", "properties": {"panelist_name": {"default": "", "title": "Panelist Name", "type": "string"}, "focus_area": {"default": "", "title": "Focus Area", "type": "string"}, "score": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Score"}, "reasoning": {"default": "", "title": "Reasoning", "type": "string"}, "warnings": {"default": [], "items": {"type": "string"}, "title": "Warnings", "type": "array"}}, "title": "AssessmentOut", "type": "object"}, "AuditOut": {"description": "Audit / explainability block \u2014 for callers that need to show their work.\n\n``panel_agreement`` is derived from the panel: ``unanimous`` (all agree),\n``majority`` (score spread <= 2), or ``split``.", "properties": {"adjudication_summary": {"default": "", "title": "Adjudication Summary", "type": "string"}, "assessments": {"default": [], "items": {"$ref": "#/components/schemas/AssessmentOut"}, "title": "Assessments", "type": "array"}, "debate_pro": {"$ref": "#/components/schemas/DebateSideOut", "default": {"role": "", "argument": "", "rebuttal": ""}}, "debate_con": {"$ref": "#/components/schemas/DebateSideOut", "default": {"role": "", "argument": "", "rebuttal": ""}}, "panel_agreement": {"default": "", "title": "Panel Agreement", "type": "string"}}, "title": "AuditOut", "type": "object"}, "CandidateClaimOut": {"description": "A single candidate claim surfaced when the framing detects multiple\ndistinct claims in the submitted text. The caller picks one via\n``POST /verify/{task_id}/select``.", "properties": {"text": {"title": "Text", "type": "string"}, "domain": {"default": "", "title": "Domain", "type": "string"}}, "required": ["text"], "title": "CandidateClaimOut", "type": "object"}, "ClaimDetailOut": {"description": "Full verification report.\n\nSlim top-level (verdict + confidence + lenz_score + sources). Audit\ndetail (panel reasoning, debate, individual panelist assessments)\nlives under ``audit``.\n\nSame field vocabulary as ``VerificationListItemOut`` (list view) and\n``AssessClaimOut`` (fast verdict). A consumer that branches on\n``verdict`` or ``confidence`` reads the same field name regardless\nof which endpoint produced the payload.", "properties": {"verification_id": {"title": "Verification Id", "type": "string"}, "claim": {"title": "Claim", "type": "string"}, "domain": {"default": "", "title": "Domain", "type": "string"}, "entities": {"default": [], "items": {"$ref": "#/components/schemas/EntityRef"}, "title": "Entities", "type": "array"}, "presumed_intent": {"default": "", "title": "Presumed Intent", "type": "string"}, "verdict": {"default": "", "title": "Verdict", "type": "string"}, "confidence": {"default": "low", "title": "Confidence", "type": "string"}, "lenz_score": {"anyOf": [{"type": "integer"}, {"type": "null"}], "default": null, "title": "Lenz Score"}, "executive_summary": {"default": "", "title": "Executive Summary", "type": "string"}, "warnings": {"default": [], "items": {"type": "string"}, "title": "Warnings", "type": "array"}, "created_at": {"default": "", "title": "Created At", "type": "string"}, "modified_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Modified At"}, "sources": {"default": [], "items": {"$ref": "#/components/schemas/SourceOut"}, "title": "Sources", "type": "array"}, "audit": {"$ref": "#/components/schemas/AuditOut", "default": {"adjudication_summary": "", "assessments": [], "debate_pro": {"argument": "", "rebuttal": "", "role": ""}, "debate_con": {"argument": "", "rebuttal": "", "role": ""}, "panel_agreement": ""}}, "language": {"title": "Language", "type": "string"}}, "required": ["verification_id", "claim", "language"], "title": "ClaimDetailOut", "type": "object"}, "DebateSideOut": {"description": "One side of the debate (pro or con). Nested under ``audit``.", "properties": {"role": {"default": "", "title": "Role", "type": "string"}, "argument": {"default": "", "title": "Argument", "type": "string"}, "rebuttal": {"default": "", "title": "Rebuttal", "type": "string"}}, "title": "DebateSideOut", "type": "object"}, "NeedsInputOut": {"description": "Inline body for webhook ``event=verification.needs_input``.\n\nThe pipeline pauses for caller input when framing splits the text into\nmultiple distinct claims (``multi_claim``), needs disambiguation\n(``clarification_required``), or finds a near-duplicate of an existing\nverification (``duplicate_found``). Only one of ``claims``,\n``candidates``, or ``similar_claims`` is populated per event, keyed\non ``reason``. Resolve by calling ``POST /verify/{task_id}/select``.", "properties": {"reason": {"title": "Reason", "type": "string"}, "claims": {"default": [], "items": {"$ref": "#/components/schemas/CandidateClaimOut"}, "title": "Claims", "type": "array"}, "candidates": {"default": [], "items": {"type": "string"}, "title": "Candidates", "type": "array"}, "similar_claims": {"default": [], "items": {"$ref": "#/components/schemas/SimilarVerificationOut"}, "title": "Similar Claims", "type": "array"}}, "required": ["reason"], "title": "NeedsInputOut", "type": "object"}, "SourceOut": {"description": "A research source used to evaluate a claim.\n\n``date`` is the source article's publication date (a source-level\nattribute, not a Lenz API event time \u2014 so it's not renamed to\n``published_at``).", "properties": {"source_name": {"default": "", "title": "Source Name", "type": "string"}, "title": {"default": "", "title": "Title", "type": "string"}, "url": {"default": "", "title": "Url", "type": "string"}, "snippet": {"default": "", "title": "Snippet", "type": "string"}, "date": {"default": "", "title": "Date", "type": "string"}}, "title": "SourceOut", "type": "object"}, "WebhookPayload": {"description": "Body the customer's endpoint receives when a verification terminates.\n\nAlways signed with HMAC-SHA256 over the raw body bytes; signature\nrides in the ``X-Lenz-Signature: sha256=<hex>`` header.\n\nThe ``attempt`` field is 1-indexed and includes the initial delivery,\nso values run 1 through 4 (initial + 3 retries).", "properties": {"event": {"title": "Event", "type": "string"}, "verification_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Verification Id"}, "task_id": {"title": "Task Id", "type": "string"}, "batch_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Batch Id"}, "status": {"title": "Status", "type": "string"}, "result": {"anyOf": [{"$ref": "#/components/schemas/ClaimDetailOut"}, {"type": "null"}], "default": null}, "needs_input": {"anyOf": [{"$ref": "#/components/schemas/NeedsInputOut"}, {"type": "null"}], "default": null}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Error"}, "attempt": {"title": "Attempt", "type": "integer"}, "delivered_at": {"title": "Delivered At", "type": "string"}}, "required": ["event", "task_id", "status", "attempt", "delivered_at"], "title": "WebhookPayload", "type": "object"}}, "securitySchemes": {"APIKeyBearerAuth": {"type": "http", "scheme": "bearer"}}}, "servers": [{"url": "https://lenz.io/api/v1", "description": "Production"}], "tags": [{"name": "Extract", "description": "Pull verifiable claims out of any text. Free, 1000/key/day."}, {"name": "Assess", "description": "Fast 3-model panel verdict on a claim. Sync, ~5-10s."}, {"name": "Verify", "description": "Deep 8-model pipeline with citations. Async, ~90s. Submit + poll status."}, {"name": "Ask", "description": "Ask follow-up questions grounded on an existing verification."}, {"name": "Verifications", "description": "List, fetch, delete, and toggle visibility on stored verifications."}, {"name": "Library", "description": "Browse the public catalog of verifications. No API key required."}, {"name": "Account", "description": "Subscription credits and rate-limit usage."}], "webhooks": {"verification.completed": {"post": {"summary": "verification.completed", "description": "Fires when a verification reaches a terminal state with a verdict.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WebhookPayload"}}}}, "responses": {"2XX": {"description": "Your endpoint should return 2xx within 5 seconds. Non-2xx triggers retries at 10s / 60s / 600s (3 retries after the initial delivery, 4 attempts total)."}}}}, "verification.failed": {"post": {"summary": "verification.failed", "description": "Fires when the verification pipeline failed and no verdict is available.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WebhookPayload"}}}}, "responses": {"2XX": {"description": "Your endpoint should return 2xx within 5 seconds. Non-2xx triggers retries at 10s / 60s / 600s (3 retries after the initial delivery, 4 attempts total)."}}}}, "verification.needs_input": {"post": {"summary": "verification.needs_input", "description": "Fires when framing detects multiple claims, ambiguity, or a duplicate.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WebhookPayload"}}}}, "responses": {"2XX": {"description": "Your endpoint should return 2xx within 5 seconds. Non-2xx triggers retries at 10s / 60s / 600s (3 retries after the initial delivery, 4 attempts total)."}}}}}}