Official Node SDK for the Lenz Claim Verification API for AI Product Teams.
Two API primitives for AI product teams. extract pulls verifiable
claims out of any text — free, 1000 calls/key/day. verify checks one
with a 7-model panel and citations, ~90s. Use them together or alone.
Built for teams whose AI output is async or document-shaped: legal-memo generators, deep-research products, due-diligence platforms, vertical agents producing structured deliverables. Not chat AI, not voice AI, not real-time copilots — pipeline runs are the wrong shape for those.
npm install lenz-ioimport { Lenz } from "lenz-io";
const client = new Lenz({ apiKey: "lenz_..." });
// 1. Pull factual claims out of your model output (free, instant)
const { claims } = await client.extract({ text: llmOutput });
// 2. Verify the ones that matter (~90s each, 7-model panel)
for (const claim of claims) {
const v = (await client.verifyAndWait({ claim })).verdict;
console.log(v?.label, v?.score, v?.confidence);
}Frame → Collect Evidence → Debate (2 models, 2 rounds) → Adjudicate (3 models: sources, logic, context) → Conclude. ~90 seconds wall-clock per claim.
import { Lenz } from "lenz-io";
const client = new Lenz({ apiKey: "lenz_..." });
const v = await client.verifyAndWait({ claim: "Sharks don't get cancer" });
console.log(v.verdict?.label, v.verdict?.score);
// false 2.0
for (const source of (v.sources ?? []).slice(0, 3)) {
console.log(" -", source.title, source.url);
}The demo claim is pre-cached so this returns in ~1.5s. Your own claims hit the full pipeline (~60-90s) — use webhooks for production async flows.
Get your webhook secret here → lenz.io/api-integration
client.verifyAndWait({ claim, ... })— submit + poll until the pipeline lands. Returns a typedVerification.client.verify({ claim })— async submit; returns atask_id. Use webhooks for the callback.client.extract({ text })— pull verifiable claims out of any text (free, capped at 1000/key/day).client.verifyBatch({ claims })— fan-out for multi-claim LLM outputs.client.verifications.{list,get,delete,setVisibility}(...)— manage past verifications.client.followup.{history,send,reset}(verificationId)— Q&A on a verification.client.library.{list,get}(...)— browse the public catalog (no API key needed).client.usage()— credits and rate-limit remaining.
import { LenzWebhooks } from "lenz-io";
import type { VerificationCompleted, VerificationNeedsInput } from "lenz-io";
const webhooks = new LenzWebhooks({ secret: "whsec_..." });
// In your Express handler (use express.raw() to get rawBody as Buffer):
app.post("/lenz-webhook", express.raw({ type: "application/json" }), (req, res) => {
const event = webhooks.parse(req.body, req.headers as Record<string, string>);
switch (event.event) {
case "verification.completed": {
const completed = event as VerificationCompleted;
const verdict = completed.result?.["verdict"];
// …persist verdict + sources
break;
}
case "verification.needs_input": {
const ni = event as VerificationNeedsInput;
// …surface candidate claims, call client.select(taskId, ...) to resolve
break;
}
}
res.status(200).send();
});Signature verification is HMAC-SHA256 over the raw bytes; the SDK does it for you and rejects tampered or replayed payloads.
See examples/core/express-webhook.ts
for a runnable receiver and examples/core/verify-llm-output.ts
for the headline extract-and-verify pattern.
Every error subclass is typed and carries a requestId you can quote on
support tickets:
import { LenzAuthError, LenzRateLimitError, LenzValidationError } from "lenz-io";
try {
await client.verifyAndWait({ claim: "..." });
} catch (exc) {
if (exc instanceof LenzAuthError) {
console.error(String(exc));
// Unauthorized
// Cause: Invalid api key
// Fix: Generate a new key at https://lenz.io/api-integration.
// Docs: https://lenz.io/docs/auth
// Request ID: req_abc123
} else if (exc instanceof LenzRateLimitError) {
await new Promise((r) => setTimeout(r, exc.retryAfter * 1000));
} else if (exc instanceof LenzValidationError) {
for (const fieldErr of exc.errors) {
console.error(fieldErr["loc"], fieldErr["msg"]);
}
} else {
throw exc;
}
}If a verifyAndWait call exceeds its timeoutMs (default 120000) or your
process dies mid-poll, the pipeline keeps running. The exception carries the
taskId:
import { LenzTimeoutError } from "lenz-io";
try {
await client.verifyAndWait({ claim: "...", timeoutMs: 30000 });
} catch (exc) {
if (exc instanceof LenzTimeoutError) {
console.error("resume later via:", exc.taskId);
}
}
// Later (different process / restart):
const status = await client.getStatus("tsk_abc123");
if (status.status === "completed") {
console.log(status.result?.verdict?.label);
}verifyAndWait sends an auto-generated Idempotency-Key on every call by
default, so a network drop after submit doesn't spawn a duplicate verification
or charge a second credit. Override with idempotencyKey: "..." to pin a
specific key, or idempotency: false to opt out.
new Lenz({
apiKey: "lenz_...", // or set LENZ_API_KEY env var
baseUrl: "https://lenz.io/api/v1", // override for staging / local
timeoutMs: 30000,
maxRetries: 3,
fetch: customFetch, // inject for tests
});Environment variables:
LENZ_API_KEY— read ifapiKeyis not passedLENZ_BASE_URL— read ifbaseUrlis not passed
- Node 18, 20, 22
- ESM + CJS dual exports
- TypeScript types included
- Works in Cloudflare Workers / edge runtimes — pass a
fetchpolyfill ifglobalThis.fetchisn't available
github.com/lenzhq/lenz-io-node/issues
For commercial use, volume pricing, or onboarding support, get in touch.
MIT. See LICENSE.