LM-Kit OneDocs2026.8.10lm-kit.com
Intelligent Document Processing/Structured Extraction

The Extraction Schema Reference

The jsonSchema string sent to POST /lmkit/v1/extract-structured-data is the complete specification of what comes back: its structure, its types, and its constraints. The engine does not merely show the schema to the model and hope: structure and hard constraints are compiled into a generation grammar that makes invalid output unrepresentable, steering hints shape token choices during generation, and normalization rules clean values afterward. This page is the exhaustive reference for that dialect: every keyword, every type, every enforcement guarantee. For the surrounding workflow (confidence, review routing, batch pipelines), see Structured Extraction.


1The shape of a schema#

The root is a JSON object. It may carry:

Root keyword Effect
properties The fields to extract, one entry per output key. The root may also BE the field map directly, with no properties wrapper.
required Array of field names that must hold a value. Anything not listed is optional and may come back null.
title, description Shown to the model as context for the whole extraction. Use description for what the document IS ("a supplier invoice"); it is the schema-level counterpart of the request's guidance.
allOf Dependent-value declarations (section 7).
$schema Accepted and ignored, so schemas copied from other tooling paste cleanly.

The extracted JSON contains every declared field, always, in declaration order: a field that was not found is present with null (or an empty date), never silently missing. Output keys are your property names exactly as written.

2Declaring a field#

Three equivalent forms, from terse to fully specified:

{
  "invoice_number": "string",

  "tags": ["string"],

  "total": {
    "type": "number",
    "description": "Grand total including tax."
  }
}
  • Shorthand: the value is a type name ("field": "string"). Array type names work here too ("field": "stringArray").
  • Array shorthand: a one-element array of a type name (["string"]) or of an object shape ([ { ...properties... } ] for an array of objects).
  • Object form: a type plus any of the keywords in section 4. This is the only form that can carry constraints.

3Types#

The type keyword accepts the engine's own names plus the standard JSON Schema aliases:

type Output Notes
string JSON string The default workhorse.
char JSON string Single character.
integer JSON integer Signed. int width applies when values are parsed.
uint, short, ushort, long, ulong JSON integer Same integer grammar; the name states the width contract for your parser.
number JSON number Alias of double. Decimals and exponents allowed.
double, float JSON number
boolean true / false Alias of bool.
date "YYYY-MM-DD" The grammar enforces the ISO calendar-date shape, whatever format the document used. An absent optional date is an empty string or null.
object Nested object Declare children under properties (section 6).
array JSON array Declare entries under items (section 5).

Arrays exist for every scalar (stringArray, integerArray, doubleArray, boolArray, dateArray, ...), reachable through {"type": "array", "items": {"type": ...}}, the ["string"] shorthand, the direct name, or "isArray": true beside a scalar type.

4Field keywords#

The complete set. Enforcement says which layer guarantees the keyword: grammar means the constraint is compiled into generation and cannot be violated in the output; steering means it is shown to the model and guides token sampling; post means it is applied to the value after generation.

Keyword Applies to Effect Enforcement
description any Tells the model what the field means. The highest-leverage line in the schema: one precise sentence beats a paragraph. steering
enum string, string arrays Closed set of allowed values, emitted exactly as declared. Include "" in the set when "none of these" must be expressible on a required field. allowedValues is accepted as an alias. grammar
format string date-time, time, email, hostname, ipv4, ipv6, uri steer generation toward the format. "format": "date" (or any type name) instead CONVERTS the field to that type, so {"type": "string", "format": "date"} is the classic way to declare a date. steering (conversion: grammar)
pattern string, string arrays Shape the whole value must match. Takes a regular expression (literals, character classes, \d \w \s, quantifiers * + ? {m,n}) or a compact descriptor: N digit, A uppercase letter, a lowercase letter, X uppercase alphanumeric, so an IBAN prefix is 2A2N. Convertible patterns compile into the grammar; a regex using features beyond that subset (groups, alternation, anchors) demotes to steering. Invalid syntax is rejected at request time. grammar (fallback: steering)
maxLength string Hard cap on the value's character count. Budgets above 1999 are enforced at 1999, the largest bound the generation grammar compiles. On numeric fields and string arrays it demotes to steering. grammar
maxItems any array Hard cap on entry count: the grammar closes the array at the limit, so generation terminates there naturally. maxEntries is accepted as an alias. Prefer it for correctness guarantees: large bounds grow the grammar and cost generation speed. grammar
caseMode string, string arrays "UpperCase" or "LowerCase": the character set itself excludes the other case during generation. grammar
disableSpacingCharacters string, string arrays true removes the space character from the field's alphabet: identifiers come out unspaced ("AB 12 34" can only generate as "AB1234"). grammar
trimStart string, string arrays String or array of candidate prefixes: the longest one matching the extracted value is removed, once, so ["INV", "INV-"] turns "INV-2024-001" into "2024-001". Declaration order does not matter. post
isArray any scalar type true turns the declared type into its array form. grammar

