Turn documents into trusted structured data.
Ovrin is a Go library that turns invoices, receipts, forms and contracts into typed, validated values — with the evidence for every one of them.
Typed output
A struct in. A struct out.
You declare the shape you want and get that shape back — not a map to assert your way through. The description in each tag is what the model reads; the rules after it are what gets checked.
type Invoice struct {
Number string `ovrin:"invoice number,required"`
Vendor string `ovrin:"vendor company name"`
Currency string `ovrin:"currency code,required,enum=UGX|USD"`
Total float64 `ovrin:"total amount,required,min=0"`
}
client := ovrin.New(ovrin.WithModel(model))
res, err := ovrin.Extract[Invoice](ctx, client, ovrin.File("in.pdf"))
if err != nil {
return err
}
fmt.Println(res.Data.Vendor, res.Data.Total)Staged extraction
Not a prompt with extra steps.
When a PDF carries its own text, reading it is exact and nearly free. Rendering those characters to pixels for a model to read back is a lossy round trip. OCR runs when there is no text layer — not before.
- 1Detect
- 2Acquire
- 3Normalise
- 4Schema
- 5Prompt
- 6Generate
- 7Validate
- 8Ground
- 9Score
client := ovrin.New(
ovrin.WithModel(model), // structured output
ovrin.WithOCR(ocr), // when there is no text layer
ovrin.WithRenderer(renderer), // when OCR needs pixels
)
res, err := ovrin.Extract[Invoice](ctx, client, ovrin.File("scan.pdf"))
if err != nil {
return err
}
fmt.Println(res.Valid, res.Confidence)Explainability
Not 0.98. The reasons for it.
Confidence is composed from named signals that fail in uncorrelated ways, and every one is recorded on the field. It is a ranking signal rather than a probability — the documentation says so too, and will until it is calibrated.
res, err := ovrin.Extract[Invoice](ctx, client, src)
if err != nil {
return err
}
if !res.Valid || res.NeedsReview {
for _, reason := range res.Reasons {
fmt.Printf("review %s: %s\n", reason.Field, reason.Why)
}
return nil
}Provenance
Every value knows whether it was found.
A field that could not be read stays absent rather than quietly becoming zero. In a payments system, “the total is zero” and “we could not read the total” are different facts, and nothing is ever guessed to fill a struct.
total := res.Fields["total"]
if !total.Found {
// Not the same fact as "the total is zero".
return queueForReview(res)
}
fmt.Println(total.Value, total.Confidence)Every result carries
- Valid
- whether every validation rule passed
- Confidence
- decomposed into named signals
- Provenance
- the page and region each value came from
- NeedsReview
- whether a person should look first