> ## Documentation Index
> Fetch the complete documentation index at: https://docs.landing.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# July 17 v2 API Format Changes

> Update code built against the pre-July 17 v2 API responses with the field-by-field Parse, Extract, and Jobs changes.

export const dpt3 = 'DPT-3';

export const ade = 'Agentic Document Extraction';

On July 17, 2026, the response format of the {ade} v2 APIs changed. This page lists every field-level change for developers who built against the v2 APIs before that date. The endpoints and authentication are unchanged, and the Markdown output format is the same.

If you are migrating from the Parse v1 API instead, see the [Migration Guide](./migration-guide).

In this reference:

* [Parse API Changes](#parse-api-changes)
* [Extract API Changes](#extract-api-changes)
* [Jobs and Job ID Changes](#jobs-and-job-id-changes)
* [One Error Format](#one-error-format)

## Parse API Changes

The `/v2/parse` response is now one tree instead of two, and the values inside it changed:

* **Grounding is inline on each block.** The top-level `grounding` field is removed. Every node in `structure` below the root now carries its own self-contained `grounding` object with the node's page, Markdown range, and bounding box. Walk one tree instead of two, or flatten it into a map by block `id`. See [Grounding](./parse-response#grounding-where-things-are).

  Before:

  ```json theme={null}
  {
    "structure": { "type": "document", "children": [ /* types, ids, spans */ ] },
    "grounding": { "type": "document", "children": [ /* boxes and parts */ ] }
  }
  ```

  After:

  ```json theme={null}
  {
    "structure": {
      "type": "document",
      "children": [{
        "type": "page",
        "grounding": { "page": 1, "range": { "start": 0, "end": 354 }, "box": { "xmin": 0, "ymin": 0, "xmax": 1, "ymax": 1 } },
        "children": [ /* blocks, each with its own grounding */ ]
      }]
    }
  }
  ```

* **Spans are now ranges.** The `span` arrays (`[start, end]`) are replaced by `range` objects (`{"start": n, "end": n}`), still `[start, end)` offsets into the top-level `markdown` string in Unicode code points. The new `metadata.range_units` field declares the units (`"unicode_codepoints"`).

* **Bounding boxes are normalized.** Boxes change from integer-pixel arrays (`[left, top, right, bottom]`) to objects (`{"xmin": n, "ymin": n, "xmax": n, "ymax": n}`) in normalized page coordinates: fractions of the page's width and height from 0 to 1, with at most 8 decimal places. The `dpi` request option and the page `width`, `height`, and `dpi` response fields are removed; to convert a box to pixels, multiply by the dimensions of whatever rendering of the page you draw on. See [Working with Boxes](./parse-response#working-with-boxes).

* **Page numbers are 1-indexed.** Page 1 is the document's first page, everywhere: in each node's `grounding.page`, in `metadata.failed_pages`, and in the `options.pages` request selector, where `0` now returns HTTP 422. Table `row` and `col` positions stay 0-indexed.

* **The `parts` array is now `atomic_grounding`.** Each entry has the same `{page, range, box}` shape as a block's own `grounding`. The field appears only on leaf blocks (every type except `table`), and the `grounding.parts` request option is replaced by `atomic_grounding`. See [Atomic Grounding](./parse-response#atomic-grounding).

* **Block ids are semantic.** Ids follow the format `<type>-<index>`, with a per-type counter in reading order: `text-0` is the document's first text block and `table_cell-3` is the fourth cell of its first table. Ids are stable within a response but not across re-parses.

* **The `blocks.<type>.caption` option is renamed to `blocks.<type>.markdown`.** Same behavior, new name. Requests that send an unknown or legacy option key (`dpi`, `grounding`, `blocks.<type>.caption`) return HTTP 422.

* **New `inline_markdown` option.** Set `options.inline_markdown` to `true` to have every structure node (document, page, block, and table cell) carry its own `markdown` slice, so you don't have to slice the top-level string by range. See [Request Options](./parse-input#request-options).

* **The `password` field moved into `options`.** Password-protected files remain unsupported; providing `options.password` returns HTTP 422.

* **Metadata renames and additions.** The `markdown_chars` field is renamed to `output_markdown_chars`, and the new `range_units` and `openapi_spec` fields declare the range units and the spec URL. See [Metadata](./parse-response#metadata).

* **Rotated images are parsed upright.** Images with EXIF orientation metadata (such as phone photos) are now normalized before parsing, so bounding boxes align with the image as a viewer sees it. Previously, boxes for rotated images were computed in the unrotated frame and appeared misaligned.

## Extract API Changes

The `/v2/extract` response follows the same conventions:

* **Spans are now ranges.** In `extraction_metadata`, each field's `spans` array of `[start, end]` pairs is replaced by a `ranges` array of `{"start": n, "end": n}` objects. See [Extract API Response](./extract-response).

  Before:

  ```json theme={null}
  { "revenue": { "value": "$4.2M", "spans": [[164, 169]] } }
  ```

  After:

  ```json theme={null}
  { "revenue": { "value": "$4.2M", "ranges": [{ "start": 164, "end": 169 }] } }
  ```

* **The `version` metadata field is renamed to `model_version`.** The `metadata.doc_id` field is unchanged.

* **New `range_units` and `openapi_spec` metadata fields.** Same meaning as on the Parse response.

* **Billing is verifiable from the response.** Credit consumption for input is now based on exactly the Markdown you submit, and `metadata` adds `input_markdown_chars` and `output_extraction_chars`, the input and output bases of the charge. See [Credit Consumption](./credit-consumption#extract).

* **The `credit_usage` metadata field is removed.** Credits are reported once, in `metadata.billing.total_credits`, matching the Parse response.

* **Extract Jobs can save output to your storage.** Pass `output_save_url` when creating a job to have the result delivered to a presigned URL; the poll response then reports `output_url` instead of `result`. See [Save Extraction Output to a URL](./extract-async#save-extraction-output-to-a-url).

* **Extract reports partial success.** The response carries new `warnings` and `schema_violation_error` fields, and a synchronous request returns HTTP 206 instead of 200 when either is set (job polls stay 200, with the signals inside `result`). See [Status 206](./extract-troubleshoot#status-206-partial-success).

## Jobs and Job ID Changes

* **The Parse Jobs poll response uses the same envelope as Extract Jobs.** On `GET /v2/parse/jobs/{job_id}`, the `data` field is renamed to `result`, and `failure_reason` is replaced by an `error` object with a `code` and `message`. Timestamps are ISO-8601 strings (previously Unix seconds), a `completed_at` timestamp is added, and the top-level `version` and `metadata` fields are removed (the result's metadata lives inside `result`). The `cancelled` status is retired. See [Parse Asynchronously](./parse-async#monitor-a-parse-job).
* **The create response reports the job's status.** Creating a job (`POST /v2/parse/jobs` or `POST /v2/extract/jobs`) returns `job_id`, `status`, and `created_at`. The status is normally `pending`, but a job that finishes very quickly can already report a terminal status.
* **The list endpoint is paginated.** On `GET /v2/parse/jobs`, each row now reports `completed_at` and `model_version`, and the response pages with `page`, `page_size`, and `has_more`. Filter with the `status` query parameter.
* **Job ids have one format across the v2 APIs.** New job ids follow `<service>-<id>` (for example, `parse-01k04g2b8xv9q3m5n7r2sd4tfe`), and the same id appears on the synchronous response, the job-create response, and every poll. Treat ids as opaque strings; existing ids remain valid.

## One Error Format

Errors across the v2 APIs now share one shape and one validation status code:

* **Every error body is `{"code": "...", "message": "..."}`.** The `code` is a stable snake\_case identifier (for example, `validation_error`, `unknown_model_version`, `invalid_url`, `invalid_api_key`, `rate_limit_exceeded`); the `message` is human-readable. This replaces the previous `detail` strings and validation arrays, and matches the `error` object on failed async jobs.
* **Request-validation failures return 422.** Unknown model versions and invalid URLs previously returned 400; they now return 422 like every other validation failure.
* **Unknown request options return 422.** Option keys the API does not recognize are rejected instead of ignored, including the retired legacy parse options (`dpi`, `grounding`, `blocks.<type>.caption`).

See [Troubleshoot Parsing](./parse-troubleshoot#error-response-format) and [Troubleshoot Extraction](./extract-troubleshoot#error-response-format).
