Schemas
Built-in schemas
Section titled “Built-in schemas”scriva.schemas ships pydantic models for the document types people
most often want to extract. Pass one to scriva.extract,
a preset, or any structured-output Worker
and you get a typed instance back.
import scriva
invoice = scriva.extract("acme-2026-05.pdf", schema=scriva.schemas.Invoice)print(invoice.total_amount, invoice.currency)for line in invoice.line_items: print(line.description, line.quantity, line.unit_price)You can also subclass any of them to add fields specific to your domain — pydantic inheritance just works, and the Worker’s structured-output prompt picks up the extra fields automatically.
Catalogue
Section titled “Catalogue”| Schema | Import path | Typical source |
|---|---|---|
Invoice | scriva.schemas.Invoice | invoices, bills |
Receipt | scriva.schemas.Receipt | retail/restaurant receipts |
IdCard | scriva.schemas.IdCard | driver’s licences, national IDs |
Passport | scriva.schemas.Passport | passport pages (MRZ-aware) |
BusinessCard | scriva.schemas.BusinessCard | business cards |
Contract | scriva.schemas.Contract | legal contracts |
BankStatement | scriva.schemas.BankStatement | bank/credit-card statements |
Resume | scriva.schemas.Resume | CVs / résumés |
MedicalForm | scriva.schemas.MedicalForm | clinic intake / lab reports |
Prescription | scriva.schemas.Prescription | prescriptions, dispensing labels |
PurchaseOrder | scriva.schemas.PurchaseOrder | POs, sales orders |
ShippingLabel | scriva.schemas.ShippingLabel | shipping/return labels |
Cheque | scriva.schemas.Cheque | cheques / cheque images |
TabularForm | scriva.schemas.TabularForm | generic grid forms |
Every schema in the table is a pydantic.BaseModel. Open one in your
editor (from scriva.schemas import Invoice; Invoice.model_json_schema())
to see the full field list.
A few shapes worth knowing
Section titled “A few shapes worth knowing”Invoice
Section titled “Invoice”class Invoice(BaseModel): invoice_number: str invoice_date: date due_date: date | None = None currency: str # ISO 4217, e.g. "USD" subtotal: Decimal | None = None tax_amount: Decimal | None = None total_amount: Decimal vendor: Party customer: Party | None = None line_items: list[LineItem] = [] payment_terms: str | None = None notes: str | None = None
class Party(BaseModel): name: str address: Address | None = None tax_id: str | None = None email: str | None = None phone: str | None = None
class LineItem(BaseModel): description: str quantity: Decimal unit_price: Decimal total: Decimal sku: str | None = None tax_rate: Decimal | None = NoneReceipt
Section titled “Receipt”class Receipt(BaseModel): merchant_name: str merchant_address: Address | None = None transaction_date: datetime currency: str subtotal: Decimal | None = None tax_amount: Decimal | None = None tip_amount: Decimal | None = None total_amount: Decimal payment_method: Literal["cash", "card", "mobile", "other"] | None = None last4: str | None = None # card last-4, if present line_items: list[LineItem] = []IdCard / Passport
Section titled “IdCard / Passport”class IdCard(BaseModel): id_number: str full_name: str date_of_birth: date sex: Literal["M", "F", "X"] | None = None issue_date: date | None = None expiry_date: date | None = None issuing_authority: str | None = None address: Address | None = None nationality: str | None = None # ISO 3166-1 alpha-3
class Passport(IdCard): passport_number: str mrz_line_1: str # raw MRZ mrz_line_2: str place_of_birth: str | None = NonePassport.mrz_* fields preserve the raw machine-readable zone so
downstream verification can re-parse it. scriva.schemas.parse_mrz(...)
returns a Passport from MRZ text alone.
BankStatement
Section titled “BankStatement”class BankStatement(BaseModel): account_holder: str account_number: str # often last-4 only statement_period_start: date statement_period_end: date currency: str opening_balance: Decimal closing_balance: Decimal transactions: list[Transaction]
class Transaction(BaseModel): date: date description: str amount: Decimal # signed: + credit, - debit balance: Decimal | None = NoneContract
Section titled “Contract”class Contract(BaseModel): title: str effective_date: date | None = None expiry_date: date | None = None parties: list[Party] governing_law: str | None = None clauses: list[Clause]
class Clause(BaseModel): heading: str | None = None body: strExtending a built-in
Section titled “Extending a built-in”Add fields, override types, change validators — standard pydantic:
from decimal import Decimalfrom scriva.schemas import Invoice, LineItem
class TaxLineItem(LineItem): tax_rate: Decimal # was Optional in the base
class MyInvoice(Invoice): purchase_order: str | None = None line_items: list[TaxLineItem] = [] # narrow the type
result = scriva.extract("acme.pdf", schema=MyInvoice)The Worker’s structured-output prompt rebuilds itself from
MyInvoice.model_json_schema() on each call, so subclassing is the
canonical way to localise a schema to your vendor mix.
Custom schemas
Section titled “Custom schemas”You don’t need a built-in — any pydantic.BaseModel works. Match the
field names to terms that appear in your documents and the VLM will
fill them in:
from pydantic import BaseModelfrom datetime import date
class LabReport(BaseModel): patient_name: str collection_date: date panel: str glucose_mg_dl: float cholesterol_mg_dl: float
report = scriva.extract("lab-2026-05.pdf", schema=LabReport)Field names matter more than descriptions — the model uses them
verbatim in the prompt. Reach for Field(description=...) when the name
alone is ambiguous (e.g. total: Decimal = Field(description="total including tax")).
Validation and coercion
Section titled “Validation and coercion”Schemas validate on construction. A field declared Decimal will reject
a string that isn’t a number; a date will reject "sometime in May".
When a field is mandatory and the VLM doesn’t return it, the Worker
raises RecognitionError rather than silently filling in None — use
schema.model_fields[name].default = None (or just make the field
Optional) when partial extraction is acceptable.
For tolerant-but-typed extraction, the convenience constructor
scriva.schemas.lenient(MyInvoice) returns a copy of the schema with
all fields demoted to Optional and validators downgraded to warnings.
Use it for first-pass classification, then validate strictly on the
fields you actually care about.
What to read next
Section titled “What to read next”scriva.extract— schema-first one-liner.- Presets — pre-tuned recipes that return these schemas.
- Worker › Prompts — how a
schemabecomes a structured-output prompt.