---
title: "Structured OCR (fields)"
method: POST
path: "/ocr/fields"
---

# Structured OCR (fields)

`POST /ocr/fields`

## Request

- Base URL: `https://api.space-ocr.com`
- URL: `https://api.space-ocr.com/ocr/fields`
- Auth: HTTP bearer

## Request body

- object: OCR fields request
  - `image` string, required: base64 or URL of the image. The JSON body caps at 28MB; base64 inflates a file by ≈1.33×, so that is roughly a 20MB original image. Over the cap returns 413 with details.limitBytes / details.receivedBytes.
  - `imageType` 'base64' | 'url': Type of `image` — "base64" or "url". Required (also accepted via the deprecated alias `image_type`).
  - `image_type` 'base64' | 'url': Deprecated alias of `imageType`.
  - `fields` FieldSpec[]
    - `name` string, required
    - `type` string: Containers are `array` / `object`; the default is `string`. Declaring a type never reformats `values` and the type is never shown to the model — `values` stays the model's reading of the page, held to the printed text by a normalized character cross-check (width / brackets / whitespace folded), not a byte-for-byte copy; for exact string reconciliation use `cells[path].evidence.printed_text`. A scalar type (`number` / `integer` / `date`) additionally produces `normalized`: the same reading parsed into that type, shaped exactly like `values`. A value that will not parse is null there, the reason lands at `cells[path].normalized.error`, and the cell is flagged `type_mismatch`. Any other string is accepted, read as string, and reported back in `review.notes`.
    - `description` string
    - `children` unknown[]
      - unknown
    - `required` boolean: Review declaration: when this field comes back empty or missing, review.flagged reports it with reason "missing". Never shown to the model — extraction is unchanged.
    - `label` union: The label printed next to the value (e.g. "合計"), or an array of candidates. When the value repeats on the page, coordinates anchor to the occurrence beside this label. Labels spanning several words ("消費税（8%）") work as written. Top-level non-container fields only (string / number / integer / date) — a label on an array or object, or on any of its children, is ignored, since a label printed once cannot say which repeating row a value belongs to; line-item children are already anchored within their own row. Fires only when the label is printed exactly once; otherwise the normal search is used and review.notes reports it as issue "label_unresolved" (the value still comes back, so that note is the only way to see the declaration is inert). Never shown to the model — text is unchanged, only the coordinate anchor moves.
      - string
      - string[]
    - `pattern` union: Regex the NORMALIZED value must satisfy (JSON-Schema semantics: unanchored, use ^…$ for a whole-value rule). String fields only. An array means any-of. Matching runs on the width-folded value, so a plain-ASCII pattern works against a full-width page. Violation → review reason "pattern_mismatch".
      - string
      - string[]
    - `min` number: Inclusive lower bound for a number/integer field, checked against the normalized value. Violation → review reason "out_of_range".
    - `max` number: Inclusive upper bound for a number/integer field, checked against the normalized value. Violation → review reason "out_of_range".
    - `enum` string[]: The slot for handing the API a value set your business already owns — a vendor master's company names, an item catalogue, a unit list (袋/本/個). A normalized value outside the set raises review reason "pattern_mismatch". Also the only handle on misreads both engines agree on (冊→申), which character cross-checking cannot see. It cannot tell two legitimate set members apart (a registered-but-wrong vendor still passes) — that spot is `near`'s job.
    - `review` 'normal' | 'off': Review sensitivity. "off" withholds the reasons the ENGINE infers (text_mismatch, low_ratio, ambiguous_occurrence, …) for this field — the cell keeps all its evidence, only the verdict is withheld. It does NOT silence "missing" or a constraint violation: those are rules the caller declared. Use on free-text columns (品名/備考) so the signal on 登録番号/合計 stays readable.
    - `near` union: Printed vocabulary this value is expected to sit beside — one term, a list of alternatives, or { terms, match } (御中/様 for an addressee, 登録番号/〒/TEL for an issuer block). The check runs over EVERY occurrence of the value: none beside a declared term raises review reason "near_mismatch" (wrong wherever it is printed), one beside a term but not the copy the coordinates landed on raises "near_ambiguous" (which copy is meant is undecided). The arithmetic rides in cells[path].evidence.near. When no declared term is printed anywhere the check abstains and review.notes reports "near_unresolved". Never shown to the model — it cannot make the pick right, it makes a wrong pick visible.
      - string
      - string[]
      - object
        - `terms` union, required
          - string
          - string[]
        - `match` string
    - `not_near` union: The mirror of `near` — printed vocabulary this value must NOT sit beside (declare 登録番号/TEL/〒 on an addressee name). Sitting beside one raises review reason "near_conflict", with the matched term and distance in cells[path].evidence.not_near. Reaches the forms `near` cannot: a form with no addressee line prints no 御中, so `near` can only abstain, while an issuer block always prints something. Absence of the vocabulary is not a violation, so this never abstains and files no note. Never shown to the model.
      - string
      - string[]
      - object
        - `terms` union, required
          - string
          - string[]
        - `match` string
  - `prompt` string
  - `autoFields` boolean: When true, the LLM proposes a schema if fields is not provided.
  - `auto_fields` boolean: Deprecated alias of `autoFields`.

