Skip to content
Talk to our solutions team

Extraction API

Every type and function that pulls structure out of an analysed document: the declarative spec types and their extractors, the heuristic field and checkbox detectors, the repeating-row extractor, master-detail grouping and multi-page stitching.

This is a lookup page. For how a capability is used — the locator model, .schema extraction, grid sections — see Fields, forms and checkboxes and Tables and repeating structures.

The names below belong to the spec stack: Go spec structs in, schema.Instance out. The .schema (JSON Schema x-*) extractors that a rule reaches through bbox are a separate stack with its own configuration surface and its own result shapes. They share vocabulary, not code.

StackConfigured byEntry pointsReturns
SpecGo structs (schema.*Spec)form.Extract, masterdetail.Extract, spread.Stitchschema.Instance
HeuristicDetector config structsfield.Detector, checkbox.Detector, field.RepeatingFieldExtractorfield.Field, checkbox.Checkbox, field.Record
.schemaJSON Schema x-* keywordsDocument.ExtractForm, Document.CheckedOption*map[string]any, query.FieldResult

The heuristic detectors run automatically during document analysis and fill Document.Fields, Document.Checkboxes and Document.CheckboxGroups. The spec-stack extractors run only when you call them.

Both the kis CLI and rules.svc reach the same analysed document, so every result described here is available on either surface. See Running rules over documents.

func Extract(doc *document.Document, spec schema.FormSpec) schema.Instance

For each FieldSpec, anchors are tried in order — the first that matches locates the label, the rest are fallbacks. The locator hint moves from the label to the value. A named pattern optionally validates and normalises it. Fields that do not resolve and are marked Required land in Instance.Missing.

FieldTypeMeaning
NamestringForm identifier; copied to Instance.Name
Region*RegionSpecSearch scope; nil means the whole document
Fields[]FieldSpecField definitions
FieldTypeMeaning
NamestringKey under which the value lands in Instance.Values
Anchors[]AnchorSpecOrdered label candidates; first match wins, the rest are fallbacks
LocatorLocatorHintHow to move from label to value
Pattern*PatternSpecNamed pattern the value must satisfy
RequiredboolWhen unresolved, adds Name to Instance.Missing and costs 0.1 confidence
FieldTypeMeaning
TextstringAnchor string, or the regex when MatchType is AnchorRegex
MatchTypeAnchorMatchComparison style
FuzzyboolAllow approximate matching up to MaxDistance
MaxDistanceint64Edit-distance budget; a value of 0 disables fuzzy matching entirely

AnchorMatch constants, and what each does in form.Extract:

ConstantString()Comparison
AnchorExact (zero value)exactCase-insensitive equality after trimming
AnchorContainscontainsCase-insensitive substring
AnchorStartsWithstarts_withCase-insensitive prefix after trimming
AnchorEndsWithends_withCase-insensitive suffix after trimming
AnchorRegexregexText compiled as a Go regex; a compile error yields no match
FieldTypeMeaning
NamestringPattern name resolved against the document’s pattern registry
RequiredboolWhen the pattern does not match, skip this anchor and try the next

A matched pattern sets Value.PatternName, Value.Normalized and Value.Confidence from the pattern match. A non-required pattern that fails leaves the raw text and falls back to the anchor-derived confidence.

ConstantString()Resolution order in form.Extract
LocUnknown (zero value)unknownRight of the label, then below
LocInlineinlineSplit the label block on the first :; if there is no right-hand part, fall back to right-of
LocAfterafterRight of the label, then below
LocBelowbelowBelow the label, then right-of
LocInRegionin_regionRight of the label only
LocInSectionin_sectionRight of the label only

Right-of takes the nearest block on the same page whose X0 is at or past the label’s X1 and which is horizontally aligned within half a line. Below takes the nearest block on the same page whose Y0 is past the label’s Y1 and which overlaps the label horizontally.

FieldTypeMeaning
StartAnchor[]AnchorSpecRegion begins below the first matching anchor on each page
EndAnchor[]AnchorSpecRegion ends above the first matching anchor on that page
PageRange*PageRangePage window, applied before anchor bounding

