Repository navigation
Conversation
…on verify Every model, the webhook parser, the error mapping and the CLI read the API's newer response shape as well as the original one. The newer field names are attributes (ExtractedClaims.claims, AssessClaim.status/failure/ more_claims, TaskStatus.failure, completed_at, claim, FailureBlock.code, the *_limit_exceeded flags, webhook event_id), and every older attribute keeps its original meaning, computed from whichever shape arrives. model_dump() returns the shape the server sent. verify and verify_batch leave webhook_url out instead of sending "" (both meant the key's default); review and citecheck bodies are unchanged. The /me/usage deprecations no longer name a removal date. Parity tests freeze what the previous release produced from each original-shape response (models, CLI text and JSON, wait outcomes, errors, webhooks, request bodies) and hold both shapes to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d on newer-shape review webhooks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arlier release Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… properties A body is read as the newer response shape only when it carries a key only that shape has; every other body goes through the same fields, values, dump, repr, exclude_unset, JSON schema and pickle as before. The newer names (claims, status, failure, more_claims, completed_at, claim, code, detail, the *_exceeded flags, webhook event_id / failure / claims / reason) are read-only properties, not fields, and the model-level serializer is gone. Errors read the newer failure block, retry_after and a computed remaining only for a newer-shape body and only where the original key is absent. Webhook events keep their dataclass fields; a needs_input option with a null domain no longer raises. The CLI renderers are back to 2.20's code. Parity fixtures are rebuilt from the real recorded pairs (responses and webhooks), extract goes through the client with the recorded locate, and the oracle now also freezes exclude_unset dumps, repr, JSON schemas and pickles. The remaining removal-date text is gone, and the changelog lists what to settle before the SDK asks for the newer shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew_id / citecheck_id as task_id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ributes keep their values Every request now sends X-Lenz-API-Version: 2026-10-11. Every attribute the SDK had in 2.x reads the value it read then, computed from the current shape where the server renames or leaves a field out: - /assess: the no-claim error sentence, the compound-claim hint on a verdict row; review deep checks, issues and verification-stage failures spell "nothing checkable" not_a_claim. - /me/usage blocks projected with the original prices when none is published, quota_used never negative. - verification.completed webhook result: every original key and default. - Errors: a 422 keeps its original code, message and errors per endpoint (map_response_to_error takes the request's endpoint); a citation-check 402 reports credit_balance; TaskAccepted.chain_id reads "" when absent. The parity suite now holds every recorded response pair to the previous release's output with narrower allowances (only values the current shape cannot carry), and the citation 402 fixture follows the current server. BREAKING CHANGE: raw access (model_dump(), exc.body, event.raw, the CLI's --json) shows the current response shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no field errors, as in 2.x Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ers webhook versions and every value that reads differently On verify and verify_batch a blank or whitespace-only webhook_url (and a None) meant the key's default webhook in 2.x; the current API version reads a blank value as "no webhook", so the SDK leaves it out. The 3.0.0 changelog now says that work submitted with 3.0 sends its webhooks in the current shape (receivers must run 2.21+ first), and lists the 409 hint, the 400 codes, older stored review rows and the review/citecheck webhook task_id among the values that read differently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s 2.x sentence - Errors: the whole original error body is rebuilt per endpoint, as the Node SDK does: `code` is "" where the 2.x error carried none (the API now sends one on every error), plus the 422 envelopes, original wait and link names, and a citation-check 402's pool balance. `exc.body` is the body as sent. - A failed poll's `error` (and the LenzPipelineError message) reads its 2.x sentence, rebuilt from the code: the fixed sentence for cancelled, task_stuck, task_error and not_a_claim, else "Pipeline stopped at: <code>". - The parity suite no longer allows a new error code; the changelog's list of what reads differently matches the Node SDK's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rror names its item - `internal_error` and `invalid_request` read as "" on the exception, as in 2.x, where the error carried no code. - A verify_batch item with an unsupported language: the message is "claims[<n>].<sentence>" again, rebuilt from the error's location. - Changelog: the headline names the one exception (raw bodies show the newer shape), and the failed-poll entry says task_error / task_stuck read one fixed 2.x sentence where 2.x had several. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.0 reads only the 2026-10-11 response shape for its own calls (webhooks of both shapes are still parsed). The current names are the primary interface in the README, examples, CLI and docstrings; every 2.x alias stays with its 2.x value, documented as deprecated, listed in the changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The current shape leaves the pool out of a citation check's 402 because it equals remaining; the endpoint-aware conversion already restores it for that call. Drop the generic fallback that invented a pool figure on every other 402. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…version raises LenzApiVersionError The header is now set per request, so an http_client= with no header or a stale default still asks for 2026-10-11. A response (success or error) whose X-Lenz-API-Version names another version raises LenzApiVersionError, carrying status_code, the body as sent and api_version, instead of being misread. A missing header proceeds; webhook parsing is not guarded; batch waits re-raise it rather than treating it as a failed poll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aim, as 2.x did TaskStatus.failure and the failure block of a verification.failed event read failure_reason 'not_a_claim' from a current-shape body. Assessment and review failure blocks keep 'no_claim'; failure.code is no_checkable_claim throughout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A completed review assessment with other claims found reads the 2.x sentence in hint; a failed one reads None, as before. The parity exemption that hid the difference is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2.x names kept as fields (claim, identified_claims, locations, error_code, hint, error, failure_*, claim_text, modified_at, *_limit_reached, the Usage capacity blocks and quota_resets_at, and the rest) carry deprecated in their schema, as candidate_claims already did. The schema-compatibility test ignores the marker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rename the stored-replay, stored-progress and live-token cases to the names the server records them under, add the cases that were missing in both shapes (a needs-input 409 and the poll it names, a purged 410, an idempotent ask replay, a retried webhook delivery) with the 2.21 oracle for each, and drop the current-shape twins of the stored replays: the API answers a replay stored by an older release in the old shape and says so in the version header, which the version-guard tests now cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e version guard List the values the API no longer sends as breaking, say truthfully what model_dump() and --json return (the 2.x-compatible fields plus the keys the server sent; exc.body and event.raw are the body as sent), document LenzApiVersionError, and name the replacements for AssessClaim.hint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API version a response is served in is not a setting a caller can change; the error now says to contact support if it persists, and that 2.x reads both versions. Same wording in both SDKs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An idempotent delete, the CLI's 404 fallbacks, the verification poll and an unreadable body could each turn LenzApiVersionError into success, a retry or another error. Each now lets it through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ignature changes Each call is sent with its options left out, empty / zero / false and set, under a pinned Idempotency-Key, through the public methods, and the body is compared key order included: a changed body turns an idempotent replay into a 422. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One random key per call, reused across that call's own retries, so a retried batch or follow-up question replays the first answer instead of being charged twice. A caller's key wins; idempotency=False sends none. An in-flight 409 is raised as before, never passed with a second key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and retryable on every error A 404 raises LenzNotFoundError (a LenzError) whose fix says what to check instead of advising a retry. A network failure raises LenzConnectionError and a transport timeout LenzRequestTimeoutError, both subclasses of the LenzAPIError 2.x raised, with the httpx exception as __cause__. Every error carries retryable, set at construction: the server's value on a failed job, else derived from the status and class; a boolean in the body's failure block wins. ReviewFailedError, ReviewTimeoutError, CitecheckFailedError and CitecheckTimeoutError alias the job errors under the Node names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… timeout wait, verify_and_wait and verify_batch_and_wait polled through every error until their deadline, so a revoked key or a wrong task id surfaced minutes later as a LenzTimeoutError. They now end with the error (a batch item is failed, the others continue); a 5xx, a 429 or a network failure is still polled again, after any wait it stated. Each poll is one request bounded by what is left of the deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For comparisons, exhaustive matching and docs, named like the Node SDK's. The model fields and method arguments stay str, so a value a later API adds still reads and still sends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nvelope VerificationCompleted, VerificationFailed and VerificationNeedsInput expose .verification: the verification as client.get_status returns it (a typed TaskStatus, the verdict under .verification.result), built from either payload shape, None when the payload cannot be read as one. A property, so the events' fields and repr are unchanged; the dict result stays. A malformed nested result no longer crashes parse_webhook. The FastAPI example reads it and handles review and citation-check events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lazily, one page when its first item is asked for, from the start page, with the page size read from each response, stopping after a short or empty page. library.iter takes list's filters and refuses sort='random', which is not exhaustive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The options they forwarded through **kwargs are keyword-only parameters with the same defaults, so editors complete them and type checkers check them. Positional claim/text and every default are unchanged, the request bodies match the frozen baseline, and an unknown option is still a TypeError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, the 3.0 changes The README opens with a ten-line first call (key, assess, verdict and confidence); the CLI moves below the review and citation-check sections. The quickstarts no longer fail when no claim was escalated, read failed rows by status == "failed", and give depth="low" its real price; one count for the API's calls. New examples/core/review_draft.py and citecheck_draft.py, and CI type-checks every example (which caught quickstart.py reading a reply field that does not exist). The README and CHANGELOG cover the new errors, retryable, waits, automatic keys, iterators, webhook .verification and the Literal aliases, with an upgrade box and the replay note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or refusal The 404 fix reads "Check the id or key the call names: nothing with it is visible to this credential. Retrying will not help." retryable is None when there was no HTTP status (a missing key, a wait timeout, a needs-input pause, a webhook signature), and a top-level boolean retryable wins after the failure block's. library.iter's sort="random" refusal uses the Node words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not_ready, unknown without a status A 409 that says "not yet" is worth resending (with the same key); the body's code is read as sent, so the 2.x attributes keep their empty code. A LenzAPIError or LenzRateLimitError with no status reads None, as in the Node SDK; the connection classes stay True. Declares LenzError.idempotency_key (None by default), wired in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… every error carries the key A call that sent an Idempotency-Key and meets the first request still running sends the same key and body again, after the stated wait (capped) or the usual backoff, inside its retry budget; still conflicting, it raises the 2.x error with retryable=True. A review or citation-check conflict that names the job still returns it at once. Every LenzError a keyed call raises (its wait included) carries exc.idempotency_key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns past the deadline One helper derives each poll's timeout from the client in use (its read timeout; None means no client cap; an injected http_client keeps its own), fixing the TypeError a non-float Lenz(timeout=) raised in every wait. A poll is bounded by what is left of the deadline and none starts once it is spent (the first poll of a wait still always runs, so timeout=0 reads once as in 2.x); the ids left are timed out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and raises a 401 or 403 A LenzApiVersionError, 404 or 410 on one item's poll is that item's outcome (failed, no status_detail); the others keep being polled. A 401 / 403 refuses the key itself, so the batch wait raises it. A single wait still raises every one of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…out a page size, and refuses a start page below 1 verifications.iter() and library.iter() also stop once the pages read reach the response's total, when the response states no usable page_size, and (without yielding it) when the server answers another page than the one asked for. A start page below 1 raises ValueError at call time, as sort="random" does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, retried and polled through httpx.TransportError (a server that hung up, a proxy failure, a read or write error) is retried like a network error and raised as LenzConnectionError; a wait polls again after one instead of aborting. UnsupportedProtocol and LocalProtocolError (a request that could never be sent) stay the httpx exception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ults on a sparse payload A key the payload left out reads as event.result (and the Node SDK) read it: visibility "private", depth "standard", created_at "" and the rest, in both payload shapes, never the Verification model's own defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A caller's list[dict[str, str]] now type-checks (a list is invariant; Sequence is not); nothing changes at run time. The examples drop the annotation they needed for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tatus is another kind A verification.completed carrying a failed run (or any status that is not the event's) is not presented as a completed verification, as in the Node SDK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the class 2.x raised A keyed call whose response cannot be decoded still raises the JSONDecodeError 2.x raised, now with exc.idempotency_key. Pins that the *_and_wait helpers keep going through the methods 2.x called (review, citecheck, wait), so an override is honoured and gets the call's key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and the batch outcomes README errors, retry and idempotency sections, the ask.send docstring and the CHANGELOG say a resend is safe only with exc.idempotency_key, that a 409 idempotency_conflict is sent again inside the call, how a wait treats its deadline and a batch item's 404 / version error / 401, and the iterator and transport-error changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y configured timeout phase Only a timeout <= 0 wait reads each status once regardless of the deadline (as in 2.x); otherwise an item reached after an earlier poll spent the deadline is timed out, not polled (with timeout=None it could hang). Each poll keeps the client's connect, read, write and pool timeouts, each capped by what is left of the deadline, instead of one scalar for every phase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ncy key Response validation of every keyed call (verify, verify_batch, extract, assess, select, ask.send, review, citecheck) runs inside the key-carrying context: a pydantic ValidationError keeps its class and gains exc.idempotency_key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n unreadable answer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d *.cancelled webhooks are typed In API version 2026-10-11 a verification, review or citation check stopped elsewhere (the website's Stop button, another process) is the status `cancelled`. The wait helpers stopped only on completed, needs_input and failed, so they polled such a task until their timeout. They now end on it with the error 2.x raised for the original shape (failure_class `cancelled`, retryable False); a batch item is a failed row; the plain getters return the cancelled status without raising. `verification.cancelled` (VerificationCancelled), `review.cancelled` and `citecheck.cancelled` parse as typed events; a cancellation of older work keeps arriving as *.failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…cancelled documented as a breaking change The verification envelope of an event now builds a cancelled status only for verification.cancelled and a pause only for verification.needs_input; any other event kind has none. A cancelled status with a null failure still reads the 2.x fields. The CLI words a cancelled batch row's details as Cancelled. The parity test anchors the failure-block exemption to the top-level blocks and compares the CLI text with the cancelled line normalised out. The example receiver handles the cancelled events. The changelog lists the cancelled status and events under Breaking and amends the receiver upgrade step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
client.cancel(task_id) returns a CancelResult (task_id, cancelled, status); cancel_review and cancel_citecheck return the same models get_review and get_citecheck do. All three answer 200 whatever the state of the run, send no body and no Idempotency-Key, and are retried on a 5xx or a dropped connection. A 409 use_review_cancel (a review's deep check) raises a LenzError whose fix names cancel_review and is sent once, never waited on or resent. The smoke test cancels a depth=low run right after it starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…dpoints) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…le after_a_while copy Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…refuses a wrong answer Every id that goes into a URL path is percent-encoded whole by one helper, so ../, ?, # and / in an id cannot leave the intended path; an empty id, . and .. raise ValueError before any request. The cancel calls keep the server's error code (not_found, use_review_cancel) and raise LenzAPIError for a 200 that is not the task, review or check asked for. The smoke test cancels twice and waits 30s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
…repeat, note path-encoded ids Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uf7t6njK4RrozsjwmNZTD
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
3.0.0 reads the API's
2026-10-11response shape. Every request sendsX-Lenz-API-Version: 2026-10-11(also with a caller-supplied HTTP client), and the SDK reads only that shape for its own calls.LenzApiVersionError: a response whoseX-Lenz-API-Versionheader names another version (in practice2026-05-13) is refused instead of misread. It is never retried, never swallowed by a 404 shortcut or a poll, and never applied to webhooks.chain_id, the review/citation-check webhooktask_id, some reworded failure sentences and hints.Developer-experience pass (in this PR)
Reviewed with fresh-eye DX reviews, an engineering plan review, Codex and adversarial reviews.
Fixes:
ask.sendsend an automaticIdempotency-Key, like the other paid calls. A retry reuses it. An in-flight409 idempotency_conflictis retried with the same key, andidempotency=Falseopts out.idempotency_key. A resend is safe only with that key.LenzNotFoundError(404),LenzConnectionErrorandLenzRequestTimeoutError. They are subclasses of what was raised before, so existingexcept/catchclauses still match.retryableon every error: the server's value when it sends one, else derived from the status..verificationon verification events. Node getsisEvent()narrowing andeventIdon every event.verificationsandlibrary(iter()/listAll()).reviewandcitecheckexamples.The CHANGELOG lists every intentional behaviour change under "Changed". Everything else behaves as on 2.21. Request bodies are byte-identical, checked across 100+ call variants.
Cancelled tasks (in this PR)
In API version 2026-10-11 a task can end as
cancelled(stopped from the website or another client). 3.0 treats it as final everywhere:wait,verify_and_wait,verify_batch_and_wait,review_and_wait,citecheck_and_waitand the CLI end at once with the same error class and failure fields as 2.x (failure_classcancelled,retryableFalse). A batch item isfailedwith the cancelled status instatus_detail.status == "cancelled";TaskStatuskeeps the 2.x flat fields filled.VerificationCancelledand thereview.cancelled/citecheck.cancelledwebhook events are typed. Older-shape cancellations still arrive as*.failed.cancelledas final, and webhook receivers must handle*.cancelled. A 2.21 receiver reads them as a plainWebhookEvent.Stopping a run (in this PR)
client.cancel(task_id) -> CancelResult,client.cancel_review(review_id) -> ReviewFullandclient.cancel_citecheck(citecheck_id) -> Citecheckcall the API's cancel endpoints. They send no body and no Idempotency-Key; repeating one is safe.cancelledis True whenever the run is cancelled, by this call or an earlier one. False comes with the run's status, normallycompletedorfailed.code(not_found). A review's deep check answers 409use_review_cancel: it is sent once, never retried, and itsfixnamescancel_review..or..raisesValueErrorbefore any request (CHANGELOG, Changed).openapi.jsonrefreshed (version 2026-10-11 and the cancel endpoints), byte-identical with the Node SDK's copy.Tests
ruff, strict mypy and the full suite pass (93.5% coverage). Parity fixtures are synced with the API's recorded responses. Every 2.x attribute is compared against 2.21.0's value. The guard is tested on success and error responses, stored replays, a custom client, polling, delete and webhooks. Deprecated fields carry PEP 702 or JSON-schema markers.
Release
Publish only once lenz.io serves
2026-10-11. Before that, the release workflow's live smoke run fails and the tag does not publish.Post-release actions and prod checks
v3.0.0. The release runs the live smoke first.extract,assessandverifyagainst lenz.io with a real key.🤖 Generated with Claude Code