Precedence rules the engine applies, in order:

  1. A format naming a type converts the field first; keywords incompatible with the new type are then ignored (an enum beside "format": "date" is dropped).
  2. enum wins over everything else on the field: values generate exactly as declared, so pattern, maxLength, caseMode, and disableSpacingCharacters are not applied to them. Avoid combining trimStart with enum: trimming runs after and can mangle a declared value.
  3. Keywords on a type they do not apply to are ignored silently (an enum on an integer does nothing: encode the constraint in description, or extract as string and parse).
  4. Unknown keywords are ignored, so schemas carrying annotations from other tools still parse.

5Arrays#

{
  "tags": {
    "type": "array",
    "items": { "type": "string" },
    "maxItems": 5,
    "description": "Up to five thematic tags."
  },
  "line_items": {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "description": { "type": "string" },
        "quantity": { "type": "number" },
        "unit_price": { "type": "number" }
      },
      "required": [ "description", "quantity", "unit_price" ]
    }
  }
}
  • Scalar arrays hold one primitive type. Entry-level constraints ride on the array field itself: an enum constrains every entry to the set, a pattern shapes every entry, caseMode and disableSpacingCharacters apply per entry.
  • Arrays of objects declare a row shape under items; each row is a full nested scope with its own required and its own dependent values.
  • maxItems bounds either kind at the grammar level.
  • An empty array is a valid result for an optional array field; a required one must produce at least the empty [] rather than null.

6Nested objects#

An object field declares children under properties; nesting recurses to any depth, and arrays of objects can contain objects containing arrays. Two scoping rules:

  • required is per level: the root's array names root fields, each object's array names its own children. Marking a child required only matters once its parent produced a value.
  • Every level's fields appear in the output in declaration order, exactly like the root.
{
  "vendor": {
    "type": "object",
    "description": "Seller information.",
    "properties": {
      "name": { "type": "string" },
      "email": { "type": "string", "format": "email" },
      "address": {
        "type": "object",
        "properties": {
          "city": { "type": "string" },
          "country": { "type": "string", "caseMode": "UpperCase", "maxLength": 2 }
        }
      }
    },
    "required": [ "name" ]
  }
}

7Dependent values: two-level taxonomies#

When the valid values of one field depend on another field's answer (category and subcategory, department and team), flat enums allow nonsense combinations. Declare the dependency with standard allOf / if / then conditionals at the level where both fields live:

{
  "properties": {
    "category": { "type": "string" },
    "sub_category": { "type": "string" }
  },
  "required": [ "category", "sub_category" ],
  "allOf": [
    {
      "if":   { "properties": { "category": { "const": "Baggage" } } },
      "then": { "properties": { "sub_category": { "enum": [ "Delay", "Damage", "Loss" ] } } }
    },
    {
      "if":   { "properties": { "category": { "const": "Flight Disruption" } } },
      "then": { "properties": { "sub_category": { "enum": [ "Cancellation", "Delay", "Downgrade" ] } } }
    }
  ]
}

The engine compiles the pair into one branching grammar unit: the model first commits to a category, and from that point only that branch's sub_category values are generatable. An undeclared combination is not merely discouraged: it cannot be produced.

The contract, precisely:

  • The discriminator (category) must be declared immediately before the dependent field: the two generate as one unit.
  • Each if names exactly one property with a const string; each then names exactly one property with a non-empty enum. Entries of any other shape are ignored, so unrelated conditionals in a pasted schema do not break parsing.
  • One dependent field follows one discriminator; declaring conditionals on two different discriminators for the same field is rejected.
  • The discriminator's own allowed values become the declared const keys automatically; an explicit enum on it must match those keys exactly.
  • An optional discriminator that generates null forces the dependent field to null: a subcategory without its category names no combination at all.
  • The declarations work at any level, including inside array-of-object rows, where each row branches independently.

8Field names carry meaning#