PageRange is {First, Last int64}. First is inclusive; Last == 0 means “to the end of the document”.

schema.Instance:

FieldTypeMeaning
NamestringThe FormSpec.Name that produced it
Valuesmap[string]ValueExtracted values keyed by FieldSpec.Name
Missing[]stringRequired field names that did not resolve
RegionBoxSpanPer-page union of every value’s box
Confidencefloat64Aggregate score
ProvenanceProvenanceInstance-level location; form.Extract leaves this zero

Accessors: Get(name) (Value, bool), Text(name) string, Has(name) boolHas reports false for a present-but-empty value.

schema.Value:

FieldTypeMeaning
TextstringRaw extracted string, whitespace-trimmed
NormalizedanyType-specific normalised form; nil when no pattern matched
PatternNamestringName of the pattern that matched
Confidencefloat64Per-value score
ProvenanceProvenanceWhere the value came from

schema.Provenance — the fields form.Extract populates:

FieldTypeMeaning
SourceTextstringThe block text that produced the value
MatchedAnchorstringThe anchor Text that located the label
FuzzyDistanceint64Edit distance of the anchor match; 0 for exact
Pageint64Page the value was read from
BoxBoxBounding box of the value block

schema.Box is {X0, Y0, X1, Y1 float64} with Width(), Height() and Empty(). schema.BoxSpan holds Pages []PageBox ({Page int64, Box Box}) with FirstPage() and LastPage(), both returning 0 for an empty span.

An anchor match with no pattern scores 0.9, scaled down linearly by fuzzy distance: 0.9 * (1 - dist/(MaxDistance+1)). A matched pattern uses the pattern’s own confidence instead. Instance.Confidence is the mean of the per-field scores minus 0.1 * len(Missing), floored at 0 — and is 0 outright when no field resolved.

spec := schema.FormSpec{
Name: "terms",
Fields: []schema.FieldSpec{{
Name: "rate",
Anchors: []schema.AnchorSpec{
{Text: "Interest Rate", MatchType: schema.AnchorExact},
{Text: "Annual Interest Rate", MatchType: schema.AnchorExact},
{Text: "Note Rate", MatchType: schema.AnchorExact},
},
Locator: schema.LocAfter,
Required: true,
}},
}
in := form.Extract(doc, spec)
rate := in.Text("rate")
func NewDetector() *Detector
func (d *Detector) DetectFields(blocks []text.Block, lines []text.Line) []Field
func (d *Detector) ExtractByDefinition(def FieldDef, blocks []text.Block, lines []text.Line) *Field

DetectFields needs no spec. It runs three strategies and deduplicates the result:

StrategyRelation producedRule
InlineRelInlineBlock text matching ^([^:]+):\s*(.+)$, label at most 50 characters
Right-ofRelRightOfAdjacent blocks in one line where the left looks like a label and the right does not, gap at most 3× the label width
BelowRelBelowConsecutive non-wrapped lines, left-aligned within 0.2, first aligned value only

Deduplication keeps the highest-confidence detection among fields that share a label (case-insensitive) or whose value boxes intersect.

FieldTypeMeaning
LabelstringLabel text, trailing : stripped
ValuestringValue text
LabelBoxgeometry.BoxLabel bounds; equals ValueBox for RelInline
ValueBoxgeometry.BoxValue bounds
RelationRelationTypeWhich spatial relationship produced it
Confidencefloat64Mirror of OverallScore.Value
LabelConfidenceconfidence.ScoreFactor breakdown for the label
ValueConfidenceconfidence.ScoreFactor breakdown for the value
OverallScoreconfidence.ScoreCombined score
PatternTypepattern.PatternTypeSet when the value matched a registered pattern
NormalizedanyNormalised value from that pattern match
func (f *Field) ComputeOverallConfidence()

Recomputes OverallScore and Confidence as a weighted average of LabelConfidence (0.4) and ValueConfidence (0.6). Call it after mutating either component score. DetectFields itself uses the geometric mean, so calling this changes the number.

