{
  "openapi": "3.1.0",
  "info": {
    "title": "LexSelect External API",
    "description": "External API for the LexSelect platform. All endpoints are mounted under `/api`.\n\n**[Examples & Quick Start (GitHub)](https://github.com/LexSelect/lexselect-api-demo)** — Go CLI, Python notebook, and TypeScript examples.\n\n## Authentication\n\nAll documented endpoints require a Bearer API key in the `Authorization` header:\n\n```\nAuthorization: Bearer lxs_your_api_key\n```\n\nAPI keys require `ProcessingAPI` scope to access document and processing endpoints.\n\n## Client Environment\n\nThis is a server-to-server API **by design**. API keys must not be embedded\nin browser applications, and cross-origin browser requests are not supported.\nThe API does not emit CORS preflight response headers, and a preflight\n`OPTIONS` request is answered with `401` — this is intentional, not a bug.\nBrowser-based integrations are out of scope for this release.\n\n## Versioning\n\nPass the `X-API-Version` header to pin to a specific API version. Omitting the header\ndefaults to the latest version (`2026-09-12`). Unsupported versions return `400`.\n\nVersion `2026-09-12` and later treat a transcript's printed **line numbers as\nfurniture**: the gutter numerals leave `text`, `blocks`, `paragraphs`, the\nrenders and the default-grain `tree` (`value.text`), and are stated once, on\neach `transcript_line` node's `value.transcript_line_number`; `tokens=true`\nis unchanged. `/parse` and each of its pages carry `transcript.line_numbered`.\nTranscript nodes carry `meta.transcript` (`line_number_source`,\n`paragraph_break`, `turn`). Class 13 is `speaker_turn` — a named speaker's or\nexaminer's turn, with `value.speaker` / `value.turn_number` / `value.role` —\nand a turn continued from the previous page carries\n`meta.continuation.continues_prev`. Earlier pinned versions are byte-identical\nto before.\n\nVersion `2026-09-03` and later report each page's OCR-detected **language**\n(`{code, confidence}` or `null`) and reading **direction** (`rtl` / `ltr`) on\n`/parse` pages and `/processing/latest/pages` rows, the document-level majority\n`language` + `direction` on `/parse` and `/processing/latest`, and a majority\n`language` per sub-document. HTML renders carry `lang` / `dir` attributes.\n\nVersion `2026-06-07` and later report processing progress as **facts**:\nper-scope `progress_details` plus `pages_done` / `pages_total` / `total_known` /\n`stage` on `GET /documents/{id}/processing/latest` — and drops the legacy\n`processing_progress` fraction (everywhere) and the legacy progress\n`page_count` (on `/processing/latest`). The `/parse` response's `page_count` /\n`total_pages` are response-batch metadata, present on all versions, and are\nunaffected. Integrators that still need the legacy shape pin\n`X-API-Version: 2026-03-06`, the only version that carries it.\n\n## Rate Limiting\n\nAuthenticated requests are subject to per-key, per-user aggregate, and optional\nper-key concurrency limits. Authenticated responses include the active\n`X-RateLimit-*` budget headers. When a per-minute rate limit is exceeded,\na `429` is returned with a `Retry-After` header indicating seconds to wait.\nWhen an optional concurrency cap is exceeded, a `429` is returned with\ndetail text such as `Too many concurrent requests. Maximum: 10.`\n\n| Header | Description |\n|--------|-------------|\n| `X-RateLimit-Limit` | Requests allowed per minute for this key tier |\n| `X-RateLimit-Remaining` | Requests remaining in the current window |\n| `X-RateLimit-Reset` | Unix timestamp (seconds) when the window resets |\n| `Retry-After` | Seconds to wait before retrying. Present on per-minute rate-limit `429` responses; omitted for concurrency-limit `429` responses. |\n\n## Usage and Spend\n\n`GET /usage` returns a current-period snapshot — pages processed,\ndocuments stored, the remaining page balance, and spend to date with the\nper-page unit price — so cost and volume can be tracked and alerted on\nprogrammatically rather than read off the developer portal.\n`GET /usage/daily` returns the same counters as a zero-filled per-day\nseries, for forecasting and for alerting when a batch parses more pages\nthan expected.\n\nWhat an account gets depends on how it is billed:\n\n- **Personal accounts** are metered by pages and carry no price. `spend`\n  is `null`, `spend_note` says so, and `period` reports the account's own\n  pages and remaining allowance. The daily series covers the key owner's\n  own processing runs.\n- **Enterprise workspaces** report the workspace's pooled pages against\n  the plan's included pages, plus overage. `spend` — subscription,\n  overage, unit price, estimated total — is returned **only to the\n  workspace billing owner**; every other member gets `spend: null` with\n  a `spend_note` saying so, and their daily series is scoped to their\n  own runs. Workspaces on `custom` commercial terms report usage\n  normally, but their money lives on the invoice, so `spend` stays\n  `null`.\n\nBoth endpoints are read-only, require the `ProcessingAPI` scope, and are\nunaffected by `X-API-Version`.\n\n## Idempotency\n\nMutating requests (POST, PUT, PATCH, DELETE) may include an `Idempotency-Key` header.\nDuplicate requests with the same key return the cached response for 24 hours.\n\n## Errors\n\nAll error responses use [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457)\nwith `Content-Type: application/problem+json`.\n\n```json\n{\n  \"type\": \"about:blank\",\n  \"title\": \"Unauthorized\",\n  \"status\": 401,\n  \"detail\": \"Missing or invalid Authorization header.\"\n}\n```\n\nEvery response includes an `X-Request-Id` header (UUID) for tracing.\n\n## Upload Flow\n\nUpload a file with a single request to `POST /documents/upload`. Send a\n`multipart/form-data` body containing the file `name`, its `size` in bytes,\nand the `file` part. In one HTTP round-trip the server stores the bytes,\nverifies their integrity, and queues the document for processing; the\nresponse is the created document object.\n\n**Requirements:**\n- Maximum file size: 250 MB.\n- Allowed extensions: `doc`, `docx`, `eml`, `html`, `msg`, `odt`, `pdf`, `rtf`.\n- The request `Content-Type` must be `multipart/form-data`.\n- Metadata fields (`name`, `size`, `content_type`, `parent_id`,\n  `strip_file_name`) must precede the `file` part.\n- If `parent_id` is omitted, the file is placed in the user's Default Project.\n\n**File names.** A folder cannot hold two items with the same name. By\ndefault (`strip_file_name=true`) the stored document name is a generated\nUUIDv4 with the original extension — e.g. `report.pdf` is stored as\n`3f2a9c1e-…-b7d4.pdf` — so repeated uploads of the same file name never\ncollide, and the name you sent is used only to validate the extension and\ninfer `content_type`. The returned document object carries the stored\nname. Send `strip_file_name=false` to keep your file name; a name already\npresent in the target folder is then stored with a ` (n)` suffix.\n\nSee `POST /documents/upload` below for the full field reference.\n\n## Webhooks\n\nConfigure webhook endpoints via the UI to receive real-time notifications\nwhen document processing state changes. Payloads are signed with HMAC-SHA256.\n\nSubscribable event types:\n\n| Event | Description |\n|-------|-------------|\n| `document.processing.queued` | Document added to processing queue |\n| `document.processing.started` | Processing has begun |\n| `document.processing.page_completed` | A page finished processing |\n| `document.processing.completed` | Processing finished successfully |\n| `document.processing.failed` | Processing failed |\n| `*` | All events (includes future event types) |\n",
    "version": "2026-06-07",
    "contact": {
      "name": "LexSelect API Support"
    }
  },
  "servers": [
    {
      "url": "/api",
      "description": "API server (relative path, environment-agnostic)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Files",
      "description": "File storage — upload, list, retrieve, and delete files and folders."
    },
    {
      "name": "Processing",
      "description": "Processing pipeline — track status and progress of document processing."
    },
    {
      "name": "Results",
      "description": "Processing results — page metadata, extracted data, and evaluations."
    },
    {
      "name": "Usage",
      "description": "Account usage and spend — pages processed, documents stored, remaining balance, and cost to date."
    }
  ],
  "paths": {
    "/folders/{folder_id}/sub-documents": {
      "get": {
        "operationId": "listFolderSubDocuments",
        "summary": "List sub-documents across a folder",
        "description": "**API version `2026-08-05` and later.** Lists every detected sub-document across the folder's direct file children as a flat, paginated browse list — the subdocument-first navigation surface: filter by `type` (\"every invoice in this folder\") and open the parent document at the returned anchor without listing or opening files. Rows come from the engine's sub-document registry (rebuilt on every reprocess); `kind` is `subdocument` today, with `section` reserved for a future stratum.\n",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "description": "Folder or project ObjectId (direct file children are scanned).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Case-insensitive exact match on the open-vocabulary `document_type` (e.g. `Notice`, `Invoice`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Row class filter.",
            "schema": {
              "type": "string",
              "enum": [
                "subdocument",
                "section"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Rows per page, 1-200 (default 50).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Rows to skip (default 0).",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated sub-document rows in per-document reading order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "folder_id",
                    "total",
                    "limit",
                    "offset",
                    "sub_documents"
                  ],
                  "properties": {
                    "folder_id": {
                      "type": "string"
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total matching rows across the folder (pre-pagination)."
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "sub_documents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FolderSubDocumentRow"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid id, filter, or pagination parameter."
          },
          "404": {
            "description": "Folder not found (or not owned by the caller)."
          },
          "422": {
            "description": "Folder exceeds the 5000-file listing cap."
          },
          "503": {
            "description": "Sub-document registry temporarily unavailable."
          }
        }
      }
    },
    "/documents": {
      "get": {
        "operationId": "listDocuments",
        "summary": "List files",
        "description": "Lists documents and folders within a parent. Without `parent_id`, returns root-level items (projects). Use the project ID as `parent_id` to list files within it.\n",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "name": "parent_id",
            "in": "query",
            "required": false,
            "description": "Parent folder or project ObjectId. Omit for root-level listing (projects).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum items per page — an integer from 1 to 100 (default 25). Out-of-range or non-integer values are rejected with `400` (not clamped).",
            "schema": {
              "type": "integer",
              "default": 25,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor from a previous response's `next_cursor` field.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "description": "Field to sort by.",
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "size",
                "created_at",
                "modified_at"
              ],
              "default": "name"
            }
          },
          {
            "name": "sort_direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filter by item type.",
            "schema": {
              "type": "string",
              "enum": [
                "file",
                "folder",
                "project"
              ]
            }
          },
          {
            "name": "include_deleted",
            "in": "query",
            "required": false,
            "description": "Include soft-deleted items.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of documents.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Parent folder not found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/documents/upload": {
      "post": {
        "operationId": "uploadDocument",
        "summary": "Upload file",
        "description": "Upload a file in a single `multipart/form-data` request. The server handles storage upload and the processing trigger internally, and returns the created document object.\n\nMaximum file size: 250 MB. Allowed extensions: doc, docx, eml, html, msg, odt, pdf, rtf.\n\nIf `parent_id` is omitted the file is placed in the user's Default Project.\n\nBy default (`strip_file_name=true`) the document is stored under a generated UUIDv4 name that keeps the original extension; `name` is then used only for extension validation and content-type inference. Send `strip_file_name=false` to keep the original file name.\n",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "size",
                  "file"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "File name including extension. Stored verbatim only when `strip_file_name=false`; by default it is replaced with a generated UUIDv4 (extension preserved) and used only to validate the extension and infer `content_type`.\n",
                    "example": "report.pdf"
                  },
                  "size": {
                    "type": "integer",
                    "example": 0,
                    "description": "File size in bytes. Must equal the file's exact byte count, or the upload is rejected. Maximum 250 MB.\n"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file to upload (max 250 MB)."
                  },
                  "content_type": {
                    "type": "string",
                    "description": "MIME type override. Inferred from file name if omitted."
                  },
                  "parent_id": {
                    "type": "string",
                    "description": "Parent folder or project ObjectId. Defaults to the user's Default Project."
                  },
                  "strip_file_name": {
                    "type": "boolean",
                    "default": true,
                    "description": "When `true` (the default) the stored document name is a generated UUIDv4 plus the original extension (`<uuid>.pdf`), so re-uploading the same file name never collides with an existing item. When `false` the `name` field is stored as sent; a duplicate name in the target folder gets a ` (n)` suffix. Accepts `true`/`false`, `1`/`0`, `yes`/`no`; any other value is rejected with `400`.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File uploaded and processing queued.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (missing name/file or invalid parent), a non-`multipart/form-data` Content-Type, or a malformed `multipart/form-data` body (missing or invalid boundary).\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/ContentTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "Storage upload failed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/documents/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Document ObjectId.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getDocument",
        "summary": "Get file",
        "description": "Retrieve a single document by ID.",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Document details.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Document not found or not owned by caller.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      },
      "delete": {
        "operationId": "deleteDocument",
        "summary": "Delete file",
        "description": "Permanently deletes a document or folder and everything derived from it. Recursive for folders. This cannot be undone: the file, its previews, page images, extracted data and search records are removed from every store, and the same job id cannot be re-uploaded to restore it. The response returns as soon as the deletion is accepted and queued; removal across stores completes shortly afterwards. Idempotent — deleting an already-deleted item returns success.\n",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Document deleted.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentDeleteResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Document not found or not owned by caller.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/documents/{id}/parse": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Document ObjectId.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getDocumentParse",
        "summary": "Get parsed document structure",
        "description": "Returns one or more projections of the document's latest processing output for every page. The default response includes the full hierarchical `tree` and the flat `kvps` map per page. Use the `include` query parameter to request alternative projections (plain `text`, extracted `tables`, or a flat list of `blocks`) instead of — or in addition to — the raw tree.\n\n**Slices:**\n\n- `tree` — hierarchical layout tree produced by the engine's\n  tree-builder stage. From API version `2026-08-01` (the default)\n  nodes are the curated public shape (`class` names, whitelisted\n  `value`, closed `meta` object — see `ParsedTreeNode` /\n  `ParsedNodeMeta`); word-level TOKEN nodes are included only with\n  `tokens=true`. Pinned versions return the legacy verbatim engine\n  shape.\n- `kvps` — flat `{key: string, value: string}` map of extracted\n  key-value pairs. Only populated when a partner-specific extraction\n  workflow has run; otherwise `null` on every page.\n- `text` — plain text in reading order, one line per LINE node,\n  joined with `\\n`.\n- `tables` — every TABLE subtree projected to\n  `{headers: [...], rows: [[...]]}`.\n- `blocks` — flat pre-order list of content blocks (HEADER, FOOTER,\n  SECTION, LINE, TABLE, SUB_DOCUMENT), each with `{id, class, text}`.\n- `paragraphs` — reading-order logical paragraphs with section and\n  heading attribution; continuation-linked runs merged within each\n  page, with seam flags for cross-page stitching.\n- `clauses` — whole-document nested outline (headings + numbered\n  clauses), stitched across pages; returned once at the response\n  top level, not per page.\n\n\n**Filtering:**\n- `pages` limits which pages are returned (1-indexed). - `exclude_node_types` strips subtrees whose class matches before\n  any projection runs, applying across `tree`, `text`, `tables`,\n  and `blocks` — with one exception from `2026-08-01`: `token` in\n  the list is ignored for the `tree` slice (the `tokens` parameter\n  is the sole TOKEN control there; derived slices still honor it).\n- `strip_geometry=true` removes per-node `coordinates` and per-page\n  `width`/`height` from the `tree` slice (no effect on other\n  slices). On pinned versions it also removes `engine_meta` and\n  `tokens` — the legacy all-or-nothing behavior.\n- `tokens=true` (from `2026-08-01`) includes word-level TOKEN nodes\n  in the `tree` slice — per-word geometry and `meta.styling`.\n\n\nFor in-progress processings, only pages with `status=SUCCESS` are included.\n",
        "tags": [
          "Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "name": "pages",
            "in": "query",
            "required": false,
            "description": "Page subset, 1-indexed, inclusive ranges. Examples: `1-10`, `1,3,5`, `1,3-5,10`. Omitted or empty returns **every page** of the document.\n\nOut-of-range values are silently clamped to the document's actual page count. A request like `pages=1-1000` against a 6-page document returns all 6 pages, not an empty list. Use the response's `total_pages` field to see the document's real size.\n\nRanges that would expand to more than **100,000 distinct pages** are rejected with `400`. Cumulative lists are checked against the same cap.\n\nInvalid syntax (non-numeric, zero, negative, reversed range) → `400`.\n",
            "schema": {
              "type": "string",
              "example": "1-10"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of slices to include per page. Valid values: `tree`, `kvps`, `text`, `tables`, `blocks`, `paragraphs`, `clauses`. Unknown values → 400. Defaults to `tree,kvps`.\n",
            "schema": {
              "type": "string",
              "example": "text,tables"
            }
          },
          {
            "name": "exclude_node_types",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of `NodeClass` names (lowercase) whose subtrees should be stripped before projection. Common use: `header,footer` to drop boilerplate. Unknown names → 400. See `ParsedTreeNode.class_id` for the full vocabulary. From API version `2026-08-01`, `token` in this list is ignored for the `tree` slice — TOKEN inclusion is governed solely by the `tokens` parameter (tokens are the text carriers; stripping them here would produce a text-less tree). Derived slices (`text`, `tables`, `blocks`, `paragraphs`) still honor the full list.\n",
            "schema": {
              "type": "string",
              "example": "header,footer"
            }
          },
          {
            "name": "strip_geometry",
            "in": "query",
            "required": false,
            "description": "When `true`, removes spatial metadata from the response: per-node `coordinates` in the `tree` slice plus per-page `width` and `height`. From `2026-08-01` it affects ONLY geometry — the curated `meta` is always kept. On pinned versions (`2026-06-07` and earlier) it additionally removes `engine_meta` and `tokens` (the legacy all-or-nothing behavior). Has no effect on `text`, `tables`, `blocks`, or `kvps` slices. Default `false`.\n",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "tokens",
            "in": "query",
            "required": false,
            "description": "From `2026-08-01`: when `true`, word-level TOKEN nodes (one per OCR word, with geometry and `meta.styling`) are included in the `tree` slice. Default `false` — line text is carried on each LINE's `value.text` instead, and responses are typically ~5x smaller. No effect on pinned versions (tokens always present there).\n",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Parsed document structure.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid document id format.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Document not found, not owned by caller, or has no processing records.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/documents/{id}/render": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Document ObjectId.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getDocumentRender",
        "summary": "Render document to Markdown or HTML (experimental)",
        "description": "**EXPERIMENTAL.** Renders the document's parsed tree to a single Markdown or HTML string and returns it as the raw response body (not JSON). Use this to quickly preview what the parser produced, paste into a Markdown viewer, or open in a browser.\n\nRendering rules (best effort, driven entirely by the parsed tree — no LLM):\n\n- `TITLE`/`HEADING` → `# `/`## ` (Markdown) or `<h1>`/`<h2>` (HTML). - `PARAGRAPH`/`ABSTRACT` → one block (child lines grouped, not one\n  per line); `LIST_ITEM` runs → a single `<ul>`.\n- `SECTION`/`SUB_DOCUMENT` → `<section>`; a run of adjacent `COLUMN`s →\n  a `lexselect-band` of columns (`HORIZONTAL_BAND` is a transparent\n  pass-through). The default HTML fragment carries the structure + class\n  names plus a per-column inline `flex` sizing hint; the band is laid out\n  **visually side-by-side only with `style=full`** (or your own CSS for\n  `lexselect-band`/`lexselect-column`). DOM order is always reading order.\n- `TRANSCRIPT_LINE`/`QUESTION`/`ANSWER` → a line-numbered transcript\n  grid (Q/A rows tagged).\n- Right-to-left pages (Arabic, Hebrew, …), **API version `2026-09-03`\n  and later**: with `style=full` the\n  `<html>` carries the document's majority `lang` + `dir`, and every\n  `<section class=\"lexselect-page\">` its own `lang`/`dir`; in the\n  default fragment mode each right-to-left page is wrapped in\n  `<div lang dir=\"rtl\">`. Left-to-right output is unchanged.\n- `IMAGE`/`CHART` → a `<figure>` placeholder; a standalone `CAPTION` →\n  `<p class=\"lexselect-caption\">` (a bare `<figcaption>` is invalid\n  outside `<figure>`).\n- `TABLE` → CommonMark table or `<table>/<thead>/<tbody>`. - `HEADER`/`FOOTER` → emitted by default. Use\n  `exclude_node_types=header,footer` to strip them.\n- bare `LINE` → one line of text (Markdown) or `<p>` (HTML).\n\nMulti-page responses join pages with `\\n\\n---\\n\\n` (Markdown) or `<hr class=\"lexselect-page-break\"/>` (HTML fragment). With `style=full` (HTML), each page becomes a `<section class=\"lexselect-page\">` inside a styled standalone document instead. Response size is not bounded — use `pages=` to cap large documents.\n\n**Stability:** output format and rules may change without notice. Not recommended for programmatic consumption — use `/parse` for stable structured output.\n\n**Security note — Markdown output:** CommonMark-style Markdown explicitly allows inline HTML, so hostile text content in the source document (e.g. `<script>...</script>` in a scanned PDF) passes through the Markdown response as-is. Consumers that render our Markdown into HTML MUST use a sanitising renderer (for example `marked` with default sanitisation, or `rehype-sanitize` in the unified pipeline). The **HTML output** is already escaped by this service — only raw text content is ever emitted and every `<`, `>`, `\"`, `'`, `&` is entity-encoded before it reaches the response body.\n",
        "tags": [
          "Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "name": "format",
            "in": "query",
            "required": true,
            "description": "Output format.",
            "schema": {
              "type": "string",
              "enum": [
                "markdown",
                "html"
              ],
              "example": "markdown"
            }
          },
          {
            "name": "pages",
            "in": "query",
            "required": false,
            "description": "Same syntax as `/parse`: 1-indexed inclusive ranges, e.g. `1-10`, `1,3,5`. Empty returns all pages.\n",
            "schema": {
              "type": "string",
              "example": "1-5"
            }
          },
          {
            "name": "exclude_node_types",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of `NodeClass` names (lowercase) whose subtrees should be stripped before rendering. Common use: `header,footer` to drop boilerplate.\n",
            "schema": {
              "type": "string",
              "example": "header,footer"
            }
          },
          {
            "name": "style",
            "in": "query",
            "required": false,
            "description": "**HTML only.** `full` (alias `document`) wraps the rendered pages in a self-contained, styled standalone HTML document — framed \"paper\" pages, multi-column layout laid out side-by-side, and a line-numbered transcript grid — suitable for opening directly in a browser. Omitted/empty returns the bare semantic body fragment (default; pages joined by `<hr class=\"lexselect-page-break\"/>`). Ignored for `format=markdown`.\n",
            "schema": {
              "type": "string",
              "enum": [
                "full",
                "document"
              ],
              "example": "full"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rendered document. Content-Type is `text/markdown; charset=utf-8` or `text/html; charset=utf-8` depending on format.\n",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "example": "# Section heading\n\nBody text line one.\nBody text line two.\n\n| Name | Age |\n| --- | --- |\n| Alice | 30 |\n"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string",
                  "example": "<section><p>Hello world</p></section>\n<table><thead><tr><th>Name</th></tr></thead></table>\n"
                }
              }
            }
          },
          "400": {
            "description": "Missing or unknown format, or invalid filter param.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Document not found, not owned by caller, or has no processing records.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/documents/{id}/processing/latest": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Document ObjectId.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getProcessingLatest",
        "summary": "Get processing status",
        "description": "Returns the most recent processing record for the document, including status, progress, and preview availability. The progress shape is versioned: versions `2026-06-07` and later return per-scope facts (`progress_details[]`, `pages_done`, `pages_total`, `total_known`, `stage`); the pinned `2026-03-06` version returns the legacy `processing_progress` fraction + `page_count`. See `ProcessingLatestResponse`.\n",
        "tags": [
          "Processing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "name": "include_provenance",
            "in": "query",
            "description": "Include the pinned processing engine/workflow version facts when available; the default response stays unchanged.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest processing status.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProcessingLatestResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Document not found, not owned by caller, or has no processing records.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/documents/{id}/processing/latest/pages": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Document ObjectId.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getProcessingPages",
        "summary": "List pages",
        "description": "Returns page metadata (index, dimensions, type) for the latest processing. No extracted content is included. For in-progress processings, only completed pages are returned.\n",
        "tags": [
          "Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum pages per response (1-100, default 50).",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor from a previous response's `next_cursor` field.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page metadata list.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Document not found, not owned by caller, or has no processing records.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Get current usage and spend",
        "description": "Returns a current-period snapshot for the account behind this API key: pages processed, the remaining page balance, live documents held, and — for the account's billing owner — spend to date with the per-page unit price. Built for telemetry and budget alerts, so volume and cost can be tracked without scraping the developer portal.\n\n`spend` is `null` for personal accounts (metered by pages, no price), for workspaces on custom commercial terms (billed from the invoice), and for workspace members who are not the billing owner; `spend_note` always says which. Other members' identities and internal workflow names are never returned.\n",
        "tags": [
          "Usage"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Current-period usage and spend.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageSummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "405": {
            "description": "Only GET is supported on this endpoint.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    },
    "/usage/daily": {
      "get": {
        "operationId": "getUsageDaily",
        "summary": "Get daily usage",
        "description": "Returns pages processed and processing runs completed per UTC calendar day, for forecasting and for alerting when a batch parses more pages than expected. Every day in the requested range is present, including days with no activity, so the series can be charted or differenced without filling gaps.\n\nThe range is inclusive on both ends and defaults to the trailing 30 days ending today (UTC). Ranges longer than 366 days, malformed dates, and a `start` after `end` are rejected with `400`.\n\nScope follows the account: an Enterprise workspace's billing owner sees the whole workspace, any other member sees only their own runs, and a personal account sees its own.\n\nDeleting a document retains processing/user identifiers, usage counts, creation/completion timestamps, and API-key display metadata. Document content and secret API key values are not retained in usage history. Usage from documents purged before retained usage history was enabled cannot be reconstructed and may be absent from historical days.\n\nDays are keyed by when a processing run COMPLETED, over any range you ask for — so this series is not a breakdown of `GET /usage`'s `period.pages_processed`, which counts only the current billing period and, on a metered personal account, only pages after the account's meter start. Expect the two to agree in the ordinary case and to differ across a period boundary or a usage reset.\n",
        "tags": [
          "Usage"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XApiVersion"
          },
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "First day of the range, `YYYY-MM-DD` (inclusive). Defaults to 29 days before `end`.",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-08-24"
            }
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "Last day of the range, `YYYY-MM-DD` (inclusive). Defaults to today (UTC).",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-09-22"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Zero-filled per-day usage series.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageDaily"
                }
              }
            }
          },
          "400": {
            "description": "Malformed date, inverted range, or a range longer than 366 days.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "405": {
            "description": "Only GET is supported on this endpoint.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API-Key",
        "description": "Bearer API key issued by LexSelect. Keys are prefixed `lxs_`. Pass as `Authorization: Bearer lxs_your_key`. Requires `ProcessingAPI` scope for document and processing endpoints.\n"
      }
    },
    "schemas": {
      "ProblemDetails": {
        "type": "object",
        "description": "RFC 9457 Problem Details error response.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "URI reference identifying the problem type.",
            "example": "about:blank"
          },
          "title": {
            "type": "string",
            "description": "Short, human-readable summary of the problem type.",
            "example": "Unauthorized"
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code.",
            "example": 401
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation specific to this occurrence.",
            "example": "Missing or invalid Authorization header."
          }
        }
      },
      "Document": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Document ObjectId.",
            "example": "665f1a2b3c4d5e6f7a8b9c0d"
          },
          "name": {
            "type": "string",
            "example": "report.pdf"
          },
          "type": {
            "type": "string",
            "enum": [
              "file",
              "folder",
              "project"
            ],
            "example": "file"
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "example": "665f1a2b3c4d5e6f7a8b9c0d"
          },
          "file_extension": {
            "type": [
              "string",
              "null"
            ],
            "example": "pdf"
          },
          "size": {
            "type": [
              "integer",
              "null"
            ],
            "example": 204800
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "modified_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted": {
            "type": "boolean",
            "example": false
          },
          "processing_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current processing status. Present on files in list responses when enrichment is available.",
            "example": "SUCCESS"
          },
          "processing_progress": {
            "type": [
              "number",
              "null"
            ],
            "description": "Processing progress (0.0 to 1.0). **Pinned version `2026-03-06` only** — omitted on `2026-06-07` and later, where progress is served by `GET /documents/{id}/processing/latest` (the document object is metadata, not a progress surface). Present on files when enrichment is available.\n",
            "example": 1
          },
          "is_preview_ready": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether a preview is available. Present on files in list responses when enrichment is available."
          },
          "document_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detected document type (e.g., \"Transcript\"). Present on files in list responses when enrichment is available.",
            "example": "Transcript"
          }
        }
      },
      "DocumentListResponse": {
        "type": "object",
        "required": [
          "items",
          "total"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Document"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor for the next page. Null if no more results."
          },
          "total": {
            "type": "integer",
            "description": "Number of items in this page.",
            "example": 25
          }
        }
      },
      "ProgressDetail": {
        "type": "object",
        "description": "One per-scope progress fact (latest API version). `done` of `total` units complete for this scope. While `total_known` is `false`, the total may still grow as fan-out is discovered — render counts, never derive a percentage from an unresolved total.\n",
        "required": [
          "scope",
          "kind",
          "done",
          "total",
          "total_known"
        ],
        "properties": {
          "scope": {
            "type": "string",
            "description": "Scope identifier (currently `page`).",
            "example": "page"
          },
          "kind": {
            "type": "string",
            "description": "Unit of work counted (currently `page`).",
            "example": "page"
          },
          "done": {
            "type": "integer",
            "description": "Completed units in this scope.",
            "example": 7
          },
          "total": {
            "type": "integer",
            "description": "Discovered units in this scope so far.",
            "example": 12
          },
          "total_known": {
            "type": "boolean",
            "description": "Whether `total` is final.",
            "example": true
          }
        }
      },
      "ProcessingLatestResponse": {
        "type": "object",
        "description": "The response shape is selected by `X-API-Version`. **Latest (`2026-06-07`, default):** progress facts — `progress_details[]` + `pages_done` / `pages_total` / `total_known` / `stage`; the legacy `processing_progress` and `page_count` are NOT returned. **Pinned `2026-03-06`:** the legacy pair `processing_progress` + `page_count`; none of the facts fields.\n",
        "properties": {
          "document_id": {
            "type": "string",
            "example": "665f1a2b3c4d5e6f7a8b9c0d"
          },
          "document_name": {
            "type": "string",
            "example": "report.pdf"
          },
          "processing_id": {
            "type": "string",
            "example": "665f1a2b3c4d5e6f7a8b9c0e"
          },
          "status": {
            "type": "string",
            "enum": [
              "in_progress",
              "completed",
              "failed"
            ],
            "example": "completed"
          },
          "language": {
            "description": "**API version `2026-09-03` and later.** The document's majority page language (`{code}`), or `null` when no page reported one. On `/processing/latest` it is `null` until the processing has completed.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/LanguageTag"
              },
              {
                "type": "null"
              }
            ]
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "rtl",
              "ltr",
              null
            ],
            "description": "**API version `2026-09-03` and later.** The reading direction of the majority-language pages; `null` when no page reported one (processed before the field existed).\n",
            "example": "rtl"
          },
          "progress_details": {
            "type": "array",
            "description": "Per-scope progress facts. **Version `2026-06-07` and later only.**\n",
            "items": {
              "$ref": "#/components/schemas/ProgressDetail"
            }
          },
          "pages_done": {
            "type": "integer",
            "description": "Successfully processed pages. **Version `2026-06-07` and later only.**\n",
            "example": 7
          },
          "pages_total": {
            "type": "integer",
            "description": "Pages discovered so far (in progress + done). **Version `2026-06-07` and later only.**\n",
            "example": 12
          },
          "total_known": {
            "type": "boolean",
            "description": "Whether `pages_total` is final. While `false`, show counts — never a percentage. **Version `2026-06-07` and later only.**\n",
            "example": true
          },
          "stage": {
            "type": "string",
            "description": "Coarse work stage label (e.g. `preparing`, `page-processing`, `done`). Label only — never a substitute for `status`. **Version `2026-06-07` and later only.**\n",
            "example": "done"
          },
          "processing_progress": {
            "type": "number",
            "format": "float",
            "description": "Legacy progress fraction (0.0 to 1.0). **Pinned version `2026-03-06` only.**\n",
            "example": 1
          },
          "engine_version": {
            "type": "integer",
            "example": 3
          },
          "processing_provenance": {
            "type": "object",
            "description": "Present only when include_provenance=true is requested. Version facts from this owned processing record, not the serving API or test runner. Missing facts are null; internal source revision identifiers are not exposed.\n",
            "properties": {
              "engine_version": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "engine_commit": {
                "type": [
                  "string",
                  "null"
                ],
                "deprecated": true,
                "description": "Always null. Retained for compatibility; internal source revisions are not exposed."
              },
              "workflow": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The resolved workflow version pinned by this processing.",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "version": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              }
            }
          },
          "page_count": {
            "type": "integer",
            "description": "Legacy count of all page rows. **Pinned version `2026-03-06` only.** Unrelated to the `/parse` response's `page_count` (response-batch metadata).\n",
            "example": 12
          },
          "preview_available": {
            "type": "boolean",
            "example": true
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "error_message": {
            "type": "string",
            "description": "Present only for failed processings."
          }
        }
      },
      "LanguageTag": {
        "type": "object",
        "description": "**API version `2026-09-03` and later.** An OCR-detected language: a BCP-47 language code as the OCR provider reports it (`ar`, `en`, `iw` for Hebrew, `zh`, …) and the provider's confidence when it gives one. Page-level: the primary language of the page. Document- and sub-document-level: the majority code across their pages (no confidence).\n",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "ar"
          },
          "confidence": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 1,
            "example": 0.97
          }
        }
      },
      "PageMetadata": {
        "type": "object",
        "properties": {
          "page_index": {
            "type": "integer",
            "example": 0
          },
          "width": {
            "type": "number",
            "format": "float",
            "example": 612
          },
          "height": {
            "type": "number",
            "format": "float",
            "example": 792
          },
          "page_type": {
            "type": [
              "string",
              "null"
            ],
            "example": "text"
          },
          "language": {
            "description": "**API version `2026-09-03` and later.** The page's primary OCR-detected language; `null` when the OCR reported none (or the page was processed before this field existed).\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/LanguageTag"
              },
              {
                "type": "null"
              }
            ]
          },
          "direction": {
            "description": "**API version `2026-09-03` and later.** The direction the page's rows read in — the same gate the parser used to order words and columns: `rtl` for right-to-left scripts (Arabic, Hebrew, Persian, Urdu, …), `ltr` otherwise; `null` for rows written before the field.\n",
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "rtl",
              "ltr",
              null
            ],
            "example": "rtl"
          }
        }
      },
      "PageListResponse": {
        "type": "object",
        "required": [
          "pages",
          "total"
        ],
        "properties": {
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PageMetadata"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor for the next page. Null if no more results."
          },
          "total": {
            "type": "integer",
            "description": "Total number of pages for this processing.",
            "example": 12
          }
        }
      },
      "DocumentDeleteResponse": {
        "type": "object",
        "required": [
          "id",
          "deleted"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "665f1a2b3c4d5e6f7a8b9c0d"
          },
          "deleted": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ParseResponse": {
        "type": "object",
        "required": [
          "document_id",
          "processing_id",
          "status",
          "document_type",
          "sub_documents",
          "total_pages",
          "page_count",
          "pages"
        ],
        "properties": {
          "document_id": {
            "type": "string",
            "example": "665f1a2b3c4d5e6f7a8b9c0d"
          },
          "processing_id": {
            "type": [
              "string",
              "null"
            ],
            "example": "665f1a2b3c4d5e6f7a8b9c0e"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "in_progress",
              "failed",
              "unknown"
            ],
            "description": "Processing status. For `in_progress` responses, only pages that have already reached `SUCCESS` are included in `pages`.\n"
          },
          "document_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "High-level document classification produced by the engine's document-type detection stage — a free-form, human-readable, broad legal-domain category (e.g. `Deposition Transcript`, `Affidavit`, `Motion`, `Order`, `Petition`). It is NOT a closed enumeration and carries no party names, case numbers, or content qualifiers: the type is derived from the detected sub-documents and named after the lead/primary instrument — supporting exhibits and attachments do NOT change the label (a bundle led by a motion reads `Motion`, not `Motion with exhibits`). `Other` when indeterminate. Treat the value as an opaque string. `null` when the classifier has not run (older processings, or pipelines that skipped the detection stage).\n",
            "examples": [
              "Deposition Transcript",
              "Affidavit",
              "Motion",
              "Other",
              null
            ]
          },
          "language": {
            "description": "**API version `2026-09-03` and later.** The document's majority page language (`{code}`), or `null` when no page reported one. On `/processing/latest` it is `null` until the processing has completed.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/LanguageTag"
              },
              {
                "type": "null"
              }
            ]
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "rtl",
              "ltr",
              null
            ],
            "description": "**API version `2026-09-03` and later.** The reading direction of the majority-language pages; `null` when no page reported one (processed before the field existed).\n",
            "example": "rtl"
          },
          "transcript": {
            "$ref": "#/components/schemas/TranscriptFlag",
            "description": "**API version `2026-09-12` and later.** `line_numbered` is true when at least one page of the processing carries a printed transcript line-number gutter the engine read.\n"
          },
          "sub_documents": {
            "type": "array",
            "description": "Detected sub-document outline. Each entry represents a contiguous page range within the parent document that the engine identified as a semantically distinct unit (an exhibit, an attached affidavit, a motion within a brief, etc.). Nested sub-documents are supported via `children`. Empty list when no sub-documents were detected.\n",
            "items": {
              "$ref": "#/components/schemas/SubDocument"
            }
          },
          "total_pages": {
            "type": "integer",
            "description": "Total number of successfully processed pages in the document, independent of any `pages=` filter on the request. Partners can use this to detect when their requested range is smaller or larger than the doc.\n",
            "example": 6
          },
          "page_count": {
            "type": "integer",
            "description": "Number of pages **returned in this response** after applying the `pages=` filter (if any), clamping against the document's real size, and the in-progress success filter. Always less than or equal to `total_pages`. Response-batch metadata on all API versions — NOT the legacy `page_count` progress field that the pinned-version `/processing/latest` response carries.\n",
            "example": 5
          },
          "truncated": {
            "type": "boolean",
            "description": "Always `false`. An omitted `pages=` returns every page — the former 50-page default cap is retired. Retained for backward compatibility with clients written against the capped behavior.\n",
            "example": false
          },
          "default_page_limit": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Always `null` — no default page cap is applied. Retained for backward compatibility with clients written against the former 50-page cap.\n",
            "example": null
          },
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParsedPage"
            }
          }
        }
      },
      "SubDocument": {
        "type": "object",
        "description": "A detected sub-document — a contiguous slice of the parent document that the engine classified as a standalone unit. From API version `2026-08-01` entries carry exactly `{id, label, page_from, page_to, document_type, children}`; the internal `starter_category` hint appears only on requests pinning `2026-06-07` or earlier. From `2026-08-05` entries also carry `start_anchor`/`end_anchor` — sub-page boundary positions for documents that share a page (gazette-style layouts); adjacent sub-documents may then overlap on exactly the shared page.\n",
        "required": [
          "id",
          "label",
          "page_from",
          "page_to",
          "children"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier for this sub-document.",
            "example": "2d07ae8a-cd0b-43ba-ae68-7cb21cecc2b0"
          },
          "label": {
            "type": "string",
            "description": "Human-readable category label. Matches the `starter_category` field for single-start sub-documents; derived from the dominant category for aggregates.\n",
            "example": "Affidavit First Page"
          },
          "page_from": {
            "type": "integer",
            "description": "Zero-based index of the first page in this sub-document (inclusive). Matches `ParsedPage.page_index` in the same response.\n",
            "example": 0
          },
          "page_to": {
            "type": "integer",
            "description": "Zero-based index of the last page in this sub-document (inclusive).\n",
            "example": 2
          },
          "document_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Open-vocabulary document type for this sub-document, derived from the LLM's per-page classification (e.g. `Corporate Resolution`). `null` when no type was resolved.\n",
            "example": "Corporate Resolution"
          },
          "language": {
            "description": "**API version `2026-09-03` and later (`/parse` only).** The majority page language over this sub-document's page span (`{code}`), or `null`; nested `children` carry their own.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/LanguageTag"
              },
              {
                "type": "null"
              }
            ]
          },
          "children": {
            "type": "array",
            "description": "Nested sub-documents contained within this one.",
            "items": {
              "$ref": "#/components/schemas/SubDocument"
            }
          },
          "start_anchor": {
            "$ref": "#/components/schemas/SubDocumentAnchor"
          },
          "end_anchor": {
            "$ref": "#/components/schemas/SubDocumentAnchor"
          },
          "starter_category": {
            "type": "string",
            "description": "**Pinned versions (`2026-06-07` and earlier) only.** The page category that triggered the sub-document boundary. Internal hint — prefer `label` for partner display.\n",
            "example": "Affidavit First Page"
          }
        }
      },
      "FolderSubDocumentRow": {
        "type": "object",
        "description": "One sub-document row from the folder registry — addressable enough to open the parent document at the anchor.\n",
        "required": [
          "id",
          "document_id",
          "kind",
          "label",
          "page_from",
          "page_to"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable sub-document identifier (regenerates on reprocess)."
          },
          "document_id": {
            "type": "string",
            "description": "Parent document — pass to `/documents/{id}/parse`."
          },
          "kind": {
            "type": "string",
            "enum": [
              "subdocument",
              "section"
            ]
          },
          "label": {
            "type": "string"
          },
          "document_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Open-vocabulary type (e.g. `Notice`)."
          },
          "page_from": {
            "type": "integer"
          },
          "page_to": {
            "type": "integer"
          },
          "start_anchor": {
            "$ref": "#/components/schemas/SubDocumentAnchor"
          },
          "end_anchor": {
            "$ref": "#/components/schemas/SubDocumentAnchor"
          }
        }
      },
      "SubDocumentAnchor": {
        "type": [
          "object",
          "null"
        ],
        "description": "**API version `2026-08-05` and later.** Sub-page boundary position: where on the shared page this sub-document starts (`start_anchor`) or ends (`end_anchor`). `null` means the boundary is page-granular on that side (top of `page_from` / bottom of `page_to`).\n",
        "properties": {
          "page_index": {
            "type": "integer",
            "description": "Zero-based page carrying the boundary block.",
            "example": 143
          },
          "bbox": {
            "type": [
              "array",
              "null"
            ],
            "description": "`[x0, y0, x1, y1]` of the boundary block in page coordinates (same space as `ParsedTreeNode.coordinates`); `null` when the detector could not ground the boundary to a block.\n",
            "items": {
              "type": "number"
            },
            "minItems": 4,
            "maxItems": 4,
            "example": [
              63,
              88.5,
              240.4,
              130.2
            ]
          }
        }
      },
      "TranscriptFlag": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "line_numbered"
        ],
        "description": "**API version `2026-09-12` and later.** Whether a printed transcript line-number gutter was read.\n",
        "properties": {
          "line_numbered": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ParsedPage": {
        "type": "object",
        "description": "A single page of parsed output. Which fields are present depends on the `include` query param. Fields for slices that were not requested are omitted entirely. `width` and `height` are omitted when `strip_geometry=true`.\n",
        "required": [
          "page_index"
        ],
        "properties": {
          "page_index": {
            "type": "integer",
            "description": "Zero-based page index within the processing.",
            "example": 0
          },
          "width": {
            "type": "number",
            "format": "float",
            "description": "Page width in points. Omitted when `strip_geometry=true`.",
            "example": 612
          },
          "height": {
            "type": "number",
            "format": "float",
            "description": "Page height in points. Omitted when `strip_geometry=true`.",
            "example": 792
          },
          "language": {
            "description": "**API version `2026-09-03` and later.** The page's primary OCR-detected language, or `null`.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/LanguageTag"
              },
              {
                "type": "null"
              }
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "rtl",
              "ltr"
            ],
            "description": "**API version `2026-09-03` and later.** The direction the page's text reads in; words, columns and table cells in `tree`, `text` and `blocks` are already in that reading order. Render right-to-left pages with `dir=\"rtl\"`.\n",
            "example": "rtl"
          },
          "transcript": {
            "$ref": "#/components/schemas/TranscriptFlag",
            "description": "**API version `2026-09-12` and later.** `line_numbered` is true when this page carries a printed transcript line-number gutter the engine read (its `transcript_line` nodes then state each number once, on `value.transcript_line_number`).\n"
          },
          "tree": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ParsedTree"
              },
              {
                "type": "null"
              }
            ],
            "description": "Hierarchical layout tree. Present only when `include` contains `tree` (default). `null` if the tree-builder task has not run for this page. Respects `exclude_node_types` and `strip_geometry`.\n"
          },
          "kvps": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "example": {
                  "Plaintiff Name": "Jane Doe",
                  "Claim Number": "CLM-12345"
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Flat `{key: string, value: string}` map of key-value pairs. Present only when `include` contains `kvps` (default). `null` when the extraction task has not run for this document — which is the common case unless a partner-specific workflow is enabled.\n"
          },
          "text": {
            "type": "string",
            "description": "Plain text in reading order, one line per LINE node joined with `\\n`. Present only when `include` contains `text`. Respects `exclude_node_types`.\n",
            "example": "Hello world\nSecond line of text"
          },
          "tables": {
            "type": "array",
            "description": "All tables on the page, each projected to `{headers, rows}`. Present only when `include` contains `tables`.\n",
            "items": {
              "$ref": "#/components/schemas/ParsedTable"
            }
          },
          "blocks": {
            "type": "array",
            "description": "Flat pre-order list of content blocks for the page. Present only when `include` contains `blocks`.\n",
            "items": {
              "$ref": "#/components/schemas/ParsedBlock"
            }
          },
          "paragraphs": {
            "type": "array",
            "description": "Reading-order logical paragraphs with section attribution (ADR 022). Present only when `include` contains `paragraphs`. Each entry merges a paragraph split across a column break into one block and carries the governing section number forward. Projected **per page**: a paragraph split across a *page* break stays two entries joined by the seam flags (`continues_next` on the last entry of page N ∧ `continues_prev` on the first of page N+1) — concatenate their `text` to reconstruct the full paragraph. The `section` number is carried across the page seam so both entries cite the same section.\n",
            "items": {
              "$ref": "#/components/schemas/ParsedParagraph"
            }
          }
        }
      },
      "ParsedTable": {
        "type": "object",
        "description": "A table extracted from the tree.",
        "required": [
          "headers",
          "rows"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable node id of the TABLE subtree this came from.",
            "example": "tbl_0"
          },
          "headers": {
            "type": "array",
            "description": "Column headers, in order. Empty if the table has no header row.",
            "items": {
              "type": "string"
            },
            "example": [
              "Name",
              "Age"
            ]
          },
          "rows": {
            "type": "array",
            "description": "Row values, each an ordered array of cell text.",
            "items": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              [
                "Alice",
                "30"
              ],
              [
                "Bob",
                "42"
              ]
            ]
          }
        }
      },
      "ParsedBlock": {
        "type": "object",
        "description": "A flat content block — one entry in the pre-order (reading-order) traversal of the tree for block-level classes. Only these six classes are emitted as blocks; structural containers (root, main_page, subpage, column, horizontal_band, content) are walked through but never emitted. Nested sections each emit their own block. See `ParsedTreeNode.class_id` for the full NodeClass vocabulary.\n",
        "required": [
          "id",
          "class",
          "text"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable node id from the tree.",
            "example": "section_0"
          },
          "class": {
            "type": "string",
            "description": "Lowercase `NodeClass` name of the emitted block.",
            "enum": [
              "header",
              "footer",
              "section",
              "line",
              "table",
              "sub_document"
            ],
            "x-enum-descriptions": [
              "Page header region (running chrome)",
              "Page footer region (running chrome)",
              "Rule-derived logical section (e.g. numbered paragraph; nested sections emit their own blocks)",
              "One OCR text line outside any richer block",
              "Table root; use the tables slice for a structured headers/rows projection",
              "Detected sub-document boundary (e.g. an exhibit in a combined PDF)"
            ],
            "example": "section"
          },
          "text": {
            "type": "string",
            "description": "Concatenated text content of the block.",
            "example": "Hello world"
          }
        }
      },
      "ParsedParagraph": {
        "type": "object",
        "description": "A reading-order logical paragraph with section attribution (ADR 022), as emitted in the `paragraphs` slice. Projected per page — see `ParsedPage.paragraphs` for the cross-page stitching contract.\n",
        "required": [
          "ids",
          "text"
        ],
        "properties": {
          "ids": {
            "type": "array",
            "description": "Stable node ids of the tree blocks merged into this logical paragraph, in reading order (a single-block paragraph has one id).\n",
            "items": {
              "type": "string"
            },
            "example": [
              "paragraph_3",
              "paragraph_4"
            ]
          },
          "class": {
            "type": "string",
            "description": "Lowercase `NodeClass` name of the paragraph block.",
            "example": "paragraph"
          },
          "section": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Governing section/paragraph number (e.g. `\"31\"`, `\"1.2\"`) carried forward from the nearest preceding marker, or `null` when no section governs this paragraph. Always a string when present.\n",
            "example": "31"
          },
          "text": {
            "type": "string",
            "description": "Concatenated text of the merged paragraph.",
            "example": "The disclosing party shall provide the information within ten days."
          },
          "continues_prev": {
            "type": "boolean",
            "description": "True when this paragraph continues one whose start is on the previous page — stitch with that page's trailing entry.\n",
            "example": false
          },
          "continues_next": {
            "type": "boolean",
            "description": "True when this paragraph continues onto the next page — stitch with that page's leading entry.\n",
            "example": false
          }
        }
      },
      "ParsedTree": {
        "type": "object",
        "description": "The hierarchical layout tree of one page. From API version `2026-08-01` `root` contains `ParsedTreeNode` nodes (the closed public shape). Requests pinning `2026-06-07` or earlier receive `ParsedTreeNodeLegacy` nodes instead — the shape is selected by the `X-API-Version` request header, which OpenAPI cannot express structurally.\n",
        "required": [
          "root"
        ],
        "properties": {
          "root": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParsedTreeNode"
            }
          }
        }
      },
      "ParsedTreeNode": {
        "type": "object",
        "additionalProperties": false,
        "description": "A node in the hierarchical layout tree — API version `2026-08-01` and later. This schema is CLOSED: nodes are exactly `{id, class, class_id, coordinates, value, meta, content}` — where `meta` is the closed, fully documented `ParsedNodeMeta` object, `value` is the closed whitelist below, and `value`/`meta` are omitted when empty. TOKEN nodes (one per OCR word) appear only when `tokens=true`; a node whose text lived on excluded tokens carries it in `value.text` instead, so the tree always contains the full text. Requests pinning `2026-06-07` or earlier receive `ParsedTreeNodeLegacy` nodes instead.\n",
        "properties": {
          "id": {
            "type": "string",
            "example": "line_0"
          },
          "class": {
            "type": "string",
            "description": "Lowercase `NodeClass` name (`paragraph`, `line`, `table`, …) — the primary discriminator. Same vocabulary as the `class_id` table below, the `exclude_node_types` parameter, and the `blocks` slice. Not present on `ParsedTreeNodeLegacy`.\n",
            "example": "paragraph"
          },
          "class_id": {
            "type": "integer",
            "description": "Node class identifier — the stable numeric twin of `class`.\nPartners may key on either; `class_id` values are never\nrenumbered, and new classes may be added. The lowercase\nclass name (e.g. `page_number` for 37) is the string accepted by\n`exclude_node_types` and emitted as `class` in the `blocks` slice.\nContent-block classes use the vision taxonomy — the same name\nappears in `meta.block` (`engine_meta.block` on pinned versions).\n\n| ID | Class | Category | Meaning |\n|---:|-------|----------|---------|\n| 0 | `root` | Structural | Synthetic tree root; one per page tree |\n| 1 | `main_page` | Structural | The page container directly under root |\n| 2 | `subpage` | Structural | Content region inside the detected content bounds |\n| 3 | `header` | Page chrome | Page header region (running chrome) |\n| 4 | `footer` | Page chrome | Page footer region (running chrome) |\n| 5 | `content` | Structural | Generic content container for a vision-labeled body region |\n| 6 | `column` | Structural | One vertical reading column |\n| 7 | `section` | Rule-derived | Logical section (e.g. numbered paragraph in a legal filing) — carries `value.paragraph_number`; sections nest |\n| 8 | `transcript_line` | Transcript / Q&A | One numbered transcript line |\n| 9 | `question` | Transcript / Q&A | Question span in a transcript/deposition |\n| 10 | `answer` | Transcript / Q&A | Answer span in a transcript/deposition |\n| 11 | `question_mark_token` | Transcript / Q&A | The literal `Q.`/`Q:` marker token opening a question |\n| 12 | `answer_mark_token` | Transcript / Q&A | The literal `A.`/`A:` marker token opening an answer |\n| 13 | `speaker_turn` | Transcript / Q&A | A named speaker's or examiner's turn (`value.speaker`, `value.turn_number`, `value.role`; `meta.transcript.turn`); children are transcript paragraphs. Named `conversation` on versions before `2026-09-12` |\n| 14 | `vision_object` | Content block | Fallback for unmapped vision labels (raw label only on the legacy shape's `engine_meta.vision_class`) |\n| 15 | `token` | Low-level text | One OCR token (word); may carry `meta.styling` |\n| 16 | `line` | Low-level text | One OCR text line (the `text` slice is exactly these in reading order) |\n| 17 | `sub_document` | Structural | Detected sub-document boundary (e.g. an exhibit in a combined PDF) |\n| 18 | `horizontal_band` | Structural | Full-width horizontal layout band; columns live inside bands |\n| 19 | `table` | Table | Table root (projected by the `tables` slice) |\n| 20 | `table_row` | Table | One table row; children are table cells |\n| 21 | `table_column_header` | Table | Column-header cell (feeds `headers` in the `tables` slice) |\n| 22 | `table_cell` | Table | One table body cell |\n| 23 | `title` | Content block | Document title block |\n| 24 | `heading` | Content block | Section heading |\n| 25 | `paragraph` | Content block | Body paragraph |\n| 26 | `list_item` | Content block | One item of a bulleted/numbered list |\n| 27 | `chart` | Content block | Chart/graph figure |\n| 28 | `image` | Content block | Image/figure in the body |\n| 29 | `formula` | Content block | Display (block-level) formula |\n| 30 | `inline_formula` | Content block | Formula embedded in a text line |\n| 31 | `formula_number` | Content block | Equation-number label next to a formula |\n| 32 | `caption` | Content block | Figure/table caption |\n| 33 | `aside` | Content block | Side/marginal text block |\n| 34 | `footnote` | Content block | Footnote block |\n| 35 | `reference` | Content block | Bibliography/reference entry |\n| 36 | `seal` | Content block | Stamp or seal mark |\n| 37 | `page_number` | Page chrome | Page-number block |\n| 38 | `code_block` | Content block | Monospaced code block |\n| 39 | `abstract` | Content block | Abstract block |\n| 40 | `header_image` | Page chrome | Image in the header region (e.g. letterhead logo) |\n| 41 | `footer_image` | Page chrome | Image in the footer region |\n"
          },
          "coordinates": {
            "type": "array",
            "description": "4-point polygon `[[x,y], [x,y], [x,y], [x,y]]`. Omitted from every node when `strip_geometry=true`.\n",
            "items": {
              "type": "array",
              "items": {
                "type": "number"
              }
            }
          },
          "content": {
            "type": "array",
            "description": "Child nodes (recursive), in reading order — the `content` order IS the document's reading order.",
            "items": {
              "$ref": "#/components/schemas/ParsedTreeNode"
            }
          },
          "value": {
            "type": "object",
            "additionalProperties": false,
            "description": "Class-specific values. This object is CLOSED — exactly these keys may appear (omitted when none apply). A LINE's `text` is synthesized from its OCR tokens when TOKEN nodes are excluded (the default).\n",
            "properties": {
              "text": {
                "type": "string",
                "example": "Annual Consent of Directors"
              },
              "paragraph_number": {
                "description": "Governing section/clause number string.",
                "example": "32.1(b)"
              },
              "transcript_line_number": {
                "type": "integer"
              },
              "question_number": {},
              "answer_number": {},
              "speaker": {
                "type": "string",
                "description": "**API version `2026-09-12` and later**, `speaker_turn` nodes only. The speaker as printed, without the colon (`MR. TAIT`, `THE DEPONENT`, `JUSTICE`).\n",
                "example": "MR. TAIT"
              },
              "turn_number": {
                "type": "string",
                "description": "**API version `2026-09-12` and later**, `speaker_turn` nodes only. The printed turn number when the transcript numbers its turns (`18` in `18. MR. TAIT: …`).\n",
                "example": "18"
              },
              "role": {
                "type": "string",
                "enum": [
                  "examiner",
                  "witness",
                  "bench",
                  "reporter"
                ],
                "description": "**API version `2026-09-12` and later**, `speaker_turn` nodes only. The speaker's courtroom role when the printed head implies one (`BY MR. TAIT:` → `examiner`, `THE DEPONENT` → `witness`, `JUSTICE` → `bench`); absent for plain names.\n"
              }
            }
          },
          "meta": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ParsedNodeMeta"
              }
            ],
            "description": "Curated node metadata. A closed object — every possible key is documented on `ParsedNodeMeta`; omitted when the node carries none.\n"
          }
        }
      },
      "ParsedTreeNodeLegacy": {
        "type": "object",
        "additionalProperties": true,
        "description": "A tree node as returned to requests pinning `X-API-Version: 2026-06-07` or earlier — the verbatim engine shape that predates the ADR-028 public projection. This schema is OPEN by construction: the response is a byte-identical passthrough of the persisted engine tree, whose vocabulary evolves with the engine (new `engine_meta`/`value` keys may appear for newly processed documents without a spec change). New integrations should use the default version and `ParsedTreeNode` instead. The properties below are the stable, commonly present ones; `class` names are absent — only numeric `class_id` (same table as `ParsedTreeNode`). TOKEN nodes are always present; `strip_geometry=true` drops `coordinates`, `engine_meta`, and `tokens` together on this shape.\n",
        "properties": {
          "id": {
            "type": "string",
            "example": "line_0"
          },
          "class_id": {
            "type": "integer",
            "description": "Node class identifier — see the table on `ParsedTreeNode.class_id` (same vocabulary, never renumbered).\n"
          },
          "coordinates": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "number"
              }
            }
          },
          "content": {
            "type": "array",
            "description": "Child nodes (recursive), in reading order.",
            "items": {
              "$ref": "#/components/schemas/ParsedTreeNodeLegacy"
            }
          },
          "value": {
            "type": "object",
            "additionalProperties": true,
            "description": "Free-form engine value bag, including internal keys (e.g. `ocr_line_number`) alongside `text`, `paragraph_number`, `transcript_line_number`.\n"
          },
          "engine_meta": {
            "type": "object",
            "additionalProperties": true,
            "description": "Raw engine scoring and layout metadata, free-form. Superseded by the curated `meta` object on the default version; integrators still reading `engine_meta` keys should migrate via the `ParsedNodeMeta` mapping (`block`, `section`, `clause`, `heading`, `continuation`, styling and table keys carry over; scoring/ordering internals do not).\n"
          },
          "tokens": {
            "type": "array",
            "description": "Raw internal token payload. On the default version word-level data is exposed as TOKEN child nodes via `tokens=true` instead.\n",
            "items": {}
          },
          "order_id": {
            "type": "integer",
            "description": "Internal ordering index — `content` order carries reading order on all versions.\n"
          }
        }
      },
      "ParsedNodeMeta": {
        "type": "object",
        "additionalProperties": false,
        "description": "Curated node metadata, API version `2026-08-01` and later. This schema is CLOSED: every key that can ever appear is listed below — new keys are added only through a versioned contract change, never as a pipeline side effect. Each key is present only on nodes where the engine produced the underlying fact.\n",
        "properties": {
          "block": {
            "type": "string",
            "description": "Canonical vision-taxonomy block name — same vocabulary as the content-block `class` names (`paragraph`, `heading`, `table`, …).\n",
            "example": "paragraph"
          },
          "sub_document": {
            "type": "object",
            "additionalProperties": false,
            "description": "**API version `2026-08-05` and later.** The sub-document this node belongs to — the DEEPEST owner when sub-documents nest (climb ancestry via the response's `sub_documents` array). On a shared boundary page, ownership switches exactly at the sub-document's `start_anchor` block; containers spanning the boundary keep the page-top owner.\n",
            "properties": {
              "id": {
                "type": "string",
                "description": "Matches `SubDocument.id` in the same response.",
                "example": "2d07ae8a-cd0b-43ba-ae68-7cb21cecc2b0"
              }
            }
          },
          "section": {
            "type": "object",
            "additionalProperties": false,
            "description": "Section-marker attribution (the citation anchor).",
            "properties": {
              "number": {
                "type": "string",
                "description": "Governing clause/section number, e.g. `32.1(b)`.",
                "example": "32.1(b)"
              },
              "is_marker": {
                "type": "boolean",
                "description": "This node's line IS the section-marker line."
              }
            }
          },
          "clause": {
            "type": "object",
            "additionalProperties": false,
            "description": "Clause-hierarchy metadata on marker nodes and clause containers. `parent` links to the nearest ancestor clause's `number`, letting clients rebuild the full hierarchy (or use the `clauses` slice for the stitched document outline).\n",
            "properties": {
              "number": {
                "type": "string",
                "example": "32.1(b)"
              },
              "level": {
                "type": "integer",
                "description": "1-based structural depth."
              },
              "parent": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full number of the nearest ancestor clause.",
                "example": "32.1"
              },
              "ordinal": {
                "type": "integer",
                "description": "Position within its sibling run."
              },
              "scheme": {
                "type": "string",
                "enum": [
                  "decimal",
                  "paren_num",
                  "alpha",
                  "roman",
                  "unknown"
                ]
              },
              "kind": {
                "type": "string",
                "enum": [
                  "clause",
                  "list_item"
                ]
              }
            }
          },
          "heading": {
            "type": "object",
            "additionalProperties": false,
            "description": "Heading style facts on HEADING/TITLE nodes. Facts are page-local and honest-null — absent means \"not measured\", not \"false\". Document-relative heading levels are resolved in the `clauses` slice outline.\n",
            "properties": {
              "size_ratio": {
                "type": "number",
                "description": "Median token height vs page body median."
              },
              "bold_ratio": {
                "type": "number",
                "description": "Median stroke-width ratio."
              },
              "caps": {
                "type": "number",
                "description": "Uppercase-word fraction (0-1)."
              },
              "centered": {
                "type": "boolean"
              },
              "keyword_rank": {
                "type": "integer",
                "description": "PART(1) > ARTICLE/SCHEDULE/…(2) > SECTION/…(3)."
              },
              "marker_level": {
                "type": "integer",
                "description": "Clause level when the heading carries a marker."
              }
            }
          },
          "heading_scope": {
            "type": "boolean",
            "description": "True on a SECTION container synthesized as a heading's scope (the heading plus the siblings it governs).\n"
          },
          "continuation": {
            "type": "object",
            "additionalProperties": false,
            "description": "Cross-page/column paragraph flow. Stitch `continues_next` on one node with `continues_prev` on the next page's leading node to reassemble a split paragraph (the `paragraphs` slice does the within-page merge for you).\n",
            "properties": {
              "continues_prev": {
                "type": "boolean"
              },
              "continues_next": {
                "type": "boolean"
              }
            }
          },
          "transcript": {
            "type": "object",
            "additionalProperties": false,
            "description": "**API version `2026-09-12` and later.** Transcript facts, each present only on the node kind that carries it.\n",
            "properties": {
              "line_number_source": {
                "type": "string",
                "enum": [
                  "read",
                  "interpolated"
                ],
                "description": "`transcript_line` nodes. Whether the line number was read off the page or filled in arithmetically between two read neighbours (the printed numeral was missing or misread).\n"
              },
              "paragraph_break": {
                "type": "string",
                "enum": [
                  "start",
                  "indent",
                  "blank",
                  "turn"
                ],
                "description": "Transcript `paragraph` nodes. What opened the paragraph: the start of the column, an indented first line, a blank row above it, or a speaker / question / answer head.\n"
              },
              "turn": {
                "type": "string",
                "enum": [
                  "speaker",
                  "examiner",
                  "interjection",
                  "continued"
                ],
                "description": "`speaker_turn` nodes. How the turn was formed: opened by a named speaker, by an examiner heading (`BY MR. TAIT:`), as an interjection inside a question or answer, or continued from the previous page's last turn (then `continuation.continues_prev` is set).\n"
              }
            }
          },
          "footnote_number": {
            "type": "string",
            "description": "Footnote marker text on FOOTNOTE nodes.",
            "example": "1."
          },
          "ignore_selection": {
            "type": "boolean",
            "description": "True on nodes that are not selectable body text — printed transcript line numbers, `Q.`/`A.` marker tokens, footnote markers, page-chrome tokens. Skip these when reconstructing clean body text.\n"
          },
          "page_chrome": {
            "type": "boolean",
            "description": "True on TOKEN nodes that belong to repeating page chrome — filing stamps, running captions, court-reporter footers — detected by cross-page repetition. Always accompanied by `ignore_selection: true`; the distinction lets you tell chrome apart from other non-selectable tokens (line numbers, section markers). Visible with `tokens=true`.\n"
          },
          "table": {
            "type": "object",
            "additionalProperties": false,
            "description": "Cross-page table stitching, on TABLE nodes. Every fragment of one logical table carries the same `id` (the starting fragment's), however many pages it spans, and a continuation fragment's `continuation_table_id` equals that `id` — join all fragments on `id` alone. A continuation is the first table on its page continuing the last table of the previous page.\n",
            "properties": {
              "id": {
                "type": "string",
                "example": "tbl_1"
              },
              "is_table_start": {
                "type": "boolean"
              },
              "continues_from_previous": {
                "type": "boolean"
              },
              "continuation_table_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "page_range": {
            "type": "object",
            "additionalProperties": false,
            "description": "Page span of a SUB_DOCUMENT node (zero-based, inclusive).\n",
            "properties": {
              "from": {
                "type": "integer"
              },
              "to": {
                "type": "integer"
              }
            }
          },
          "styling": {
            "type": "object",
            "additionalProperties": false,
            "description": "Visual text styling. On TOKEN nodes (visible with `tokens=true`) only detected attributes appear, each with its confidence. From API version `2026-08-10`, nodes with TOKEN children (LINEs) also carry the handwriting rollup of their words — `text_type` `handwriting` (every word handwritten) or `mixed` (some are, listed in `handwritten_tokens`) — so the default token-dropped tree still reports handwriting.\n",
            "properties": {
              "strikethrough": {
                "type": "boolean"
              },
              "underline": {
                "type": "boolean"
              },
              "text_color": {
                "type": "string",
                "description": "Hex of detected non-default ink.",
                "example": "#C0152A"
              },
              "strikethrough_confidence": {
                "type": "number"
              },
              "underline_confidence": {
                "type": "number"
              },
              "text_color_confidence": {
                "type": "number"
              },
              "text_type": {
                "type": "string",
                "enum": [
                  "handwriting",
                  "mixed"
                ],
                "description": "On a TOKEN: present when OCR classified the word as handwritten; printed tokens omit the key (absent means printed). On a node with TOKEN children (API version `2026-08-10` and later): `handwriting` when every word is handwritten, `mixed` when only some are (see `handwritten_tokens`).\n"
              },
              "handwritten_tokens": {
                "type": "array",
                "description": "**API version `2026-08-10` and later.** Only on `mixed` lines: the handwritten words. `index` is the word's position in the line's word sequence — the same order the line's `value.text` is synthesized from — so entries map back into the text unambiguously even when a word repeats.\n",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "properties": {
                    "index": {
                      "type": "integer"
                    },
                    "text": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "UsageAccount": {
        "type": "object",
        "description": "The account this API key bills against. Pricing-sensitive fields are withheld from workspace members who are not the billing owner.\n",
        "additionalProperties": false,
        "required": [
          "kind",
          "plan",
          "tier",
          "status"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "personal",
              "workspace"
            ],
            "description": "`personal` — a standalone developer account, metered from its own processing runs. `workspace` — an Enterprise workspace whose page usage is pooled across its members.\n",
            "example": "personal"
          },
          "plan": {
            "type": "string",
            "enum": [
              "trial",
              "personal",
              "enterprise"
            ],
            "description": "`trial` — a metered allowance is in force and `limits` describes it. `personal` — an unmetered personal account: no page ceiling and no price. `enterprise` — an active workspace entitlement.\n",
            "example": "trial"
          },
          "tier": {
            "type": [
              "string",
              "null"
            ],
            "description": "Enterprise price tier — `tier_1`, `tier_2`, `tier_3`, or `custom` for individually negotiated commercial terms. `null` on personal accounts and for workspace members who are not the billing owner.\n",
            "example": "tier_2"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "active",
              "past_due",
              "suspended",
              null
            ],
            "description": "Subscription status, or `null` when no subscription applies.",
            "example": "active"
          }
        }
      },
      "UsagePeriod": {
        "type": "object",
        "description": "The metering window the page counters below belong to.",
        "additionalProperties": false,
        "required": [
          "start",
          "end",
          "resets_at",
          "included_pages",
          "unlimited",
          "pages_processed",
          "remaining_pages",
          "overage_pages"
        ],
        "properties": {
          "start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Start of the current period (inclusive)."
          },
          "end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "End of the current period (exclusive)."
          },
          "resets_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When `pages_processed` next resets to zero. `null` when the allowance never resets (a lifetime personal allowance).\n"
          },
          "included_pages": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Pages included in the plan for this period, or `null` when no page cap applies (an unmetered account, or a plan billed purely per page).\n",
            "example": 100000
          },
          "unlimited": {
            "type": "boolean",
            "description": "`true` only for accounts with no page ceiling at all. A `null` `included_pages` alone is ambiguous — this flag is the explicit signal.\n",
            "example": false
          },
          "pages_processed": {
            "type": "integer",
            "description": "Successful pages processed in this period.",
            "example": 12345
          },
          "remaining_pages": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`included_pages - pages_processed`, floored at zero — the remaining balance. `null` when no cap applies or the caller is a workspace member whose usage is scoped to their own runs; the pooled balance cannot be inferred from that member-only total.\n",
            "example": 87655
          },
          "overage_pages": {
            "type": "integer",
            "description": "Pages processed beyond `included_pages` in this period.",
            "example": 0
          }
        }
      },
      "UsageLimits": {
        "type": "object",
        "description": "Per-request ceilings enforced while a trial allowance is in force. `null` on paid plans, which are metered by total pages only.\n",
        "additionalProperties": false,
        "required": [
          "total_pages",
          "document_pages",
          "file_size_bytes"
        ],
        "properties": {
          "total_pages": {
            "type": "integer",
            "description": "Total pages the trial allowance permits.",
            "example": 2500
          },
          "document_pages": {
            "type": "integer",
            "description": "Maximum pages in any single document.",
            "example": 2000
          },
          "file_size_bytes": {
            "type": "integer",
            "description": "Maximum upload size in bytes.",
            "example": 262144000
          }
        }
      },
      "UsageSpend": {
        "type": "object",
        "description": "Money for the current period, in minor units of `currency`. Present only for the billing owner of an account with published pricing. It is `null` — with the reason in `spend_note` — for personal accounts (metered by pages, no price), for `custom` commercial terms (billed from the invoice), and for workspace members who are not the billing owner.\n",
        "additionalProperties": false,
        "required": [
          "currency",
          "subscription_cents",
          "overage_rate_micros",
          "overage_cents",
          "estimated_total_cents"
        ],
        "properties": {
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code of every amount below.",
            "example": "usd"
          },
          "subscription_cents": {
            "type": "integer",
            "description": "The period's fixed subscription amount.",
            "example": 500000
          },
          "overage_rate_micros": {
            "type": "integer",
            "description": "Unit price for one page beyond the included allowance, in micros (1,000,000 micros = 1.00 of `currency`). `0` when the plan carries no metered overage.\n",
            "example": 30000
          },
          "overage_cents": {
            "type": "integer",
            "description": "Cost of `period.overage_pages` at the unit price so far.",
            "example": 0
          },
          "estimated_total_cents": {
            "type": "integer",
            "description": "`subscription_cents + overage_cents` — spend to date for this period. An estimate: the invoice is issued by the payment provider at period close.\n",
            "example": 500000
          }
        }
      },
      "UsageSummary": {
        "type": "object",
        "description": "Current-period usage and spend for the account behind this API key.\n",
        "additionalProperties": false,
        "required": [
          "as_of",
          "account",
          "period",
          "limits",
          "documents_stored",
          "spend",
          "spend_note"
        ],
        "properties": {
          "as_of": {
            "type": "string",
            "format": "date-time",
            "description": "When this snapshot was taken."
          },
          "account": {
            "$ref": "#/components/schemas/UsageAccount"
          },
          "period": {
            "$ref": "#/components/schemas/UsagePeriod"
          },
          "limits": {
            "description": "Trial ceilings, or `null` when no trial allowance is in force.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/UsageLimits"
              },
              {
                "type": "null"
              }
            ]
          },
          "documents_stored": {
            "type": "integer",
            "description": "Current live, non-deleted files created by the API key's user, across upload sources. This is a current stored-file count, not a count of processing runs or the whole workspace's files.\n",
            "example": 123
          },
          "spend": {
            "description": "Money for the current period, or `null` — see `spend_note`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/UsageSpend"
              },
              {
                "type": "null"
              }
            ]
          },
          "spend_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why `spend` is `null`, in one sentence; `null` when `spend` is present.\n",
            "example": "Personal accounts are metered by pages and carry no price."
          }
        }
      },
      "UsageDay": {
        "type": "object",
        "description": "One UTC calendar day of usage.",
        "additionalProperties": false,
        "required": [
          "date",
          "pages_processed",
          "documents_processed"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "The UTC calendar day, `YYYY-MM-DD`.",
            "example": "2026-08-24"
          },
          "pages_processed": {
            "type": "integer",
            "description": "Successful pages processed on this day.",
            "example": 120
          },
          "documents_processed": {
            "type": "integer",
            "description": "Successful processing runs completed on this day.",
            "example": 7
          }
        }
      },
      "UsageDaily": {
        "type": "object",
        "description": "A zero-filled per-day usage series: every UTC calendar day in the requested range is present, in ascending order, including days with no activity.\n",
        "additionalProperties": false,
        "required": [
          "start",
          "end",
          "days",
          "total_pages",
          "total_documents"
        ],
        "properties": {
          "start": {
            "type": "string",
            "format": "date",
            "description": "First day of the range (inclusive).",
            "example": "2026-08-24"
          },
          "end": {
            "type": "string",
            "format": "date",
            "description": "Last day of the range (inclusive).",
            "example": "2026-09-22"
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageDay"
            }
          },
          "total_pages": {
            "type": "integer",
            "description": "Sum of `pages_processed` across the range.",
            "example": 3400
          },
          "total_documents": {
            "type": "integer",
            "description": "Sum of `documents_processed` across the range.",
            "example": 210
          }
        }
      }
    },
    "headers": {
      "X-Request-Id": {
        "description": "Unique identifier for this request, assigned by the gateway. Present on all responses.",
        "schema": {
          "type": "string",
          "format": "uuid",
          "example": "550e8400-e29b-41d4-a716-446655440000"
        }
      },
      "X-RateLimit-Limit": {
        "description": "Maximum number of requests allowed per minute for this API key tier.",
        "schema": {
          "type": "integer",
          "example": 60
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Number of requests remaining in the current rate-limit window.",
        "schema": {
          "type": "integer",
          "example": 59
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp (seconds) at which the current rate-limit window resets.",
        "schema": {
          "type": "integer",
          "example": 1741802460
        }
      },
      "Retry-After": {
        "description": "Number of seconds to wait before retrying. Present on per-minute rate-limit 429 responses.",
        "schema": {
          "type": "integer",
          "example": 30
        }
      }
    },
    "parameters": {
      "XApiVersion": {
        "name": "X-API-Version",
        "in": "header",
        "required": false,
        "description": "API version to use. Defaults to the latest version (`2026-09-12`) if omitted. Unsupported versions return `400 Bad Request`. Version cutovers: `2026-09-12` — transcript line numbers are furniture (out of every text surface, stated once on `value.transcript_line_number`), `/parse` and pages carry `transcript.line_numbered`, transcript nodes carry `meta.transcript`, class 13 is `speaker_turn` with `value.speaker` / `turn_number` / `role`. `2026-09-03` — pages carry `language` + `direction`, documents and sub-documents their majority `language`. `2026-08-10` — LINE nodes carry the handwriting rollup in `meta.styling`. `2026-08-05` — sub-documents carry `start_anchor`/`end_anchor` (sub-page boundary positions), and every tree node carries `meta.sub_document` (its deepest owning sub-document).\n`2026-08-01` — `/parse` returns the public tree shape (curated `meta` instead of raw `engine_meta`, `class` names on nodes, TOKEN nodes opt-in via `tokens=true`, `strip_geometry` affects coordinates only); pin `2026-06-07` to keep the verbatim engine tree. `2026-06-07` — per-scope progress details; pin `2026-03-06` to keep the legacy progress shape (`processing_progress` + `page_count`).\n",
        "schema": {
          "type": "string",
          "example": "2026-09-03"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Client-generated key for idempotent requests (POST, PUT, PATCH, DELETE). Duplicate requests with the same key return the original cached response for 24 hours.\n",
        "schema": {
          "type": "string",
          "example": "order-abc-attempt-1"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "about:blank",
              "title": "Unauthorized",
              "status": 401,
              "detail": "Missing or invalid Authorization header."
            }
          }
        }
      },
      "Forbidden": {
        "description": "API key is valid but does not have permission for this operation.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "examples": {
              "insufficientPermissions": {
                "value": {
                  "type": "about:blank",
                  "title": "Forbidden",
                  "status": 403,
                  "detail": "Insufficient permissions for this operation."
                }
              },
              "trialLimitReached": {
                "value": {
                  "type": "about:blank",
                  "title": "Forbidden",
                  "status": 403,
                  "detail": "Your free trial limit has been reached. Contact your account administrator or reach out to LexSelect to upgrade your plan."
                }
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "A per-key, per-user, IP, or concurrency limit was exceeded.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "about:blank",
              "title": "Too Many Requests",
              "status": 429,
              "detail": "Rate limit exceeded. Retry after 30 seconds."
            }
          }
        }
      },
      "InternalServerError": {
        "description": "An unexpected server error occurred.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "about:blank",
              "title": "Internal Server Error",
              "status": 500,
              "detail": "An unexpected error occurred."
            }
          }
        }
      },
      "GatewayTimeout": {
        "description": "The orchestrator did not respond within the timeout window.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "about:blank",
              "title": "Gateway Timeout",
              "status": 504,
              "detail": "The server did not receive a timely response."
            }
          }
        }
      },
      "ContentTooLarge": {
        "description": "The uploaded file exceeds the 250 MB limit.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "about:blank",
              "title": "Content Too Large",
              "status": 413,
              "detail": "File exceeds the maximum allowed size of 250 MB."
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "The uploaded file extension is not supported.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "about:blank",
              "title": "Unsupported Media Type",
              "status": 415,
              "detail": "Unsupported file type. Allowed formats: doc, docx, eml, html, msg, odt, pdf, rtf."
            }
          }
        }
      }
    }
  }
}