release: lenz-io v1.0.0-rc.1 — first public Python SDK#1
Merged
Conversation
Official Python client for the Lenz Hallucination Verification API. Targets
the Public API v1 surface (X-Lenz-API-Version: 2026-05-13).
Core surface
- `Lenz(api_key=...)` — top-level client with persistent httpx.Client for
HTTP keep-alive across calls.
- Marquee verbs (top-level): verify, verify_and_wait, verify_batch,
extract, select, get_status, usage.
- Resource namespaces (Stripe pattern): verifications.{list,get,delete,
set_visibility}, followup.{history,send,reset}, library.{list,get}.
- Library endpoints work without an API key for sandbox exploration.
Ergonomic helpers
- `verify_and_wait(claim, timeout=120, idempotency=True)` — submits + polls
with exponential backoff (2s/4s/8s cap 10s). Returns a typed Verification
on success; raises LenzNeedsInputError on framing interrupt with task_id
and payload; LenzTimeoutError carries task_id for later resume.
- Auto-idempotency on verify_and_wait so a network drop after submit
doesn't spawn a duplicate task or charge a second credit.
- Auto-retry on 5xx + 429 with Retry-After honored (3 attempts).
- DELETE retry on 404 normalizes to True (idempotent semantics).
Errors
- Tier-2 Rust-style format with problem + cause + fix + doc_url +
request_id on every error. Table-driven map_response_to_error in
errors.py is the cross-language invariant (TS SDK mirrors).
- Exception hierarchy: LenzError -> LenzAuthError, LenzQuotaExceededError
(credits_remaining), LenzValidationError (errors[]), LenzRateLimitError
(retry_after), LenzAPIError, LenzTimeoutError (task_id),
LenzNeedsInputError (task_id, kind, payload), LenzPipelineError
(task_id, failure_reason), LenzWebhookSignatureError.
Webhooks
- `LenzWebhooks(secret)` stateful handler. `parse(raw_body, headers)`
verifies HMAC-SHA256 over raw bytes (matches server's webhook_signing.py
byte for byte), enforces a 5-minute replay window via delivered_at,
and returns one of VerificationCompleted / VerificationFailed /
VerificationNeedsInput / WebhookEvent.
- Low-level `verify_signature(raw_body, signature, secret)` for callers
who want only the signature check.
Models
- Hand-written Pydantic v2 schemas mirroring lenz/api/schemas/public_api.py
server-side: Verdict, Source, Verification, VerificationListItem,
VerificationList, LibraryItem, LibraryList, ExtractedClaims, TaskStatus,
TaskAccepted, BatchAccepted, Usage, FollowupHistory, FollowupReply,
CandidateClaim, SimilarVerification, Audit, Assessment, DebateSide.
- `extra="allow"` so minor server additions don't break customer code.
Config
- `base_url=` overrideable; reads LENZ_BASE_URL env if not passed.
- `api_key=` reads LENZ_API_KEY env if not passed.
- `http_client=` injection for testing.
- `User-Agent: lenz-io-python/<version> (httpx <httpx-version>)`.
- `X-Lenz-API-Version` sent on every request.
Tests
- 67 unit tests + 3 smoke (release-only via -m smoke marker). All unit
tests mock httpx via respx so no real network.
- Construction: env vars, base_url override, namespaces, version header.
- Marquee verbs: happy + edge cases (idempotency header, batch shape,
extract claims).
- verify_and_wait state machine: completed, needs_input, failed, timeout,
idempotency default, explicit key, idempotency=False.
- Resources: full CRUD on verifications/followup/library.
- Auto-retry: 503-503-200 sequence, 429 + Retry-After.
- Connection reuse: single httpx.Client across 5 calls.
- Webhook signing + replay + event discrimination + lowercase headers.
- Error mapping table: parametrized over all known statuses.
Distribution
- pyproject via hatchling. py3.9+. Tested 3.9/3.10/3.11/3.12 x ubuntu/macos
in CI.
- GitHub Actions: ci.yml (lint + mypy strict + pytest matrix), release.yml
(OIDC trusted publisher to PyPI on tag push, gated behind smoke tests).
- MIT licensed.
- CHANGELOG.md (keep-a-changelog), CONTRIBUTING.md, .github/ISSUE_TEMPLATE/*.
Examples
- examples/core/quickstart.py — 30s integration story.
- examples/core/verify_llm_output.py — the headline extract+verify pattern.
- examples/core/fastapi_webhook.py — runnable FastAPI receiver.
OpenAPI snapshot pinned at openapi.json; `make regen` refreshes from a
local Lenz repo. The SDK is hand-written rather than Fern-generated — the
endpoint surface is small and stable enough that hand-writing keeps the
SDK code auditable by customers and avoids vendor lock-in.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… verify_batch
Closes two related gaps in the SDK surface around claim visibility:
1. `verify_and_wait(claim=..., visibility="public")` — previously the
kwarg existed on `_verify_submit` but wasn't surfaced via the helper.
AI engs reaching for "make this public" found nothing to type.
2. `verify_batch(claims=[...], visibility="public")` — new batch-wide
default that the server applies to every item unless the item has its
own visibility override. Mirrors the existing batch-level webhook_url.
Before:
client.verify_batch(claims=[
{"text": "a", "visibility": "public"},
{"text": "b", "visibility": "public"},
{"text": "c", "visibility": "public"},
])
After:
client.verify_batch(claims=[{"text": "a"}, {"text": "b"}, {"text": "c"}],
visibility="public")
Per-item visibility still wins when both are set. When `visibility=""`
(default) the SDK omits the field from the body so the server applies
the user's account default.
OpenAPI snapshot refreshed to reflect the new `VerifyBatchIn.visibility`
property.
Tests: 3 new cases covering verify_and_wait pass-through, batch-level
visibility in the request body, and omission when unset. 70/70 unit
tests pass (was 67).
Four changes mirror the v1.0.0-rc.2 surface tightening in the main Lenz
repo. Pre-release so no compat shim needed.
1. New `EntityRef` model and exposed at module top level. Replaces
`list[str]` on `Verification.entities`. Carries `name` plus optional
`qid` (Wikidata Q identifier). Customers can join Lenz output to
their own Wikidata-indexed corpora.
2. `_extract` drops `country` and `city` kwargs. The wire body sent to
/extract is now `{"text": ...}` only — country/city were leaked from
the consumer product and aren't useful on the B2B SDK surface.
3. `Verification.is_time_dependent` removed. Internal cache/discovery
signal the server no longer surfaces.
4. `Assessment.weakest_sources` / `logical_fallacies` / `missing_context`
collapse into a single `warnings: list[str]`. Kind is implicit in
`focus_area`. Net win: the Source Auditor's weakest_sources now reach
customer code (the previous shape only carried two of the three).
Pydantic models stay `extra="allow"` so customers reading old fixtures
won't crash; the new shape is the documented one.
OpenAPI snapshot refreshed.
Tests
- 70/70 unit + 3 smoke skipped. Existing tests that mocked
`verify_and_wait` responses didn't pin the old field shape; respx mocks
validate against the relaxed Pydantic models so nothing in the suite
needed an update.
Wraps the new `GET /verifications/{id}/related` endpoint with a typed
helper:
related = client.verifications.related("vid_abc", limit=5)
for r in related.items:
print(r.verification_id, r.claim, r.verdict_label, r.distance)
Returns the public-library claims closest by pgvector ANN to a given
verification — useful for "see also" UX next to a verdict, or for
content moderation pipelines surfacing related fact-checks.
New `RelatedVerifications(items: list[SimilarVerification])` model
reuses the existing SimilarVerification shape (same as the
duplicate_found needs-input branch). Both surface from the same
server-side schema, so one Python type covers both callers.
Server-side: read-only, no LLM calls, ~10ms ANN query.
Access: any verification the caller owns + any public library item.
`limit` clamped to [1, 10] server-side; default 5.
OpenAPI snapshot refreshed.
Tests — 2 new cases in TestVerifications:
- related returns typed items + propagates `limit` query param
- empty items list when no semantic matches
72/72 unit + 3 smoke skipped pass.
…el panel) Bumps the README intro to match the lenz.io /developers + /api/v1/docs positioning refresh: - Drops "fact-check API for your LLM features" framing (mis-positions us as runtime when our buyer is async/document-shaped). - Leads with the two co-equal primitives (extract free + verify 7-model). - Adds the audience qualifier (legal-memo, deep-research, due-diligence, vertical agents; explicitly NOT chat / voice / real-time copilots). - Adds the canonical extract → verify code sample BEFORE the magical- moment "Sharks" example — matches the brief's "extract first" rule. - Adds the "7-model panel — the work is the product" section, owning latency as value prop instead of apologizing for it. - Keeps the pre-cached "Sharks don't get cancer" magical-moment demo intact (~1.5s response) — moved to its own section below the canonical integration. Rest of the README (What you get, Webhooks, etc.) unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cation API for AI Product Teams" Mirrors the main-repo brand rename. Updates the SDK descriptor in: - pyproject.toml (project description) - README.md (intro line; also retargets the linked URL from https://lenz.io → https://lenz.io/developers so first-time visitors land on the dev-marketing page, not the consumer homepage) - openapi.json (top-level title) - CONTRIBUTING.md (intro line) - src/lenz_io/__init__.py (package docstring) "Claim verification" matches the brand voice (DESIGN.md "Voice & Copy": say "verify" / "claim verification", not "fact-check"). The audience qualifier "for AI Product Teams" is folded into the descriptor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the main-repo change. The "7-model panel — the work is the
product" section had two problems:
- Internal positioning prose ("the work is the product", ChatGPT
comparison paragraph, "report you can defend") that doesn't belong
in a developer-facing README.
- Wrong pipeline stages: real flow is Frame → Collect Evidence →
Debate → Adjudicate → Conclude. "Cite" was never a stage; citations
are produced as part of Conclude's output.
Renames the section to the neutral "## How verification works" with
just the architecture line + timing. No ChatGPT comparison, no marketing
rhetoric.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Line 7 (the intro paragraph) and line 12 (the audience qualifier directly under it) both mentioned 90 seconds back-to-back. Replaced "90 seconds is the wrong shape for those" with "pipeline runs are the wrong shape for those" — same argument, no number-collision with the line above. Other ~90s mentions in the README (code comment, How verification works section, magical-moment demo footer) stay — they live in distinct sections and each carries different information. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
lenz-io v1.0.0rc1)client.verifications.related()semantic neighbor lookupvisibilitykwarg onverify_and_wait+ batch-level visibility onverify_batchRelease plan
After merge, tag
v1.0.0rc1to trigger the PyPI OIDC publish workflow.🤖 Generated with Claude Code