## Response `200`

OK

- OcrFieldsResponse
  - `status` string, required: Always "success" here — errors come back as HTTP 4xx/5xx with the common error envelope instead.
  - `data` object, required
    - `values` object, required: Pure user data, exactly your declared schema — the model's reading of the page, held to the printed text by the character cross-check (not a byte-for-byte copy; for exact string matching use cells[path].evidence.printed_text).
    - `normalized` object: Present only when some field declared a scalar type (number / integer / date, or a string carrying pattern / enum): a sparse tree shaped exactly like values, with the leaves parsed into the declared type. A leaf that would not parse is null — the reason is at cells[path].normalized.error. values itself is left alone; it is the side carrying the coordinates and the verification.
    - `cells` object, required: Flat, path-keyed coordinate/verification map — keys share review.flagged[].path grammar.
    - `review` Review, required
      - `unit` string, required: "field" on /ocr/fields, "element" on /ocr/markdown, "block" on /ocr/text.
      - `declared` integer: /ocr/fields only — every declared slot, empty and unreturned required included (a fixed denominator).
      - `returned` integer: /ocr/fields only — non-empty values.
      - `total` integer: /ocr/markdown and /ocr/text — total counted units (each table cell counts on its own).
      - `boxed` integer, required: Units that carry a box.
      - `verified` integer, required: Units with verified: true — nothing flagged and a check actually ran. Flagged units are not counted here; their count is flagged.length.
      - `flagged` FlaggedEntry[], required: The review list — the review count IS its length (no separate counter).
        - `path` string, required: Same grammar as the cells map keys — a direct lookup. Note: missing is raised only for fields declared required; an undeclared field that never comes back raises no flag.
        - `reasons` string[], required: Every rule broken, ordered by rank — index 0 is the primary verdict (always an array, even at length one). Vocabulary: "type_mismatch" | "out_of_range" | "pattern_mismatch" | "near_mismatch" | "near_ambiguous" | "near_conflict" (the first six are rules the caller declared, ranked above engine-inferred reasons) | "nobox" | "text_mismatch" | "crop_mismatch" | "low_ratio" | "weak_source" | "low_ocr_confidence" | "ambiguous_occurrence" | "overwide_box" | "missing". New codes may be added later — fall back to a generic "needs review" for unknown ones.
      - `by_reason` object, required: Per-reason tally. One cell can break a declared rule AND carry an engine suspicion, so every entry in reasons is counted — the total is therefore at least flagged.length.
      - `coverage` object: How much of the page came back (document endpoints; LLM path only on /ocr/text).
        - `recovered_blocks` number
        - `vision_tokens` number
        - `tokens_claimed` number
        - `token_coverage` number
      - `notes` ReviewNote[]: Present only when a declaration did not run as written — branch on issue.
        - `path` string, required
        - `issue` string, required: What did not run as declared: "type_coerced" (unsupported declared type read as string; declared_type / applied_type ride along) | "label_unresolved" (a declared label anchored nothing — the value still comes back, so this note is the only trace that the declaration is inert) | "near_unresolved" (no declared near term is printed anywhere — the check abstained).
        - `description` string
    - `image` ImageInfo, required: The size in pixels of the page as read — the frame every coordinate lives in (pixel_x = xmin / 1000 × width). Measured after the page is turned upright (EXIF) and after any downscale, so it can differ from the file you sent: a sideways 4000×3000 photo can come back 3000×4000.
      - `width` integer, required
      - `height` integer, required
  - `message` string

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `402`: Payment Required
- `403`: Forbidden
- `404`: Not Found
- `413`: Payload Too Large
- `429`: Too Many Requests
- `500`: Internal Error
- `502`: OCR engine error (charge auto-refunded)
- `504`: OCR engine timeout (charge auto-refunded)

---

[API](https://skmtc.dev/space-ocr/apis/space-ocr-public-api.md) · [All operations](https://skmtc.dev/space-ocr/apis/space-ocr-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/space-ocr/apis/space-ocr-public-api/revisions/d7831cf1168e?raw)