The engine reads your property names semantically. A field named email, iban, siret, phone, vat_number, or zip (among several hundred recognized names and their French, German, Spanish, Italian, and Portuguese equivalents) is assigned an entity kind, which activates a dedicated validator on the extracted value. That is what feeds the per-field validation status in the response: valid, repaired (auto-corrected, with the original_value kept), invalid, or not_applicable (no validator for this field).

Recognized families include, by example:

Family Names that trigger it
Contact email, phone, mobile, fax, website, url
Identity first_name, last_name, full_name, company, job_title
Address street_address, city, postal_code, zip, state, country, country_code
Financial iban, swift, bic, vat, tax_id, amount, total, currency, account_number
Identifiers passport_number, national_id, license_plate, serial_number, invoice_number, uuid, isbn
Temporal date, birth_date, datetime, timestamp, time, duration, age
Network ip_address, ipv6, mac_address, hostname
Measurement quantity, weight, percentage, unit, latitude, longitude

Detection is forgiving about separators and casing (customerEmail, customer_email, and Customer Email all read as an email address) and checks type compatibility: amount on a number activates the currency validator, but amount on a boolean activates nothing. The practical rule: name fields what they are, and validation, repair, and review routing come free. A generic name (field_3, value) extracts fine but validates as not_applicable.

9Everything in one schema#

{
  "title": "Claim intake",
  "description": "A customer claim letter, possibly scanned.",
  "type": "object",
  "properties": {
    "claim_reference": {
      "type": "string",
      "description": "The claim file reference.",
      "trimStart": [ "REF:", "Ref." ],
      "disableSpacingCharacters": true,
      "caseMode": "UpperCase",
      "pattern": "3A4N"
    },
    "claim_date": { "type": "string", "format": "date" },
    "claimant": {
      "type": "object",
      "properties": {
        "full_name": { "type": "string" },
        "email": { "type": "string", "format": "email" },
        "iban": { "type": "string" }
      },
      "required": [ "full_name" ]
    },
    "category": { "type": "string" },
    "sub_category": { "type": "string" },
    "claimed_amount": { "type": "number", "description": "Total amount claimed, taxes included." },
    "attachments_mentioned": {
      "type": "array",
      "items": { "type": "string" },
      "maxItems": 10
    },
    "is_repeat_claim": { "type": "boolean" }
  },
  "required": [ "claim_reference", "claim_date", "category", "sub_category" ],
  "allOf": [
    {
      "if":   { "properties": { "category": { "const": "Baggage" } } },
      "then": { "properties": { "sub_category": { "enum": [ "Delay", "Damage", "Loss" ] } } }
    },
    {
      "if":   { "properties": { "category": { "const": "Booking" } } },
      "then": { "properties": { "sub_category": { "enum": [ "Refund", "Change", "No-show" ] } } }
    }
  ]
}

Every mechanism above is at work here: grammar-enforced reference shape, a normalized date, entity validation on full_name, email, iban, and claimed_amount, a branching taxonomy that cannot produce an undeclared pair, a bounded array, and a boolean that can only be true, false, or null.

10The request around the schema#

The schema rides in a request whose other fields are quickly told: input carries the document as plain text, a Base64-encoded file, or an uploaded file's identifier (input_format says which), or as [image, caption] pairs for multimodal extraction from photos and scans; enable_ocr (default true) lets pages without a text layer be read by the configured OCR engine; guidance adds free-text interpretation rules that apply across the whole document; model overrides the default; include_elements returns the per-field detail block. Accepted file formats are the document pipeline's own, listed in Document Processing.

Three behaviors worth knowing when designing schemas:

  • Doubt resolves to null. The endpoint extracts conservatively: when the document does not support a value, the field comes back null rather than guessed. A missing REQUIRED field additionally raises the review flag, so "required but absent" becomes a routed exception instead of an invention.
  • Long documents are windowed. Content beyond the model's context is read in overlapping windows and merged, so the schema's size budget is about output, not input: a 200-page contract with a 12-field schema is fine.
  • Slow extractions become jobs. Past the configured timeout the endpoint returns 202 Accepted with a job_id to poll, the jobs contract every task endpoint shares.

One distinction to keep sharp: the chat API's response_format.json_schema shapes a CONVERSATION's reply and follows the OpenAI structured-outputs dialect. This page's dialect belongs to the extraction endpoint, where documents, OCR, confidence, validation, and source coordinates live. When the task is "read this document into this shape", use extraction.