Repository navigation
Expand file tree
/
Copy pathopenapi.json
More file actions
13048 lines (13048 loc) · 437 KB
/
Copy pathopenapi.json
File metadata and controls
13048 lines (13048 loc) · 437 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
"openapi": "3.1.0",
"info": {
"title": "Lenz \u2014 AI Fact-Checking API",
"version": "1.0.0",
"description": "# Four API calls and one to run them\n\nA research-depth ladder: find claims, judge fast, prove deep, citecheck; `/review` runs them all.\n\n- `POST /extract` \u2014 enumerate every major verifiable claim in a document (up to 100, most check-worthy first, inputs to 50k chars), or in a public web page given its URL; an optional `focus` narrows the list to the claims you care about, and `locate: true` keeps only the claims traced back to the text, with where each is made. Costs no credits; fair-use cap 1,000/account/day.\n- `POST /assess` \u2014 a fast panel verdict, with the pages the reviewers looked at, typically 10-25s. Sync, 1 credit per claim. Send one text in `claim`, or a list of up to 20 in `claims` \u2014 one row per claim, same order, all checked in parallel. A text that makes more claims than one call checks gets the most check-worthy checked and the rest back as `more_claims`, free, to send on.\n- `POST /verify` \u2014 a full investigation, with verified citations, in ~90s. Async, 10 credits \u2014 or 5 with `depth: \"low\"`, a shallower check that comes back sooner.\n- `POST /citecheck` \u2014 does each cited source say what the text attributes to it: send pairs of a statement and its URL or DOI, or a whole text. Async; 1 credit per checked citation, refunded when a citation could not be checked ([guide](https://lenz.io/docs/citations)).\n- `POST /review` \u2014 the extract \u2192 assess \u2192 verify ladder on a whole draft, returning the issues with suggested rewrites in 2-4 min. Async, 1 credit per claim assessed + 10 (5 at `depth: \"low\"`) per claim verified. With `escalate.max_citations` (0-20, default 0) it also checks whether each source the draft cites says what the draft attributes to it, at 1 credit per checked citation (one that could not be checked is refunded) ([guide](https://lenz.io/docs/citations)). `more_claims` and `more_citations` list what the review found and did not check, at no cost; send them to `/assess` and `/citecheck` to check the rest.\n- Also `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## API versions\n\nLenz versions the shape of its responses by date: send `X-Lenz-API-Version: 2026-10-11` to choose this shape. This reference describes `2026-10-11`. A field `2026-05-13` still sends under an older name is listed `deprecated` beside the current one, and where its shape differs, the operation names the `...Legacy` component it receives (`x-lenz-versions` on the response). Every response carries `X-Lenz-API-Version`, naming the version it is in. Which version a request gets without the header, and what changed: [https://lenz.io/docs/api-versions](https://lenz.io/docs/api-versions).\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: one /review call\n\n```python\nfrom lenz_io import Lenz\nclient = Lenz(api_key=\"lenz_...\")\n\n# Extract, assess, and verify the doubtful claims in one call (async, 2-4 min)\ndraft = \"The EU AI Act took effect in March 2024. It sorts AI systems into four risk tiers.\"\nreview = client.review_and_wait(text=draft)\nfor i in review.issues:\n print(i.verdict, i.claim, i.suggested_rewrite)\n```\n\nThe default policy: a quick verdict on every claim (up to 20), and the deep check on the rows whose quick verdict is `False`, `Mostly False`, `Mixed` or `No Public Record`, or whose confidence is `low`, up to five. Change it in `escalate`. To deep-check the rows the cap left out (`escalation.disposition == \"cap\"`), send them to `/verify/batch`. A resend with the same `Idempotency-Key` within 24 hours returns the same review; a new key is a new review.\n\n## The primitives, call by call: extract \u2192 assess \u2192 conditional verify\n\n```python\nfrom lenz_io import Lenz\nclient = Lenz(api_key=\"lenz_...\")\n\n# 1. Enumerate the factual claims in your model output (free)\n# extracted.claims lists every claim found, most check-worthy first:\n# always a list (one entry for one claim, [] for none).\n# Pass focus=\"market size and competitors\" to narrow the list;\n# status is \"no_match\" when the document has claims but none match.\nextracted = client.extract(text=llm_output)\nclaims = [c.claim for c in extracted.claims]\n\n# 2. Fast verdict in ONE call (typically 10-25s, 3-model panel): a list of up\n# to 20, one row per claim, in the order you sent them. extract finds up to\n# 100, most check-worthy first, so this checks the first 20.\nquick = client.assess(claims=claims[:20]).claims if claims else []\n\n# 3. Escalate the low-confidence ones to /verify in one batch (~90s, multi-model pipeline)\n# Failed rows are free. A retryable one (a provider, or the time budget)\n# is worth resending; the rest (no_checkable_claim, internal) are not.\nretry = [c.claim for c in quick if c.failure and c.failure.retryable]\nquick += client.assess(claims=retry).claims if retry else []\ndoubtful = [{\"claim\": c.claim} for c in quick if c.status == \"completed\" and c.confidence == \"low\"]\nfor r in client.verify_batch_and_wait(claims=doubtful) if doubtful else []:\n if r.verification:\n print(r.verification.verdict, r.verification.lenz_score, r.verification.key_finding)\n```\n\n## How /verify works under the hood\n\nFrame \u2192 Collect Evidence \u2192 Debate (2 models, 2 rounds) \u2192 Adjudicate (three reviewers on models from different vendors, two more when they disagree) \u2192 Conclude. ~90 seconds wall-clock per claim.\n\n### While /verify is running\n\n`GET /verify/status/{task_id}` answers one of five shapes, keyed on `status`: `processing`, `needs_input`, `completed`, `failed`, and, after you cancel the run (see Stopping a run), `cancelled`. Every one echoes `task_id`, and only the fields belonging to that shape are present \u2014 read the ones for the branch you are in, and treat an absent key as absent rather than empty.\n\nA `processing` body carries `progress`:\n\n```json\n{\"status\": \"processing\", \"task_id\": \"\u2026\",\n \"progress\": {\"step\": \"research\", \"index\": 2, \"total\": 5,\n \"elapsed_seconds\": 42, \"poll_after_seconds\": 5}}\n```\n\n`step` is a closed set \u2014 `starting`, `framing`, `research`, `debate`, `adjudication`, `conclusion` \u2014 so it is safe to switch on. `index` is the 1-based stage position (0 while `starting`) out of `total`; read `total` off the response rather than hard-coding it. `poll_after_seconds` is how long to wait before looking again, and `elapsed_seconds` is how long the run has been going \u2014 a measurement, not an estimate of what is left.\n\nNote that `index` is stage **position**, not elapsed work: the stages are uneven, so a progress bar driven by it will sit on `research` for roughly half the run.\n\n`progress` is **advisory**. It says the work is pending and which stage it is on; it is not a place to read results from. The verdict arrives once, under `result`, when `status` is `completed`.\n\n### When /verify asks for input\n\n`/verify` is not only submit-and-poll. `GET /verify/status/{task_id}` can terminate at `needs_input` \u2014 the submitted text held several claims, or was too vague to check as written \u2014 and the task waits rather than guessing which claim you meant. Several claims in one sentence count the same as several sentences.\n\nResolve it with `POST /verify/{task_id}/select`, passing the exact wording of the claim(s) you want from the ones the status offered. Selection is **by text, not index**; anything that was not offered is rejected with a 422. Each selected claim fans out into its own pipeline, so the response carries one `task_id` per claim \u2014 poll each the way you would a fresh `/verify`.\n\nClaims returned by `/extract` skip this entirely: submitted verbatim, they are never bounced back for rephrasing.\n\n### Covered verification\n\nEvery verification a paid Pro or Scale account runs is put in front of a gate. The ones that qualify carry a contractual warranty, and every result tells you whether it qualified \u2014 a `coverage` block on the completed `/verify/status/{task_id}` body, on `GET /verifications/{id}` and on the `verification.completed` webhook.\n\nIt is on by default, with no request field to set; an account on Pro or Scale can turn certificates off on the API credentials page, for checks submitted from then on (reason `account`; a verification that already carries a certificate keeps it).\n\n`status` is `covered`, `pending_timestamp` or `uncovered`; `reasons` says why not, in a closed seven-value vocabulary. **If you publish automatically, key on the `certificate.timestamped` webhook, not `verification.completed`** \u2014 cover requires the qualified timestamp to precede what you publish, and `completed` fires while the qualified timestamp is still pending.\n\nFull wire format, the reason vocabulary and the offline verifier: [lenz.io/docs/coverage](https://lenz.io/docs/coverage). What is covered and what is not: [lenz.io/warranty](https://lenz.io/warranty).\n\n### When /verify fails\n\nA `failed` status (and the `verification.failed` webhook) carries a `failure` block. `code` says what a caller can do about it, a closed set: `no_checkable_claim` (send a checkable statement), `upstream_unavailable` (a provider or our own executor failed; retry), `internal` (retrying the same input will not help) and `cancelled`. `detail` is one sentence on the cause (which step stopped, or that the search was unavailable). `failure_class` says **why**, and `retryable` is derived from it:\n\n| `failure_class` | Meaning | `retryable` |\n|---|---|---|\n| `upstream_unavailable` | Model or search providers were rate-limited or down, the search for sources did not finish, or the task was never run \u2014 resubmit after a short wait | `true` |\n| `invalid_input` | Not a checkable claim | `false` |\n| `cancelled` | You stopped it with `POST /verify/{task_id}/cancel`; not charged | `false` |\n| `internal` | Anything else \u2014 retrying the same input will not help | `false` |\n| `insufficient_evidence` | Version `2026-05-13` only: too few sources to judge the claim (`research_empty` / `research_insufficient`); the current version answers such a run with \"No Public Record\" (below) | `false` |\n\nBranch on `retryable`. `failure_class` and `code` are **closed sets**. A failed body also carries `docs_url`, pointing at the explanation of its class. A failed `/verify` is not charged.\n\nVersion `2026-05-13` sends the same facts flat: `failure_reason` (the webhook's `error`) is the same code (`not_a_claim` for `no_checkable_claim`), except `research_empty` / `research_insufficient` with `failure_class: \"insufficient_evidence\"`, which is how that version reads a \"No Public Record\" answer. It used to name the pipeline step (`framing_failed`, `conclusion_failed`, `task_stuck`, \u2026); those now read as the code above.\n\n### When an /assess row, a review or a citation check fails\n\nThe same `failure` block, and one vocabulary for `code`: `no_checkable_claim`, `page_unreadable` (a link /review or /extract was sent could not be read), `upstream_unavailable` (a provider or the time budget; retry), `internal`, `cancelled`, `no_citations` and `insufficient_credits`. `detail` names the cause (out of time, no row could be assessed, ...). Branch on `retryable`, never on a list of codes. Version `2026-05-13` keeps the earlier causes as codes (`timeout`, `framing_failed`, `assessment_failed`, `citation_check_failed`, `invalid_input`), and `/extract` answers an unreadable link with its 502 `extraction_failed` where the current version answers 422 `page_unreadable`.\n\n### No Public Record\n\nA claim whose central assertion no public record in the evidence settles (a private fact, an entity nothing records, a record that would carry it and was not found) completes with `verdict: \"No Public Record\"`, `lenz_score: null` and a `confidence`. It is an answer, charged like a verdict, never warranted and always private (`visibility` is ignored for it). It does not make the claim false. On a review it is an issue (in `issues[]`, with no suggested edit; the review reads `outcome: \"issues_found\"`). Version `2026-05-13` never receives it: it reads such a run as the failure it was before (`research_empty` when no source was found, else `research_insufficient`; `GET /verifications/{id}` answers 409 `verification_failed`; `GET /verifications` leaves it out); a run submitted in that version is not charged for it.\n\nIn the next API version a cancelled run is not a failure: `GET /verify/status/{task_id}` answers `{\"status\": \"cancelled\", \"task_id\": \"\u2026\"}`, and a `webhook_url` receives `verification.cancelled`. A request made in 2026-05-13 keeps `status: \"failed\"` with `failure_class: \"cancelled\"` and receives `verification.failed`.\n\n### Stopping a run\n\n`POST /verify/{task_id}/cancel` stops a verification that has not finished. A cancelled run is not charged and saves no verification; model work already under way is not billed to you, and the run stops at its next step. The call is safe to repeat and answers 200 in every state: `cancelled: true` with `status: \"cancelled\"`, or `cancelled: false` with `status: \"completed\"` (it finished first and was charged as usual) or `\"failed\"`. It needs no `Idempotency-Key`. An unknown task, another account's task and a task not submitted through the API answer 404 `not_found`; a task that is a `/review`'s deep check answers 409 `use_review_cancel`. Each item of a `/verify/batch` has its own `task_id`, so cancel per task; there is no call for a whole batch. `POST /reviews/{review_id}/cancel` and `POST /citechecks/{citecheck_id}/cancel` stop everything in a review or a citation check. They refund the quick checks not yet served and the citations not yet checked, and keep what was delivered charged.\n\nOne of those codes comes from framing rather than from a pipeline step and carries `failure_class: invalid_input`: `no_checkable_claim` (`not_a_claim` in 2026-05-13; the input is not a checkable statement, or is too vague to have a checkable reading). A vague input that has a likely reading is verified on it, and the result's `claim` is that reading. On the webhook the same value rides in `error`, as an ordinary `verification.failed` delivery; before 2026-09-03 it fired nothing at all, so a task submitted with a `webhook_url` could terminate in silence.\n\n### What may change without notice\n\nNew keys may be added to status and webhook bodies at any time \u2014 ignore keys you do not recognise. Fields documented as **advisory** \u2014 `progress` and everything inside it \u2014 may also change or disappear without a version bump. The verdict fields under `result` may not: those change only with a versioned, announced release.\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- **Whole drafts**: `POST /review` with `webhook_url`; handle `review.completed` (a `ReviewWebhookPayload`, deduped on `event_id`) and read `review.issues[]` and `review.citation_issues[]`.\n- **CI / pre-release**: `/extract` + batch `/verify` your golden set on every deploy. The free `/extract` tier covers most of this. Claims returned by `/extract` verify cleanly \u2014 submitted verbatim to `/verify`, they are never bounced back for rephrasing.\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\"`; a verification or an /assess row can also be `\"No Public Record\"` (see above; an /assess row in version `2026-05-13` never is, and can be `\"Error\"`).\n- **`confidence`** \u2014 `\"high\" | \"medium\" | \"low\"`.\n- **`lenz_score`** \u2014 1\u201310 integer score (deep payloads); `null` for `\"No Public Record\"`.\n- **`key_finding`** \u2014 one declarative sentence stating the most important fact the analysis established. For a false claim it states the CORRECTED fact rather than restating the claim, so render it next to the verdict label, never as a standalone headline. `\"\"` on older verifications predating the field.\n- **`suggested_rewrite`** \u2014 a suggested rewrite of `claim` that the verification's findings support. It has not been verified itself: before using it, review it or run it through `/verify`. `null` for a true claim, when no correction is established, and on older verifications. On every verification, single or listed (`GET /verifications/{id}`, `/verify/status`, the `verification.completed` webhook, `GET /verifications`, `GET /library`). On `/assess` rows too when the request sets `suggest_rewrite`: written from the quick check's reasoning for a claim it found False or Mostly False with high confidence, at no extra credit; `null` otherwise, and on every row when not requested.\n- **`sources`** \u2014 evidence with title, URL and snippet: the sentence(s) quoted from the page, verbatim and in the page's own language (`/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 a fast verdict, with the pages the reviewers looked at.\n- **Verify** \u2014 the full investigation: submit one claim or a batch, poll status, and resolve a `needs_input` interrupt by selecting claims.\n- **Verifications** \u2014 list, fetch, delete, and toggle visibility on stored verifications.\n- **Library** \u2014 browse the public catalog. No API key needed.\n- **Ask** \u2014 follow-up questions, 1 credit each.\n- **Account** \u2014 your credit balance and the price list.\n\n## Credits\n\nOne pool per account funds every billable call, at a fixed weight:\n\n| Endpoint | Credits |\n|---|---|\n| `POST /verify` (and `/verify/batch`, `/select`) | 10 per claim |\n| `POST /verify` with `depth: \"low\"` | 5 per claim |\n| `POST /assess` | 1 per claim \u2014 per row that carries a verdict; error rows and rows answered from the cache are free |\n| `POST /ask` | 1 |\n| `POST /extract` | 0 \u2014 free, fair-use capped |\n\n`depth: \"low\"` caps research breadth \u2014 fewer discovery queries, a hard extraction ceiling, no recovery tiers on the happy path \u2014 while every reasoning step runs unchanged. It is not a model downgrade. You are charged for the depth you REQUESTED. A `/verify/batch` may mix depths per item and is billed per item.\n\nAn answer served from the last hour's cache is free: a `/verify`, `/assess` or `/review` claim that gets back a verdict checked in the last hour costs nothing, so a tool that resends a request is not charged twice. The exception is a `/verify` that issues you a new warranty certificate, charged at the depth you requested.\n\n`GET /me/usage` reports the balance under `credits` and the weights under `costs`, with parameter-dependent prices such as `depth` nested under `cost_options`. In version `2026-05-13` the per-capability blocks beside them (`verify`, `assess`, `ask`) are **projections** of that one balance into each capability's own unit \u2014 how many of those calls the remaining credits would buy \u2014 not separate allowances. Spending on any one of them moves all of them.\n\n## Authentication\n\n1. [Create a Lenz account](https://lenz.io/auth).\n2. Generate an API key on the [API credentials](https://lenz.io/api-credentials) page.\n3. Send it on every request:\n ```\n Authorization: Bearer lenz_...\n ```\n\nOr skip steps 1\u20132: **[lenz.io/setup](https://lenz.io/setup)** issues a test key and generates setup instructions for your environment \u2014 MCP client, SDK or plain HTTP \u2014 that you paste into your coding agent to do the wiring.\n\n### One balance, whichever door you come through\n\nLenz also runs a remote MCP server at `https://lenz.io/mcp`, so an agent can fact-check inside a conversation. It is **not** part of this surface \u2014 there are no MCP endpoints below, and its tools are documented at [lenz.io/integrations/mcp-server](https://lenz.io/integrations/mcp-server).\n\nIt matters here for one reason: that server is a client of this API. It takes the same `lenz_...` key and forwards it to the endpoints below, so MCP traffic spends the same credits, counts against the same rate limits, and shows up in `GET /me/usage` \u2014 there is no separate MCP quota to check, and a 402 can just as easily be an agent as your own code.\n\n## Idempotency\n\nSend an `Idempotency-Key` header on `POST /extract`, `POST /assess`, `POST /verify`, `POST /verify/batch`, `POST /verify/{task_id}/select`, `POST /review`, `POST /citecheck` and `POST /ask/{verification_id}` 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 generate a key for `extract`, `assess`, `select`, `verify`, `verify_and_wait`, `verify_batch`, `verify_batch_and_wait`, `ask.send`, `review` and `citecheck` and reuse it across their own retries (lenz-io 3.0; 2.x sent a key on `verify_batch` and `ask.send` only when you passed `idempotency_key` yourself). An error from a keyed call carries the key (`idempotency_key` in Python, `idempotencyKey` in Node): resend with that same key, never as a plain new call, which sends a new key and can run the work twice.\n\nOn `/ask` a key also keeps the retry out of the conversation: without one, a repeated question is a second turn, a second credit and a second answer that the next turn reads as context. A retry that lands while the first call is still running gets 409 rather than the reply \u2014 nothing is finalized yet to replay \u2014 so treat it as work already in progress and retry the same key once the first call has had time to finish. `GET /ask/{verification_id}` returns the message history, but carries no request id, so it can only tell you which answer was yours when one question is in flight at a time. lenz-io 3.0 sends a fresh key on every `ask.send` call and reuses it across the retries inside that call, so a retried question is not asked twice. Asking the same question again is a new call with a new key, and is asked again, because a repeated question is usually deliberate; pin the key to make a retry from another process mean \"the same question, once\": `ask.send(..., idempotency_key=...)` in Python, `ask.send(id, { message, idempotencyKey })` in Node. The n8n node's Ask operation sends a key scoped to the workflow execution, so a retry replays rather than re-asks. The Zapier app sends none on ask.\n\n## Errors\n\nThe status code says what to do next; the body says what happened. Every response carries an `X-Request-ID` header \u2014 quote it on a support ticket. lenz-io 3.0 reads the last column for you: `retryable` is set on every SDK error (true for a 429, a 5xx, a dropped connection, a request timeout and a 409 that means \"not yet\"; false for any other 4xx), so branch on it instead of the status.\n\n| Status | Condition | Retry? |\n|---|---|---|\n| 401 | Missing, malformed or unknown key | After fixing credentials |\n| 402 | Out of credits, or the plan is the limit | No \u2014 billing action |\n| 403 | Authenticated but not allowed (private verification, IP block) | No |\n| 409 | A request with the same `Idempotency-Key` is still running, or the work the call names is not in the state it needs (`no_selection_pending`, `verification_not_ready`, `verification_failed`, `use_review_cancel`) | Same-key conflict: yes, with the same key. `verification_not_ready`: poll, or choose the claims when it needs input. `no_selection_pending`: poll the status first. `use_review_cancel`: cancel the review instead |\n| 422 | Malformed input | No |\n| 429 | Rate limit: the per-account `/extract` daily cap, or too many reviews or citation checks running (`review_in_flight`, `citecheck_in_flight`) | Yes, after `Retry-After` |\n| 502 | A sync step (`/assess`, `/extract`, `/ask`) failed deterministically \u2014 a schema or parse failure (`/ask`: `ask_failed`) | No \u2014 same input, same outcome |\n| 503 | Our model providers were rate-limited or down for the step and every fallback was exhausted (`code: upstream_unavailable`) or the pipeline has no capacity (`code: capacity`) | Yes, after `Retry-After` (body `retry_after` too) |\n\n**402** carries `detail`, `code` (always `no_credits`), `upgrade_url`, `doc_url`, `wall_id`, `cost` (what the rejected call would have taken), and \u2014 when resolvable \u2014 `remaining`, `credits_remaining` and `resets_at`. `remaining` and `requested` are in the *capability's* unit (verifications, assesses); `credits_remaining` and `cost` are in credits, so a client can tell \"not enough for a 10-credit verify\" from \"empty\". The resolvable fields are omitted rather than sent as `null`, so an absent key means \"unknown\", not \"zero\".\n\n`wall_id` identifies this specific rejection and is already appended to `upgrade_url`, so sending a user to that URL is all that is needed \u2014 it exists as its own field only for clients that build their own upgrade link. Treat `upgrade_url` as opaque and follow it whole rather than matching it against a fixed string.\n\nThe `code` values are deliberately plan-agnostic: they name the condition, never the plan that would fix it. Send users to `upgrade_url` rather than mapping codes to plan names \u2014 the set of plans changes, the conditions do not.\n\nAn empty balance is deliberately **not** a 429: a 429 tells every client to retry, and an empty balance never succeeds on retry. **429** carries `code`, `limit`, `reset_in_seconds`, `upgrade_url` and the `Retry-After` header. The in-flight 429 of `/review` and `/citecheck` (`review_in_flight`, `citecheck_in_flight`) carries `retry_after_seconds` and `Retry-After` instead. The daily cap is the same on every plan; only a key Lenz has marked unlimited for `/extract` bypasses it.\n\n**Migrating (August 2026).** These rejections were 403, which the SDKs map to their auth error; they are now 402, which maps to the quota error \u2014 not a subclass. If you catch the auth error to handle an empty balance, that branch stops firing. This reaches you on every SDK version, including ones released before the change. `detail` strings are unchanged. Full reference: [lenz.io/docs/errors](https://lenz.io/docs/errors#quota).\n\n## Webhooks\n\nOn `POST /verify` and `POST /review` 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 credentials](https://lenz.io/api-credentials)). 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. Events: `verification.completed`, `verification.failed`, `verification.cancelled`, `certificate.timestamped`, and for a review `review.completed` / `review.failed` / `review.cancelled` and for a citation check `citecheck.completed` / `citecheck.failed` / `citecheck.cancelled` (switch on `event`; a review's own deep checks fire no `verification.*` events). The `*.cancelled` events fire once, when you cancel, in the next API version; a submission made in 2026-05-13 receives the matching `*.failed` event with `failure_class: \"cancelled\"` instead.\n\nFor commercial use, volume pricing, or onboarding support, [get in touch](https://lenz.io/contact).",
"termsOfService": "https://lenz.io/terms"
},
"paths": {
"/api/v1/": {
"get": {
"operationId": "apiRoot",
"summary": "Api Root",
"parameters": [
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiRootOut"
}
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"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.\n\n**Versions.** default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"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
},
{
"in": "query",
"name": "curated",
"schema": {
"default": "",
"title": "Curated",
"type": "string"
},
"required": false
},
{
"in": "query",
"name": "verdict",
"schema": {
"default": "",
"title": "Verdict",
"type": "string"
},
"required": false
},
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerificationListOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/VerificationListOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/VerificationListOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"422": {
"description": "A field to fix",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ValidationErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ValidationErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"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`, `most_true`, `most_untrue`,\n`relevance` (only when `search` is provided), and `random`.\n\n**`curated`** restricts results to one or more named curated collections,\ncomma-separated. Currently supported: `trivia` (the LLM-curated,\ntrivia-worthy pool behind the open-source quiz demo).\n\n**`verdict`** filters to one or more verdict labels, comma-separated\n(e.g. `True,False`). Labels: `True`, `Mostly True`, `Mixed`,\n`Mostly False`, `False`.\n\n**Versions.** 200: `2026-10-11` receives `VerificationListOut`, `2026-05-13` receives `VerificationListOutLegacy`; 422: `2026-10-11` receives `ValidationErrorOut`, `2026-05-13` receives `ValidationErrorOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"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.list({ 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": [
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"202": {
"description": "Accepted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifyAcceptedOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/VerifyAcceptedOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/VerifyAcceptedOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"200": {
"description": "Sent only to callers on `2026-05-13`: the same receipt for a repeat of a `/verify` answered from the first submission. Every other version answers 202.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifyAcceptedOutLegacy"
}
}
},
"x-lenz-versions": {
"2026-05-13": {
"$ref": "#/components/schemas/VerifyAcceptedOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"422": {
"description": "A field to fix",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ValidationErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ValidationErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"description": "Submit a claim for verification.\n\nThe claim goes in ``claim``. (``text`` is accepted as an alias \u2014 a\ndocument is ``text``, a claim is ``claim``; ``POST /extract`` is the\nendpoint that takes a document.)\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.\n\n**Duplicate submissions are collapsed.** With no ``Idempotency-Key``\nheader, the request body itself is the key: resubmitting an identical\nbody within 10 minutes returns the original ``task_id`` instead of\nstarting a second run. To run the same text twice on purpose, send your\nown ``Idempotency-Key`` \u2014 a supplied key always wins.\n\n**``depth``** selects how much work the check does. ``standard``\n(default) is the full pipeline. ``low`` searches fewer sources, skips\nthe recovery fetch tiers and stops the debate after the opening\narguments (no rebuttal round), so it comes back sooner \u2014 with less\nevidence behind the verdict. Framing, the adjudication panel and the\nconclusion run unchanged, and every step runs the same models either\nway; ``low`` is a volume lever, not a model downgrade.\n\n``low`` costs **half the credits**, and you are charged for the depth you\nREQUESTED. A claim answered from a check made in the last hour is free\n(unless it issues you a new warranty certificate, which is charged at the\ndepth you requested). The completed result echoes the depth the verdict\nwas actually produced with, which is why that field can read ``standard``\non a ``low`` request: the echo describes the evidence.\n\n**Versions.** 202: `2026-10-11` receives `VerifyAcceptedOut`, `2026-05-13` receives `VerifyAcceptedOutLegacy`; 422: `2026-10-11` receives `ValidationErrorOut`, `2026-05-13` receives `ValidationErrorOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"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).\n# Add depth=\"low\" for a shallower check that returns sooner.\nprint(v.verdict, v.lenz_score)\n# False 2\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\" });\n// Add depth: \"low\" for a shallower check that returns sooner.\nconsole.log(v.verdict, v.lenz_score);\n// False 2\n"
}
]
}
},
"/api/v1/verify/batch": {
"post": {
"operationId": "batch",
"summary": "Verify Batch",
"parameters": [
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"202": {
"description": "Accepted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BatchAcceptedOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/BatchAcceptedOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/BatchAcceptedOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"422": {
"description": "A field to fix",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ValidationErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ValidationErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"description": "Fan-out wrapper around /verify for multi-claim LLM responses.\n\nBody: ``{claims: [{text, source_url?, webhook_url?, visibility?, depth?},\n...]}`` capped 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.\n\n``depth`` works like ``visibility``: a batch-wide default that any item\ncan override. See ``POST /verify`` for what the values mean.\n\nEach item carries its claim in ``claim`` (``text`` is accepted as an\nalias), plus the same optional fields as ``POST /verify``.\n\n**Versions.** 202: `2026-10-11` receives `BatchAcceptedOut`, `2026-05-13` receives `BatchAcceptedOutLegacy`; 422: `2026-10-11` receives `ValidationErrorOut`, `2026-05-13` receives `ValidationErrorOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"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 {\"claim\": \"The Eiffel Tower is in Berlin.\"},\n {\"claim\": \"Water boils at 100\u00b0C at sea level.\"},\n])\nfor item in batch.items:\n print(item.task_id, item.claim)\n"
},
{
"lang": "TypeScript",
"source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst batch = await client.verifyBatch({\n claims: [\n { claim: \"The Eiffel Tower is in Berlin.\" },\n { claim: \"Water boils at 100\u00b0C at sea level.\" },\n ],\n});\nbatch.items.forEach(i => console.log(i.task_id, i.claim));\n"
}
]
}
},
"/api/v1/assess": {
"post": {
"operationId": "assess",
"summary": "Assess",
"parameters": [
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssessOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/AssessOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/AssessOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"422": {
"description": "A field to fix",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ValidationErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ValidationErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"description": "Fast verdict via a 3-model panel (sync, ~10-25s).\n\nEscalation contract: a ``claims[].claim`` text POSTed back verbatim to\n``/verify`` within 24 hours is verified as-is \u2014 in normal operation it\ndoes not return ``needs_input`` and its wording is not rewritten.\n\nTwo input forms, one response shape::\n\n claim / text \u2500\u25ba ONE text. Lenz reads the claims in it, and each becomes a\n row, up to 20 a call; the rest go back, unchecked and\n free, in ``more_claims``.\n claims[] \u2500\u25ba a LIST of up to 20 items, each one claim. One row per item, same order.\n A compound item is assessed on its primary reading and\n the rest is listed in that row's ``more_claims``.\n\nThe list form is the batch rung of the extract \u2192 assess \u2192 verify ladder:\none ``/assess`` call answers up to 20 of ``/extract``'s claims (it finds up\nto 100) in about the time a single call takes, because every item is\nassessed in parallel.\n\nA verdict can be served from memory: a completed ``/verify`` of the exact\nsame claim text, or a prior ``/assess`` of it, within the last hour,\nwhichever account ran it \u2014 the verdict is a function of the input.\n``verification_url`` is set only when the caller could read that\nverification by id.\n\nBilling: 1 credit per row that carries a verdict; error rows are free; a\nduplicate item is a second row and a second credit (the panel runs once).\n\n**Versions.** 200: `2026-10-11` receives `AssessOut`, `2026-05-13` receives `AssessOutLegacy`; 422: `2026-10-11` receives `ValidationErrorOut`, `2026-05-13` receives `ValidationErrorOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"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(claim=\"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 if c.rationale:\n print(\" \", c.rationale)\n\n# Several claims at once: one row per item, same order (up to 20).\nrows = client.assess(claims=[\"Coffee causes cancer.\", \"The earth is flat.\"]).claims\nfor c in rows:\n if c.failure: # status == \"failed\": no verdict, nothing charged\n print(\"No verdict:\", c.failure.code, c.failure.hint)\n else:\n print(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 claim: \"Coffee causes cancer and the earth is flat.\",\n});\nout.claims.forEach(c => {\n console.log(c.claim, \"\u2192\", c.verdict, c.confidence);\n if (c.rationale) console.log(\" \", c.rationale);\n});\n\n// Several claims at once: one row per item, same order (up to 20).\nconst rows = (await client.assess({\n claims: [\"Coffee causes cancer.\", \"The earth is flat.\"],\n})).claims;\nrows.forEach(c => {\n // status \"failed\": no verdict, nothing charged\n if (c.status === \"failed\") console.log(\"No verdict:\", c.failure?.code, c.failure?.hint);\n else console.log(c.verdict, c.confidence);\n});\n"
}
]
}
},
"/api/v1/extract": {
"post": {
"operationId": "extract",
"summary": "Extract",
"parameters": [
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtractOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ExtractOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ExtractOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"422": {
"description": "A field to fix",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ValidationErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ValidationErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"description": "Extract verifiable claims from arbitrary text.\n\nRuns the extraction step on its own \u2014 no research, no debate, no credit\ncharge. Enumerates every distinct major factual\nclaim in the input (up to 100), ordered most-check-worthy-first, in\n`claims` (one item for a single claim, `[]` for none). Claims returned\nhere verify cleanly when submitted verbatim to `POST /verify`.\n\nPass `focus` to narrow the result to the claims you care about \u2014 a short\ndescription such as `\"market size, growth and competitors\"`, at most 300\ncharacters. A focus selects from the claims the extractor found and\nnothing else: it cannot add a claim, reword one, reorder them, change the\nlanguage, or change what counts as a claim. Ordering stays\nmost-check-worthy-first.\n\n`status` is one of:\n\n- `ready` \u2014 claims were found (and, with a `focus`, at least one matched).\n- `no_checkable_claim` \u2014 no claim in the input could be checked against\n public evidence.\n- `no_match` \u2014 claims were found but none fall within your `focus`. The\n unfocused list is never substituted; widen the focus and call again.\n\n`text` can also be a single public web page URL (http or https, nothing\nelse in the field). Lenz reads the page, or a YouTube video's transcript,\nand extracts the claims from its first 50,000 characters. Pages behind a\nlogin (Facebook, Instagram, Threads, LinkedIn) can't be read. A URL call\ntypically takes 5-40 seconds, so give it a 90-second client timeout (SDK\n2.13 and later do). A page that cannot be read answers 502\n`extraction_failed`; when the services that read pages are down, 503\n`upstream_unavailable` with `Retry-After`. Neither uses a unit of the\ndaily cap.\n\nPass `locate: true` to keep only the claims that can be traced directly\nback to your text, and get each claim's `positions`: the passages that\nmake it, with `start` / `end` offsets into `text` as you\nsent it, in Unicode code points. A claim the text does not make, or makes\nwith a different figure, is left out, so the list says what the author\nwrote and nothing else. Offsets count code points, not UTF-16 units: in\nJavaScript slice with `Array.from(text).slice(start, end).join('')`.\nFor a URL the offsets are `null` and each position carries its passage.\nLocating adds a few seconds, never more than 20. If it cannot run,\nevery `positions` is `null` and the claims are returned unfiltered. `locate`\ndefaults to `false`.\n\nAccepts up to 50,000 characters (longer input is truncated). Free for\nAPI-key holders, capped at 1000 calls per ACCOUNT per day (resets at\n00:00 UTC) regardless of input length \u2014 a focused call costs the same one\nunit as an unfocused one. The cap is shared across all of the account's\nkeys.\n\n**Versions.** 200: `2026-10-11` receives `ExtractOut`, `2026-05-13` receives `ExtractOutLegacy`; 422: `2026-10-11` receives `ValidationErrorOut`, `2026-05-13` receives `ValidationErrorOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"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_...\")\nout = client.extract(text=\"Coffee causes cancer and the earth is flat.\")\n# Add language=\"es\" to return extracted claims in Spanish.\nfor c in out.claims:\n print(c.claim)\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});\n(out.claims ?? []).forEach(c => console.log(c.claim));\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
},
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"202": {
"description": "Accepted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BatchAcceptedOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/BatchAcceptedOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/BatchAcceptedOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"422": {
"description": "A field to fix",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ValidationErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ValidationErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"description": "Resolve a multi-claim interrupt by selecting one or more of the offered\nclaims.\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}], partial?}``. Poll each\n``task_id`` via ``GET /verify/status/{task_id}``.\n\nSend the chosen claims in ``claims`` (``texts`` is accepted as an alias).\nEvery selected claim must match one that was offered in the prior\n``needs_input`` response (server-validated against the recorded offer set);\narbitrary client-asserted text is rejected.\n\n**Versions.** 202: `2026-10-11` receives `BatchAcceptedOut`, `2026-05-13` receives `BatchAcceptedOutLegacy`; 422: `2026-10-11` receives `ValidationErrorOut`, `2026-05-13` receives `ValidationErrorOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"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
},
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatusOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/StatusOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/StatusOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"description": "Poll processing status for a submitted verification.\n\nFive shapes keyed on ``status`` \u2014 ``processing``, ``needs_input``,\n``completed``, ``failed``, ``cancelled`` (``2026-05-13`` reads a cancelled\nrun as ``failed``) \u2014 and only the fields belonging to the current one are\npresent (see the response schema). A field that does not belong\nto the current shape is omitted rather than sent as null, so a client may\ntest for presence.\n\n**Versions.** 200: `2026-10-11` receives `StatusOut`, `2026-05-13` receives `StatusOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"tags": [
"Verify"
],
"security": [
{
"APIKeyBearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "Python",
"source": "from lenz_io import Lenz\n\n# typed progress needs lenz-io >= 2.11.0\nclient = Lenz(api_key=\"lenz_...\")\nstatus = client.get_status(\"tsk_abc123\")\n\n# \"processing\" | \"needs_input\" | \"completed\" | \"failed\" | \"cancelled\"\nif status.status == \"processing\" and status.progress:\n p = status.progress\n print(f\"{p.step} \u2014 step {p.index} of {p.total}\")\nelif status.status == \"completed\" and status.result:\n print(status.result.verdict, status.result.key_finding)\nelif status.failure: # failed or cancelled\n print(status.status, status.failure.code, status.failure.retryable)\n"
},
{
"lang": "TypeScript",
"source": "// typed progress needs lenz-io >= 2.11.0\nimport { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst s = await client.getStatus(\"tsk_abc123\");\n\nif (s.status === \"processing\" && s.progress) {\n const { step, index, total } = s.progress;\n console.log(`${step} \u2014 step ${index} of ${total}`);\n} else if (s.status === \"completed\") {\n console.log(s.result!.verdict, s.result!.key_finding);\n} else if (s.failure) {\n // \"failed\" or \"cancelled\"\n console.log(s.status, s.failure.code, s.failure.retryable);\n}\n"
}
]
}
},
"/api/v1/verify/{task_id}/cancel": {
"post": {
"operationId": "cancel",
"summary": "Cancel Verification",
"parameters": [
{
"in": "path",
"name": "task_id",
"schema": {
"title": "Task Id",
"type": "string"
},
"required": true
},
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CancelOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/CancelOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/CancelOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
},
"default": {
"description": "An error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/ErrorOut"
},
"2026-05-13": {
"$ref": "#/components/schemas/ErrorOutLegacy"
}
},
"headers": {
"X-Lenz-API-Version": {
"$ref": "#/components/headers/XLenzApiVersion"
}
}
}
},
"description": "Stop a verification that has not finished.\n\nA cancelled run is not charged and produces no verification: nothing is\nsaved. Work already under way when you cancel (a model call in flight) is\nnot billed to you, and the run itself stops at its next step. A run waiting\non `POST /verify/{task_id}/select` is cancelled too.\n\nThe call is safe to repeat and answers 200 whatever the state, so a client\nthat gives up never has to handle an error for losing a race:\n\n- `cancelled: true`, `status: \"cancelled\"`: the run is cancelled, by this\n call or an earlier one.\n- `cancelled: false`, `status: \"completed\"`: it finished first. The\n verification exists and was charged as usual.\n- `cancelled: false`, `status: \"failed\"`: it had already failed.\n\n`GET /verify/status/{task_id}` answers `cancelled` from then on, and a\n`webhook_url` receives `verification.cancelled` (a `2026-05-13` submission\nreceives `verification.failed` with `failure_class: \"cancelled\"`).\n\nAnother account's task, an unknown one, and a web task answer 404. A task\nthat is a `/review`'s deep check answers 409 `use_review_cancel`: cancel\nthe review (`POST /reviews/{review_id}/cancel`), which cancels everything in\nit.\n\n**Versions.** 200: `2026-10-11` receives `CancelOut`, `2026-05-13` receives `CancelOutLegacy`; default: `2026-10-11` receives `ErrorOut`, `2026-05-13` receives `ErrorOutLegacy`.",
"tags": [
"Verify"
],
"security": [
{
"APIKeyBearerAuth": []
}
],
"x-codeSamples": [
{
"lang": "Python",
"source": "from lenz_io import Lenz\n\nclient = Lenz(api_key=\"lenz_...\")\nresult = client.cancel(\"tsk_abc123\") # lenz-io >= 3.0.0\n# cancelled=True: stopped (by this call or an earlier one), not charged.\n# cancelled=False: it had ended; status is \"completed\" or \"failed\",\n# or \"needs_input\" when select already resolved it: cancel each task id\n# select returned (those keep running).\nprint(result.cancelled, result.status)\n"
},
{
"lang": "TypeScript",
"source": "import { Lenz } from \"lenz-io\";\n\nconst client = new Lenz({ apiKey: \"lenz_...\" });\nconst result = await client.cancel(\"tsk_abc123\"); // lenz-io >= 3.0.0\n// cancelled: true = stopped (by this call or an earlier one), not charged.\n// cancelled: false = it had ended; status is \"completed\" or \"failed\",\n// or \"needs_input\" when select already resolved it: cancel each task id\n// select returned (those keep running).\nconsole.log(result.cancelled, result.status);\n"
}
]
}
},
"/api/v1/me/usage": {
"get": {
"operationId": "getUsage",
"summary": "Me Usage",
"parameters": [
{
"$ref": "#/components/parameters/XLenzApiVersion"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MeUsageOut"
}
}
},
"x-lenz-versions": {
"2026-10-11": {
"$ref": "#/components/schemas/MeUsageOut"