Get started Quickstart
Get started

Quickstart

On this page
1

Get an API key

Sign up at velrim.com/signup with an email. No card. New accounts start with ~500 pages free, claimed in the dashboard. Password signups get a verification link by email at sign-up. Click it to confirm your address and sign in.

Create a key under Keys in the dashboard. It is shown once; store it where your code reads secrets. The examples below read it from VELRIM_API_KEY.

.env
VELRIM_API_KEY="vk_live_…"
2

Make your first request

POST /v1/extract

Pass a Zod or Pydantic model and a document; the SDK compiles the model to JSON Schema and validates the response against it. Both SDKs also accept a plain JSON Schema object in place of a model; the response data then arrives unvalidated on the client (unknown in TypeScript, a dict in Python). On raw HTTP you send the JSON Schema yourself.

// npm install @velrim/sdk zod
import { z } from "zod";
import { VelrimClient } from "@velrim/sdk";
const Invoice = z.object({
invoice_number: z.string(),
total: z.number(),
currency: z.enum(["USD", "EUR"]),
});
const velrim = new VelrimClient({ apiKey: process.env.VELRIM_API_KEY! });
const result = await velrim.extract(Invoice, { bytes: pdfBytes });
result.data.total; // number, parsed with YOUR schema
result.field("/total")?.state; // "present" | "null" | "missing"

schema and document are the only required fields. Every request option is documented on Extract.

3

Read the response

data is the extracted object in the shape of your schema. fields reports every schema leaf, keyed by RFC 6901 JSON Pointer.

200 response, excerpt
{
"data": {
"invoice_number": "INV-2041",
"total": 4210.55
},
"fields": {
"/invoice_number": {
"state": "present",
"reason": null,
"value": "INV-2041",
"confidence": 0.93,
"grounding": "ungrounded"
},
"/total": {
"state": "present",
"reason": null,
"value": 4210.55,
"confidence": 0.89,
"anchor": {
"page": 2,
"bbox": [451.0, 700.2, 512.4, 714.0],
"snippet": "Total due $4,210.55",
"page_dims": { "width": 612, "height": 792 }
},
"grounding": "verified"
},
"/currency": { "state": "missing", "reason": "not_found" }
},
"meta": {
"calibrator_version": "cal-2026.08-4",
"pages": 2,
"billed_pages": 2,
"request_id": "req_a1b2c3…"
}
}
state
Whether the field was extracted: "present", "null" or "missing". A missing field arrives inside a 200 with a reason, not as a request error.
confidence
A calibrated score in [0, 1]. Not every field carries one. Measured behavior per calibrator version is published at /reliability.
anchor
Where the value came from: the page, a bounding box and the matched snippet.

The TypeScript SDK collects the fields that need review: missing() returns the "missing" entries, conflicts() returns the fields where extraction passes disagreed. In Python, filter result.fields on state and confidence.

review.ts
const needsReview = [
...result.missing(), // state === "missing"
...result.conflicts(), // conflict === true
];
if (needsReview.length > 0) {
await queueForHumanReview(doc, needsReview);
}

result.belowThreshold(t) returns pointers with confidence under t, plus all missing fields. Derive t from a labeled set of your own documents.

Every field entry and meta key is documented on Extract.