ConstantMeaning
RelInline (zero value)Label: Value inside one block
RelRightOfValue to the right of the label on the same line
RelBelowValue on the line under the label
RelTableCellDeclared only. No detector produces it, and ExtractByDefinition treats it as the default hint — right of the label, then below

The caller-supplied contract for targeted lookup through ExtractByDefinition.

FieldTypeMeaning
NamestringBecomes Field.Label on the result
LabelPatterns[]stringRegexes tried first, each compiled case-insensitively; an uncompilable pattern is skipped
LabelAnchors[]*anchor.NgramN-gram anchors tried after every label pattern fails
ValueTypepattern.PatternTypeWhen set, the value is run through this pattern type
LocationHintRelationTypeDirection to search from the label
RequiredboolNever read
Validatorfunc(string) boolRejects a candidate value; the search continues past a rejection

Search extent: RelRightOf searches 5× the label width, RelBelow searches 3× the label height, any other hint tries right first and then below at the anchor defaults. ExtractByDefinition returns nil when nothing resolves.

Confidence is fixed rather than computed: 0.85 for a label-pattern hit, raised to 0.95 when ValueType also matched, and anchorConfidence * 0.9 for an n-gram hit. The 0.95 raise applies only on the label-pattern path — an n-gram hit whose value matches ValueType sets PatternType and Normalized but keeps anchorConfidence * 0.9.

func GroupFields(fields []Field, maxGap float64) []FieldGroup

Clusters fields whose label-value union boxes are within maxGap of each other. Returns FieldGroup{Name string, Fields []Field, Box geometry.Box}. Returns nil for an empty input.

Exported and read-only — DetectFields has no config struct.

ConstantValueApplies to
DefaultMaxLabelLength50Inline detection: longer labels are rejected
DefaultShortLabelLength20Threshold for the short-label boost
DefaultLabelConfidenceNormalizationFactor100.0Divisor for the inline label-length factor
DefaultGapToWidthMultiplier3.0Max label-to-value gap, as a multiple of label width or height
DefaultAlignmentToleranceRatio0.5Vertical alignment tolerance, as a fraction of label height
DefaultBaselineTolerance0.3Horizontal alignment tolerance, as a fraction of label width
DefaultBaseLabelScore0.5Starting score for the label-pattern heuristic
DefaultColonBoost0.3Added when the label ends with :
DefaultShortLabelBoost0.1Added when the label is under DefaultShortLabelLength
DefaultCapitalBoost0.1Added when the label starts with A–Z
DefaultDigitPenalty0.2Subtracted when the label contains a digit
DefaultPatternMatchConfidence0.95Value confidence on a pattern match

Domain vocabulary — field names, label anchors, expected value types — enters through packs. It is never built into the detector.

type Pack struct {
Name string
Fields []FieldDef
}
func RegisterPack(p Pack)
func RegisteredPacks() []Pack
func RegisteredFieldDefs() []FieldDef
func RegisteredFieldDef(name string) *FieldDef
func ResetPacks()
FunctionReturnsBehaviour
RegisterPackAppends to the registry; safe to call concurrently
RegisteredPacks[]PackIndependent snapshot in registration order
RegisteredFieldDefs[]FieldDefEvery definition across all packs, in registration order
RegisteredFieldDef*FieldDefFirst case-insensitive name match, as a copy; nil when absent
ResetPacksClears the registry. Tests only

Document.ExtractRegisteredFields() runs every registered definition against a document and returns map[string]*field.Field keyed by field name, skipping definitions that resolve to nil.

func NewRepeatingFieldExtractor(labels ...string) *RepeatingFieldExtractor
func (e *RepeatingFieldExtractor) Extract(lines []text.Line) []Record

A record starts wherever the first label appears in a line. Each subsequent label’s value runs from the end of the label to the next label or the nearest end marker, whichever comes first. The record is emitted only when the fraction of labels that produced a non-empty value reaches MinMatchRatio.

The setters return the receiver, so they chain.

