---
title: "Plain text extraction (reading-order text + optional block boxes)"
method: POST
path: "/ocr/text"
---

# Plain text extraction (reading-order text + optional block boxes)

`POST /ocr/text`

## Request

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

## Request body

- object: Plain text (reading-order text + optional per-block boxes) 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`.
  - `languageHints` string[]: BCP-47-ish OCR language hints, e.g. ["ja","en"]. Defaults to ja/ko/en.
  - `language_hints` string[]: Deprecated alias of `languageHints`.
  - `useLlm` boolean: Default true — the model reads the image and puts the blocks in true reading order, rejoining wrapped lines. Set false for a raw OCR-order transcription.
  - `use_llm` boolean: Deprecated alias of `useLlm`.
  - `includeBlocks` boolean: When true the response also carries `blocks` — per-block text with bbox/vertices and a text_verified flag.
  - `include_blocks` boolean: Deprecated alias of `includeBlocks`.
  - `prompt` string: Custom transcription prompt prefix (optional).

## Response `200`

OK

- OcrTextResponse
  - `status` string, required
  - `data` object, required
    - `values` object, required
      - `text` string, required: The full text — blocks joined in reading order.
      - `blocks` object[]: Content-only blocks (when includeBlocks: true) — coordinates and verification flags live in cells.
        - `text` string, required
    - `cells` object: Path-keyed map (blocks[7]). Present when includeBlocks: true.
    - `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
    - `source` string: "llm" (reading-order pass) | "vision" (automatic fallback when the LLM pass fails).
  - `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)
