diff --git a/.github/workflows/pr-gates.yml b/.github/workflows/pr-gates.yml new file mode 100644 index 0000000..3aba0b7 --- /dev/null +++ b/.github/workflows/pr-gates.yml @@ -0,0 +1,52 @@ +name: PR Gates +on: + pull_request: + branches: [main] + +jobs: + surface-lock: + # Escape hatch for intentional breaking changes (major releases): label the PR + # `breaking-change-approved` to skip the released-surface lock. + if: ${{ !contains(github.event.pull_request.labels.*.name, 'breaking-change-approved') }} + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # tags for the release-tag baseline + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> "$GITHUB_PATH" + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + - name: Install dependencies + run: rye sync --all-features + - name: Surface lock (no breaking change to released API) + run: ./scripts/spec-sync/surface-lock.sh + + contract-tests: + # Only spec-sync PRs are gated on the live staging API. Gating *every* PR would let a + # staging outage or transient 5xx block all merges repo-wide. Keep this job OUT of the + # required-checks list for ordinary PRs (it is skipped there); require it only where it runs. + if: startsWith(github.head_ref, 'spec-sync/') + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> "$GITHUB_PATH" + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + - name: Install dependencies + run: rye sync --all-features + - name: Contract tests vs staging + env: + LANDINGAI_ADE_STAGING_APIKEY: ${{ secrets.LANDINGAI_ADE_STAGING_APIKEY }} + # -n 0 forces serial execution: these hit the live API, and the repo's default + # `-n auto` (xdist) would run them in parallel, inviting flakiness/rate-limits. + run: rye run pytest tests/contract -m contract -n 0 -v diff --git a/.github/workflows/spec-sync.yml b/.github/workflows/spec-sync.yml new file mode 100644 index 0000000..2970c1a --- /dev/null +++ b/.github/workflows/spec-sync.yml @@ -0,0 +1,136 @@ +name: Spec Sync +on: + schedule: + - cron: '0 * * * *' # hourly; cron covers correctness, dispatch cuts latency + workflow_dispatch: {} + +# Serialize runs so a cron tick and a manual dispatch can't race on the sync branch. +concurrency: + group: spec-sync + cancel-in-progress: false + +permissions: + contents: read # all writes go through SPEC_SYNC_TOKEN, never GITHUB_TOKEN + +jobs: + spec-sync: + # Guard so the scheduled/dispatch job only runs on the canonical repo — forks lack the + # secrets and would just produce noisy failing runs. + if: github.repository == 'landing-ai/ade-python' + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + V1_SPEC_URL: https://api.va.staging.landing.ai/v1/ade/openapi.json # staging drives the loop + SYNC_BRANCH: spec-sync/v1 # fixed branch: reruns update one PR in place, never a pile of them + steps: + - uses: actions/checkout@v6 + with: + # SPEC_SYNC_TOKEN is a fine-grained PAT (Contents: RW, Pull requests: RW) scoped to this + # repo. It must NOT be the default GITHUB_TOKEN: pushes/PRs authored by GITHUB_TOKEN do + # not trigger the gate workflows (anti-recursion). + token: ${{ secrets.SPEC_SYNC_TOKEN }} + fetch-depth: 0 # tags needed for surface-lock baseline + + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> "$GITHUB_PATH" + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Detect drift + id: drift + run: | + set +e + ./scripts/spec-sync/check-drift.sh "$V1_SPEC_URL" specs/v1-ade.json + echo "code=$?" >> "$GITHUB_OUTPUT" + + - name: No drift + if: steps.drift.outputs.code == '0' + run: echo "specs in sync; nothing to do." + + - name: Fail on fetch error + if: steps.drift.outputs.code != '0' && steps.drift.outputs.code != '10' + run: | + echo "spec fetch/normalize failed (exit ${{ steps.drift.outputs.code }})" + exit 1 + + # If a sync PR is already open, do nothing: it's awaiting human review, and re-running + # would duplicate gate runs and Claude API spend every hour until it merges. + - name: Check for an open sync PR + id: existing + if: steps.drift.outputs.code == '10' + env: + GH_TOKEN: ${{ secrets.SPEC_SYNC_TOKEN }} + run: | + if gh pr list --state open --head "$SYNC_BRANCH" --json number --jq '.[0].number' | grep -q .; then + echo "open=true" >> "$GITHUB_OUTPUT" + else + echo "open=false" >> "$GITHUB_OUTPUT" + fi + + - name: Sync PR already open — skip + if: steps.drift.outputs.code == '10' && steps.existing.outputs.open == 'true' + run: echo "A spec-sync PR is already open; skipping until it is merged or closed." + + # ---- Phase 1: mechanical (deterministic) ---- + - name: Mechanical commit + push + if: steps.drift.outputs.code == '10' && steps.existing.outputs.open != 'true' + run: | + git config user.name "spec-sync[bot]" + git config user.email "spec-sync@users.noreply.github.com" + git checkout -B "$SYNC_BRANCH" + ./scripts/spec-sync/gen-models.sh specs/v1-ade.json specs/_generated/v1_models.py + git add specs/v1-ade.json specs/_generated/v1_models.py + git commit -m "chore(spec-sync): update V1 spec snapshot + regenerated reference models" + git push --force origin "$SYNC_BRANCH" + + # Open the PR now — before the AI step — so a run that dies in phase 2 leaves a visible PR + # (with just the mechanical commit) instead of an orphan branch. + - name: Open sync PR + if: steps.drift.outputs.code == '10' && steps.existing.outputs.open != 'true' + env: + GH_TOKEN: ${{ secrets.SPEC_SYNC_TOKEN }} + run: | + gh pr create --base main --head "$SYNC_BRANCH" \ + --title "spec-sync: track V1 spec drift" \ + --body $'Automated spec-sync PR.\n\n- **Commit 1 (mechanical):** normalized spec snapshot + regenerated reference models.\n- **Commit 2 (AI):** resources/methods/tests/docs wired from the spec diff (added after this PR opened).\n\nGates (surface-lock, contract tests, lint/test/typecheck) must pass. **Human review required before merge.**' + + # ---- Phase 2: AI wiring — edits ONLY. The agent holds no git/push capability, so a + # prompt-injected spec description cannot route around review to push to a branch. ---- + - name: AI wiring (edits only) + if: steps.drift.outputs.code == '10' && steps.existing.outputs.open != 'true' + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.SPEC_SYNC_TOKEN }} + prompt: | + The previous commit updated specs/v1-ade.json and regenerated reference models in + specs/_generated/v1_models.py. Edit the SDK to match the spec diff: add or adjust + resource classes, method signatures, param TypedDicts, response models, tests, api.md, + and README examples, mirroring the conventions in + src/landingai_ade/resources/parse_jobs.py. Then run `./scripts/format`. + Rules: PURELY ADDITIVE — never modify or remove an existing public signature (the + surface-lock CI job will fail the PR). Do NOT run git or push; a later workflow step + commits and pushes your edits. + claude_args: | + --max-turns 40 + --allowedTools "Edit,Write,Read,Bash(rye *),Bash(./scripts/*)" + + # Deterministic: commit whatever the AI edited and push to the sync branch. This guarantees + # the AI changes actually reach the PR (the action's automation mode does not push), and it + # is the only step with push capability. + - name: Commit + push AI wiring + if: steps.drift.outputs.code == '10' && steps.existing.outputs.open != 'true' + run: | + git add -A + if git diff --cached --quiet; then + echo "AI step produced no changes." + else + git commit -m "feat(spec-sync): wire SDK to spec diff (AI)" + git push origin HEAD:"$SYNC_BRANCH" + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05466a6..7aa75e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,3 +126,44 @@ You can release to package managers by using [the `Publish PyPI` GitHub action]( If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on the environment. + +## Spec-sync pipeline + +The SDK tracks the live ADE OpenAPI spec automatically via `.github/workflows/spec-sync.yml` +(hourly cron + manual `workflow_dispatch`). It is driven by the **staging** spec; releases gate on +the **production** spec ("staging in, production out"). + +On each run it fetches and normalizes the live spec (`scripts/spec-sync/fetch-normalize.sh`) and +diffs it against the committed snapshot `specs/v1-ade.json` (`scripts/spec-sync/check-drift.sh`). +On drift it opens one PR with two attributed commits: + +1. **Mechanical** — updated `specs/v1-ade.json` snapshot plus regenerated *reference* models in + `specs/_generated/v1_models.py` (`scripts/spec-sync/gen-models.sh`, `datamodel-code-generator`). + These reference models are an input for the AI step and for review — they are **not** shipped and + do **not** replace `src/landingai_ade/types/*`. +2. **AI** — `anthropics/claude-code-action` (automation mode) wires the resources, methods, param + types, tests, and docs from the spec diff, following existing conventions. + +Every spec-sync PR (and any PR to `main`) must pass `.github/workflows/pr-gates.yml`: + +- **surface-lock** (`scripts/spec-sync/surface-lock.sh`, `griffe`) — baseline is the **last release + tag**, so any change to *released* public surface fails mechanically. Merged-but-unreleased surface + stays mutable. +- **contract-tests** — `tests/contract` (marker `contract`) run against staging when + `LANDINGAI_ADE_STAGING_APIKEY` is set; skipped otherwise. + +Spec-sync PRs are AI-drafted and **require human review** before merge. + +**Secrets required:** `SPEC_SYNC_TOKEN` (a fine-grained PAT scoped to this repo with +`Contents: Read and write` and `Pull requests: Read and write`), `ANTHROPIC_API_KEY`, and +`LANDINGAI_ADE_STAGING_APIKEY`. `SPEC_SYNC_TOKEN` must **not** be the default `GITHUB_TOKEN`: +pushes and PRs authored by `GITHUB_TOKEN` do not trigger the gate workflows (GitHub +anti-recursion), so the gates would never run on the sync PR. A GitHub App installation token +(org-owned) is the cleaner long-term choice and can replace the PAT without other workflow +changes. + +**V2:** once the aide gateway serves its curated spec unauthenticated, add `specs/v2-aide.json` and +its URL as a second drift-check in the workflow; the two-phase machinery is unchanged. + +The same pipeline shape ports to `ade-typescript` with `openapi-typescript` (mechanical) and +`api-extractor` (surface-lock), tracked separately. diff --git a/pyproject.toml b/pyproject.toml index 54f387f..7186974 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,10 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "rich>=13.7.1", "pytest-xdist>=3.6.1", + # Exact pins: codegen output is version-sensitive, so the committed reference models + # under specs/_generated/ must be generated by the same version CI installs. + "datamodel-code-generator==0.45.0", + "griffe==1.14.0", ] [tool.rye.scripts] @@ -130,10 +134,16 @@ replacement = '[\1](https://github.com/landing-ai/ade-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short -n auto" +# Contract tests (marker `contract`) hit the live staging API — opt-in only, so a plain +# `rye run pytest` (or the pydantic-v1 nox session) never silently calls staging even when +# LANDINGAI_ADE_STAGING_APIKEY is exported. CI's explicit `-m contract` overrides this. +addopts = "--tb=short -n auto -m 'not contract'" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" +markers = [ + "contract: hits the live staging API; requires LANDINGAI_ADE_STAGING_APIKEY", +] filterwarnings = [ "error" ] @@ -150,6 +160,7 @@ exclude = [ ".venv", ".nox", ".git", + "specs", ] reportImplicitOverride = true @@ -168,7 +179,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/landingai_ade/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ['src/landingai_ade/_files.py', '_dev/.*.py', 'tests/.*', 'specs/.*'] strict_equality = true implicit_reexport = true @@ -214,6 +225,9 @@ ignore_missing_imports = true line-length = 120 output-format = "grouped" target-version = "py38" +# specs/_generated holds datamodel-code-generator reference models (not shipped); +# they are not held to the SDK's lint standards. +extend-exclude = ["specs"] [tool.ruff.format] docstring-code-format = true diff --git a/requirements-dev.lock b/requirements-dev.lock index f07940f..f899736 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -23,6 +23,7 @@ anyio==4.12.1 # via httpx # via landingai-ade argcomplete==3.6.3 + # via datamodel-code-generator # via nox async-timeout==5.0.1 # via aiohttp @@ -31,11 +32,18 @@ attrs==25.4.0 # via nox backports-asyncio-runner==1.2.0 # via pytest-asyncio +black==25.11.0 + # via datamodel-code-generator certifi==2026.1.4 # via httpcore # via httpx +click==8.1.8 + # via black +colorama==0.4.6 + # via griffe colorlog==6.10.1 # via nox +datamodel-code-generator==0.45.0 dependency-groups==1.3.1 # via nox dirty-equals==0.11 @@ -53,6 +61,9 @@ filelock==3.19.1 frozenlist==1.8.0 # via aiohttp # via aiosignal +genson==1.3.1 + # via datamodel-code-generator +griffe==1.14.0 h11==0.16.0 # via httpcore httpcore==1.0.9 @@ -70,28 +81,45 @@ idna==3.11 # via httpx # via yarl importlib-metadata==8.7.1 + # via isort + # via typeguard +inflect==7.5.0 + # via datamodel-code-generator iniconfig==2.1.0 # via pytest +isort==6.1.0 + # via datamodel-code-generator +jinja2==3.1.6 + # via datamodel-code-generator markdown-it-py==3.0.0 # via rich +markupsafe==3.0.3 + # via jinja2 mdurl==0.1.2 # via markdown-it-py +more-itertools==10.8.0 + # via inflect multidict==6.7.0 # via aiohttp # via yarl mypy==1.17.0 mypy-extensions==1.1.0 + # via black # via mypy nodeenv==1.10.0 # via pyright nox==2025.11.12 packaging==25.0 + # via black + # via datamodel-code-generator # via dependency-groups # via nox # via pytest pathspec==1.0.3 + # via black # via mypy platformdirs==4.4.0 + # via black # via virtualenv pluggy==1.6.0 # via pytest @@ -99,6 +127,7 @@ propcache==0.4.1 # via aiohttp # via yarl pydantic==2.12.5 + # via datamodel-code-generator # via landingai-ade pydantic-core==2.41.5 # via pydantic @@ -113,6 +142,10 @@ pytest-asyncio==1.2.0 pytest-xdist==3.8.0 python-dateutil==2.9.0.post0 # via time-machine +pytokens==0.4.1 + # via black +pyyaml==6.0.3 + # via datamodel-code-generator respx==0.22.0 rich==14.2.0 ruff==0.14.13 @@ -122,13 +155,18 @@ sniffio==1.3.1 # via landingai-ade time-machine==2.19.0 tomli==2.4.0 + # via black + # via datamodel-code-generator # via dependency-groups # via mypy # via nox # via pytest +typeguard==4.5.2 + # via inflect typing-extensions==4.15.0 # via aiosignal # via anyio + # via black # via exceptiongroup # via landingai-ade # via multidict @@ -137,6 +175,7 @@ typing-extensions==4.15.0 # via pydantic-core # via pyright # via pytest-asyncio + # via typeguard # via typing-inspection # via virtualenv typing-inspection==0.4.2 diff --git a/scripts/spec-sync/check-drift.sh b/scripts/spec-sync/check-drift.sh new file mode 100755 index 0000000..1a788cf --- /dev/null +++ b/scripts/spec-sync/check-drift.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Compare a live spec against its committed snapshot. +# exit 0 -> no drift +# exit 10 -> drift detected; updated in place with the live spec +# other -> operational error (e.g. fetch failure) +set -euo pipefail + +if [ "$#" -ne 2 ]; then + echo "usage: check-drift.sh " >&2 + exit 2 +fi + +url="$1" +committed="$2" +here="$(cd "$(dirname "$0")" && pwd)" +tmp="$(mktemp)" +trap 'rm -f "$tmp"' EXIT + +"$here/fetch-normalize.sh" "$url" > "$tmp" + +if [ -f "$committed" ] && diff -q "$committed" "$tmp" >/dev/null; then + echo "no drift: $committed" + exit 0 +fi + +mkdir -p "$(dirname "$committed")" +cp "$tmp" "$committed" +echo "drift detected -> updated $committed" +exit 10 diff --git a/scripts/spec-sync/fetch-normalize.sh b/scripts/spec-sync/fetch-normalize.sh new file mode 100755 index 0000000..82213ce --- /dev/null +++ b/scripts/spec-sync/fetch-normalize.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Fetch a live OpenAPI spec and emit normalized (stable, key-sorted) JSON to stdout. +# Normalization makes byte-diffs meaningful: same spec content -> identical bytes. +set -euo pipefail + +if [ "$#" -ne 1 ]; then + echo "usage: fetch-normalize.sh " >&2 + exit 2 +fi + +url="$1" +# `jq -S` sorts object keys only; array element order (e.g. `required`, `enum`, `tags`) is +# preserved as emitted by the backend. This assumes the gateway emits arrays deterministically. +# If it ever reorders them, drift detection would fire on cosmetic churn (phantom PRs) — start +# debugging false drift here. +curl -fsSL --max-time 30 --retry 3 --retry-delay 2 "$url" | jq -S . diff --git a/scripts/spec-sync/gen-models.sh b/scripts/spec-sync/gen-models.sh new file mode 100755 index 0000000..74e6632 --- /dev/null +++ b/scripts/spec-sync/gen-models.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Generate REFERENCE pydantic models from a committed spec snapshot. +# Output is committed under specs/_generated/ as an input for the AI wiring phase +# and for human review — it is NOT shipped and does not replace src/landingai_ade/types/*. +set -euo pipefail + +if [ "$#" -ne 2 ]; then + echo "usage: gen-models.sh " >&2 + exit 2 +fi + +spec="$1" +out="$2" +mkdir -p "$(dirname "$out")" +rye run datamodel-codegen \ + --input "$spec" \ + --input-file-type openapi \ + --output "$out" \ + --output-model-type pydantic_v2.BaseModel \ + --use-standard-collections \ + --use-schema-description \ + --field-constraints \ + --disable-timestamp diff --git a/scripts/spec-sync/release-gate.sh b/scripts/spec-sync/release-gate.sh new file mode 100755 index 0000000..4fa8cf5 --- /dev/null +++ b/scripts/spec-sync/release-gate.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Block a release while the SDK implements a route that is not yet in PRODUCTION. +# (staging-only features hold the release train until they reach prod or are reverted.) +set -euo pipefail + +prod_url="https://api.va.landing.ai/v1/ade/openapi.json" +here="$(cd "$(dirname "$0")" && pwd)" +prod="$(mktemp)" +trap 'rm -f "$prod"' EXIT +"$here/fetch-normalize.sh" "$prod_url" > "$prod" + +# The committed staging snapshot is the surface we ship code for; every route in it +# must also exist in production before we release. A route is a (path, method) pair, +# so we compare at method granularity — a path present in prod but missing a specific +# method (e.g. a newly added POST) must still block the release. +missing="$(jq -r --slurpfile p "$prod" ' + ($p[0].paths) as $prod + | .paths + | to_entries[] + | .key as $path + | (.value | keys[]) as $method + | select(["get","put","post","delete","patch","options","head","trace"] | index($method)) + | select(($prod[$path] // {} | has($method)) | not) + | "\($method | ascii_upcase) \($path)" +' specs/v1-ade.json)" + +if [ -n "$missing" ]; then + echo "release-gate: these implemented routes are not in production yet:" >&2 + echo "$missing" >&2 + exit 1 +fi +echo "release-gate: all implemented routes exist in production." diff --git a/scripts/spec-sync/surface-lock.sh b/scripts/spec-sync/surface-lock.sh new file mode 100755 index 0000000..a217010 --- /dev/null +++ b/scripts/spec-sync/surface-lock.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Fail if the RELEASED public API of landingai_ade changed in a breaking way. +# Baseline = last release tag: released surface is the promise to users; merged-but- +# unreleased surface stays mutable. +set -euo pipefail + +baseline="$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || true)" +if [ -z "$baseline" ]; then + echo "surface-lock: no release tag found; skipping (nothing released yet)." + exit 0 +fi + +echo "surface-lock: checking landingai_ade against $baseline" +rye run griffe check landingai_ade --against "$baseline" --search src diff --git a/specs/_generated/v1_models.py b/specs/_generated/v1_models.py new file mode 100644 index 0000000..ca30b21 --- /dev/null +++ b/specs/_generated/v1_models.py @@ -0,0 +1,854 @@ +# generated by datamodel-codegen: +# filename: v1-ade.json + +from __future__ import annotations + +from enum import Enum +from typing import Any, Optional, Union + +from pydantic import BaseModel, Field, RootModel + + +class AsyncExtractRequest(BaseModel): + """ + Request model for async extract endpoint. + + Extends ExtractRequest with output_save_url for ZDR support. + """ + + markdown: Optional[Union[bytes, str]] = Field( + None, + description='The Markdown file or Markdown content to extract data from.', + title='Markdown', + ) + markdown_url: Optional[str] = Field( + None, + description='The URL to the Markdown file to extract data from.', + title='Markdown Url', + ) + model: Optional[str] = Field( + None, + description='The version of the model to use for extraction. Use `extract-latest` to use the latest version.', + title='Model', + ) + output_save_url: Optional[str] = Field( + None, + description='If zero data retention (ZDR) is enabled, you must enter a URL for the extracted output to be saved to. When ZDR is enabled, the extracted content will not be in the API response.', + title='Output Save Url', + ) + schema_: str = Field( + ..., + alias='schema', + description='JSON schema for field extraction. This schema determines what key-values pairs are extracted from the Markdown. The schema must be a valid JSON object and will be validated before processing the document.', + title='Schema', + ) + strict: Optional[bool] = Field( + False, + description='If True, reject schemas with unsupported fields (HTTP 422). If False, prune unsupported fields and continue. Only applies to extract versions that support schema validation.', + title='Strict', + ) + + +class BuildSchemaRequest(BaseModel): + markdown_urls: Optional[list[str]] = Field( + None, + description='URLs to Markdown files to analyze for schema generation.', + title='Markdown Urls', + ) + markdowns: Optional[list[Union[bytes, 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[str] = Field( + None, + description='The version of the model to use for schema generation. Use `extract-latest` to use the latest version.', + 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 ClassificationItem(BaseModel): + """ + A single page-level classification result. + """ + + class_: str = Field( + ..., + alias='class', + description="Predicted class label or 'unknown'.", + title='Class', + ) + page: int = Field(..., description='Page number (0-based).', title='Page') + reason: Optional[str] = Field( + '', description='Reason for the classification (for debugging).', title='Reason' + ) + suggested_class: Optional[str] = Field( + None, + description="Proposed class when the prediction is 'unknown'.", + title='Suggested Class', + ) + + +class ClassifyClass(BaseModel): + """ + A single classification option: a class name plus optional description. + """ + + class_: str = Field( + ..., alias='class', description='Name of the class.', title='Class' + ) + description: Optional[str] = Field( + None, + description='Detailed description of what this class represents.', + title='Description', + ) + + +class ClassifyMetadata(BaseModel): + """ + Metadata for the classify response. + """ + + credit_usage: float = Field(..., title='Credit Usage') + duration_ms: int = Field(..., title='Duration Ms') + filename: str = Field(..., title='Filename') + job_id: Optional[str] = Field('', title='Job Id') + org_id: Optional[str] = Field(None, title='Org Id') + page_count: int = Field(..., title='Page Count') + version: Optional[str] = Field(None, title='Version') + + +class ClassifyRequest(BaseModel): + classes: list[ClassifyClass] = Field( + ..., + description="The possible classes that can be assigned to pages in the document. Each entry is an object with a `class` name and an optional `description`. Only one class is assigned per page; unclassifiable pages receive 'unknown'. Can be provided as a JSON string in form data.", + title='Classes', + ) + document: Optional[bytes] = Field( + None, + description='A file to be classified. Either this parameter or the `document_url` parameter must be provided.', + title='Document', + ) + document_url: Optional[str] = Field( + None, + description='The URL of the document to be classified. Either this parameter or the `document` parameter must be provided.', + title='Document Url', + ) + model: Optional[str] = Field( + None, + description='Classification model version. Defaults to the latest.', + title='Model', + ) + + +class ClassifyResponse(BaseModel): + """ + Response model for the classify endpoint. + """ + + classification: list[ClassificationItem] = Field(..., title='Classification') + metadata: ClassifyMetadata + + +class CustomPromptKey(Enum): + """ + Chunk types that support custom prompts. + """ + + figure = 'figure' + + +class CustomPrompts(RootModel[dict[str, str]]): + """ + Map of chunk type to custom prompt string. + """ + + root: dict[str, str] + + +class ExtractRequest(BaseModel): + markdown: Optional[Union[bytes, str]] = Field( + None, + description='The Markdown file or Markdown content to extract data from.', + title='Markdown', + ) + markdown_url: Optional[str] = Field( + None, + description='The URL to the Markdown file to extract data from.', + title='Markdown Url', + ) + model: Optional[str] = Field( + None, + description='The version of the model to use for extraction. Use `extract-latest` to use the latest version.', + title='Model', + ) + schema_: str = Field( + ..., + alias='schema', + description='JSON schema for field extraction. This schema determines what key-values pairs are extracted from the Markdown. The schema must be a valid JSON object and will be validated before processing the document.', + title='Schema', + ) + strict: Optional[bool] = Field( + False, + description='If True, reject schemas with unsupported fields (HTTP 422). If False, prune unsupported fields and continue. Only applies to extract versions that support schema validation.', + title='Strict', + ) + + +class ExtractWarningCode(Enum): + nonconformant_schema = 'nonconformant_schema' + nonconformant_output = 'nonconformant_output' + + +class GroundingType(Enum): + chunkLogo = 'chunkLogo' + chunkCard = 'chunkCard' + chunkAttestation = 'chunkAttestation' + chunkScanCode = 'chunkScanCode' + chunkForm = 'chunkForm' + chunkTable = 'chunkTable' + chunkFigure = 'chunkFigure' + chunkText = 'chunkText' + chunkMarginalia = 'chunkMarginalia' + chunkTitle = 'chunkTitle' + chunkPageHeader = 'chunkPageHeader' + chunkPageFooter = 'chunkPageFooter' + chunkPageNumber = 'chunkPageNumber' + chunkKeyValue = 'chunkKeyValue' + table = 'table' + tableCell = 'tableCell' + + +class JobCreationResponse(BaseModel): + job_id: str = Field(..., title='Job Id') + + +class JobSummary(BaseModel): + """ + Summary of a job for listing. + """ + + created_at: Optional[int] = Field( + 0, + description='Unix timestamp (seconds) for when the job was created. Mirrors received_at; exposed so clients have an explicit creation time.', + title='Created At', + ) + failure_reason: Optional[str] = Field(None, title='Failure Reason') + job_id: str = Field(..., title='Job Id') + progress: float = Field( + ..., + description='Job completion as a decimal from 0 (not started) to 1 (complete).', + ge=0.0, + le=1.0, + title='Progress', + ) + received_at: int = Field(..., title='Received At') + status: str = Field(..., title='Status') + + +class JobsListResponse(BaseModel): + """ + Response for listing jobs. + """ + + has_more: Optional[bool] = Field(False, title='Has More') + jobs: list[JobSummary] = Field(..., title='Jobs') + org_id: Optional[str] = Field(None, title='Org Id') + + +class ParseGroundingBox(BaseModel): + bottom: float = Field(..., title='Bottom') + left: float = Field(..., title='Left') + right: float = Field(..., title='Right') + top: float = Field(..., title='Top') + + +class ParseMetadata(BaseModel): + credit_usage: float = Field(..., title='Credit Usage') + duration_ms: int = Field(..., title='Duration Ms') + failed_pages: Optional[list[int]] = Field(None, title='Failed Pages') + filename: str = Field(..., title='Filename') + job_id: str = Field(..., title='Job Id') + org_id: Optional[str] = Field(..., title='Org Id') + page_count: int = Field(..., title='Page Count') + version: Optional[str] = Field(..., title='Version') + + +class ParseResponseTableCellGroundingPosition(BaseModel): + chunk_id: str = Field(..., title='Chunk Id') + col: int = Field(..., title='Col') + colspan: int = Field(..., title='Colspan') + row: int = Field(..., title='Row') + rowspan: int = Field(..., title='Rowspan') + + +class ParseSplit(BaseModel): + chunks: list[str] = Field(..., title='Chunks') + class_: str = Field(..., alias='class', title='Class') + identifier: str = Field(..., title='Identifier') + markdown: str = Field(..., title='Markdown') + pages: list[int] = Field(..., title='Pages') + + +class Patch(BaseModel): + confidence: float = Field(..., title='Confidence') + span: list[Any] = Field(..., max_length=2, min_length=2, title='Span') + text: str = Field(..., title='Text') + + +class SectionMetadata(BaseModel): + """ + Public metadata for section response. + """ + + credit_usage: float = Field(..., title='Credit Usage') + duration_ms: int = Field(..., title='Duration Ms') + filename: str = Field(..., title='Filename') + job_id: Optional[str] = Field('', title='Job Id') + org_id: Optional[str] = Field(None, title='Org Id') + version: Optional[str] = Field(None, title='Version') + + +class SectionRequest(BaseModel): + """ + Request model for section endpoint. + """ + + guidelines: Optional[str] = Field( + None, + description="Natural-language instructions to control hierarchy. Examples: 'Group by topic', 'Treat each numbered section as a top-level entry'.", + title='Guidelines', + ) + markdown: Optional[Union[bytes, str]] = Field( + None, + description="Parsed markdown with reference anchors (). This is the markdown field from a parse response.", + title='Markdown', + ) + markdown_url: Optional[str] = Field( + None, description='URL to fetch the markdown from.', title='Markdown Url' + ) + model: Optional[str] = Field( + None, description='Section model version. Defaults to latest.', title='Model' + ) + + +class SectionTOCEntry(BaseModel): + """ + A single entry in the flat table of contents. + """ + + level: int = Field(..., title='Level') + section_number: str = Field(..., title='Section Number') + start_reference: str = Field(..., title='Start Reference') + title: str = Field(..., title='Title') + + +class SplitClass(BaseModel): + """ + Model for split classification option. + """ + + description: Optional[str] = Field( + None, + description='Detailed description of what this split type represents', + title='Description', + ) + identifier: Optional[str] = Field( + None, + description='Identifier to partition/group the splits by', + title='Identifier', + ) + name: str = Field( + ..., description='Name of the split classification type', title='Name' + ) + + +class SplitData(BaseModel): + """ + Split data for split classification endpoint. + """ + + classification: str = Field(..., title='Classification') + identifier: Optional[str] = Field(..., title='Identifier') + markdowns: list[str] = Field(..., title='Markdowns') + pages: list[int] = Field(..., title='Pages') + + +class SplitMetadata(BaseModel): + """ + Metadata for split classification response. + """ + + credit_usage: float = Field(..., title='Credit Usage') + duration_ms: int = Field(..., title='Duration Ms') + filename: str = Field(..., title='Filename') + job_id: Optional[str] = Field( + '', description='Inference history job ID', title='Job Id' + ) + org_id: Optional[str] = Field(None, description='Organization ID', title='Org Id') + page_count: int = Field(..., title='Page Count') + version: Optional[str] = Field( + None, description='Model version used for split classification', title='Version' + ) + + +class SplitRequest(BaseModel): + """ + Request model for split classification endpoint. + """ + + markdown: Optional[Union[bytes, str]] = Field( + None, + description='The Markdown file or Markdown content to split.', + title='Markdown', + ) + markdown_url: Optional[str] = Field( + None, description='The URL to the Markdown file to split.', title='Markdown Url' + ) + model: Optional[str] = Field( + 'split-20251105', + description='Model version to use for split classification. Defaults to the latest version.', + title='Model', + ) + split_class: list[SplitClass] = Field( + ..., + description='List of split classification options/configuration. Can be provided as JSON string in form data.', + title='Split Class', + ) + + +class SplitResponse(BaseModel): + """ + Response model for split classification endpoint. + """ + + metadata: SplitMetadata + splits: list[SplitData] = Field(..., title='Splits') + + +class SplitType(Enum): + page = 'page' + + +class SpreadsheetParseMetadata(BaseModel): + """ + Metadata for spreadsheet parsing result. + """ + + credit_usage: Optional[float] = Field( + 0.0, description='Credits charged', title='Credit Usage' + ) + duration_ms: int = Field( + ..., description='Processing duration in milliseconds', title='Duration Ms' + ) + filename: str = Field(..., description='Original filename', title='Filename') + job_id: Optional[str] = Field( + '', description='Inference history job ID', title='Job Id' + ) + org_id: Optional[str] = Field(None, description='Organization ID', title='Org Id') + sheet_count: int = Field( + ..., description='Number of sheets processed', title='Sheet Count' + ) + total_cells: int = Field( + ..., description='Total non-empty cells across all sheets', title='Total Cells' + ) + total_chunks: int = Field( + ..., + description='Total chunks (tables + images) extracted', + title='Total Chunks', + ) + total_images: Optional[int] = Field( + 0, description='Total images extracted', title='Total Images' + ) + total_rows: int = Field( + ..., description='Total rows across all sheets', title='Total Rows' + ) + version: Optional[str] = Field( + None, description='Model version for parsing images', title='Version' + ) + + +class SpreadsheetSplit(BaseModel): + """ + Sheet-based split from spreadsheet parsing. + + Similar to ParseSplit but grouped by sheet instead of page. + Supports both 'page' (per-sheet) and 'full' (all sheets) split types. + """ + + chunks: list[str] = Field( + ..., description='Chunk IDs in this split', title='Chunks' + ) + class_: str = Field( + ..., + alias='class', + description="Split class: 'page' for per-sheet splits, 'full' for single split with all content", + title='Class', + ) + identifier: str = Field( + ..., + description="Split identifier: sheet name for 'page' splits, 'full' for full split", + title='Identifier', + ) + markdown: str = Field( + ..., description='Combined markdown for this split', title='Markdown' + ) + sheets: list[int] = Field( + ..., + description="Sheet indices: single element for 'page' splits, all indices for 'full' split", + title='Sheets', + ) + + +class ValidationError(BaseModel): + loc: list[Union[str, int]] = Field(..., title='Location') + msg: str = Field(..., title='Message') + type: str = Field(..., title='Error Type') + + +class AsyncParseRequestWithEncryptedPassword(BaseModel): + custom_prompts: Optional[str] = Field( + None, + description='Optional JSON string mapping chunk types to custom parsing prompts. Only the `figure` key is supported, for example \'{"figure":"Describe axis labels in detail."}\'.', + title='Custom Prompts', + ) + document: Optional[bytes] = Field( + None, + description='A file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document_url` parameter must be provided.', + title='Document', + ) + document_url: Optional[str] = Field( + None, + description='The URL to the file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document` parameter must be provided.', + title='Document Url', + ) + model: Optional[str] = Field( + None, description='The version of the model to use for parsing.', title='Model' + ) + output_save_url: Optional[str] = Field( + None, + description='If zero data retention (ZDR) is enabled, you must enter a URL for the parsed output to be saved to. When ZDR is enabled, the parsed content will not be in the API response.', + title='Output Save Url', + ) + password: Optional[str] = Field( + None, + description='Password for encrypted document files. If the document is password-protected, provide the password to decrypt and process the document. Ignored for unencrypted documents.', + title='Password', + ) + split: Optional[SplitType] = Field( + None, + description='If you want to split documents into smaller sections, include the split parameter. Set the parameter to page to split documents at the page level. The splits object in the API output will contain a set of data for each page.', + ) + + +class ExtractWarning(BaseModel): + code: ExtractWarningCode = Field( + ..., + description='The type of warning, used to translate to a status code downstream', + ) + msg: str = Field( + ..., + description='Human-readable description of the warning with more details', + title='Msg', + ) + + +class HTTPValidationError(BaseModel): + detail: Optional[list[ValidationError]] = Field(None, title='Detail') + + +class ParseGrounding(BaseModel): + box: ParseGroundingBox + page: int = Field(..., title='Page') + + +class ParseRequestWithEncryptedPassword(BaseModel): + custom_prompts: Optional[str] = Field( + None, + description='Optional JSON string mapping chunk types to custom parsing prompts. Only the `figure` key is supported, for example \'{"figure":"Describe axis labels in detail."}\'.', + title='Custom Prompts', + ) + document: Optional[bytes] = Field( + None, + description='A file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document_url` parameter must be provided.', + title='Document', + ) + document_url: Optional[str] = Field( + None, + description='The URL to the file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document` parameter must be provided.', + title='Document Url', + ) + model: Optional[str] = Field( + None, description='The version of the model to use for parsing.', title='Model' + ) + password: Optional[str] = Field( + None, + description='Password for encrypted document files. If the document is password-protected, provide the password to decrypt and process the document. Ignored for unencrypted documents.', + title='Password', + ) + split: Optional[SplitType] = Field( + None, + description='If you want to split documents into smaller sections, include the split parameter. Set the parameter to page to split documents at the page level. The splits object in the API output will contain a set of data for each page.', + ) + + +class ParseResponseGrounding(BaseModel): + box: ParseGroundingBox + confidence: Optional[float] = Field(None, title='Confidence') + low_confidence_spans: Optional[list[Patch]] = Field( + None, title='Low Confidence Spans' + ) + page: int = Field(..., title='Page') + type: GroundingType + + +class ParseResponseTableCellGrounding(BaseModel): + box: ParseGroundingBox + confidence: Optional[float] = Field(None, title='Confidence') + page: int = Field(..., title='Page') + position: Optional[ParseResponseTableCellGroundingPosition] = None + type: GroundingType + + +class SectionResponse(BaseModel): + """ + Response model for section endpoint. + """ + + metadata: SectionMetadata + table_of_contents: list[SectionTOCEntry] = Field(..., title='Table Of Contents') + table_of_contents_md: str = Field(..., title='Table Of Contents Md') + + +class SpreadsheetChunk(BaseModel): + """ + Chunk from spreadsheet parsing. + + Can represent: + - Table chunks from spreadsheet cells + - Parsed content chunks from embedded images (text, table, figure, etc.) + """ + + grounding: Optional[ParseGrounding] = Field( + None, + description='Visual grounding coordinates from /parse API (only for chunks derived from embedded images)', + ) + id: str = Field( + ..., + description="Chunk ID - format: '{sheet_name}-{cell_range}' for tables, '{sheet_name}-image-{index}-{anchor_cell}-chunk-{i}-{type}' for parsed image chunks", + title='Id', + ) + markdown: str = Field( + ..., + description='Chunk content as HTML table with anchor tag (for tables) or parsed markdown content (for chunks from images)', + title='Markdown', + ) + type: str = Field( + ..., + description="Chunk type: 'table' for spreadsheet tables, or types from /parse (text, table, figure, form, etc.) for chunks derived from embedded images", + title='Type', + ) + + +class SpreadsheetParseResponse(BaseModel): + """ + Response from /ade/parse-spreadsheet endpoint. + + Similar structure to ParseResponse but without grounding. + """ + + chunks: list[SpreadsheetChunk] = Field( + ..., description='List of table chunks (HTML)', title='Chunks' + ) + markdown: str = Field( + ..., + description='Full document as HTML with anchor tags and tables', + title='Markdown', + ) + metadata: SpreadsheetParseMetadata = Field(..., description='Parsing metadata') + splits: list[SpreadsheetSplit] = Field( + ..., description='Sheet-based splits', title='Splits' + ) + + +class BuildSchemaMetadata(BaseModel): + credit_usage: Optional[float] = Field(0.0, title='Credit Usage') + duration_ms: Optional[int] = Field(0, title='Duration Ms') + filename: Optional[str] = Field(None, title='Filename') + job_id: Optional[str] = Field('', title='Job Id') + org_id: Optional[str] = Field(None, title='Org Id') + version: Optional[str] = Field(None, title='Version') + warnings: Optional[list[ExtractWarning]] = Field( + None, + description="Structured warnings from the extraction process. Each warning is an instance of ExtractWarning with 'code' (e.g. 'nonconformant_schema') and 'msg' (human-readable description). Present only for extract versions from extract-20260314 and above that support structured warnings.", + title='Warnings', + ) + + +class BuildSchemaResponse(BaseModel): + extraction_schema: str = Field( + ..., + description='The generated JSON schema as a string.', + title='Extraction Schema', + ) + metadata: BuildSchemaMetadata = Field( + ..., description='The metadata for the schema generation process.' + ) + + +class ExtractMetadata(BaseModel): + credit_usage: float = Field(..., title='Credit Usage') + duration_ms: int = Field(..., title='Duration Ms') + fallback_model_version: Optional[str] = Field( + None, + description='The extract model that was actually used to extract the data when the initial extraction attempt failed with the requested version.', + title='Fallback Model Version', + ) + filename: str = Field(..., title='Filename') + job_id: str = Field(..., title='Job Id') + org_id: Optional[str] = Field(..., title='Org Id') + schema_violation_error: Optional[str] = Field( + None, + description='A detailed error message shows why the extracted data does not fully conform to the input schema. Null means the extraction result is consistent with the input schema.', + title='Schema Violation Error', + ) + version: Optional[str] = Field(..., title='Version') + warnings: Optional[list[ExtractWarning]] = Field( + None, + description="Structured warnings from the extraction process. Each warning is an instance of ExtractWarning with 'code' (e.g. 'nonconformant_schema') and 'msg' (human-readable description). Present only for extract versions from extract-20260314 and above that support structured warnings.", + title='Warnings', + ) + + +class ExtractResponse(BaseModel): + extraction: dict[str, Any] = Field( + ..., description='The extracted key-value pairs.', title='Extraction' + ) + extraction_metadata: dict[str, Any] = Field( + ..., + description='The extracted key-value pairs and the chunk_reference for each one.', + title='Extraction Metadata', + ) + metadata: ExtractMetadata = Field( + ..., description='The metadata for the extraction process.' + ) + + +class ParseChunk(BaseModel): + grounding: ParseGrounding + id: str = Field(..., title='Id') + markdown: str = Field(..., title='Markdown') + type: str = Field(..., title='Type') + + +class ParseResponse(BaseModel): + chunks: list[ParseChunk] = Field(..., title='Chunks') + grounding: Optional[ + dict[str, Union[ParseResponseGrounding, ParseResponseTableCellGrounding]] + ] = Field(None, title='Grounding') + markdown: str = Field(..., title='Markdown') + metadata: ParseMetadata + splits: list[ParseSplit] = Field(..., title='Splits') + + +class ExtractJobStatusResponse(BaseModel): + """ + The status of an extract job, plus the results once it completes. + """ + + created_at: Optional[int] = Field( + 0, + description='Unix timestamp (in seconds) for when the job was created.', + title='Created At', + ) + data: Optional[ExtractResponse] = Field( + None, + description='The extraction results, returned here when the job is complete and you did not set an `output_save_url`. Large results are returned through `output_url` instead.', + ) + failure_reason: Optional[str] = Field( + None, + description='If the job failed, a message describing what went wrong.', + title='Failure Reason', + ) + job_id: str = Field( + ..., description='A unique identifier for this extract job.', title='Job Id' + ) + metadata: Optional[ExtractMetadata] = Field( + None, + description='Information about the extraction, such as the model version, duration, credit usage, and any schema warnings.', + ) + org_id: Optional[str] = Field(None, description='Organization ID.', title='Org Id') + output_url: Optional[str] = Field( + None, + description='A URL to download the extraction results. Provided when the job is complete and either you set an `output_save_url` or the result is larger than 1 MB. URLs for large results are temporary and expire one hour after you request the job.', + title='Output Url', + ) + progress: float = Field( + ..., + description='Job completion. Either 0.0 (not yet complete) or 1.0 (complete).', + ge=0.0, + le=1.0, + title='Progress', + ) + received_at: int = Field( + ..., + description='Unix timestamp (in seconds) for when the job was received.', + title='Received At', + ) + status: str = Field( + ..., + description='The current state of the job: `pending`, `processing`, `completed`, `failed`, or `cancelled`.', + title='Status', + ) + version: Optional[str] = Field( + None, + description='The exact model snapshot used for the extraction.', + title='Version', + ) + + +class JobStatusResponse(BaseModel): + """ + Unified response for job status endpoint. + """ + + created_at: Optional[int] = Field( + 0, + description='Unix timestamp (seconds) for when the job was created. Mirrors received_at; exposed so clients have an explicit creation time.', + title='Created At', + ) + data: Optional[Union[ParseResponse, SpreadsheetParseResponse]] = Field( + None, + description='The parsed output (ParseResponse for documents, SpreadsheetParseResponse for spreadsheets), if the job is complete and the `output_save_url` parameter was not used.', + title='Data', + ) + failure_reason: Optional[str] = Field(None, title='Failure Reason') + job_id: str = Field(..., title='Job Id') + metadata: Optional[ParseMetadata] = None + org_id: Optional[str] = Field(None, title='Org Id') + output_url: Optional[str] = Field( + None, + description='The URL to the parsed content. This field contains a URL when the job is complete and either you specified the `output_save_url` parameter or the result is larger than 1MB. When the result exceeds 1MB, the URL is a presigned S3 URL that expires after 1 hour. Each time you GET the job, a new presigned URL is generated.', + title='Output Url', + ) + progress: float = Field( + ..., + description='Job completion progress as a decimal from 0 to 1, where 0 is not started, 1 is finished, and values between 0 and 1 indicate work in progress.', + ge=0.0, + le=1.0, + title='Progress', + ) + received_at: int = Field(..., title='Received At') + status: str = Field(..., title='Status') + version: Optional[str] = Field(None, title='Version') diff --git a/specs/v1-ade.json b/specs/v1-ade.json new file mode 100644 index 0000000..be7d01e --- /dev/null +++ b/specs/v1-ade.json @@ -0,0 +1,2660 @@ +{ + "components": { + "schemas": { + "AsyncExtractRequest": { + "description": "Request model for async extract endpoint.\n\nExtends ExtractRequest with output_save_url for ZDR support.", + "properties": { + "markdown": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The Markdown file or Markdown content to extract data from.", + "title": "Markdown" + }, + "markdown_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The URL to the Markdown file to extract data from.", + "title": "Markdown Url" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The version of the model to use for extraction. Use `extract-latest` to use the latest version.", + "title": "Model" + }, + "output_save_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "If zero data retention (ZDR) is enabled, you must enter a URL for the extracted output to be saved to. When ZDR is enabled, the extracted content will not be in the API response.", + "title": "Output Save Url" + }, + "schema": { + "description": "JSON schema for field extraction. This schema determines what key-values pairs are extracted from the Markdown. The schema must be a valid JSON object and will be validated before processing the document.", + "title": "Schema", + "type": "string" + }, + "strict": { + "default": false, + "description": "If True, reject schemas with unsupported fields (HTTP 422). If False, prune unsupported fields and continue. Only applies to extract versions that support schema validation.", + "title": "Strict", + "type": "boolean" + } + }, + "required": [ + "schema" + ], + "title": "AsyncExtractRequest", + "type": "object" + }, + "AsyncParseRequestWithEncryptedPassword": { + "properties": { + "custom_prompts": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "$ref": "#/components/schemas/CustomPrompts" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Optional JSON string mapping chunk types to custom parsing prompts. Only the `figure` key is supported, for example '{\"figure\":\"Describe axis labels in detail.\"}'.", + "title": "Custom Prompts" + }, + "document": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "A file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document_url` parameter must be provided.", + "title": "Document" + }, + "document_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The URL to the file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document` parameter must be provided.", + "title": "Document Url" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The version of the model to use for parsing.", + "title": "Model" + }, + "output_save_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "If zero data retention (ZDR) is enabled, you must enter a URL for the parsed output to be saved to. When ZDR is enabled, the parsed content will not be in the API response.", + "title": "Output Save Url" + }, + "password": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Password for encrypted document files. If the document is password-protected, provide the password to decrypt and process the document. Ignored for unencrypted documents.", + "title": "Password" + }, + "split": { + "anyOf": [ + { + "$ref": "#/components/schemas/SplitType" + }, + { + "type": "null" + } + ], + "description": "If you want to split documents into smaller sections, include the split parameter. Set the parameter to page to split documents at the page level. The splits object in the API output will contain a set of data for each page." + } + }, + "title": "AsyncParseRequestWithEncryptedPassword", + "type": "object" + }, + "BuildSchemaMetadata": { + "properties": { + "credit_usage": { + "default": 0.0, + "title": "Credit Usage", + "type": "number" + }, + "duration_ms": { + "default": 0, + "title": "Duration Ms", + "type": "integer" + }, + "filename": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filename" + }, + "job_id": { + "default": "", + "title": "Job Id", + "type": "string" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Org Id" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + }, + "warnings": { + "description": "Structured warnings from the extraction process. Each warning is an instance of ExtractWarning with 'code' (e.g. 'nonconformant_schema') and 'msg' (human-readable description). Present only for extract versions from extract-20260314 and above that support structured warnings.", + "items": { + "$ref": "#/components/schemas/ExtractWarning" + }, + "title": "Warnings", + "type": "array" + } + }, + "title": "BuildSchemaMetadata", + "type": "object" + }, + "BuildSchemaRequest": { + "properties": { + "markdown_urls": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "URLs to Markdown files to analyze for schema generation.", + "title": "Markdown Urls" + }, + "markdowns": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "type": "array" + }, + { + "type": "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": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The version of the model to use for schema generation. Use `extract-latest` to use the latest version.", + "title": "Model" + }, + "prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Instructions for how to generate or modify the schema.", + "title": "Prompt" + }, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Existing JSON schema to iterate on or refine.", + "title": "Schema" + } + }, + "title": "BuildSchemaRequest", + "type": "object" + }, + "BuildSchemaResponse": { + "properties": { + "extraction_schema": { + "description": "The generated JSON schema as a string.", + "title": "Extraction Schema", + "type": "string" + }, + "metadata": { + "$ref": "#/components/schemas/BuildSchemaMetadata", + "description": "The metadata for the schema generation process." + } + }, + "required": [ + "extraction_schema", + "metadata" + ], + "title": "BuildSchemaResponse", + "type": "object" + }, + "ClassificationItem": { + "description": "A single page-level classification result.", + "properties": { + "class": { + "description": "Predicted class label or 'unknown'.", + "title": "Class", + "type": "string" + }, + "page": { + "description": "Page number (0-based).", + "title": "Page", + "type": "integer" + }, + "reason": { + "default": "", + "description": "Reason for the classification (for debugging).", + "title": "Reason", + "type": "string" + }, + "suggested_class": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Proposed class when the prediction is 'unknown'.", + "title": "Suggested Class" + } + }, + "required": [ + "class", + "page" + ], + "title": "ClassificationItem", + "type": "object" + }, + "ClassifyClass": { + "description": "A single classification option: a class name plus optional description.", + "properties": { + "class": { + "description": "Name of the class.", + "title": "Class", + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Detailed description of what this class represents.", + "title": "Description" + } + }, + "required": [ + "class" + ], + "title": "ClassifyClass", + "type": "object" + }, + "ClassifyMetadata": { + "description": "Metadata for the classify response.", + "properties": { + "credit_usage": { + "title": "Credit Usage", + "type": "number" + }, + "duration_ms": { + "title": "Duration Ms", + "type": "integer" + }, + "filename": { + "title": "Filename", + "type": "string" + }, + "job_id": { + "default": "", + "title": "Job Id", + "type": "string" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Org Id" + }, + "page_count": { + "title": "Page Count", + "type": "integer" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + } + }, + "required": [ + "filename", + "page_count", + "duration_ms", + "credit_usage" + ], + "title": "ClassifyMetadata", + "type": "object" + }, + "ClassifyRequest": { + "properties": { + "classes": { + "description": "The possible classes that can be assigned to pages in the document. Each entry is an object with a `class` name and an optional `description`. Only one class is assigned per page; unclassifiable pages receive 'unknown'. Can be provided as a JSON string in form data.", + "items": { + "$ref": "#/components/schemas/ClassifyClass" + }, + "title": "Classes", + "type": "array" + }, + "document": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "A file to be classified. Either this parameter or the `document_url` parameter must be provided.", + "title": "Document" + }, + "document_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The URL of the document to be classified. Either this parameter or the `document` parameter must be provided.", + "title": "Document Url" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Classification model version. Defaults to the latest.", + "title": "Model" + } + }, + "required": [ + "classes" + ], + "title": "ClassifyRequest", + "type": "object" + }, + "ClassifyResponse": { + "description": "Response model for the classify endpoint.", + "properties": { + "classification": { + "items": { + "$ref": "#/components/schemas/ClassificationItem" + }, + "title": "Classification", + "type": "array" + }, + "metadata": { + "$ref": "#/components/schemas/ClassifyMetadata" + } + }, + "required": [ + "classification", + "metadata" + ], + "title": "ClassifyResponse", + "type": "object" + }, + "CustomPromptKey": { + "const": "figure", + "description": "Chunk types that support custom prompts.", + "enum": [ + "figure" + ], + "title": "CustomPromptKey", + "type": "string" + }, + "CustomPrompts": { + "additionalProperties": { + "type": "string" + }, + "description": "Map of chunk type to custom prompt string.", + "title": "CustomPrompts", + "type": "object" + }, + "ExtractJobStatusResponse": { + "description": "The status of an extract job, plus the results once it completes.", + "properties": { + "created_at": { + "default": 0, + "description": "Unix timestamp (in seconds) for when the job was created.", + "title": "Created At", + "type": "integer" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ExtractResponse" + }, + { + "type": "null" + } + ], + "description": "The extraction results, returned here when the job is complete and you did not set an `output_save_url`. Large results are returned through `output_url` instead." + }, + "failure_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "If the job failed, a message describing what went wrong.", + "title": "Failure Reason" + }, + "job_id": { + "description": "A unique identifier for this extract job.", + "title": "Job Id", + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "$ref": "#/components/schemas/ExtractMetadata" + }, + { + "type": "null" + } + ], + "description": "Information about the extraction, such as the model version, duration, credit usage, and any schema warnings." + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Organization ID.", + "title": "Org Id" + }, + "output_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "A URL to download the extraction results. Provided when the job is complete and either you set an `output_save_url` or the result is larger than 1 MB. URLs for large results are temporary and expire one hour after you request the job.", + "title": "Output Url" + }, + "progress": { + "description": "Job completion. Either 0.0 (not yet complete) or 1.0 (complete).", + "maximum": 1.0, + "minimum": 0.0, + "title": "Progress", + "type": "number" + }, + "received_at": { + "description": "Unix timestamp (in seconds) for when the job was received.", + "title": "Received At", + "type": "integer" + }, + "status": { + "description": "The current state of the job: `pending`, `processing`, `completed`, `failed`, or `cancelled`.", + "title": "Status", + "type": "string" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The exact model snapshot used for the extraction.", + "title": "Version" + } + }, + "required": [ + "job_id", + "status", + "received_at", + "progress" + ], + "title": "ExtractJobStatusResponse", + "type": "object" + }, + "ExtractMetadata": { + "properties": { + "credit_usage": { + "title": "Credit Usage", + "type": "number" + }, + "duration_ms": { + "title": "Duration Ms", + "type": "integer" + }, + "fallback_model_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The extract model that was actually used to extract the data when the initial extraction attempt failed with the requested version.", + "title": "Fallback Model Version" + }, + "filename": { + "title": "Filename", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Org Id" + }, + "schema_violation_error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "A detailed error message shows why the extracted data does not fully conform to the input schema. Null means the extraction result is consistent with the input schema.", + "title": "Schema Violation Error" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + }, + "warnings": { + "description": "Structured warnings from the extraction process. Each warning is an instance of ExtractWarning with 'code' (e.g. 'nonconformant_schema') and 'msg' (human-readable description). Present only for extract versions from extract-20260314 and above that support structured warnings.", + "items": { + "$ref": "#/components/schemas/ExtractWarning" + }, + "title": "Warnings", + "type": "array" + } + }, + "required": [ + "filename", + "org_id", + "duration_ms", + "credit_usage", + "job_id", + "version" + ], + "title": "ExtractMetadata", + "type": "object" + }, + "ExtractRequest": { + "properties": { + "markdown": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The Markdown file or Markdown content to extract data from.", + "title": "Markdown" + }, + "markdown_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The URL to the Markdown file to extract data from.", + "title": "Markdown Url" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The version of the model to use for extraction. Use `extract-latest` to use the latest version.", + "title": "Model" + }, + "schema": { + "description": "JSON schema for field extraction. This schema determines what key-values pairs are extracted from the Markdown. The schema must be a valid JSON object and will be validated before processing the document.", + "title": "Schema", + "type": "string" + }, + "strict": { + "default": false, + "description": "If True, reject schemas with unsupported fields (HTTP 422). If False, prune unsupported fields and continue. Only applies to extract versions that support schema validation.", + "title": "Strict", + "type": "boolean" + } + }, + "required": [ + "schema" + ], + "title": "ExtractRequest", + "type": "object" + }, + "ExtractResponse": { + "properties": { + "extraction": { + "description": "The extracted key-value pairs.", + "title": "Extraction", + "type": "object" + }, + "extraction_metadata": { + "description": "The extracted key-value pairs and the chunk_reference for each one.", + "title": "Extraction Metadata", + "type": "object" + }, + "metadata": { + "$ref": "#/components/schemas/ExtractMetadata", + "description": "The metadata for the extraction process." + } + }, + "required": [ + "extraction", + "extraction_metadata", + "metadata" + ], + "title": "ExtractResponse", + "type": "object" + }, + "ExtractWarning": { + "properties": { + "code": { + "$ref": "#/components/schemas/ExtractWarningCode", + "description": "The type of warning, used to translate to a status code downstream" + }, + "msg": { + "description": "Human-readable description of the warning with more details", + "title": "Msg", + "type": "string" + } + }, + "required": [ + "code", + "msg" + ], + "title": "ExtractWarning", + "type": "object" + }, + "ExtractWarningCode": { + "enum": [ + "nonconformant_schema", + "nonconformant_output" + ], + "title": "ExtractWarningCode", + "type": "string" + }, + "GroundingType": { + "enum": [ + "chunkLogo", + "chunkCard", + "chunkAttestation", + "chunkScanCode", + "chunkForm", + "chunkTable", + "chunkFigure", + "chunkText", + "chunkMarginalia", + "chunkTitle", + "chunkPageHeader", + "chunkPageFooter", + "chunkPageNumber", + "chunkKeyValue", + "table", + "tableCell" + ], + "title": "GroundingType", + "type": "string" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "title": "Detail", + "type": "array" + } + }, + "title": "HTTPValidationError", + "type": "object" + }, + "JobCreationResponse": { + "properties": { + "job_id": { + "title": "Job Id", + "type": "string" + } + }, + "required": [ + "job_id" + ], + "title": "JobCreationResponse", + "type": "object" + }, + "JobStatusResponse": { + "description": "Unified response for job status endpoint.", + "properties": { + "created_at": { + "default": 0, + "description": "Unix timestamp (seconds) for when the job was created. Mirrors received_at; exposed so clients have an explicit creation time.", + "title": "Created At", + "type": "integer" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ParseResponse" + }, + { + "$ref": "#/components/schemas/SpreadsheetParseResponse" + }, + { + "type": "null" + } + ], + "description": "The parsed output (ParseResponse for documents, SpreadsheetParseResponse for spreadsheets), if the job is complete and the `output_save_url` parameter was not used.", + "title": "Data" + }, + "failure_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Failure Reason" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "$ref": "#/components/schemas/ParseMetadata" + }, + { + "type": "null" + } + ] + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Org Id" + }, + "output_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The URL to the parsed content. This field contains a URL when the job is complete and either you specified the `output_save_url` parameter or the result is larger than 1MB. When the result exceeds 1MB, the URL is a presigned S3 URL that expires after 1 hour. Each time you GET the job, a new presigned URL is generated.", + "title": "Output Url" + }, + "progress": { + "description": "Job completion progress as a decimal from 0 to 1, where 0 is not started, 1 is finished, and values between 0 and 1 indicate work in progress.", + "maximum": 1.0, + "minimum": 0.0, + "title": "Progress", + "type": "number" + }, + "received_at": { + "title": "Received At", + "type": "integer" + }, + "status": { + "title": "Status", + "type": "string" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + } + }, + "required": [ + "job_id", + "status", + "received_at", + "progress" + ], + "title": "JobStatusResponse", + "type": "object" + }, + "JobSummary": { + "description": "Summary of a job for listing.", + "properties": { + "created_at": { + "default": 0, + "description": "Unix timestamp (seconds) for when the job was created. Mirrors received_at; exposed so clients have an explicit creation time.", + "title": "Created At", + "type": "integer" + }, + "failure_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Failure Reason" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "progress": { + "description": "Job completion as a decimal from 0 (not started) to 1 (complete).", + "maximum": 1.0, + "minimum": 0.0, + "title": "Progress", + "type": "number" + }, + "received_at": { + "title": "Received At", + "type": "integer" + }, + "status": { + "title": "Status", + "type": "string" + } + }, + "required": [ + "job_id", + "status", + "received_at", + "progress" + ], + "title": "JobSummary", + "type": "object" + }, + "JobsListResponse": { + "description": "Response for listing jobs.", + "properties": { + "has_more": { + "default": false, + "title": "Has More", + "type": "boolean" + }, + "jobs": { + "items": { + "$ref": "#/components/schemas/JobSummary" + }, + "title": "Jobs", + "type": "array" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Org Id" + } + }, + "required": [ + "jobs" + ], + "title": "JobsListResponse", + "type": "object" + }, + "ParseChunk": { + "properties": { + "grounding": { + "$ref": "#/components/schemas/ParseGrounding" + }, + "id": { + "title": "Id", + "type": "string" + }, + "markdown": { + "title": "Markdown", + "type": "string" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "markdown", + "type", + "id", + "grounding" + ], + "title": "ParseChunk", + "type": "object" + }, + "ParseGrounding": { + "properties": { + "box": { + "$ref": "#/components/schemas/ParseGroundingBox" + }, + "page": { + "title": "Page", + "type": "integer" + } + }, + "required": [ + "box", + "page" + ], + "title": "ParseGrounding", + "type": "object" + }, + "ParseGroundingBox": { + "properties": { + "bottom": { + "title": "Bottom", + "type": "number" + }, + "left": { + "title": "Left", + "type": "number" + }, + "right": { + "title": "Right", + "type": "number" + }, + "top": { + "title": "Top", + "type": "number" + } + }, + "required": [ + "left", + "top", + "right", + "bottom" + ], + "title": "ParseGroundingBox", + "type": "object" + }, + "ParseMetadata": { + "properties": { + "credit_usage": { + "title": "Credit Usage", + "type": "number" + }, + "duration_ms": { + "title": "Duration Ms", + "type": "integer" + }, + "failed_pages": { + "items": { + "type": "integer" + }, + "title": "Failed Pages", + "type": "array" + }, + "filename": { + "title": "Filename", + "type": "string" + }, + "job_id": { + "title": "Job Id", + "type": "string" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Org Id" + }, + "page_count": { + "title": "Page Count", + "type": "integer" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + } + }, + "required": [ + "filename", + "org_id", + "page_count", + "duration_ms", + "credit_usage", + "job_id", + "version" + ], + "title": "ParseMetadata", + "type": "object" + }, + "ParseRequestWithEncryptedPassword": { + "properties": { + "custom_prompts": { + "anyOf": [ + { + "contentMediaType": "application/json", + "contentSchema": { + "$ref": "#/components/schemas/CustomPrompts" + }, + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Optional JSON string mapping chunk types to custom parsing prompts. Only the `figure` key is supported, for example '{\"figure\":\"Describe axis labels in detail.\"}'.", + "title": "Custom Prompts" + }, + "document": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "A file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document_url` parameter must be provided.", + "title": "Document" + }, + "document_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The URL to the file to be parsed. The file can be a PDF or an image. See the list of supported file types here: https://docs.landing.ai/ade/ade-file-types. Either this parameter or the `document` parameter must be provided.", + "title": "Document Url" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The version of the model to use for parsing.", + "title": "Model" + }, + "password": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Password for encrypted document files. If the document is password-protected, provide the password to decrypt and process the document. Ignored for unencrypted documents.", + "title": "Password" + }, + "split": { + "anyOf": [ + { + "$ref": "#/components/schemas/SplitType" + }, + { + "type": "null" + } + ], + "description": "If you want to split documents into smaller sections, include the split parameter. Set the parameter to page to split documents at the page level. The splits object in the API output will contain a set of data for each page." + } + }, + "title": "ParseRequestWithEncryptedPassword", + "type": "object" + }, + "ParseResponse": { + "properties": { + "chunks": { + "items": { + "$ref": "#/components/schemas/ParseChunk" + }, + "title": "Chunks", + "type": "array" + }, + "grounding": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/components/schemas/ParseResponseGrounding" + }, + { + "$ref": "#/components/schemas/ParseResponseTableCellGrounding" + } + ] + }, + "title": "Grounding", + "type": "object" + }, + "markdown": { + "title": "Markdown", + "type": "string" + }, + "metadata": { + "$ref": "#/components/schemas/ParseMetadata" + }, + "splits": { + "items": { + "$ref": "#/components/schemas/ParseSplit" + }, + "title": "Splits", + "type": "array" + } + }, + "required": [ + "markdown", + "chunks", + "splits", + "metadata" + ], + "title": "ParseResponse", + "type": "object" + }, + "ParseResponseGrounding": { + "properties": { + "box": { + "$ref": "#/components/schemas/ParseGroundingBox" + }, + "confidence": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Confidence" + }, + "low_confidence_spans": { + "items": { + "$ref": "#/components/schemas/Patch" + }, + "title": "Low Confidence Spans", + "type": "array" + }, + "page": { + "title": "Page", + "type": "integer" + }, + "type": { + "$ref": "#/components/schemas/GroundingType" + } + }, + "required": [ + "box", + "page", + "type" + ], + "title": "ParseResponseGrounding", + "type": "object" + }, + "ParseResponseTableCellGrounding": { + "properties": { + "box": { + "$ref": "#/components/schemas/ParseGroundingBox" + }, + "confidence": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Confidence" + }, + "page": { + "title": "Page", + "type": "integer" + }, + "position": { + "anyOf": [ + { + "$ref": "#/components/schemas/ParseResponseTableCellGroundingPosition" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/components/schemas/GroundingType" + } + }, + "required": [ + "box", + "page", + "type" + ], + "title": "ParseResponseTableCellGrounding", + "type": "object" + }, + "ParseResponseTableCellGroundingPosition": { + "properties": { + "chunk_id": { + "title": "Chunk Id", + "type": "string" + }, + "col": { + "title": "Col", + "type": "integer" + }, + "colspan": { + "title": "Colspan", + "type": "integer" + }, + "row": { + "title": "Row", + "type": "integer" + }, + "rowspan": { + "title": "Rowspan", + "type": "integer" + } + }, + "required": [ + "row", + "col", + "rowspan", + "colspan", + "chunk_id" + ], + "title": "ParseResponseTableCellGroundingPosition", + "type": "object" + }, + "ParseSplit": { + "properties": { + "chunks": { + "items": { + "type": "string" + }, + "title": "Chunks", + "type": "array" + }, + "class": { + "title": "Class", + "type": "string" + }, + "identifier": { + "title": "Identifier", + "type": "string" + }, + "markdown": { + "title": "Markdown", + "type": "string" + }, + "pages": { + "items": { + "type": "integer" + }, + "title": "Pages", + "type": "array" + } + }, + "required": [ + "class", + "identifier", + "pages", + "markdown", + "chunks" + ], + "title": "ParseSplit", + "type": "object" + }, + "Patch": { + "properties": { + "confidence": { + "title": "Confidence", + "type": "number" + }, + "span": { + "maxItems": 2, + "minItems": 2, + "prefixItems": [ + { + "type": "integer" + }, + { + "type": "integer" + } + ], + "title": "Span", + "type": "array" + }, + "text": { + "title": "Text", + "type": "string" + } + }, + "required": [ + "text", + "span", + "confidence" + ], + "title": "Patch", + "type": "object" + }, + "SectionMetadata": { + "description": "Public metadata for section response.", + "properties": { + "credit_usage": { + "title": "Credit Usage", + "type": "number" + }, + "duration_ms": { + "title": "Duration Ms", + "type": "integer" + }, + "filename": { + "title": "Filename", + "type": "string" + }, + "job_id": { + "default": "", + "title": "Job Id", + "type": "string" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Org Id" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version" + } + }, + "required": [ + "filename", + "duration_ms", + "credit_usage" + ], + "title": "SectionMetadata", + "type": "object" + }, + "SectionRequest": { + "description": "Request model for section endpoint.", + "properties": { + "guidelines": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Natural-language instructions to control hierarchy. Examples: 'Group by topic', 'Treat each numbered section as a top-level entry'.", + "title": "Guidelines" + }, + "markdown": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Parsed markdown with reference anchors (). This is the markdown field from a parse response.", + "title": "Markdown" + }, + "markdown_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "URL to fetch the markdown from.", + "title": "Markdown Url" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Section model version. Defaults to latest.", + "title": "Model" + } + }, + "title": "SectionRequest", + "type": "object" + }, + "SectionResponse": { + "description": "Response model for section endpoint.", + "properties": { + "metadata": { + "$ref": "#/components/schemas/SectionMetadata" + }, + "table_of_contents": { + "items": { + "$ref": "#/components/schemas/SectionTOCEntry" + }, + "title": "Table Of Contents", + "type": "array" + }, + "table_of_contents_md": { + "title": "Table Of Contents Md", + "type": "string" + } + }, + "required": [ + "table_of_contents", + "table_of_contents_md", + "metadata" + ], + "title": "SectionResponse", + "type": "object" + }, + "SectionTOCEntry": { + "description": "A single entry in the flat table of contents.", + "properties": { + "level": { + "title": "Level", + "type": "integer" + }, + "section_number": { + "title": "Section Number", + "type": "string" + }, + "start_reference": { + "title": "Start Reference", + "type": "string" + }, + "title": { + "title": "Title", + "type": "string" + } + }, + "required": [ + "title", + "level", + "section_number", + "start_reference" + ], + "title": "SectionTOCEntry", + "type": "object" + }, + "SplitClass": { + "description": "Model for split classification option.", + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Detailed description of what this split type represents", + "title": "Description" + }, + "identifier": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Identifier to partition/group the splits by", + "title": "Identifier" + }, + "name": { + "description": "Name of the split classification type", + "title": "Name", + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "SplitClass", + "type": "object" + }, + "SplitData": { + "description": "Split data for split classification endpoint.", + "properties": { + "classification": { + "title": "Classification", + "type": "string" + }, + "identifier": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Identifier" + }, + "markdowns": { + "items": { + "type": "string" + }, + "title": "Markdowns", + "type": "array" + }, + "pages": { + "items": { + "type": "integer" + }, + "title": "Pages", + "type": "array" + } + }, + "required": [ + "classification", + "identifier", + "pages", + "markdowns" + ], + "title": "SplitData", + "type": "object" + }, + "SplitMetadata": { + "description": "Metadata for split classification response.", + "properties": { + "credit_usage": { + "title": "Credit Usage", + "type": "number" + }, + "duration_ms": { + "title": "Duration Ms", + "type": "integer" + }, + "filename": { + "title": "Filename", + "type": "string" + }, + "job_id": { + "default": "", + "description": "Inference history job ID", + "title": "Job Id", + "type": "string" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Organization ID", + "title": "Org Id" + }, + "page_count": { + "title": "Page Count", + "type": "integer" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model version used for split classification", + "title": "Version" + } + }, + "required": [ + "filename", + "page_count", + "duration_ms", + "credit_usage" + ], + "title": "SplitMetadata", + "type": "object" + }, + "SplitRequest": { + "description": "Request model for split classification endpoint.", + "properties": { + "markdown": { + "anyOf": [ + { + "format": "binary", + "type": "string" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The Markdown file or Markdown content to split.", + "title": "Markdown" + }, + "markdown_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The URL to the Markdown file to split.", + "title": "Markdown Url" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "split-20251105", + "description": "Model version to use for split classification. Defaults to the latest version.", + "title": "Model" + }, + "split_class": { + "description": "List of split classification options/configuration. Can be provided as JSON string in form data.", + "items": { + "$ref": "#/components/schemas/SplitClass" + }, + "title": "Split Class", + "type": "array" + } + }, + "required": [ + "split_class" + ], + "title": "SplitRequest", + "type": "object" + }, + "SplitResponse": { + "description": "Response model for split classification endpoint.", + "properties": { + "metadata": { + "$ref": "#/components/schemas/SplitMetadata" + }, + "splits": { + "items": { + "$ref": "#/components/schemas/SplitData" + }, + "title": "Splits", + "type": "array" + } + }, + "required": [ + "splits", + "metadata" + ], + "title": "SplitResponse", + "type": "object" + }, + "SplitType": { + "const": "page", + "enum": [ + "page" + ], + "title": "SplitType", + "type": "string" + }, + "SpreadsheetChunk": { + "description": "Chunk from spreadsheet parsing.\n\nCan represent:\n- Table chunks from spreadsheet cells\n- Parsed content chunks from embedded images (text, table, figure, etc.)", + "properties": { + "grounding": { + "anyOf": [ + { + "$ref": "#/components/schemas/ParseGrounding" + }, + { + "type": "null" + } + ], + "description": "Visual grounding coordinates from /parse API (only for chunks derived from embedded images)" + }, + "id": { + "description": "Chunk ID - format: '{sheet_name}-{cell_range}' for tables, '{sheet_name}-image-{index}-{anchor_cell}-chunk-{i}-{type}' for parsed image chunks", + "title": "Id", + "type": "string" + }, + "markdown": { + "description": "Chunk content as HTML table with anchor tag (for tables) or parsed markdown content (for chunks from images)", + "title": "Markdown", + "type": "string" + }, + "type": { + "description": "Chunk type: 'table' for spreadsheet tables, or types from /parse (text, table, figure, form, etc.) for chunks derived from embedded images", + "title": "Type", + "type": "string" + } + }, + "required": [ + "markdown", + "type", + "id" + ], + "title": "SpreadsheetChunk", + "type": "object" + }, + "SpreadsheetParseMetadata": { + "description": "Metadata for spreadsheet parsing result.", + "properties": { + "credit_usage": { + "default": 0.0, + "description": "Credits charged", + "title": "Credit Usage", + "type": "number" + }, + "duration_ms": { + "description": "Processing duration in milliseconds", + "title": "Duration Ms", + "type": "integer" + }, + "filename": { + "description": "Original filename", + "title": "Filename", + "type": "string" + }, + "job_id": { + "default": "", + "description": "Inference history job ID", + "title": "Job Id", + "type": "string" + }, + "org_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Organization ID", + "title": "Org Id" + }, + "sheet_count": { + "description": "Number of sheets processed", + "title": "Sheet Count", + "type": "integer" + }, + "total_cells": { + "description": "Total non-empty cells across all sheets", + "title": "Total Cells", + "type": "integer" + }, + "total_chunks": { + "description": "Total chunks (tables + images) extracted", + "title": "Total Chunks", + "type": "integer" + }, + "total_images": { + "default": 0, + "description": "Total images extracted", + "title": "Total Images", + "type": "integer" + }, + "total_rows": { + "description": "Total rows across all sheets", + "title": "Total Rows", + "type": "integer" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model version for parsing images", + "title": "Version" + } + }, + "required": [ + "filename", + "sheet_count", + "total_rows", + "total_cells", + "total_chunks", + "duration_ms" + ], + "title": "SpreadsheetParseMetadata", + "type": "object" + }, + "SpreadsheetParseResponse": { + "description": "Response from /ade/parse-spreadsheet endpoint.\n\nSimilar structure to ParseResponse but without grounding.", + "properties": { + "chunks": { + "description": "List of table chunks (HTML)", + "items": { + "$ref": "#/components/schemas/SpreadsheetChunk" + }, + "title": "Chunks", + "type": "array" + }, + "markdown": { + "description": "Full document as HTML with anchor tags and tables", + "title": "Markdown", + "type": "string" + }, + "metadata": { + "$ref": "#/components/schemas/SpreadsheetParseMetadata", + "description": "Parsing metadata" + }, + "splits": { + "description": "Sheet-based splits", + "items": { + "$ref": "#/components/schemas/SpreadsheetSplit" + }, + "title": "Splits", + "type": "array" + } + }, + "required": [ + "markdown", + "chunks", + "splits", + "metadata" + ], + "title": "SpreadsheetParseResponse", + "type": "object" + }, + "SpreadsheetSplit": { + "description": "Sheet-based split from spreadsheet parsing.\n\nSimilar to ParseSplit but grouped by sheet instead of page.\nSupports both 'page' (per-sheet) and 'full' (all sheets) split types.", + "properties": { + "chunks": { + "description": "Chunk IDs in this split", + "items": { + "type": "string" + }, + "title": "Chunks", + "type": "array" + }, + "class": { + "description": "Split class: 'page' for per-sheet splits, 'full' for single split with all content", + "title": "Class", + "type": "string" + }, + "identifier": { + "description": "Split identifier: sheet name for 'page' splits, 'full' for full split", + "title": "Identifier", + "type": "string" + }, + "markdown": { + "description": "Combined markdown for this split", + "title": "Markdown", + "type": "string" + }, + "sheets": { + "description": "Sheet indices: single element for 'page' splits, all indices for 'full' split", + "items": { + "type": "integer" + }, + "title": "Sheets", + "type": "array" + } + }, + "required": [ + "class", + "identifier", + "sheets", + "markdown", + "chunks" + ], + "title": "SpreadsheetSplit", + "type": "object" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "title": "Location", + "type": "array" + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + }, + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError", + "type": "object" + } + }, + "securitySchemes": { + "Basic Auth": { + "bearerFormat": "Basic", + "description": "Your unique API key for authentication.\n\nGet your API key here: https://va.landing.ai/settings/api-key.\n\nIf using the EU endpoint, get your API key here: https://va.eu-west-1.landing.ai/settings/api-key.", + "scheme": "bearer", + "type": "http" + } + } + }, + "info": { + "description": "OpenAPI schema for ADE endpoints only", + "title": "ADE API", + "version": "0.1.0" + }, + "openapi": "3.1.0", + "paths": { + "/v1/ade/classify": { + "post": { + "description": "Classify the pages of a document into classes you define.\n\nThis endpoint accepts PDFs, images, and other supported file types\n(either as a `document` upload or `document_url`) together with a\nlist of `classes`, and returns a classification result for each page.\n\nFor EU users, use this endpoint:\n\n`https://api.va.eu-west-1.landing.ai/v1/ade/classify`.", + "operationId": "tool_ade_classify_v1_ade_classify_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/ClassifyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassifyResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "ADE Classify", + "tags": [ + "Tools" + ] + } + }, + "/v1/ade/extract": { + "post": { + "description": "Extract structured data from Markdown using a JSON schema.\n\nThis endpoint\n processes Markdown content and extracts structured data according to the provided\n JSON schema.\n\nFor EU users, use this endpoint:\n\n\n `https://api.va.eu-west-1.landing.ai/v1/ade/extract`.", + "operationId": "tool_ade_extract_v1_ade_extract_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/ExtractRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtractResponse" + } + } + }, + "description": "Successful Response" + }, + "206": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtractResponse" + } + } + }, + "description": "Extraction completed with success but there was a schema validation error." + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + }, + "429": { + "description": "Rate limit exceeded" + } + }, + "summary": "ADE Extract", + "tags": [ + "Tools" + ] + } + }, + "/v1/ade/extract/build-schema": { + "post": { + "description": "Generate a JSON schema from Markdown using AI.\n\nThis endpoint analyzes Markdown\n content and generates a JSON schema suitable for use with the extract endpoint.\n It can also refine an existing schema based on new documents or iterate on a schema\n based on prompt instructions.\n\nFor EU users, use this endpoint:\n\n\n `https://api.va.eu-west-1.landing.ai/v1/ade/extract/build-schema`.", + "operationId": "tool_ade_build_schema_v1_ade_extract_build_schema_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/BuildSchemaRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuildSchemaResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "ADE Build Extract Schema", + "tags": [ + "Tools" + ] + } + }, + "/v1/ade/parse": { + "post": { + "description": "Parse a document or spreadsheet.\n\nThis endpoint parses documents (PDF, images)\n and spreadsheets (XLSX, CSV) into structured Markdown, chunks, and metadata.\n \n\n For EU users, use this endpoint:\n\n\n `https://api.va.eu-west-1.landing.ai/v1/ade/parse`.", + "operationId": "tool_ade_parse_v1_ade_parse_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/ParseRequestWithEncryptedPassword" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParseResponse" + } + } + }, + "description": "Successful Response" + }, + "206": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParseResponse" + } + } + }, + "description": "There were some pages that failed to be parsed." + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + }, + "429": { + "description": "Rate limit exceeded" + } + }, + "summary": "ADE Parse", + "tags": [ + "Tools" + ] + } + }, + "/v1/ade/parse/jobs": { + "get": { + "description": "List all async parse jobs associated with your API key. Returns the list of jobs\nor an error response. For EU users, use this endpoint:\n\n\n`https://api.va.eu-west-1.landing.ai/v1/ade/parse/jobs`.", + "operationId": "tool_ade_list_parse_jobs_v1_ade_parse_jobs_get", + "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": "pageSize", + "required": false, + "schema": { + "default": 10, + "description": "Number of items per page", + "maximum": 100, + "minimum": 1, + "title": "Pagesize", + "type": "integer" + } + }, + { + "description": "Filter by job status.", + "in": "query", + "name": "status", + "required": false, + "schema": { + "anyOf": [ + { + "enum": [ + "cancelled", + "completed", + "failed", + "pending", + "processing" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Filter by job status.", + "title": "Status" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobsListResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "ADE List Parse Jobs", + "tags": [ + "Tools" + ] + }, + "post": { + "description": "Parse documents asynchronously.\n\nThis endpoint creates a job that handles the\n processing for both large documents and large batches of documents.\n\n For EU\n users, use this endpoint:\n\n\n `https://api.va.eu-west-1.landing.ai/v1/ade/parse/jobs`.", + "operationId": "tool_ade_parse_jobs_v1_ade_parse_jobs_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/AsyncParseRequestWithEncryptedPassword" + } + } + }, + "required": true + }, + "responses": { + "202": { + "content": { + "application/json": { + "example": { + "job_id": "12345678-1234-1234-1234-123456789012" + }, + "schema": { + "$ref": "#/components/schemas/JobCreationResponse" + } + } + }, + "description": "Job queued successfully" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + }, + "429": { + "description": "Rate limit exceeded" + } + }, + "summary": "ADE Parse Jobs", + "tags": [ + "Tools" + ] + } + }, + "/v1/ade/parse/jobs/{job_id}": { + "get": { + "description": "Get the status for an async parse job.\n\nReturns the job status or an error\n response. For EU users, use this endpoint:\n\n\n `https://api.va.eu-west-1.landing.ai/v1/ade/parse/jobs/{job_id}`.", + "operationId": "tool_ade_get_parse_jobs_v1_ade_parse_jobs__job_id__get", + "parameters": [ + { + "in": "path", + "name": "job_id", + "required": true, + "schema": { + "title": "Job Id", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatusResponse" + } + } + }, + "description": "Successful Response" + }, + "206": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatusResponse" + } + } + }, + "description": "There were some pages that failed to be parsed." + }, + "404": { + "description": "Job ID not found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "ADE Get Parse Jobs", + "tags": [ + "Tools" + ] + } + }, + "/v1/ade/section": { + "post": { + "description": "Section parsed markdown into a hierarchical table of contents.\n\nThis endpoint accepts the markdown output from /ade/parse\n(with reference anchors) and returns a flat, reading-order list of\nsections with hierarchy levels and reference ranges.\n\nFor EU users, use this endpoint:\n\n`https://api.va.eu-west-1.landing.ai/v1/ade/section`.", + "operationId": "tool_ade_section_v1_ade_section_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/SectionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SectionResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "ADE Section", + "tags": [ + "Tools" + ] + } + }, + "/v1/ade/split": { + "post": { + "description": "Split classification for documents.\n\nThis endpoint classifies document sections\n based on markdown content and split options.\n\nFor EU users, use this endpoint:\n\n\n `https://api.va.eu-west-1.landing.ai/v1/ade/split`.", + "operationId": "tool_ade_split_v1_ade_split_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/SplitRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SplitResponse" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "ADE Split", + "tags": [ + "Tools" + ] + } + } + } +} diff --git a/tests/contract/__init__.py b/tests/contract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/contract/test_v1_smoke.py b/tests/contract/test_v1_smoke.py new file mode 100644 index 0000000..99a6a1b --- /dev/null +++ b/tests/contract/test_v1_smoke.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import os +from typing import Iterator + +import pytest + +from landingai_ade import LandingAIADE + +pytestmark = pytest.mark.contract + +STAGING_KEY = os.environ.get("LANDINGAI_ADE_STAGING_APIKEY") +# V1 staging host. (Once Problem 2 adds the 4-environment map, this can become +# environment="staging"; until then we target staging via base_url.) +STAGING_V1_BASE_URL = "https://api.va.staging.landing.ai" + + +@pytest.fixture() +def staging_client() -> Iterator[LandingAIADE]: + if not STAGING_KEY: + pytest.skip("LANDINGAI_ADE_STAGING_APIKEY not set") + # Context-managed so the underlying HTTP client is closed in teardown (no socket leak). + with LandingAIADE(apikey=STAGING_KEY, base_url=STAGING_V1_BASE_URL) as client: + yield client + + +def test_parse_jobs_list_reachable(staging_client: LandingAIADE) -> None: + """The implemented V1 surface answers on staging (auth + routing sanity).""" + result = staging_client.parse_jobs.list(page=0, page_size=1) + assert hasattr(result, "jobs")