FieldTypeDefaultSetter
Labels[]stringconstructor argument
EndMarkers[]stringnoneSetEndMarkers(markers ...string)
VerticalFields[]stringnoneSetVerticalFields(labels ...string)
MaxLinesint10SetMaxLines(n int)
MaxWordsint3SetMaxWords(n int)
FieldMaxWordsmap[string]intnilSetFieldMaxWords(label string, n int)
MinMatchRatiofloat640.5set the field directly — there is no setter

MaxLines caps how many lines are folded into one record before it is flushed. MaxWords caps words per value; FieldMaxWords overrides it per label. A label listed in VerticalFields takes the nearest block below the label that overlaps it horizontally, searched across every line rather than just the record’s lines.

FieldTypeMeaning
Fieldsmap[string]stringLabel to extracted value; labels that produced nothing are absent
Linetext.LineThe record’s first line

Reachable from a rule as ExtractRepeatingFields(labels ...string) []field.Record and RepeatingExtractingFields(...) []map[string]any, which parses comma-separated label, end-marker, label:n word-cap and vertical-field strings into the same extractor. See Tables and repeating structures.

func NewDetector() *Detector
func NewDetectorWithConfig(config DetectorConfig) *Detector
func DefaultConfig() DetectorConfig
func (d *Detector) DetectCheckboxes(blocks []text.Block, lines []text.Line) []Checkbox
func (d *Detector) DetectCheckboxGroups(blocks []text.Block, lines []text.Line) []CheckboxGroup

DetectCheckboxes runs three methods, deduplicates the results, then assigns labels. DetectCheckboxGroups runs DetectCheckboxes and then groups.

MethodFindsConfidence
Unicode symbol scanChecked and unchecked glyphs anywhere in block text0.95
Regex patternsBracket, paren and underscore forms0.85
Geometry, single markA near-square block holding one of X x ✓ ✔ •0.7
Geometry, emptyA near-square block with no text0.6

These four constants are the whole accuracy model. There is no OCR-confidence blending in this package. Deduplication keeps the highest-confidence detection among boxes that intersect on the same page — identical coordinates on different pages are different checkboxes.

The geometric method accepts a block only when width and height are both between 5 and 30 units and the aspect ratio is between 0.7 and 1.4.

FieldTypeDefaultMeaning
BracketPatterns[]string\[\s*[xX✓✔]\s*\], \[\s*\]Regexes for [X] / [ ] forms
ParenPatterns[]string\(\s*[xX✓✔•]\s*\), \(\s*\)Regexes for (X) / ( ) forms
BoxPatterns[]string☑ ☒ ■ ▣, ☐ □ ▢Never read
CirclePatterns[]string● ◉ ⊙ ◆, ○ ◯ ◇Never read
UnderscorePatterns[]string_[xX]_, _{2,5}Regexes for _X_ / ___ forms
MaxLabelDistancefloat64200Furthest a block can sit and still be taken as the label
LabelPositionLabelPositionLabelRightWhere to look for the label
GroupByProximitybooltrueWhen false, every checkbox becomes its own single-member group
GroupMaxDistancefloat64100Manhattan distance within which two checkboxes can group
CommonGroupLabels[]stringYes, No, N/A, True, False, plus marital, housing, occupancy and loan-purpose termsOnly checkboxes whose label is in this list are grouped with each other
ConstantDirection searchedAlignment required
LabelRight (zero value)Blocks starting past the checkbox’s right edgeMid-Y within half the taller box
LabelLeftBlocks ending before the checkbox’s left edgeMid-Y within half the taller box
LabelBothEither sideMid-Y within half the taller box
LabelAnyAny direction, by Manhattan distanceNone

Blocks that intersect the checkbox, sit on another page, or themselves look like a checkbox pattern are skipped. A checkbox with no qualifying block keeps an empty Label.

checkbox.Checkbox:

