diff --git a/api.md b/api.md
index daa31df..5cbbc15 100644
--- a/api.md
+++ b/api.md
@@ -100,7 +100,7 @@ from landingai_ade.types.v2 import (
```
- Job -- unified job shape: `job_id`, `status` (JobStatus: `pending` / `processing` / `completed` / `failed` / `cancelled`), `created_at`, `completed_at`, `progress`, `result` (a `V2ParseResponse` for parse jobs, a `V2ExtractResult` for extract jobs, a `V2BuildSchemaResponse` for build-schema jobs, or `None` until completion), `error` (JobError), `metadata` (the result's metadata receipt as a `dict`, populated top-level only when `output_save_url` was set and the result was delivered to `output_url` instead of inline; `None` otherwise, since inline jobs carry it on `result.metadata`), `raw` (the full original envelope as a `dict`), and the `.is_terminal` property.
-- V2ParseResponse -- `markdown`, `structure`, `grounding`, `metadata` (V2ParseMetadata, which nests V2ParseBilling and carries `output_markdown_chars`, `range_units`, and `openapi_spec`). `structure` is a typed V2ParseStructure tree (`document` → V2ParsePage → V2ParseElement); each node below the root carries its spatial data inline in a V2ParseNodeGrounding (`page`, V2ParseRange, V2ParseBox, normalized page coordinates), and leaf elements additionally carry an `atomic_grounding` list. With `options.inline_markdown`, each node also carries its `markdown` slice. The legacy top-level `grounding` tree (V2ParseGrounding → `V2ParseGroundingPage` → `V2ParseGroundingElement` → `V2ParseGroundingEntry`) is retained for older gateway responses. Element `type`/page `status` are permissive strings and unknown keys are retained.
+- V2ParseResponse -- `markdown`, `structure`, `grounding`, `metadata` (V2ParseMetadata, which nests V2ParseBilling and carries `output_markdown_chars`, `range_units`, and `openapi_spec`). `structure` is a typed V2ParseStructure tree (`document` → V2ParsePage → V2ParseElement); each node below the root carries its spatial data inline in a V2ParseNodeGrounding (`page`, V2ParseRange, V2ParseBox, normalized page coordinates), and leaf elements additionally carry an `atomic_grounding` list. Word-granularity `atomic_grounding` segments (from `dpt-3-fast`) also carry a `confidence` score in `[0, 1]`; it is `None` on node-level grounding and on line-granularity models (`dpt-3-pro`). With `options.inline_markdown`, each node also carries its `markdown` slice. The legacy top-level `grounding` tree (V2ParseGrounding → `V2ParseGroundingPage` → `V2ParseGroundingElement` → `V2ParseGroundingEntry`) is retained for older gateway responses. Element `type`/page `status` are permissive strings and unknown keys are retained.
- V2ExtractResult -- `extraction`, `extraction_metadata`, `markdown`, `output_ref`, `schema_violation_error` (set when `strict=False` and the schema had unextractable fields), `warnings`, and `metadata` (V2ExtractMetadata, which carries `model_version`, `input_markdown_chars`, `output_extraction_chars`, `range_units`, `openapi_spec`, and nests V2ExtractBilling).
- V2BuildSchemaResponse -- `extraction_schema` (the generated JSON Schema serialized as a string) and `metadata` (V2BuildSchemaMetadata: `job_id`, `duration_ms`, `openapi_spec`, `filename`/`org_id`/`version` (retained for compatibility), a `warnings` list of V2BuildSchemaWarning (`code`, `msg`), and nested V2BuildSchemaBilling).
- V2GroundResult -- `grounding` (a tree mirroring the input `extraction_metadata`, each `{value, ranges}` leaf replaced by the list of `structure` blocks its ranges overlap) and `metadata` (V2GroundMetadata: `job_id`, `duration_ms`, `openapi_spec`, and nested V2GroundBilling).
diff --git a/docs/v2-testing.md b/docs/v2-testing.md
index ae51ba8..ec3f4d6 100644
--- a/docs/v2-testing.md
+++ b/docs/v2-testing.md
@@ -37,9 +37,14 @@ LANDINGAI_ADE_STAGING_APIKEY=... rye run pytest tests/contract/test_v2_smoke.py
`"unicode_codepoints"`).
- `box` (`V2ParseBox`) -- `{xmin, ymin, xmax, ymax}` as `[0, 1]` fractions of
the page width/height (a page node's box is the full page `{0, 0, 1, 1}`).
+ - `confidence` -- an optional `[0, 1]` OCR-confidence score. Present only on
+ word-granularity `atomic_grounding` segments (`dpt-3-fast`); `None` on
+ node-level grounding and on line-granularity models (`dpt-3-pro`).
- Leaf elements additionally carry `atomic_grounding` -- a list of
- `V2ParseNodeGrounding` segments (visual lines today). Omitted when
- `options.atomic_grounding` is `false`.
+ `V2ParseNodeGrounding` segments at whichever granularity the model reads at:
+ one entry per visual line for `dpt-3-pro`, one per word (each with its
+ `confidence`) for `dpt-3-fast`. Omitted when `options.atomic_grounding` is
+ `false`.
- With `options.inline_markdown=true`, the document root, each page, and each
element also carry their own `markdown` slice.
- `metadata` (`V2ParseMetadata`) -- `job_id`, `model_version`, `page_count`,
diff --git a/specs/_generated/v2_models.py b/specs/_generated/v2_models.py
index 4c7f67b..93df55e 100644
--- a/specs/_generated/v2_models.py
+++ b/specs/_generated/v2_models.py
@@ -431,6 +431,337 @@ class WorkflowStepOptions(BaseModel):
)
+class Model(RootModel[str]):
+ root: str = Field(
+ ...,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent.',
+ max_length=256,
+ title='Model',
+ )
+
+
+class V1ExtractBuildSchemaPostRequest(BaseModel):
+ """
+ Input to V1BuildSchemaOperationWorkflow — the ``/v1/extract/build-schema``
+ request body, i.e. VTRA's ``/v1/ade/extract/build-schema`` wire.
+
+ The v2 request already mirrors VTRA's ``BuildSchemaRequest`` field-for-field
+ (``markdowns`` / ``markdown_urls`` / ``prompt`` / ``schema``, plus the
+ at-least-one-source validator), so this INHERITS all of it and adds back the
+ one field v2 deliberately dropped: ``model``.
+
+ Why the delta exists at all: v2 build-schema is intentionally version-free —
+ schema generation runs on the build engine's default model, so v2 takes no
+ ``model`` and always answers ``metadata.version: null``. VTRA's v1 wire DOES
+ accept ``model``, and a caller that sends it must not get a 422 for an unknown
+ form field. So v1 accepts it and ECHOES it back in ``metadata.version``
+ (``operations/v1_build_schema.py``'s ``map_result``).
+
+ It is an ECHO, not a selector — the value does not reach the engine and does
+ not change which model builds the schema. That is the honest shape: v1 and v2
+ drive the same ``extractor.build()``, and pretending ``model`` picks a version
+ would be a lie in the contract. Documented on the field below so a caller
+ isn't misled into thinking they pinned anything.
+ """
+
+ markdown_urls: Optional[list[str]] = Field(
+ None,
+ description='URLs to Markdown files to analyze for schema generation.',
+ title='Markdown Urls',
+ )
+ markdowns: Optional[list[str]] = Field(
+ None,
+ description='Markdown files or inline content strings to analyze for schema generation. Multiple documents can be provided for better schema coverage.',
+ title='Markdowns',
+ )
+ model: Optional[Model] = Field(
+ None,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent.',
+ title='Model',
+ )
+ prompt: Optional[str] = Field(
+ None,
+ description='Instructions for how to generate or modify the schema.',
+ title='Prompt',
+ )
+ schema_: Optional[str] = Field(
+ None,
+ alias='schema',
+ description='Existing JSON schema to iterate on or refine.',
+ title='Schema',
+ )
+
+
+class Model1(RootModel[str]):
+ root: str = Field(
+ ...,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent. JSON-serialized string in form data.',
+ max_length=256,
+ title='Model',
+ )
+
+
+class V1ExtractBuildSchemaPostRequest1(BaseModel):
+ markdown_urls: Optional[list[str]] = Field(
+ None,
+ description='URLs to Markdown files to analyze for schema generation. JSON-serialized string in form data.',
+ title='Markdown Urls',
+ )
+ markdowns: Optional[list[Union[str, bytes]]] = Field(
+ None, description='Repeat the field for each file upload.'
+ )
+ model: Optional[Model1] = Field(
+ None,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent. JSON-serialized string in form data.',
+ title='Model',
+ )
+ prompt: Optional[str] = Field(
+ None,
+ description='Instructions for how to generate or modify the schema. JSON-serialized string in form data.',
+ title='Prompt',
+ )
+ schema_: Optional[str] = Field(
+ None,
+ alias='schema',
+ description='Existing JSON schema to iterate on or refine. JSON-serialized string in form data.',
+ title='Schema',
+ )
+
+
+class V1ExtractBuildSchemaPostResponse(BaseModel):
+ """
+ Result returned by V2BuildSchemaOperationWorkflow — the
+ ``/v2/extract/build-schema`` response body (VTRA ``BuildSchemaResponse``).
+
+ ``extraction_schema`` is the generated JSON Schema serialized as a STRING
+ (VTRA parity — the v1 field is a string, not an object).
+ """
+
+ extraction_schema: str = Field(
+ ...,
+ description='The generated JSON schema as a string.',
+ title='Extraction Schema',
+ )
+ metadata: V2BuildSchemaMetadata = Field(
+ ..., description='The metadata for the schema generation process.'
+ )
+
+
+class V1ExtractBuildSchemaJobsGetParametersQuery(BaseModel):
+ page: Optional[int] = Field(
+ 0, description='Page number (0-indexed).', ge=0, title='Page'
+ )
+ page_size: Optional[int] = Field(
+ 10, description='Number of items per page.', ge=1, le=100, title='Page Size'
+ )
+ status: Optional[str] = Field(
+ None, description='Filter by job status.', title='Status'
+ )
+
+
+class Status1(Enum):
+ pending = 'pending'
+ processing = 'processing'
+ completed = 'completed'
+ failed = 'failed'
+
+
+class Job(BaseModel):
+ completed_at: Optional[str] = None
+ created_at: Optional[str] = None
+ failure_reason: Optional[str] = None
+ job_id: Optional[str] = Field(
+ None,
+ description='The unique identifier for this v1-build-schema job. Format: ``extract-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.',
+ )
+ model_version: Optional[str] = None
+ status: Optional[Status1] = None
+
+
+class V1ExtractBuildSchemaJobsGetResponse(BaseModel):
+ has_more: Optional[bool] = None
+ jobs: Optional[list[Job]] = None
+ page: Optional[int] = None
+ page_size: Optional[int] = None
+
+
+class Model2(RootModel[str]):
+ root: str = Field(
+ ...,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent.',
+ max_length=256,
+ title='Model',
+ )
+
+
+class ServiceTier2(Enum):
+ """
+ Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.
+ """
+
+ standard = 'standard'
+ priority = 'priority'
+
+
+class V1ExtractBuildSchemaJobsPostRequest(BaseModel):
+ """
+ Input to V1BuildSchemaOperationWorkflow — the ``/v1/extract/build-schema``
+ request body, i.e. VTRA's ``/v1/ade/extract/build-schema`` wire.
+
+ The v2 request already mirrors VTRA's ``BuildSchemaRequest`` field-for-field
+ (``markdowns`` / ``markdown_urls`` / ``prompt`` / ``schema``, plus the
+ at-least-one-source validator), so this INHERITS all of it and adds back the
+ one field v2 deliberately dropped: ``model``.
+
+ Why the delta exists at all: v2 build-schema is intentionally version-free —
+ schema generation runs on the build engine's default model, so v2 takes no
+ ``model`` and always answers ``metadata.version: null``. VTRA's v1 wire DOES
+ accept ``model``, and a caller that sends it must not get a 422 for an unknown
+ form field. So v1 accepts it and ECHOES it back in ``metadata.version``
+ (``operations/v1_build_schema.py``'s ``map_result``).
+
+ It is an ECHO, not a selector — the value does not reach the engine and does
+ not change which model builds the schema. That is the honest shape: v1 and v2
+ drive the same ``extractor.build()``, and pretending ``model`` picks a version
+ would be a lie in the contract. Documented on the field below so a caller
+ isn't misled into thinking they pinned anything.
+ """
+
+ markdown_urls: Optional[list[str]] = Field(
+ None,
+ description='URLs to Markdown files to analyze for schema generation.',
+ title='Markdown Urls',
+ )
+ markdowns: Optional[list[str]] = Field(
+ None,
+ description='Markdown files or inline content strings to analyze for schema generation. Multiple documents can be provided for better schema coverage.',
+ title='Markdowns',
+ )
+ model: Optional[Model2] = Field(
+ None,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent.',
+ title='Model',
+ )
+ prompt: Optional[str] = Field(
+ None,
+ description='Instructions for how to generate or modify the schema.',
+ title='Prompt',
+ )
+ schema_: Optional[str] = Field(
+ None,
+ alias='schema',
+ description='Existing JSON schema to iterate on or refine.',
+ title='Schema',
+ )
+ service_tier: Optional[ServiceTier2] = Field(
+ None,
+ description='Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.',
+ )
+
+
+class Model3(RootModel[str]):
+ root: str = Field(
+ ...,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent. JSON-serialized string in form data.',
+ max_length=256,
+ title='Model',
+ )
+
+
+class V1ExtractBuildSchemaJobsPostRequest1(BaseModel):
+ markdown_urls: Optional[list[str]] = Field(
+ None,
+ description='URLs to Markdown files to analyze for schema generation. JSON-serialized string in form data.',
+ title='Markdown Urls',
+ )
+ markdowns: Optional[list[Union[str, bytes]]] = Field(
+ None, description='Repeat the field for each file upload.'
+ )
+ model: Optional[Model3] = Field(
+ None,
+ description='Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent. JSON-serialized string in form data.',
+ title='Model',
+ )
+ prompt: Optional[str] = Field(
+ None,
+ description='Instructions for how to generate or modify the schema. JSON-serialized string in form data.',
+ title='Prompt',
+ )
+ schema_: Optional[str] = Field(
+ None,
+ alias='schema',
+ description='Existing JSON schema to iterate on or refine. JSON-serialized string in form data.',
+ title='Schema',
+ )
+ service_tier: Optional[ServiceTier2] = Field(
+ None,
+ description='Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.',
+ )
+
+
+class V1ExtractBuildSchemaJobsPostResponse(BaseModel):
+ created_at: Optional[str] = None
+ job_id: Optional[str] = Field(
+ None,
+ description='The unique identifier for this v1-build-schema job. Format: ``extract-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.',
+ )
+ status: Optional[Status1] = None
+
+
+class Error(BaseModel):
+ """
+ Present once status is ``failed``.
+ """
+
+ code: Optional[str] = Field(
+ None, description='Stable error code (``internal_error`` when unmapped).'
+ )
+ message: Optional[str] = None
+
+
+class Result(BaseModel):
+ """
+ Result returned by V2BuildSchemaOperationWorkflow — the
+ ``/v2/extract/build-schema`` response body (VTRA ``BuildSchemaResponse``).
+
+ ``extraction_schema`` is the generated JSON Schema serialized as a STRING
+ (VTRA parity — the v1 field is a string, not an object).
+ """
+
+ extraction_schema: str = Field(
+ ...,
+ description='The generated JSON schema as a string.',
+ title='Extraction Schema',
+ )
+ metadata: V2BuildSchemaMetadata = Field(
+ ..., description='The metadata for the schema generation process.'
+ )
+
+
+class V1ExtractBuildSchemaJobsJobIdGetResponse(BaseModel):
+ completed_at: Optional[str] = Field(
+ None, description='Present once the job is terminal.'
+ )
+ created_at: Optional[str] = None
+ error: Optional[Error] = Field(
+ None, description='Present once status is ``failed``.'
+ )
+ job_id: Optional[str] = Field(
+ None,
+ description='The unique identifier for this v1-build-schema job. Format: ``extract-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.',
+ )
+ progress: Optional[float] = Field(
+ None,
+ description='Estimated completion as a decimal from 0 to 1 — an estimate, not a measurement: it typically advances between polls while the job is ``processing``, may jump forward when the service reports a real milestone (e.g. parsed pages), and approaches but never reaches 1 (long-running jobs plateau near 0.98 — completion is signaled by ``status``, and a job may complete from any progress value). Present while ``processing``.',
+ ge=0.0,
+ le=1.0,
+ )
+ result: Optional[Result] = Field(
+ None, description='Present once status is ``completed``.'
+ )
+ status: Optional[Status1] = None
+
+
class V2ExtractPostRequest(BaseModel):
"""
Input to V2ExtractOperationWorkflow.
@@ -558,14 +889,7 @@ class V2ExtractJobsGetParametersQuery(BaseModel):
)
-class Status1(Enum):
- pending = 'pending'
- processing = 'processing'
- completed = 'completed'
- failed = 'failed'
-
-
-class Job(BaseModel):
+class Job1(BaseModel):
completed_at: Optional[str] = None
created_at: Optional[str] = None
failure_reason: Optional[str] = None
@@ -579,20 +903,11 @@ class Job(BaseModel):
class V2ExtractJobsGetResponse(BaseModel):
has_more: Optional[bool] = None
- jobs: Optional[list[Job]] = None
+ jobs: Optional[list[Job1]] = None
page: Optional[int] = None
page_size: Optional[int] = None
-class ServiceTier2(Enum):
- """
- Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.
- """
-
- standard = 'standard'
- priority = 'priority'
-
-
class V2ExtractJobsPostRequest(BaseModel):
"""
Input to V2ExtractOperationWorkflow.
@@ -698,18 +1013,7 @@ class V2ExtractJobsPostResponse(BaseModel):
status: Optional[Status1] = None
-class Error(BaseModel):
- """
- Present once status is ``failed``.
- """
-
- code: Optional[str] = Field(
- None, description='Stable error code (``internal_error`` when unmapped).'
- )
- message: Optional[str] = None
-
-
-class Result(BaseModel):
+class Result1(BaseModel):
"""
Result returned by V2ExtractOperationWorkflow — the ``/v2/extract``
response body (``docs/extract-v2-proposal.md`` → Response).
@@ -772,7 +1076,7 @@ class V2ExtractJobsJobIdGetResponse(BaseModel):
ge=0.0,
le=1.0,
)
- result: Optional[Result] = Field(
+ result: Optional[Result1] = Field(
None,
description='Present once status is ``completed`` and ``output_save_url`` was not set. When ``output_save_url`` was set, the result is delivered there and ``output_url`` is returned instead.',
)
@@ -888,7 +1192,7 @@ class V2ParseJobsGetParametersQuery(BaseModel):
)
-class Status4(Enum):
+class Status7(Enum):
"""
The job's current status: ``pending``, ``processing``, ``completed``, or ``failed``.
"""
@@ -899,7 +1203,7 @@ class Status4(Enum):
failed = 'failed'
-class Job1(BaseModel):
+class Job2(BaseModel):
completed_at: Optional[str] = Field(
None, description='ISO-8601 timestamp for when the job finished, if terminal.'
)
@@ -917,7 +1221,7 @@ class Job1(BaseModel):
model_version: Optional[str] = Field(
None, description='The model snapshot used to parse the document.'
)
- status: Optional[Status4] = Field(
+ status: Optional[Status7] = Field(
None,
description="The job's current status: ``pending``, ``processing``, ``completed``, or ``failed``.",
)
@@ -928,14 +1232,14 @@ class V2ParseJobsGetResponse(BaseModel):
None,
description='Whether more jobs exist beyond this page; request the next ``page`` to fetch them.',
)
- jobs: Optional[list[Job1]] = Field(
+ jobs: Optional[list[Job2]] = Field(
None, description="The caller's parse jobs for this page, newest first."
)
page: Optional[int] = Field(None, description='The 0-indexed page number.')
page_size: Optional[int] = Field(None, description='Items per page.')
-class ServiceTier4(Enum):
+class ServiceTier6(Enum):
"""
Async service tier (``POST /jobs`` only). ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.
"""
@@ -944,7 +1248,7 @@ class ServiceTier4(Enum):
priority = 'priority'
-class Status5(Enum):
+class Status8(Enum):
"""
The job's status at creation — normally ``pending`` (a just-created job that is still running is reported as ``pending``), but may already be a terminal ``completed`` / ``failed`` if the job finished before the create response was rendered.
"""
@@ -963,13 +1267,13 @@ class V2ParseJobsPostResponse(BaseModel):
...,
description='The unique identifier for the created parse job. Poll ``GET /v2/parse/jobs/{job_id}`` for its status and result. Format: ``-<26-character Crockford base32 ULID>`` matching ``^(parse|extract)-[0-9a-hjkmnp-tv-z]{26}$``. Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.',
)
- status: Status5 = Field(
+ status: Status8 = Field(
...,
description="The job's status at creation — normally ``pending`` (a just-created job that is still running is reported as ``pending``), but may already be a terminal ``completed`` / ``failed`` if the job finished before the create response was rendered.",
)
-class Error1(BaseModel):
+class Error2(BaseModel):
"""
Present once the job has ``failed`` — the failure code + message.
"""
@@ -978,7 +1282,7 @@ class Error1(BaseModel):
message: Optional[str] = None
-class Status6(Enum):
+class Status9(Enum):
"""
The job's current status: ``pending``, ``processing``, ``completed``, or ``failed``.
"""
@@ -1037,14 +1341,14 @@ class V2WorkflowJobsGetParametersQuery(BaseModel):
)
-class Status7(Enum):
+class Status10(Enum):
pending = 'pending'
processing = 'processing'
completed = 'completed'
failed = 'failed'
-class Job2(BaseModel):
+class Job3(BaseModel):
completed_at: Optional[str] = None
created_at: Optional[str] = None
failure_reason: Optional[str] = None
@@ -1053,17 +1357,17 @@ class Job2(BaseModel):
description='The unique identifier for this v2-workflow job. Format: ``v2-workflow-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.',
)
model_version: Optional[str] = None
- status: Optional[Status7] = None
+ status: Optional[Status10] = None
class V2WorkflowJobsGetResponse(BaseModel):
has_more: Optional[bool] = None
- jobs: Optional[list[Job2]] = None
+ jobs: Optional[list[Job3]] = None
page: Optional[int] = None
page_size: Optional[int] = None
-class ServiceTier5(Enum):
+class ServiceTier7(Enum):
"""
Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.
"""
@@ -1078,10 +1382,10 @@ class V2WorkflowJobsPostResponse(BaseModel):
None,
description='The unique identifier for this v2-workflow job. Format: ``v2-workflow-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.',
)
- status: Optional[Status7] = None
+ status: Optional[Status10] = None
-class Error2(BaseModel):
+class Error3(BaseModel):
"""
Present once status is ``failed``.
"""
@@ -1092,7 +1396,7 @@ class Error2(BaseModel):
message: Optional[str] = None
-class Result1(BaseModel):
+class Result2(BaseModel):
"""
Result returned by V2WorkflowOperationWorkflow.
@@ -1133,7 +1437,7 @@ class V2WorkflowJobsJobIdGetResponse(BaseModel):
None, description='Present once the job is terminal.'
)
created_at: Optional[str] = None
- error: Optional[Error2] = Field(
+ error: Optional[Error3] = Field(
None, description='Present once status is ``failed``.'
)
job_id: Optional[str] = Field(
@@ -1146,10 +1450,10 @@ class V2WorkflowJobsJobIdGetResponse(BaseModel):
ge=0.0,
le=1.0,
)
- result: Optional[Result1] = Field(
+ result: Optional[Result2] = Field(
None, description='Present once status is ``completed``.'
)
- status: Optional[Status7] = None
+ status: Optional[Status10] = None
class BlocksOptions(BaseModel):
@@ -1179,6 +1483,11 @@ class Grounding(BaseModel):
...,
description="Bounding box in normalized page coordinates (`0`–`1` fractions of page width/height, at most 8 decimal places). A page node's box is always the full page `{0, 0, 1, 1}`.",
)
+ confidence: Optional[float] = Field(
+ None,
+ description='How sure the model is of the text in this segment, in `[0, 1]`. Present only on word-granularity `atomic_grounding` entries (`dpt-3-fast`), where it is the lowest per-character OCR confidence in the word — so a word is only as trustworthy as its weakest character. Omitted on node-level grounding and on models that ground at line granularity.',
+ title='Confidence',
+ )
page: int = Field(
...,
description="1-indexed page number this grounding is on. On a page node, the page's own number.",
@@ -1291,7 +1600,7 @@ class V2ParseJobsPostRequest(BaseModel):
None,
description="Public URL the full response is delivered to; the API response then carries ``output_url`` instead of inline data. A presigned URL must stay valid until the job COMPLETES, not just past submit: an already-expired URL, or one whose remaining validity is too short for the document's page count, is rejected at submit (422). By default the URL must retain at least 15 minutes of validity at submit, plus 3 seconds per document page; the 422 message names the exact window required. Sign with credentials that outlive the expected job duration — a URL signed with temporary (assumed-role/session) credentials dies when that session expires, regardless of the URL's stated expiry.",
)
- service_tier: Optional[ServiceTier4] = Field(
+ service_tier: Optional[ServiceTier6] = Field(
None,
description='Async service tier (``POST /jobs`` only). ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.',
)
@@ -1441,7 +1750,7 @@ class V2WorkflowJobsPostRequest(BaseModel):
],
title='Output',
)
- service_tier: Optional[ServiceTier5] = Field(
+ service_tier: Optional[ServiceTier7] = Field(
None,
description='Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.',
)
@@ -1482,7 +1791,7 @@ class V2WorkflowJobsPostRequest1(BaseModel):
],
title='Output',
)
- service_tier: Optional[ServiceTier5] = Field(
+ service_tier: Optional[ServiceTier7] = Field(
None,
description='Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``.',
)
@@ -1515,7 +1824,7 @@ class Element(BaseModel):
atomic_grounding: Optional[list[Grounding]] = Field(
None,
- description="Fine-grained grounding segments at the model's current granularity (visual lines today; finer in future versions, same schema). Present only on leaf elements — every type except `table`. `[]` only when segments are structurally impossible: `table_cell` (a cell has no finer granularity than itself) and elements whose markdown is suppressed via `blocks..markdown=false`. Any other leaf the model could not segment finer carries a single entry covering the element's full range and box. Omitted entirely when `options.atomic_grounding` is `false`.",
+ description="Fine-grained grounding segments, at whichever granularity the model reads at: one entry per visual line for `dpt-3-pro`, one per **word** — each with its `confidence` — for `dpt-3-fast`, including the words inside table cells. Present only on leaf elements — every type except `table`. `[]` in three cases: an element whose markdown is suppressed via `blocks..markdown=false`; a `table_cell` on a line-granularity model (a cell has no finer granularity than itself there); and a `table_cell` on a word-granularity model whose words cannot be located in the rendered cell text — a `|` escaped on the way into a pipe table, or a character escaped on the way into an HTML table — where the segments are dropped rather than risk reporting offsets that point at the wrong characters. Any other leaf the model could not segment finer carries a single entry covering the element's full range and box. Omitted entirely when `options.atomic_grounding` is `false`.",
title='Atomic Grounding',
)
children: Optional[list[Element]] = Field(
@@ -1643,7 +1952,7 @@ class V2ParseJobsJobIdGetResponse(BaseModel):
created_at: Optional[str] = Field(
None, description='ISO-8601 timestamp for when the job was created.'
)
- error: Optional[Error1] = Field(
+ error: Optional[Error2] = Field(
None,
description='Present once the job has ``failed`` — the failure code + message.',
)
@@ -1667,7 +1976,7 @@ class V2ParseJobsJobIdGetResponse(BaseModel):
None,
description='The parse response, present once the job has ``completed`` and ``output_save_url`` was not set. When ``output_save_url`` was set, the result is delivered there and ``output_url`` is returned instead.',
)
- status: Optional[Status6] = Field(
+ status: Optional[Status9] = Field(
None,
description="The job's current status: ``pending``, ``processing``, ``completed``, or ``failed``.",
)
diff --git a/specs/v2-aide.json b/specs/v2-aide.json
index 2de1e9a..7c4ab7a 100644
--- a/specs/v2-aide.json
+++ b/specs/v2-aide.json
@@ -148,7 +148,7 @@
}
],
"default": null,
- "description": "Fine-grained grounding segments at the model's current granularity (visual lines today; finer in future versions, same schema). Present only on leaf elements — every type except `table`. `[]` only when segments are structurally impossible: `table_cell` (a cell has no finer granularity than itself) and elements whose markdown is suppressed via `blocks..markdown=false`. Any other leaf the model could not segment finer carries a single entry covering the element's full range and box. Omitted entirely when `options.atomic_grounding` is `false`.",
+ "description": "Fine-grained grounding segments, at whichever granularity the model reads at: one entry per visual line for `dpt-3-pro`, one per **word** — each with its `confidence` — for `dpt-3-fast`, including the words inside table cells. Present only on leaf elements — every type except `table`. `[]` in three cases: an element whose markdown is suppressed via `blocks..markdown=false`; a `table_cell` on a line-granularity model (a cell has no finer granularity than itself there); and a `table_cell` on a word-granularity model whose words cannot be located in the rendered cell text — a `|` escaped on the way into a pipe table, or a character escaped on the way into an HTML table — where the segments are dropped rather than risk reporting offsets that point at the wrong characters. Any other leaf the model could not segment finer carries a single entry covering the element's full range and box. Omitted entirely when `options.atomic_grounding` is `false`.",
"title": "Atomic Grounding"
},
"children": {
@@ -303,6 +303,19 @@
"$ref": "#/components/schemas/Box",
"description": "Bounding box in normalized page coordinates (`0`–`1` fractions of page width/height, at most 8 decimal places). A page node's box is always the full page `{0, 0, 1, 1}`."
},
+ "confidence": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "How sure the model is of the text in this segment, in `[0, 1]`. Present only on word-granularity `atomic_grounding` entries (`dpt-3-fast`), where it is the lowest per-character OCR confidence in the word — so a word is only as trustworthy as its weakest character. Omitted on node-level grounding and on models that ground at line granularity.",
+ "title": "Confidence"
+ },
"page": {
"description": "1-indexed page number this grounding is on. On a page node, the page's own number.",
"title": "Page",
@@ -1013,6 +1026,730 @@
},
"openapi": "3.1.0",
"paths": {
+ "/v1/extract/build-schema": {
+ "post": {
+ "description": "Generate or edit a JSON Schema for extraction from one or more source Markdown documents and/or a natural-language prompt. v1-compatible wire, running the same schema-build engine as `/v2/extract/build-schema`. The one difference: this route accepts `model` and echoes it back as `metadata.version`. Schema generation is version-free, so `model` does NOT select a model and does not change the generated schema. Runs synchronously and returns the result inline.",
+ "operationId": "v1-build-schema_run_sync",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "description": "Input to V1BuildSchemaOperationWorkflow — the ``/v1/extract/build-schema``\nrequest body, i.e. VTRA's ``/v1/ade/extract/build-schema`` wire.\n\nThe v2 request already mirrors VTRA's ``BuildSchemaRequest`` field-for-field\n(``markdowns`` / ``markdown_urls`` / ``prompt`` / ``schema``, plus the\nat-least-one-source validator), so this INHERITS all of it and adds back the\none field v2 deliberately dropped: ``model``.\n\nWhy the delta exists at all: v2 build-schema is intentionally version-free —\nschema generation runs on the build engine's default model, so v2 takes no\n``model`` and always answers ``metadata.version: null``. VTRA's v1 wire DOES\naccept ``model``, and a caller that sends it must not get a 422 for an unknown\nform field. So v1 accepts it and ECHOES it back in ``metadata.version``\n(``operations/v1_build_schema.py``'s ``map_result``).\n\nIt is an ECHO, not a selector — the value does not reach the engine and does\nnot change which model builds the schema. That is the honest shape: v1 and v2\ndrive the same ``extractor.build()``, and pretending ``model`` picks a version\nwould be a lie in the contract. Documented on the field below so a caller\nisn't misled into thinking they pinned anything.",
+ "properties": {
+ "markdown_urls": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "URLs to Markdown files to analyze for schema generation.",
+ "title": "Markdown Urls"
+ },
+ "markdowns": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Markdown files or inline content strings to analyze for schema generation. Multiple documents can be provided for better schema coverage.",
+ "title": "Markdowns"
+ },
+ "model": {
+ "anyOf": [
+ {
+ "maxLength": 256,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent.",
+ "title": "Model"
+ },
+ "prompt": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Instructions for how to generate or modify the schema.",
+ "title": "Prompt"
+ },
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Existing JSON schema to iterate on or refine.",
+ "title": "Schema"
+ }
+ },
+ "title": "V1BuildSchemaRequest",
+ "type": "object"
+ }
+ },
+ "multipart/form-data": {
+ "schema": {
+ "properties": {
+ "markdown_urls": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "URLs to Markdown files to analyze for schema generation. JSON-serialized string in form data.",
+ "title": "Markdown Urls"
+ },
+ "markdowns": {
+ "description": "Repeat the field for each file upload.",
+ "items": {
+ "anyOf": [
+ {
+ "description": "Markdown files or inline content strings to analyze for schema generation. Multiple documents can be provided for better schema coverage.",
+ "type": "string"
+ },
+ {
+ "description": "File upload.",
+ "format": "binary",
+ "type": "string"
+ }
+ ]
+ },
+ "type": "array"
+ },
+ "model": {
+ "anyOf": [
+ {
+ "maxLength": 256,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent. JSON-serialized string in form data.",
+ "title": "Model"
+ },
+ "prompt": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Instructions for how to generate or modify the schema. JSON-serialized string in form data.",
+ "title": "Prompt"
+ },
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Existing JSON schema to iterate on or refine. JSON-serialized string in form data.",
+ "title": "Schema"
+ }
+ },
+ "type": "object"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "description": "Result returned by V2BuildSchemaOperationWorkflow — the\n``/v2/extract/build-schema`` response body (VTRA ``BuildSchemaResponse``).\n\n``extraction_schema`` is the generated JSON Schema serialized as a STRING\n(VTRA parity — the v1 field is a string, not an object).",
+ "properties": {
+ "extraction_schema": {
+ "description": "The generated JSON schema as a string.",
+ "title": "Extraction Schema",
+ "type": "string"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/V2BuildSchemaMetadata",
+ "description": "The metadata for the schema generation process."
+ }
+ },
+ "required": [
+ "extraction_schema",
+ "metadata"
+ ],
+ "title": "V2BuildSchemaResponse",
+ "type": "object"
+ }
+ }
+ },
+ "description": "v1-build-schema result"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request validation failed."
+ }
+ },
+ "summary": "ADE Extract",
+ "tags": [
+ "Extract"
+ ]
+ }
+ },
+ "/v1/extract/build-schema/jobs": {
+ "get": {
+ "description": "List your Extract jobs, newest first.",
+ "operationId": "v1-build-schema_list_jobs",
+ "parameters": [
+ {
+ "description": "Page number (0-indexed).",
+ "in": "query",
+ "name": "page",
+ "required": false,
+ "schema": {
+ "default": 0,
+ "description": "Page number (0-indexed).",
+ "minimum": 0,
+ "title": "Page",
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Number of items per page.",
+ "in": "query",
+ "name": "page_size",
+ "required": false,
+ "schema": {
+ "default": 10,
+ "description": "Number of items per page.",
+ "maximum": 100,
+ "minimum": 1,
+ "title": "Page Size",
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Filter by job status.",
+ "in": "query",
+ "name": "status",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Filter by job status.",
+ "title": "Status"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "properties": {
+ "has_more": {
+ "type": "boolean"
+ },
+ "jobs": {
+ "items": {
+ "properties": {
+ "completed_at": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "created_at": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "failure_reason": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "job_id": {
+ "description": "The unique identifier for this v1-build-schema job. Format: ``extract-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.",
+ "type": "string"
+ },
+ "model_version": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "processing",
+ "completed",
+ "failed"
+ ],
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "page": {
+ "type": "integer"
+ },
+ "page_size": {
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ }
+ }
+ },
+ "description": "The caller's jobs, newest first"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request validation failed."
+ }
+ },
+ "summary": "ADE List Extract Jobs",
+ "tags": [
+ "Extract"
+ ]
+ },
+ "post": {
+ "description": "Generate or edit a JSON Schema for extraction from one or more source Markdown documents and/or a natural-language prompt. v1-compatible wire, running the same schema-build engine as `/v2/extract/build-schema`. The one difference: this route accepts `model` and echoes it back as `metadata.version`. Schema generation is version-free, so `model` does NOT select a model and does not change the generated schema. Runs asynchronously and returns a job ID; use it to poll for status and retrieve the result once processing completes.",
+ "operationId": "v1-build-schema_create_job",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "description": "Input to V1BuildSchemaOperationWorkflow — the ``/v1/extract/build-schema``\nrequest body, i.e. VTRA's ``/v1/ade/extract/build-schema`` wire.\n\nThe v2 request already mirrors VTRA's ``BuildSchemaRequest`` field-for-field\n(``markdowns`` / ``markdown_urls`` / ``prompt`` / ``schema``, plus the\nat-least-one-source validator), so this INHERITS all of it and adds back the\none field v2 deliberately dropped: ``model``.\n\nWhy the delta exists at all: v2 build-schema is intentionally version-free —\nschema generation runs on the build engine's default model, so v2 takes no\n``model`` and always answers ``metadata.version: null``. VTRA's v1 wire DOES\naccept ``model``, and a caller that sends it must not get a 422 for an unknown\nform field. So v1 accepts it and ECHOES it back in ``metadata.version``\n(``operations/v1_build_schema.py``'s ``map_result``).\n\nIt is an ECHO, not a selector — the value does not reach the engine and does\nnot change which model builds the schema. That is the honest shape: v1 and v2\ndrive the same ``extractor.build()``, and pretending ``model`` picks a version\nwould be a lie in the contract. Documented on the field below so a caller\nisn't misled into thinking they pinned anything.",
+ "properties": {
+ "markdown_urls": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "URLs to Markdown files to analyze for schema generation.",
+ "title": "Markdown Urls"
+ },
+ "markdowns": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Markdown files or inline content strings to analyze for schema generation. Multiple documents can be provided for better schema coverage.",
+ "title": "Markdowns"
+ },
+ "model": {
+ "anyOf": [
+ {
+ "maxLength": 256,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent.",
+ "title": "Model"
+ },
+ "prompt": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Instructions for how to generate or modify the schema.",
+ "title": "Prompt"
+ },
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Existing JSON schema to iterate on or refine.",
+ "title": "Schema"
+ },
+ "service_tier": {
+ "anyOf": [
+ {
+ "enum": [
+ "standard",
+ "priority"
+ ],
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``."
+ }
+ },
+ "title": "V1BuildSchemaRequest",
+ "type": "object"
+ }
+ },
+ "multipart/form-data": {
+ "schema": {
+ "properties": {
+ "markdown_urls": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "URLs to Markdown files to analyze for schema generation. JSON-serialized string in form data.",
+ "title": "Markdown Urls"
+ },
+ "markdowns": {
+ "description": "Repeat the field for each file upload.",
+ "items": {
+ "anyOf": [
+ {
+ "description": "Markdown files or inline content strings to analyze for schema generation. Multiple documents can be provided for better schema coverage.",
+ "type": "string"
+ },
+ {
+ "description": "File upload.",
+ "format": "binary",
+ "type": "string"
+ }
+ ]
+ },
+ "type": "array"
+ },
+ "model": {
+ "anyOf": [
+ {
+ "maxLength": 256,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Accepted for v1 wire compatibility and echoed back as `metadata.version`. Schema generation is version-free — this value does NOT select a model and does not change the generated schema. Bounded to 256 characters; blank values are treated as absent. JSON-serialized string in form data.",
+ "title": "Model"
+ },
+ "prompt": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Instructions for how to generate or modify the schema. JSON-serialized string in form data.",
+ "title": "Prompt"
+ },
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "description": "Existing JSON schema to iterate on or refine. JSON-serialized string in form data.",
+ "title": "Schema"
+ },
+ "service_tier": {
+ "anyOf": [
+ {
+ "enum": [
+ "standard",
+ "priority"
+ ],
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Async service tier. ``priority`` runs in the fast lane at the sync billing rate; absent → ``standard``."
+ }
+ },
+ "type": "object"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "202": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "properties": {
+ "created_at": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "job_id": {
+ "description": "The unique identifier for this v1-build-schema job. Format: ``extract-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.",
+ "type": "string"
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "processing",
+ "completed",
+ "failed"
+ ],
+ "type": "string"
+ }
+ },
+ "type": "object"
+ }
+ }
+ },
+ "description": "Job created"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request validation failed."
+ }
+ },
+ "summary": "ADE Extract Jobs",
+ "tags": [
+ "Extract"
+ ]
+ }
+ },
+ "/v1/extract/build-schema/jobs/{job_id}": {
+ "get": {
+ "description": "Get the status of an async Extract job, including its result once the job has completed.",
+ "operationId": "v1-build-schema_get_job",
+ "parameters": [
+ {
+ "description": "The identifier of the job to retrieve, as returned by the create-job request.",
+ "in": "path",
+ "name": "job_id",
+ "required": true,
+ "schema": {
+ "description": "The identifier of the job to retrieve, as returned by the create-job request.",
+ "title": "Job Id",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "properties": {
+ "completed_at": {
+ "description": "Present once the job is terminal.",
+ "type": "string"
+ },
+ "created_at": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "error": {
+ "description": "Present once status is ``failed``.",
+ "properties": {
+ "code": {
+ "description": "Stable error code (``internal_error`` when unmapped).",
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "job_id": {
+ "description": "The unique identifier for this v1-build-schema job. Format: ``extract-<26-character Crockford base32 ULID>`` (``[0-9a-hjkmnp-tv-z]{26}`` tail). Opaque, server-minted, and stable for the life of the job — the same id is returned on the sync response, the async 202, and every poll. Treat it as opaque; older id formats remain accepted indefinitely and are never re-issued.",
+ "type": "string"
+ },
+ "progress": {
+ "description": "Estimated completion as a decimal from 0 to 1 — an estimate, not a measurement: it typically advances between polls while the job is ``processing``, may jump forward when the service reports a real milestone (e.g. parsed pages), and approaches but never reaches 1 (long-running jobs plateau near 0.98 — completion is signaled by ``status``, and a job may complete from any progress value). Present while ``processing``.",
+ "maximum": 1,
+ "minimum": 0,
+ "type": "number"
+ },
+ "result": {
+ "anyOf": [
+ {
+ "description": "Result returned by V2BuildSchemaOperationWorkflow — the\n``/v2/extract/build-schema`` response body (VTRA ``BuildSchemaResponse``).\n\n``extraction_schema`` is the generated JSON Schema serialized as a STRING\n(VTRA parity — the v1 field is a string, not an object).",
+ "properties": {
+ "extraction_schema": {
+ "description": "The generated JSON schema as a string.",
+ "title": "Extraction Schema",
+ "type": "string"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/V2BuildSchemaMetadata",
+ "description": "The metadata for the schema generation process."
+ }
+ },
+ "required": [
+ "extraction_schema",
+ "metadata"
+ ],
+ "title": "V2BuildSchemaResponse",
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Present once status is ``completed``."
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "processing",
+ "completed",
+ "failed"
+ ],
+ "type": "string"
+ }
+ },
+ "type": "object"
+ }
+ }
+ },
+ "description": "Job status / result"
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Not found (e.g. no such job)."
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request validation failed."
+ }
+ },
+ "summary": "ADE Get Extract Jobs",
+ "tags": [
+ "Extract"
+ ]
+ }
+ },
"/v2/extract": {
"post": {
"description": "Extract structured data from a Markdown document according to a JSON schema, with character-span grounding into the source Markdown. Runs synchronously and returns the result inline.",
diff --git a/src/landingai_ade/types/v2/parse_response.py b/src/landingai_ade/types/v2/parse_response.py
index c2a1b28..4e0ce17 100644
--- a/src/landingai_ade/types/v2/parse_response.py
+++ b/src/landingai_ade/types/v2/parse_response.py
@@ -76,6 +76,11 @@ class V2ParseNodeGrounding(BaseModel):
page: Optional[int] = None
range: Optional[V2ParseRange] = None
box: Optional[V2ParseBox] = None
+ # How sure the model is of the text in this segment, in `[0, 1]`. Present only on
+ # word-granularity `atomic_grounding` entries (`dpt-3-fast`), where it is the
+ # lowest per-character OCR confidence in the word. Omitted on node-level grounding
+ # and on models that ground at line granularity (`dpt-3-pro`).
+ confidence: Optional[float] = None
# --- `structure`: the logical document tree ------------------------------------
@@ -96,8 +101,10 @@ class V2ParseElement(BaseModel):
span: Optional[List[int]] = None
# The element's spatial data (`{page, range, box}`), inline on the node.
grounding: Optional[V2ParseNodeGrounding] = None
- # Fine-grained grounding segments (visual lines today). Present on leaf
- # elements only; omitted entirely when `options.atomic_grounding` is false.
+ # Fine-grained grounding segments, at whichever granularity the model reads at:
+ # one entry per visual line for `dpt-3-pro`, one per word (each with its
+ # `confidence`) for `dpt-3-fast`. Present on leaf elements only; omitted entirely
+ # when `options.atomic_grounding` is false.
atomic_grounding: Optional[List[V2ParseNodeGrounding]] = None
# The element's slice of the top-level `markdown`; only when
# `options.inline_markdown` is true.
diff --git a/tests/api_resources/v2/test_parse.py b/tests/api_resources/v2/test_parse.py
index a05098b..b978900 100644
--- a/tests/api_resources/v2/test_parse.py
+++ b/tests/api_resources/v2/test_parse.py
@@ -134,6 +134,8 @@
"page": 1,
"range": {"start": 0, "end": 9},
"box": {"xmin": 0.1, "ymin": 0.1, "xmax": 0.9, "ymax": 0.2},
+ # Word-granularity (`dpt-3-fast`) segments carry a `confidence`.
+ "confidence": 0.91,
}
],
}
@@ -372,6 +374,9 @@ def test_parse_sync_inline_grounding_structure() -> None:
assert el.atomic_grounding is not None and len(el.atomic_grounding) == 1
seg = el.atomic_grounding[0]
assert seg.range is not None and seg.range.start == 0
+ # Word-granularity segments carry a `confidence`; node-level grounding does not.
+ assert seg.confidence == 0.91
+ assert el.grounding.confidence is None
assert result.metadata is not None
assert result.metadata.output_markdown_chars == 9
diff --git a/tests/contract/test_v2_smoke.py b/tests/contract/test_v2_smoke.py
index 3aaa58e..47f026f 100644
--- a/tests/contract/test_v2_smoke.py
+++ b/tests/contract/test_v2_smoke.py
@@ -84,6 +84,35 @@ def test_parse_sync_inline_grounding_and_metadata(staging_client: LandingAIADE)
assert resp.metadata.output_markdown_chars is not None
+def test_parse_sync_atomic_grounding_confidence(staging_client: LandingAIADE) -> None:
+ # Exercise the fine-grained `atomic_grounding` segments and the new per-segment
+ # `confidence` field. `confidence` is populated only on word-granularity models
+ # (`dpt-3-fast`), so where it is present assert it is a well-formed `[0, 1]`
+ # score; node-level grounding never carries it.
+ pdf = Path(__file__).parent / "sample.pdf"
+ resp = staging_client.v2.parse(document=pdf, options={"atomic_grounding": True})
+ assert isinstance(resp, V2ParseResponse)
+ assert resp.structure is not None and resp.structure.children
+
+ def leaves(el: object) -> Iterator[object]:
+ children = getattr(el, "children", None)
+ if children:
+ for child in children:
+ yield from leaves(child)
+ else:
+ yield el
+
+ saw_segment = False
+ for page in resp.structure.children:
+ assert page.grounding is None or page.grounding.confidence is None
+ for el in leaves(page):
+ for seg in getattr(el, "atomic_grounding", None) or []:
+ saw_segment = True
+ if seg.confidence is not None:
+ assert 0.0 <= seg.confidence <= 1.0
+ assert saw_segment
+
+
def test_ground_sync(staging_client: LandingAIADE) -> None:
# Ground is a stateless join: parse the doc, extract against it, then ground
# the extraction back onto the parse structure the markdown came from.
diff --git a/tests/test_v2_types.py b/tests/test_v2_types.py
index 76c3534..0819285 100644
--- a/tests/test_v2_types.py
+++ b/tests/test_v2_types.py
@@ -189,6 +189,8 @@ def test_parse_response_inline_grounding_and_metadata() -> None:
"page": 1,
"range": {"start": 0, "end": 4},
"box": {"xmin": 0.1, "ymin": 0.1, "xmax": 0.9, "ymax": 0.2},
+ # Word-granularity (`dpt-3-fast`) segments carry a `confidence`.
+ "confidence": 0.87,
}
],
}
@@ -221,6 +223,10 @@ def test_parse_response_inline_grounding_and_metadata() -> None:
assert el.atomic_grounding is not None and len(el.atomic_grounding) == 1
seg = el.atomic_grounding[0]
assert seg.range is not None and seg.range.start == 0
+ # `confidence` is populated on word-granularity segments and absent (None) on
+ # node-level grounding.
+ assert seg.confidence == 0.87
+ assert el.grounding.confidence is None
assert r.metadata is not None
assert r.metadata.output_markdown_chars == 4
assert r.metadata.range_units == "unicode_codepoints"