FieldTypeMeaning
Boxgeometry.BoxCheckbox bounds, estimated from character position within the block for pattern and symbol hits
CheckedboolWhether the mark reads as set
LabelstringAssigned label text
LabelBoxgeometry.BoxBounds of the label block
GroupNamestringAlways empty — the detector never sets it. Group names live on CheckboxGroup.Name
Confidencefloat64One of the four fixed method constants
TypeCheckboxTypeWhich form was matched
PageintPage the checkbox was found on

checkbox.CheckboxGroup:

FieldTypeMeaning
NamestringInferred group name, or the single member’s label
Checkboxes[]CheckboxMembers
Selected[]stringLabels of the members whose Checked is true
TypeGroupTypeGroupMultiple only when more than one member is checked

CheckboxType: TypeCheckbox (zero value), TypeRadio, TypeBracket, TypeParen, TypeUnderscore. The symbol scan assigns TypeRadio for ○ ● ◯ ◉ and TypeCheckbox otherwise; the geometric method always assigns TypeCheckbox.

GroupType: GroupSingle (zero value), GroupMultiple.

Reachable from a rule as GetCheckboxByLabel(label), GetCheckboxGroup(name) and GetCheckedValue(groupName) []string, all matching case-insensitively on trimmed text.

func Extract(doc *document.Document, spec schema.MasterDetailSpec) []schema.MasterDetailGroup

Pure composition over form and repeater extraction. It adds no detection of its own — it runs both extractors and decides which detail belongs to which master.

FieldTypeMeaning
MasterForm*FormSpecSingle master; used only when it extracted at least one value
MasterRepeater*RepeaterSpecRepeating masters; takes precedence over MasterForm
DetailRepeaterSpecThe detail records

With neither master field set, Extract returns nil. It also returns nil when the master extractor produced no instances.

schema.RepeaterSpec — the detail configuration:

FieldTypeMeaning
NamestringResult name
RowFormSpecThe per-record form; every anchor, locator and pattern rule above applies
Region*RegionSpecSearch scope
SeparatorRowSeparatorHow record boundaries are found
EndAnchors[]AnchorSpecStop extracting at the first match
MaxRecordsint64Cap on records emitted
MinMatchRatiofloat64Fraction of row fields that must resolve for a record to count

RowSeparator constants are SepAnchorRestart (zero value), SepBlankRow, SepRuledLine, SepStride, SepTableRow and SepIndentChange, with String() returning anchor_restart, blank_row, ruled_line, stride, table_row and indent_change. See Tables and repeating structures.

Masters and details are each sorted by first page, then by the smallest Y0 across their page boxes. Each detail attaches to the latest master that is on a page at or before the detail’s page and, on the same page, above it.

SituationOutcome
Detail before every masterDropped
Master with no detailsKept, with an empty Details slice
Detail on a page with no masterAttaches to the nearest master on an earlier page

schema.MasterDetailGroup is {Master Instance, Details []Instance}.

func Stitch(instances []schema.Instance, spec schema.StitchSpec) []schema.Instance

A post-pass over already-extracted instances, independent of which extractor produced them. It walks the input in order and merges each instance onto the previous output entry when every condition holds; otherwise it starts a new entry. Input order is preserved and an empty input returns nil.

FieldTypeMeaning
SpecNamestringRestricts stitching to instances with this Name; empty means stitch each Name group independently
ContinuationAnchors[]AnchorSpecWhen set, at least one must appear in the later instance before it merges
EndAnchors[]AnchorSpecWhen any appears in the earlier instance, it is terminal and never merges
ConditionRule
NameBoth instances share the same Name, and match SpecName when it is set
Adjacencycur.Region.FirstPage() equals tail.Region.LastPage() or that plus 1
End anchorsNo EndAnchors text present in the earlier instance
ContinuationWhen ContinuationAnchors are set, at least one is present in the later instance
Result fieldRule
ValuesEarlier value wins on key conflict; later-only keys are added
MissingUnion of both, minus any name now present in Values
RegionPer-page union of both BoxSpans
ConfidenceField-count-weighted mean: (tailConf*len(tailValues) + curConf*len(curValues)) / total
ProvenanceThe earlier instance’s