
Document Extraction
- 46 installs
- 13.9k repo stars
- Updated May 31, 2026
- andrewyng/context-hub
document-extraction is a Claude skill that uses LandingAI's Agentic Document Extraction SDK to parse, extract, and classify documents into structured Markdown and schema-based data with visual grounding.
About
A Claude skill for intelligent document processing using LandingAI's Agentic Document Extraction (ADE) SDK. It parses PDFs, images, spreadsheets, and presentations into structured Markdown, extracts fields via Pydantic or JSON schemas, and classifies multi-document batches. A developer uses it to write scripts that process sets of documents with visual grounding instead of loading each document into the agent context window.
- Wraps LandingAI Agentic Document Extraction (ADE) to parse, extract, and split documents
- Extracts structured data from PDFs and images using Pydantic or JSON schemas
- Provides visual grounding with bounding boxes and page numbers, no ML training needed
Document Extraction by the numbers
- 46 all-time installs (skills.sh)
- Ranked #7,539 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
document-extraction capabilities & compatibility
Requires a LandingAI VISION_AGENT_API_KEY; Extract uses ADE credits per call.
- Capabilities
- pdf parsing · data analysis
- Works with
- openai · anthropic
- Use cases
- pdf parsing · data analysis
- Pricing
- Bring your own API key
What document-extraction says it does
LandingAI's Agentic Document Extraction (ADE) is a document processing SaaS that parses, extracts, and classifies documents without requiring templates or training.
Supports 20+ file formats (PDF, images, spreadsheets, presentations)
Never install packages globally without user approval. Always check for a local Python environment first.
npx skills add https://github.com/andrewyng/context-hub --skill document-extractionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 46 |
|---|---|
| repo stars | ★ 13.9k |
| Last updated | May 31, 2026 |
| Repository | andrewyng/context-hub ↗ |
What it does
Parse and extract structured data from documents with the LandingAI ADE SDK, with visual grounding.
Who is it for?
Writing scripts that parse and extract structured data from batches of PDFs, images, and other documents.
Skip if: One-off single-document reads better handled by dropping the image into the prompt, or installing packages globally without approval.
When should I use this skill?
Parsing documents into structured Markdown, extracting fields via schemas, classifying multi-document batches, or needing bounding-box grounding.
What you get
Structured Markdown plus schema-extracted fields with bounding boxes and page numbers from any document layout.
- scripts that parse, extract, and split documents into structured Markdown and schema data
By the numbers
- supports 20+ file formats
- up to 1GB/1000 pages async
- 3 main capabilities (parse, extract, split)
Files
Document Extraction (ADE)
Overview
LandingAI's Agentic Document Extraction (ADE) is a document processing SaaS that parses, extracts, and classifies documents without requiring templates or training. It provides three main capabilities:
1. Parse: Convert documents into structured Markdown with hierarchical JSON representation 2. Extract: Pull specific structured data using JSON schemas or Pydantic models 3. Split: Classify and separate multi-document batches by type
Key Benefits:
- No ML training or templates required
- Layout-agnostic parsing (works with any document structure)
- Supports 20+ file formats (PDF, images, spreadsheets, presentations)
- Precise visual grounding (bounding boxes, page numbers)
- Multiple models optimized for different document types
Quick Start
1. Installation
Never install packages globally without user approval. Always check for a local Python environment first.
1. .venv/bin/python — uv-managed (this project)
2. venv/bin/python — standard Python venv
3. uv run python — if pyproject.toml exists
4. poetry run python — if poetry.lock exists
5. python3 — system fallback; warn the userUse the local environment to install: landingai-ade, python-dotenv
2. API Key Setup
The user may have already setup a .env file in the same directory as the document-extraction skill with the API key. You MUST check this path first (ls -la .*/skills/document-extraction/.env). Also try checking on the same directory as this SKILL.md file.
If not, provide instructions to create one. The script below will search for .env in common locations and load it.
.venv/bin/python - << 'EOF'
import os
from pathlib import Path
from dotenv import load_dotenv
# Load API key: prefer existing env var, then .env file lookup
if os.environ.get("VISION_AGENT_API_KEY"):
print("API key found in existing environment variable")
else:
def _find_env():
for d in [Path.cwd().resolve(), *Path.cwd().resolve().parents]:
for candidate in [
# ADD the directory where the document-extraction skill is located
d / '.env',
d / 'document-extraction/.env',
d / 'skills/document-extraction/.env',
]:
if candidate.is_file():
return candidate
return None
env = _find_env()
if env:
load_dotenv(env)
print(f"API key loaded from: {env}")
else:
print("Warning: VISION_AGENT_API_KEY not set and no .env found")
EOFIf not key is found instruct the user to get an API key from https://va.landing.ai/settings/api-key
Copy .env-sample to .env and add your API key:
cp .env-sample .envEdit .env and add your key:
VISION_AGENT_API_KEY=your_actual_api_key_hereNote: The .env file is gitignored for security. Advanced users can also set the environment variable directly: export VISION_AGENT_API_KEY=<your-key>
EU Endpoint: If using the EU endpoint, set environment="eu" when initializing the client.
3. Basic Parse Example
from dotenv import load_dotenv
load_dotenv() # Load API key from .env
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
# Parse a document
response = client.parse(
document=Path("document.pdf"),
model="dpt-2-latest"
)
# Access results
print(f"Pages: {response.metadata.page_count}")
print(f"Chunks: {len(response.chunks)}")
print("\nMarkdown output:")
print(response.markdown[:500]) # First 500 chars
# Save Markdown for extraction
with open("output.md", "w", encoding="utf-8") as f:
f.write(response.markdown)4. Basic Extract Example
from dotenv import load_dotenv
load_dotenv()
from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema
from pydantic import BaseModel, Field
from pathlib import Path
# Define extraction schema using Pydantic
class Invoice(BaseModel):
invoice_number: str = Field(description="Invoice number")
invoice_date: str = Field(description="Invoice date")
total_amount: float = Field(description="Total amount in USD")
vendor_name: str = Field(description="Vendor name")
# Convert to JSON schema
schema = pydantic_to_json_schema(Invoice)
client = LandingAIADE()
# Extract from parsed markdown
response = client.extract(
schema=schema,
markdown=Path("output.md"), # From parse step
model="extract-latest"
)
# Access extracted data
print(response.extraction)
# Output: {'invoice_number': 'INV-12345', 'invoice_date': '2024-01-15', ...}
# Check extraction metadata (traceability)
print(response.extraction_metadata)Document Parsing
Parse Local Files
from dotenv import load_dotenv
load_dotenv()
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
response = client.parse(
document=Path("/path/to/document.pdf"),
model="dpt-2-latest"
)
# Work with chunks
for chunk in response.chunks:
print(f"Type: {chunk.type}, Page: {chunk.grounding.page}")
print(f"Content: {chunk.markdown[:100]}...")Parse Remote URLs
response = client.parse(
document_url="https://example.com/document.pdf",
model="dpt-2-latest"
)Parse Spreadsheets
Spreadsheets (CSV, XLSX) return a different response type than documents. Key differences:
| Field | Documents (ParseResponse) | Spreadsheets (SpreadsheetParseResponse) |
|---|---|---|
metadata.page_count | ✓ | ✗ (uses sheet_count, total_rows, total_cells, total_chunks, total_images) |
splits[].pages | ✓ | ✗ (uses sheets — array of sheet indices) |
grounding (top-level) | ✓ | ✗ (not present for spreadsheets) |
| Chunk grounding | Always present | Optional (null for table chunks, present for embedded image chunks) |
response = client.parse(
document=Path("data.xlsx"),
model="dpt-2-latest"
)
# Spreadsheet metadata
print(f"Sheets: {response.metadata.sheet_count}")
print(f"Total rows: {response.metadata.total_rows}")
print(f"Total cells: {response.metadata.total_cells}")
# Splits use 'sheets' instead of 'pages'
for split in response.splits:
print(f"Sheet indices: {split.sheets}")
print(f"Markdown: {split.markdown[:200]}...")Model Selection
Choose the right model for your documents:
| Model | Best For | Chunk Types |
|---|---|---|
| dpt-2-latest | Complex documents with logos, signatures, ID cards | text, table, figure, logo, card, attestation, scan_code, marginalia |
| dpt-2-mini | Simple, digitally-native documents (faster, cheaper) | text, table, figure, marginalia |
| dpt-1 | ⚠️ Deprecated March 31, 2026 — migrate to dpt-2 | text, table, figure, marginalia |
Recommendation: Use dpt-2-latest unless you have simple documents where cost/speed is critical.
Version Pinning: For production, use dated versions (e.g., dpt-2-20260302) for reproducibility.
Parse Large Files (Async)
For files up to 1 GB or 6,000 pages, use Parse Jobs:
import time
from dotenv import load_dotenv
load_dotenv()
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
# Step 1: Create parse job
job = client.parse_jobs.create(
document=Path("large_document.pdf"),
model="dpt-2-latest"
)
job_id = job.job_id
print(f"Job {job_id} created")
# Step 2: Poll for completion
while True:
response = client.parse_jobs.get(job_id)
if response.status == "completed":
print(f"Job {job_id} completed")
break
print(f"Progress: {response.progress * 100:.0f}%")
time.sleep(5)
# Step 3: Access results
# Results are in response.data (or response.output_url for large results)
if response.data:
print(f"Chunks: {len(response.data.chunks)}")
with open("output.md", "w", encoding="utf-8") as f:
f.write(response.data.markdown)
elif response.output_url:
# Results > 1MB are returned as a presigned URL
print(f"Download results from: {response.output_url}")Job Status Response Fields:
job_id,status(pending, processing, completed, failed, cancelled),progress(0-1)data: TheParseResponse(orSpreadsheetParseResponse) when complete and result < 1MBoutput_url: Presigned S3 URL when result > 1MB or whenoutput_save_urlwas used. Expires after 1 hour; a new URL is generated on each GET.metadata: Same as sync parse (filename,page_count,duration_ms, etc.)failure_reason: Error message if job failed
Zero Data Retention (ZDR)
If ZDR is enabled for your organization, you must provide an output_save_url where parsed results will be saved. The results will not be returned in the API response. ZDR is not enabled by default. Typically output_save_url is a presigned url with write permissions to your S3 bucket, but you can also use other storage solutions that support file uploads via HTTP PUT requests.
job = client.parse_jobs.create(
document=Path("sensitive_document.pdf"),
model="dpt-2-latest",
output_save_url="https://your-bucket.s3.amazonaws.com/output.json"
)List Parse Jobs
List all async parse jobs with optional pagination and status filtering:
# List recent jobs
jobs_response = client.parse_jobs.list(page=0, page_size=10)
for job in jobs_response.jobs:
print(f"{job.job_id}: {job.status} ({job.progress:.0%})")
# Filter by status
completed = client.parse_jobs.list(status="completed", page_size=5)
print(f"Completed jobs: {len(completed.jobs)}, more: {completed.has_more}")Available status filters: pending, processing, completed, failed, cancelled
Understanding Parse Outputs
Parse returns a ParseResponse with:
- `markdown`: Complete document in Markdown with HTML anchor tags
- `chunks`: Array of extracted elements (each with unique ID, type, content, and per-chunk grounding)
- `grounding`: Dictionary mapping element IDs to detailed location data (page, bounding box, grounding type, and table cell position). See JSON Response for structure.
- `metadata`: Processing info —
filename,org_id,page_count,duration_ms,credit_usage(float),job_id,version,failed_pages - `splits`: Array of split objects grouping chunks. Always present — contains a single
"full"split by default, or per-page splits ifsplit="page"was used. Note: Parse splits use aclassfield (values:"full"or"page"), which is different from the Split API'sclassificationfield.
Common chunk types: text, table, figure, logo, card, attestation, scan_code, marginalia
For detailed chunk type reference, see references/chunk-types.md
Anchor tag prefix in `chunk.markdown`: Every chunk's markdown fieldis prefixed with an HTML anchor tag embedding the chunk UUID:
<a id='abc123...'></a>\n\nActual content…. This is how the full documentmarkdown links back to individual chunks. Strip it before string matching,
display, or RAG indexing:
>
```python
import re
_ANCHOR_RE = re.compile(r"<a[^>]></a>\s", re.IGNORECASE)
>
def chunk_text(ch) -> str:
"""Return clean chunk markdown without the anchor prefix."""
return _ANCHOR_RE.sub("", ch.markdown or "").strip()
>
# Example: fingerprint match against a section of the full markdown
intro_chunks = [ch for ch in response.chunks
if chunk_text(ch)[:80] in intro_markdown]
```
Saving Parse Responses
The SDK provides a built-in save_to parameter on parse(), extract(), and split() that automatically saves the JSON response to a folder:
from pathlib import Path
# Parse and auto-save response JSON to output/ folder
response = client.parse(
document=Path("document.pdf"),
model="dpt-2-latest",
save_to="output/" # Creates output/document_parse_output.json
)
# Response is still returned normally for immediate use
print(response.markdown[:200])The save_to parameter:
- Creates the folder if it doesn't exist
- Names the file
{input_filename}_{method}_output.json(e.g.,document_parse_output.json) - Works on
client.parse(),client.extract(), andclient.split() - Is a client-side convenience — it saves the full response locally after the API call
For manual serialization (e.g., custom filenames or selective saving), use model_dump():
import json
response_dict = response.model_dump()
with open("parse_response.json", "w", encoding="utf-8") as f:
json.dump(response_dict, f, indent=2, ensure_ascii=False)
# Save markdown separately for extraction
with open("document_parsed.md", "w", encoding="utf-8") as f:
f.write(response.markdown)Important: Always use model_dump() to serialize the complete response. Do not manually construct dictionaries with selected fields, as you may miss important data like the splits array or complete grounding information.
Parse Parameters
response = client.parse(
document=Path("document.pdf"),
model="dpt-2-latest",
split="page", # Optional: organize chunks by page
password="secret", # Optional: decrypt protected files (ZDR only)
save_to="output/", # Optional: auto-save response JSON
)Parse Password-Protected Files
Organizations with Zero Data Retention (ZDR) enabled can parse password-protected files by passing the password parameter. Supported formats: PDF, DOC, DOCX, ODT, PPT, PPTX, XLSX.
# Sync parse
response = client.parse(
document=Path("encrypted.pdf"),
password="document_password",
model="dpt-2-latest"
)
# Async parse jobs
job = client.parse_jobs.create(
document=Path("encrypted.pdf"),
password="document_password",
model="dpt-2-latest"
)Note: Without ZDR the API returns HTTP 422. If the password is wrong the API
returns HTTP 422 with a decryption error. The parameter is ignored for unencrypted documents.
Structured Data Extraction
Schema Definition
Define what to extract using JSON Schema or Pydantic models.
Pydantic approach (recommended for Python):
from pydantic import BaseModel, Field
from landingai_ade.lib import pydantic_to_json_schema
class BankStatement(BaseModel):
account_holder: str = Field(description="Account holder name")
account_number: str = Field(description="Account number")
beginning_balance: float = Field(description="Beginning balance in USD")
ending_balance: float = Field(description="Ending balance in USD")
schema = pydantic_to_json_schema(BankStatement)JSON Schema approach:
schema = {
"type": "object",
"properties": {
"account_holder": {
"type": "string",
"description": "Account holder name"
},
"account_number": {
"type": "string",
"description": "Account number"
},
"beginning_balance": {
"type": "number",
"description": "Beginning balance in USD"
},
"ending_balance": {
"type": "number",
"description": "Ending balance in USD"
}
},
"required": ["account_holder", "account_number"]
}Extraction Workflow
from dotenv import load_dotenv
load_dotenv()
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
# Step 1: Parse document
parse_response = client.parse(
document=Path("bank_statement.pdf"),
model="dpt-2-latest"
)
# Save markdown
with open("parsed.md", "w", encoding="utf-8") as f:
f.write(parse_response.markdown)
# Step 2: Extract structured data
extract_response = client.extract(
schema=schema, # Your JSON schema
markdown=Path("parsed.md"),
model="extract-latest"
)
# Access extracted data
print(extract_response.extraction)
# Check traceability (which chunks provided each field)
for field, metadata in extract_response.extraction_metadata.items():
print(f"{field}: from chunks {metadata.chunk_ids}")Extract from URL
You can extract from a remotely-hosted Markdown file using markdown_url:
extract_response = client.extract(
schema=schema,
markdown_url="https://example.com/parsed_document.md",
model="extract-latest"
)Common Schema Patterns
For detailed schema patterns, see references/extraction-schemas.md
Nested objects:
class Address(BaseModel):
street: str
city: str
zip_code: str
class Invoice(BaseModel):
invoice_number: str
billing_address: Address # Nested objectArrays (lists):
class LineItem(BaseModel):
description: str
quantity: int
amount: float
class Invoice(BaseModel):
invoice_number: str
line_items: list[LineItem] # Array of objectsEnums (restricted values):
class BankStatement(BaseModel):
account_type: str = Field(
description="Account type",
enum=["Checking", "Savings"] # Only these values allowed
)Nullable fields:
class Patient(BaseModel):
first_name: str
middle_name: str | None = Field(default=None) # Optional field
last_name: strDocument Classification
Classify documents before extracting type-specific fields:
from dotenv import load_dotenv
load_dotenv()
from landingai_ade import LandingAIADE
from pydantic import BaseModel, Field
from landingai_ade.lib import pydantic_to_json_schema
from pathlib import Path
# Step 1: Define classification schema
class DocumentType(BaseModel):
document_type: str = Field(
description="Document classification",
enum=["Invoice", "Receipt", "Bank Statement", "Other"]
)
client = LandingAIADE()
# Step 2: Parse document
parse_response = client.parse(
document=Path("document.pdf"),
model="dpt-2-latest"
)
# Step 3: Classify document
classification_schema = pydantic_to_json_schema(DocumentType)
classification_response = client.extract(
schema=classification_schema,
markdown=parse_response.markdown,
model="extract-latest"
)
doc_type = classification_response.extraction["document_type"]
print(f"Classified as: {doc_type}")
# Step 4: Extract based on type
if doc_type == "Invoice":
schema = pydantic_to_json_schema(InvoiceSchema)
elif doc_type == "Receipt":
schema = pydantic_to_json_schema(ReceiptSchema)
else:
print("Unsupported document type")
exit()
# Extract type-specific fields
extract_response = client.extract(
schema=schema,
markdown=parse_response.markdown,
model="extract-latest"
)Document Classification & Splitting
When to Use Split API
Use the Split API when you have multi-document batches on single file that need to be separated:
- Financial services: Separate bank statements, utility bills, ID documents
- Healthcare: Split intake forms, medical reports, medication lists
- Accounting: Separate multiple invoices and receipts
- Academic: Separate article bodies from references
Split Classification
Define how to classify and separate documents using split_class:
from dotenv import load_dotenv
load_dotenv()
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
# Step 1: Parse multi-document PDF
parse_response = client.parse(
document=Path("batch.pdf"),
model="dpt-2-latest"
)
# Step 2: Define split classes
split_classes = [
{
"name": "Invoice",
"description": "Commercial invoices with itemized charges",
"identifier": "Invoice Number" # Separate by invoice number
},
{
"name": "Receipt",
"description": "Payment receipts showing transaction details",
"identifier": "Receipt Date"
},
{
"name": "Bank Statement",
"description": "Monthly bank account statements"
}
]
# Step 3: Split document
split_response = client.split(
markdown=parse_response.markdown,
split_class=split_classes
)
# Step 4: Process each split
for split in split_response.splits:
print(f"Type: {split.classification}")
print(f"Identifier: {split.identifier}")
print(f"Pages: {split.pages}")
print(f"Content: {split.markdowns[0][:200]}...")Split Class Components:
- name (required): Document classification label (e.g., "Invoice")
- description (optional): Context for classification (more detail = better accuracy)
- identifier (optional): Field that makes each instance unique (creates separate split per unique value)
- Limit: Maximum 19 split classes per request
Split from URL: You can also split from a remotely-hosted Markdown file:
split_response = client.split(
markdown_url="https://example.com/parsed_document.md",
split_class=split_classes
)Output Formats
Markdown
ADE converts documents to structured Markdown:
# Document Title
## Section 1
Paragraph text...
| Column 1 | Column 2 |
|----------|----------|
| Data 1 | Data 2 |
<::Caption: Bar chart showing quarterly revenue::>Features:
- HTML anchor tags for traceability (link to chunk IDs)
- Special delimiters for visual elements:
<::Caption: description::> - HTML tables for spreadsheet data
- Preserved structure and hierarchy
JSON Response
Parse returns structured JSON with five top-level fields:
{
"markdown": "# Document...",
"chunks": [
{
"id": "7d58c5cf-e4f5-4a7e-ba34-0cd7bc6a6506",
"type": "text",
"markdown": "Content...",
"grounding": {
"page": 0,
"box": { "left": 0.1, "top": 0.2, "right": 0.9, "bottom": 0.3 }
}
}
],
"splits": [
{
"class": "full",
"identifier": "full",
"pages": [0],
"markdown": "# Document...",
"chunks": ["7d58c5cf-e4f5-4a7e-ba34-0cd7bc6a6506"]
}
],
"grounding": {
"7d58c5cf-e4f5-4a7e-ba34-0cd7bc6a6506": {
"box": { "left": 0.1, "top": 0.2, "right": 0.9, "bottom": 0.3 },
"page": 0,
"type": "chunkText",
"confidence": 0.95,
"low_confidence_spans": []
},
"0-1": {
"box": { "left": 0.15, "top": 0.4, "right": 0.85, "bottom": 0.7 },
"page": 0,
"type": "table"
},
"0-2": {
"box": { "left": 0.15, "top": 0.4, "right": 0.5, "bottom": 0.55 },
"page": 0,
"type": "tableCell",
"position": { "row": 0, "col": 0, "rowspan": 1, "colspan": 1,
"chunk_id": "ef24b1ea-..." }
}
},
"metadata": {
"filename": "document.pdf",
"org_id": "org-123",
"page_count": 5,
"duration_ms": 1500,
"credit_usage": 2.0,
"job_id": "abc-123",
"version": "dpt-2-20260302",
"failed_pages": []
}
}Top-level `grounding` is a dictionary keyed by element ID (UUID for chunks, {page}-{base62} for tables/cells). Each value contains box, page, type, and optionally confidence and low_confidence_spans (see Confidence Scores). Table cell entries also include a position field (see Grounding and Traceability).
Grounding Type Mapping
Grounding types use a chunk prefix to distinguish them from chunk types. The table and tableCell types are grounding-only (no corresponding chunk type):
| Grounding Type | Chunk Type | Description |
|---|---|---|
chunkText | text | Text content |
chunkTable | table | Table chunk (overall location) |
chunkFigure | figure | Figures and images |
chunkMarginalia | marginalia | Headers, footers, page numbers |
chunkLogo | logo | Company logos (DPT-2) |
chunkCard | card | ID cards, licenses (DPT-2) |
chunkAttestation | attestation | Signatures, stamps (DPT-2) |
chunkScanCode | scan_code | QR codes, barcodes (DPT-2) |
table | _(grounding only)_ | HTML <table> element within a table chunk |
tableCell | _(grounding only)_ | Individual cell within a table |
Extract returns:
{
"extraction": {
"invoice_number": "INV-12345",
"total": 1250.00
},
"extraction_metadata": {
"invoice_number": {
"chunk_ids": ["chunk-uuid-1"]
},
"total": {
"chunk_ids": ["chunk-uuid-2"],
"cell_ids": ["2-a5"]
}
},
"metadata": {
"filename": "markdown.md",
"org_id": "org-123",
"duration_ms": 850,
"credit_usage": 1.0,
"job_id": "abc-456",
"version": "extract-20251024",
"schema_violation_error": null,
"fallback_model_version": null
}
}Extract Metadata Fields:
- `schema_violation_error`:
nullwhen extraction matches schema. Contains a detailed error message when the extracted data doesn't fully conform (HTTP 206 response). Extraction still returns partial data and consumes credits. - `fallback_model_version`:
nullnormally. Contains the model version actually used when the initial extraction attempt failed with the requested version and a fallback was used.
Grounding and Traceability
Every parsed element includes precise location information in the top-level grounding dictionary:
- Page references: Zero-indexed page numbers
- Bounding boxes: Normalized coordinates (0-1) for position
left,top,right,bottom- Convert to pixels: multiply by image dimensions
- Element IDs: UUID for chunks,
{page}-{base62}for tables and table cells - Table/cell IDs use sequential base62 numbering per page:
0-1,0-2, ...,0-9,0-a, ...,0-z,0-A, ...,0-Z,0-10, etc. - Numbering restarts on each page (e.g., first table on page 1 →
1-1) - Grounding types: Each entry has a
typefield using prefixed names (e.g.,chunkText,chunkTable). See Grounding Type Mapping. - Table cell position:
tableCellentries include apositionobject withrow,col(zero-indexed),rowspan,colspan, andchunk_id(the parent table chunk UUID) - Extraction metadata: Shows which chunks/cells provided each field
Per-chunk grounding (on each chunk object) contains only box and page. The top-level grounding dictionary adds type and, for table cells, position.
Example:
# Per-chunk grounding (basic location)
for chunk in response.chunks:
print(f"Chunk {chunk.id} on page {chunk.grounding.page}")
bbox = chunk.grounding.box
print(f"Location: ({bbox.left}, {bbox.top}) to ({bbox.right}, {bbox.bottom})")
# Top-level grounding (detailed, with type and position)
# NOTE: grounding values are Pydantic models — use attribute access, not dict access
for elem_id, info in response.grounding.items():
print(f"{elem_id}: type={info.type}, page={info.page}")
if info.type == "tableCell" and info.position:
print(f" Cell at row={info.position.row}, col={info.position.col}")Important:response.groundingis aDict[str, Grounding]— the outer container is a dict (so.items(),.get()work), but each value is a Pydantic model. Use attribute access (info.type,info.box.left) not dict access (info["type"]).
Confidence Scores {#confidence-scores}
Top-level grounding entries may include confidence information:
- `confidence` (
float | None): Overall confidence score (0.0–1.0) for the chunk's transcription - `low_confidence_spans` (
list | None): Specific text spans with low confidence, each containing: confidence(float): Span-level confidence scoretext(str): The low-confidence textspan(list): Position markers within the chunk
# Access confidence scores from top-level grounding
for elem_id, info in response.grounding.items():
if info.confidence is not None:
print(f"{elem_id}: confidence={info.confidence:.2f}")
for span in info.low_confidence_spans or []:
print(f" Low confidence ({span.confidence:.2f}): "
f"'{span.text}'")Notes:
- Confidence is only present in top-level grounding (not per-chunk grounding)
- Not all grounding entries will have confidence (e.g.,
table/tableCelltypes may not) - Use confidence scores to flag chunks that may need human review
Best Practices
Model Selection
- Use dpt-2-latest for most documents (complex layouts, logos, signatures)
- Use dpt-2-mini for simple, digitally-native documents (faster, cheaper)
- Pin versions in production for reproducibility (e.g.,
dpt-2-20260302) - Use extract-latest for extraction (automatically uses newest model)
- Do NOT use dpt-1 — deprecated March 31, 2026; migrate to dpt-2
Schema Design
- Be specific: Use descriptive field names (
invoice_numbernotnumber) - Add descriptions: Include format requirements ("in USD", "as YYYY-MM-DD")
- Keep it simple: Start with few fields, add more as needed
- Limit complexity: Under 30 properties for optimal performance
- Match document structure: Order fields as they appear in document
For detailed schema patterns, see references/extraction-schemas.md
Error Handling
try:
response = client.parse(document=Path("doc.pdf"), model="dpt-2-latest")
except Exception as e:
print(f"Parse error: {e}")
# Handle error (check file format, file size, API key, etc.)
try:
extract_response = client.extract(schema=schema, markdown=response.markdown)
except Exception as e:
print(f"Extract error: {e}")
# Handle error (check schema validity, markdown format, etc.)Handling Partial Results (HTTP 206)
Both Parse and Extract APIs can return HTTP 206 (Partial Content) when processing partially succeeds:
Parse 206: Some pages failed to parse. Check metadata.failed_pages:
response = client.parse(document=Path("doc.pdf"), model="dpt-2-latest")
if response.metadata.failed_pages:
print(f"Failed pages: {response.metadata.failed_pages}")
# Remaining pages were parsed successfullyExtract 206: Extraction completed but data doesn't fully match schema. Check metadata.schema_violation_error:
response = client.extract(schema=schema, markdown=markdown)
err = response.metadata.schema_violation_error
if err:
print(f"Schema violation: {err}")
# Extraction still returns partial data; credits are consumedNote: 206 responses still consume credits. The API returns the best results it could produce.
Performance
- Large files: Use Parse Jobs API (async) for files > 50 pages or > 10 MB
- Batch processing: Process documents in parallel when possible
- Cache parse results: Save markdown to avoid re-parsing for multiple extractions
- Optimize parsing: Use the
split="page"parameter when you need page-level organization
File Formats
- Prefer PDF for native documents (no conversion needed)
- Use high-resolution images (300+ DPI) for better OCR
- Password-protected files: Use the
passwordparameter (requires ZDR). Without ZDR, remove password protection before parsing - Test conversion for DOCX/PPTX files (layout may change)
For complete file format reference, see references/file-formats.md
Use Cases
See references/use-cases.md for complete worked examples: invoice processing, form data extraction, multi-document classification, table extraction, and figure cropping with PyMuPDF.
Troubleshooting
See references/troubleshooting.md for HTTP error codes, parse failures, extraction accuracy issues, schema validation errors, and performance guidance.
Links
Official Documentation
- LandingAI ADE Documentation
- Parse API Reference
- Extract API Reference
- Split API Reference
- Python Library (GitHub)
API Key
Reference Files
- Extraction Schema Patterns - Detailed schema examples
- Chunk Types Reference - Complete chunk type guide
- File Formats - Supported formats and considerations
- Use Cases - Worked examples for invoices, forms, tables, and figure extraction
- Troubleshooting - HTTP error codes and common issues
# LandingAI ADE API Key
# Get your API key from: https://va.landing.ai/settings/api-key
VISION_AGENT_API_KEY=your_api_key_here
.env
Chunk Types Reference
Overview
A chunk is a discrete element extracted from a document during parsing. When you send a document to ADE, it analyzes the content, breaks it down into meaningful elements, and returns each as a chunk with structured metadata describing its content and location.
What is Semantic Chunking?
ADE uses semantic chunking, which intelligently groups content based on meaning rather than just layout or formatting. Instead of splitting at arbitrary points (like fixed lengths or paragraph breaks), ADE identifies coherent units of information and extracts them as individual chunks.
Benefits:
- Enables efficient processing of large documents (avoids token limits)
- Improves retrieval granularity for downstream tasks
- Supports semantic search and embeddings
- Maintains human readability and logical relationships
Chunk Type Comparison by Model
| Chunk Type | DPT-1 ⚠️ | DPT-2 | Confidence | Description |
|---|---|---|---|---|
| text | ✓ | ✓ | ✓ | Paragraphs, headings, lists, forms, code |
| table | ✓ | ✓ | ✗ | Grids of data, receipts, spreadsheets |
| figure | ✓ | ✓ | ✓ | Images, graphs, flowcharts, diagrams |
| marginalia | ✓ | ✓ | ✓ | Headers, footers, page numbers, margin notes |
| logo | ✗ | ✓ | ✓ | Company logos |
| card | ✗ | ✓ | ✓ | ID cards, driver licenses |
| attestation | ✗ | ✓ | ✓ | Signatures, stamps, seals |
| scan_code | ✗ | ✓ | ✓ | QR codes, barcodes |
⚠️ DPT-1 Deprecation: DPT-1 will be removed on March 31, 2026. Migrate to DPT-2 now.
Note: DPT-2 provides more granular chunk types. In DPT-1, logos, QR codes, barcodes, stamps, signatures, and ID cards are all classified as figure. The Confidence column indicates which chunk types support confidence scores in top-level grounding (Preview feature).
Chunk Type Details
text
Description: Content consisting entirely of characters (letters and numbers).
Includes:
- Paragraphs
- Titles and headings
- Lists (bulleted, numbered)
- Form fields
- Checkboxes and radio buttons
- Equations
- Code blocks
- Handwritten text
Key-Value Pairs: If text contains form fields with key-value pairs, the extracted data is returned as key-value pairs separated by line breaks (\n).
Example Output:
## Solar Energy Benefits
Solar energy is a renewable and clean source of power that has numerous advantages:
- Reduces electricity bills
- Environmentally friendly
- Low maintenance costs
- Energy independencetable
Description: Grids of rows and columns containing data.
Includes:
- Traditional tables with gridlines
- Well-aligned data without gridlines (e.g., receipt line items)
- Spreadsheet data
Note: ADE doesn't require gridlines to be present. It interprets well-aligned sets of data as tables.
Example Output:
| Item | Quantity | Price |
|------|----------|-------|
| Coffee | 2 | $5.00 |
| Sandwich | 1 | $8.50 |
| **Total** | | **$13.50** |marginalia
Description: Text in the top, bottom, or side margins of a document.
Includes:
- Page headers
- Page footers
- Page numbers
- Handwritten notes in margins
- Line numbers
Example Output:
_Confidential Report - Page 3_figure
Description: Visual or graphical non-text content.
Includes:
- Pictures and photographs
- Graphs (bar, line, pie charts)
- Flowcharts
- Diagrams
DPT-1 also includes: logos, QR codes, barcodes, stamps, signatures, ID cards (these have dedicated types in DPT-2)
Example Output:
<::Caption: Bar chart showing quarterly revenue growth from Q1 to Q4::>logo
Description: Company logos and branding elements.
Availability: DPT-2 only
Example Output:
<::Caption: Landing AI logo::>card
Description: Identification cards and licenses.
Includes:
- ID cards
- Driver licenses
Availability: DPT-2 only
Example Output:
<::Caption: Driver's license with photo and personal information::>attestation
Description: Elements that serve as authentication or approval.
Includes:
- Signatures (handwritten or digital)
- Stamps
- Seals
Availability: DPT-2 only
Example Output:
<::Caption: Handwritten signature::>scan_code
Description: Machine-readable codes.
Includes:
- QR codes
- Barcodes (UPC, Code 39, Code 128, etc.)
Availability: DPT-2 only
Example Output:
<::Caption: Barcode::>Working with Chunks
Accessing Chunks in Python
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
response = client.parse(
document=Path("document.pdf"),
model="dpt-2-latest"
)
# Access all chunks
for chunk in response.chunks:
print(f"Type: {chunk.type}")
print(f"ID: {chunk.id}")
print(f"Content: {chunk.markdown}")
print(f"Page: {chunk.grounding.page}")
print("---")Filtering by Chunk Type
# Get all text chunks
text_chunks = [chunk for chunk in response.chunks if chunk.type == 'text']
# Get all tables
tables = [chunk for chunk in response.chunks if chunk.type == 'table']
# Get all figures
figures = [chunk for chunk in response.chunks if chunk.type == 'figure']Filtering by Page
# Get all chunks from page 0 (first page)
page_0_chunks = [chunk for chunk in response.chunks
if chunk.grounding.page == 0]Accessing Chunk Location (Grounding)
for chunk in response.chunks:
print(f"Chunk ID: {chunk.id}")
print(f"Page: {chunk.grounding.page}")
print(f"Bounding box: {chunk.grounding.box}")
# Bounding box format: {left, top, right, bottom}
# Values are normalized 0-1Grounding Type Mapping
The top-level grounding dictionary in the Parse response uses grounding types (prefixed with chunk) rather than chunk types. This allows the grounding dictionary to also include table-specific entries (table, tableCell) that don't correspond to chunk types.
| Grounding Type | Chunk Type | Notes |
|---|---|---|
chunkText | text | |
chunkTable | table | Overall table location |
chunkFigure | figure | |
chunkMarginalia | marginalia | |
chunkLogo | logo | DPT-2 only |
chunkCard | card | DPT-2 only |
chunkAttestation | attestation | DPT-2 only |
chunkScanCode | scan_code | DPT-2 only |
table | _(grounding only)_ | HTML <table> element within a table chunk |
tableCell | _(grounding only)_ | Individual cell; includes position (row, col, rowspan, colspan, chunk_id) |
Example — looking up grounding type for a chunk:
for chunk in response.chunks:
grounding_entry = response.grounding.get(chunk.id)
if grounding_entry:
print(f"Chunk type: {chunk.type}")
print(f"Grounding type: {grounding_entry.type}")
# e.g. chunk type "text" → grounding type "chunkText"Common Use Cases by Chunk Type
text
- Extract document content for RAG systems
- Build searchable document indices
- Extract form field values
- Process questionnaires and surveys
table
- Extract financial data from statements
- Process invoices and receipts
- Parse spreadsheet data
- Extract structured data from reports
figure
- Index images for visual search
- Generate image captions for accessibility
- Extract diagrams for documentation
- Archive visual content
marginalia
- Track document metadata (page numbers, headers)
- Extract document versioning information
- Identify document sections from headers
logo (DPT-2)
- Brand recognition and classification
- Document authenticity verification
- Company identification from documents
card (DPT-2)
- KYC (Know Your Customer) processing
- Identity verification workflows
- License verification
attestation (DPT-2)
- Contract validation
- Approval workflow tracking
- Signature verification
- Document authenticity checks
scan_code (DPT-2)
- Inventory tracking (barcodes)
- Payment processing (QR codes)
- Product identification
- Link extraction from QR codes
Deprecated Chunk Types
Note: The following chunk types were deprecated and consolidated (as of May 22, 2025):
Consolidated into `marginalia`:
page_headerpage_footerpage_number
Consolidated into `text`:
titleformkey_value
Action Required: If your code references these deprecated types, update to use the new consolidated types (marginalia or text).
Best Practices
1. Choose the Right Model
- Use DPT-2 for documents with logos, signatures, ID cards, or barcodes
- Use DPT-2 mini for simple, digitally-native documents
2. Filter Chunks by Type
Filter chunks to focus on relevant content for your use case:
# Extract only text and tables for data extraction
data_chunks = [c for c in response.chunks
if c.type in ['text', 'table']]3. Use Chunk IDs for Traceability
Each chunk has a unique ID that can be referenced in extraction metadata:
# Extract data and trace back to source chunks
extract_response = client.extract(schema=schema, markdown=response.markdown)
for field, metadata in extract_response.extraction_metadata.items():
print(f"{field} extracted from chunks: {metadata.chunk_ids}")4. Handle Visual Elements
For figures, logos, attestations, and scan_codes, the markdown includes a caption:
# Check if chunk is visual
visual_types = ['figure', 'logo', 'card', 'attestation', 'scan_code']
if chunk.type in visual_types:
print(f"Visual element: {chunk.markdown}")
# Caption format: <::Caption: description::>References
Extraction Schema Patterns
This reference provides patterns and examples for creating extraction schemas for the LandingAI ADE Extract API.
Overview
Extraction schemas are JSON Schema objects that define what structured data should be extracted from parsed documents. You can use either JSON Schema format (for API calls) or Pydantic models (when using the Python library).
Basic Structure
Every extraction schema must follow this structure:
{
"type": "object",
"properties": {
"field_name": {
"type": "string",
"description": "Description of what to extract"
}
},
"required": ["field_name"]
}Key points:
- Top-level
typemust be"object" - Define fields in the
propertiesobject - Use
requiredarray for mandatory fields - Add
descriptionfor better accuracy
Supported Field Types
| Type | Description | Example Use Case |
|---|---|---|
string | Text values | Names, addresses, IDs |
number | Numeric values with decimals | Prices, amounts, percentages |
integer | Whole numbers | Counts, quantities |
boolean | True/false values | Checkboxes, yes/no questions |
array | Lists of items | Line items, charges, addresses |
object | Nested structures | Address with street/city/zip |
Common Patterns
1. Basic Field Extraction
Extract simple fields from a document:
{
"type": "object",
"properties": {
"patient_name": {
"type": "string",
"description": "The name of the patient"
},
"doctor": {
"type": "string",
"description": "Primary care physician of the patient"
},
"copay": {
"type": "number",
"description": "Copay that the patient is required to pay before services are rendered"
}
},
"required": ["patient_name"]
}2. Nested Objects
Extract hierarchical data with up to 5 levels of nesting:
{
"type": "object",
"properties": {
"invoice": {
"type": "object",
"properties": {
"number": {
"type": "string",
"description": "Invoice number"
},
"date": {
"type": "string",
"description": "Invoice date in YYYY-MM-DD format"
},
"total": {
"type": "number",
"description": "Total amount in USD"
}
}
},
"vendor": {
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {"type": "string"},
"phone": {"type": "string"}
}
}
}
}3. Arrays (Lists)
Extract repeating items from tables or lists:
{
"type": "object",
"properties": {
"charges": {
"type": "array",
"description": "List of charges on the utility bill",
"items": {
"type": "object",
"properties": {
"charge_type": {
"type": "string",
"description": "Type of charge (e.g., electricity, gas, water)"
},
"amount": {
"type": "number",
"description": "Charge amount in USD"
},
"usage": {
"type": "string",
"description": "Usage amount with unit (e.g., '450 kWh', '25 CCF')"
}
}
}
}
}
}4. Enum (Restricted Values)
Limit extracted values to a specific set (string enums only):
{
"type": "object",
"properties": {
"account_type": {
"type": "string",
"enum": ["Premium Checking", "Standard Checking"],
"description": "Bank account type"
}
}
}5. Document Classification
Use enum to classify documents and extract different fields per type:
{
"type": "object",
"properties": {
"document_type": {
"type": "string",
"enum": ["Passport", "Invoice", "Receipt", "Other"]
}
},
"required": ["document_type"]
}After classification, make a second extraction call with type-specific schema.
6. Nullable Fields
For extract-20251024 (recommended):
{
"type": "object",
"properties": {
"middle_name": {
"type": "string",
"nullable": true,
"description": "Patient's middle name, if provided"
}
}
}For extract-20250930:
{
"type": "object",
"properties": {
"middle_name": {
"type": ["string", "null"],
"description": "Patient's middle name, if provided"
}
}
}7. Union Types
When a field can accept multiple types, use anyOf (especially with objects or arrays):
{
"type": "object",
"properties": {
"field1": {"type": "string"},
"field2": {
"anyOf": [
{"type": "number"},
{"type": "object"}
]
}
}
}Validation rule: Every sub-schema withinanyOfmust include either atypeoranyOfkeyword. If a sub-schema is missing both, the API returns a 400 error identifying the invalid path. For example,"anyOf": [{"description": "a number"}]will fail because the sub-schema has notype.
Pydantic Example (Python Library)
When using the landingai-ade Python library, use Pydantic models:
from pydantic import BaseModel, Field
from landingai_ade.lib import pydantic_to_json_schema
class Invoice(BaseModel):
invoice_number: str = Field(description="Invoice number")
invoice_date: str = Field(description="Invoice date")
total_amount: float = Field(description="Total amount in USD")
vendor_name: str = Field(description="Vendor name")
# Convert to JSON schema
schema = pydantic_to_json_schema(Invoice)Best Practices
1. Use Descriptive Field Names
- Good:
invoice_number,patient_name,total_amount - Bad:
number,name,amount
2. Add Detailed Descriptions
Include in descriptions:
- Exactly what to extract
- Format requirements ("in USD", "as YYYY-MM-DD")
- What to include/exclude ("excluding tax", "including area code")
Example:
{
"total_amount": {
"type": "number",
"description": "Total amount in USD, excluding tax"
}
}3. Match Document Structure
Order fields in your schema to match their order in the document.
4. Limit Complexity
- Keep schemas under 30 properties for optimal performance
- Start with a few fields, add more as needed
- Keep names short but descriptive
- Flatten nested arrays when possible
- Reduce optional properties
5. Use Appropriate Types
- Use
numberfor monetary values or calculations - Use
integerfor counts - Use
arrayfor repeating items (tables, lists) - Use
objectfor hierarchical data
Model-Specific Considerations
extract-20251024 (Latest, Recommended)
Supported Keywords:
type,properties,required,description,titleenum(string only),nullablearray,items,maxItems,minItemsnumber,maximum,minimumanyOf,$ref,$defsformat,propertyOrdering
Behavior:
- Missing fields return
null(even if required) - Falls back to extract-20250930 if schema is too complex
extract-20250930 (Previous Version)
Unsupported Keywords:
allOf,not,dependentRequired,dependentSchemas,if,then,else
Behavior:
- Inconsistent handling of missing fields (may return
null,0, empty string, etc.) - Use type array for nullable:
"type": ["string", "null"]
Troubleshooting
Schema Validation Errors (422)
- Ensure top-level type is "object"
- Check JSON syntax
- Verify required structure
Partial Extraction (206)
- Extracted data doesn't match schema
- API returns partial results and consumes credits
- Review field types and descriptions
Low Accuracy
- Add more detailed descriptions
- Use more specific field names
- Match schema to document structure
- Reduce schema complexity
Examples by Use Case
Invoice Processing
{
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"invoice_date": {"type": "string", "description": "Date in YYYY-MM-DD format"},
"due_date": {"type": "string", "description": "Due date in YYYY-MM-DD format"},
"vendor_name": {"type": "string"},
"total_amount": {"type": "number", "description": "Total in USD"},
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {"type": "string"},
"quantity": {"type": "integer"},
"unit_price": {"type": "number"},
"amount": {"type": "number"}
}
}
}
},
"required": ["invoice_number", "total_amount"]
}Bank Statement
{
"type": "object",
"properties": {
"account_holder": {"type": "string"},
"account_number": {"type": "string"},
"statement_period": {"type": "string"},
"beginning_balance": {"type": "number"},
"ending_balance": {"type": "number"},
"transactions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": {"type": "string"},
"description": {"type": "string"},
"amount": {"type": "number"},
"type": {"type": "string", "enum": ["Debit", "Credit"]}
}
}
}
}
}Medical Form
{
"type": "object",
"properties": {
"patient": {
"type": "object",
"properties": {
"first_name": {"type": "string"},
"middle_name": {"type": "string", "nullable": true},
"last_name": {"type": "string"},
"date_of_birth": {"type": "string"},
"insurance_id": {"type": "string"}
}
},
"provider": {
"type": "object",
"properties": {
"name": {"type": "string"},
"specialty": {"type": "string"}
}
},
"has_allergies": {"type": "boolean"},
"allergies": {
"type": "array",
"items": {"type": "string"}
}
}
}References
Supported File Formats
Overview
LandingAI ADE supports 20+ file formats across PDFs, images, documents, presentations, and spreadsheets. This reference details supported formats, limitations, and considerations for each category.
Quick Reference
| Category | Formats | Notes |
|---|---|---|
| Up to 100 pages in Playground; see rate limits for API | ||
| Images | JPEG, JPG, PNG, + 15 more | Common formats fully supported |
| Documents | DOC, DOCX, ODT | Converted to PDF before parsing |
| Presentations | PPT, PPTX, ODP | Converted to PDF before parsing |
| Spreadsheets | CSV, XLSX | Up to 10 MB in Playground; no limit in API |
PDFs
Supported
- Standard PDF files (.pdf)
- Multi-page PDFs (up to 100 pages in Playground)
- Scanned PDFs (OCR applied automatically)
API Limits
- Playground: Up to 100 pages
- API: See Rate Limits
- Parse Jobs (async): Up to 1 GB or 6,000 pages
Usage Example
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
response = client.parse(
document=Path("document.pdf"),
model="dpt-2-latest"
)Images
Fully Supported (Playground + API)
- JPEG - Joint Photographic Experts Group
- JPG - JPEG variant
- PNG - Portable Network Graphics
API-Only Supported
- APNG - Animated PNG
- BMP - Bitmap
- DCX - Multi-page PCX
- DDS - DirectDraw Surface
- DIB - Device Independent Bitmap
- GD - GD Graphics
- GIF - Graphics Interchange Format
- ICNS - Apple Icon
- JP2 - JPEG 2000
- PCX - PC Paintbrush
- PPM - Portable Pixmap
- PSD - Photoshop Document
- TGA - Truevision Graphics Adapter
- TIFF - Tagged Image File Format
- WEBP - Web Picture format
Considerations
- All images are processed with OCR if they contain text
- Images with complex layouts benefit from DPT-2
- Scanned documents work best as images or PDFs
Usage Example
# Parse an image file
response = client.parse(
document=Path("receipt.jpg"),
model="dpt-2-latest"
)
# Parse from URL
response = client.parse(
document_url="https://example.com/invoice.png",
model="dpt-2-latest"
)Text Documents
Supported Formats
- DOC - Microsoft Word (legacy)
- DOCX - Microsoft Word
- ODT - OpenDocument Text (LibreOffice)
Important: File Conversion
All text documents are converted to PDF before parsing.
Impact:
- Layout may change during conversion
- Unsupported fonts are replaced (may cause text wrapping changes)
- Page count may increase or decrease
- Text may overflow onto additional pages
Parsing Quality: Despite layout changes, ADE still parses content correctly. The semantic chunking and content extraction work as expected.
Best Practices
- Test with sample documents to understand conversion impact
- For critical layout preservation, convert to PDF manually first
- Use DPT-2 for documents with complex formatting
Usage Example
# Parse Word document
response = client.parse(
document=Path("contract.docx"),
model="dpt-2-latest"
)Presentations
Supported Formats
- PPT - Microsoft PowerPoint (legacy)
- PPTX - Microsoft PowerPoint
- ODP - OpenDocument Presentation (LibreOffice)
Important: File Conversion
All presentations are converted to PDF before parsing.
Impact:
- Each slide becomes a page in the PDF
- Animations and transitions are lost
- Layout may change (same considerations as text documents)
- Speaker notes may not be preserved
Parsing Quality: Slide content, text, images, and tables are extracted correctly.
Best Practices
- Test presentation conversion with sample files
- For critical slides, export to PDF manually first
- Use DPT-2 for slides with complex graphics or logos
Usage Example
# Parse PowerPoint
response = client.parse(
document=Path("presentation.pptx"),
model="dpt-2-latest"
)Spreadsheets
Supported Formats
- CSV - Comma-Separated Values
- XLSX - Microsoft Excel
Limits
| Environment | CSV Limit | XLSX Limit | Sheets/Rows/Columns |
|---|---|---|---|
| Playground | 10 MB | 10 MB | Unlimited |
| API/Library | Unlimited | Unlimited | Unlimited |
Note: In Playground, a render limit applies and only a truncated version is displayed. This does not affect parsing results.
How Spreadsheets are Parsed
Each table/sheet:
- Extracted as
tablechunk type - Cell-level IDs generated for traceability
- Converted to HTML tables in Markdown
- Grounding includes cell positions
Best Practices
- Use CSV for simple data (faster processing)
- Use XLSX for multi-sheet workbooks
- For large spreadsheets, use Parse Jobs API (async)
- Filter specific sheets if only subset needed
Usage Example
# Parse Excel file
response = client.parse(
document=Path("sales_data.xlsx"),
model="dpt-2-latest"
)
# Access table chunks
tables = [chunk for chunk in response.chunks if chunk.type == 'table']
for table in tables:
print(table.markdown) # HTML tableLoading from Bytes
Python Library Support
You can load documents from bytes (useful for API responses, web uploads, or in-memory processing):
from pathlib import Path
# Load PDF from bytes
with open("document.pdf", "rb") as f:
pdf_bytes = f.read()
response = client.parse(
document=pdf_bytes, # Pass bytes directly
model="dpt-2-latest"
)
# Load image from bytes
with open("image.jpg", "rb") as f:
image_bytes = f.read()
response = client.parse(
document=image_bytes,
model="dpt-2-latest"
)Use Cases:
- Processing files from web uploads without saving to disk
- Working with files from cloud storage APIs
- Processing encrypted files after decryption in memory
Format Selection Guide
When to Use Each Format
| Format | Best For | Considerations |
|---|---|---|
| Any document type, forms, reports | Native format - no conversion | |
| Images | Receipts, photos, scanned docs | Use high resolution for better OCR |
| DOCX | Contracts, reports, letters | Layout may change during conversion |
| PPTX | Slide content extraction | Animations lost |
| XLSX | Financial data, tables, lists | Best for structured data |
| CSV | Simple tabular data | Fast processing |
Troubleshooting
Error: "document closed or encrypted"
Cause: Password-protected document Solution: Pass password="..." parameter (requires ZDR enabled). Without ZDR, remove password protection before parsing.
Poor OCR Results from Images
Possible Causes:
- Low resolution (< 300 DPI)
- Poor image quality
- Blurry or skewed text
Solutions:
- Increase image resolution
- Ensure good lighting for scanned documents
- Use deskewing tools if needed
Unexpected Layout Changes (DOCX/PPTX)
Cause: File conversion to PDF Solutions:
- Convert to PDF manually with preferred tool
- Test conversion with sample files
- Accept layout changes if content extraction is primary goal
Large File Processing Slow
Solutions:
- Use Parse Jobs API for files > 50 pages
- Compress images before processing
- Split multi-document PDFs if possible
Spreadsheet Too Large
Solutions:
- Filter to specific sheets before parsing
- Split large XLSX into smaller files
- Use CSV format if possible (faster)
- Use Parse Jobs API (async processing)
API vs Playground Support
| Feature | Playground | API/Library |
|---|---|---|
| PDF Pages | Up to 100 | See rate limits |
| Common Images | ✓ | ✓ |
| Extended Images | ✗ | ✓ |
| Documents | ✓ | ✓ |
| Presentations | ✓ | ✓ |
| Spreadsheets | Up to 10 MB | Unlimited |
| Parse Jobs | ✗ | ✓ |
| Bytes Loading | ✗ | ✓ |
References
Troubleshooting
HTTP Error Codes
| Code | Meaning | Common Causes | Action |
|---|---|---|---|
| 400 | Bad Request | anyOf sub-schema missing type/anyOf keyword; invalid parameter | Fix schema per error message |
| 401 | Unauthorized | Missing or invalid VISION_AGENT_API_KEY | Check .env file and key validity |
| 413 | Payload Too Large | File exceeds sync parse limit | Use Parse Jobs API for large files |
| 422 | Unprocessable Entity | Invalid JSON schema; unsupported keywords; top-level type not "object"; password-protected file without ZDR or with wrong password | Validate schema structure; check password; enable ZDR |
| 429 | Rate Limited | Too many concurrent requests | Add retry with exponential backoff |
| 206 | Partial Content | Some pages failed (parse) or schema violation (extract) | Check metadata.failed_pages or metadata.schema_violation_error |
Parse Failures
- Password-protected file: Pass
password="..."parameter (requires ZDR). Without ZDR, remove password protection before parsing - Unsupported format: Check file formats reference
- File too large: Use Parse Jobs API (
client.parse_jobs.create()) for files > 50 pages or > 10 MB - Poor OCR quality: Use high-resolution scans (300+ DPI); consider
dpt-2-latestoverdpt-2-minifor scanned docs
Low Extraction Accuracy
- Add more detailed field descriptions (include format hints: "as YYYY-MM-DD", "in USD")
- Use more specific field names (
invoice_total_usdrather thantotal) - Match schema field order to how data appears in the document
- Reduce schema complexity — stay under 30 properties for best results
- Try
model="extract-20251024"if the latest model misses fields it should return asnull
Missing Fields
- Verify the field actually exists in the document
- Check that the field description clearly identifies the data
extract-20251024returnsnullfor absent fields;extract-latestmay omit them entirely- Check
extraction_metadata— if the field haschunk_ids, the model found it but may have returned an unexpected value
Schema Validation Errors (HTTP 422)
- Top-level schema must have
"type": "object" anyOf/oneOfsub-schemas each need their owntypeoranyOfkeyword- Avoid unsupported JSON Schema keywords (e.g.,
if/then,$ref) - Use
pydantic_to_json_schema()fromlandingai_ade.libfor reliable schema generation
Performance Issues
- Use
dpt-2-minifor simple, digitally-native documents (faster and cheaper) - Enable Parse Jobs (
client.parse_jobs.create()) for large files to avoid timeouts - Process documents in parallel with
ThreadPoolExecutor— see document-workflows batch-processing.md - Cache parse results (save
response.markdownto disk) when running multiple extractions on the same document
Partial Results (HTTP 206)
Parse 206 — Some pages failed:
response = client.parse(document=Path("doc.pdf"), model="dpt-2-latest")
if response.metadata.failed_pages:
print(f"Failed pages: {response.metadata.failed_pages}")
# Remaining pages were parsed successfully; credits are consumedExtract 206 — Schema violation:
response = client.extract(schema=schema, markdown=markdown)
err = response.metadata.schema_violation_error
if err:
print(f"Schema violation: {err}")
# Partial data is still returned; credits are consumedUse Cases
Common document processing patterns using LandingAI ADE. All examples assume the client and dependencies are set up per SKILL.md.
Invoice Processing
from pydantic import BaseModel, Field
from landingai_ade.lib import pydantic_to_json_schema
from landingai_ade import LandingAIADE
from pathlib import Path
class LineItem(BaseModel):
description: str = Field(description="Item description")
quantity: int = Field(description="Quantity")
unit_price: float = Field(description="Unit price in USD")
amount: float = Field(description="Line total in USD")
class Invoice(BaseModel):
invoice_number: str = Field(description="Invoice number")
invoice_date: str = Field(description="Invoice date as YYYY-MM-DD")
vendor_name: str = Field(description="Vendor company name")
vendor_address: str = Field(description="Vendor address")
total_amount: float = Field(description="Total amount due in USD")
line_items: list[LineItem] = Field(description="List of line items")
client = LandingAIADE()
parse_response = client.parse(document=Path("invoice.pdf"), model="dpt-2-latest")
extract_response = client.extract(
schema=pydantic_to_json_schema(Invoice),
markdown=parse_response.markdown,
model="extract-latest"
)
invoice = extract_response.extraction
print(f"Invoice #{invoice['invoice_number']} — ${invoice['total_amount']}")
for item in invoice['line_items']:
print(f" {item['description']}: {item['quantity']} x ${item['unit_price']}")Form Data Extraction
from pydantic import BaseModel, Field
from typing import Optional
class PatientIntake(BaseModel):
patient_name: str = Field(description="Full patient name")
date_of_birth: str = Field(description="Date of birth as YYYY-MM-DD")
insurance_id: str = Field(description="Insurance ID number")
emergency_contact: str = Field(description="Emergency contact name and phone")
allergies: list[str] = Field(description="List of known allergies")
has_existing_conditions: bool = Field(description="Whether patient has existing conditions")
primary_complaint: Optional[str] = Field(default=None, description="Primary complaint or reason for visit")
# Parse and extract
parse_response = client.parse(document=Path("intake_form.pdf"), model="dpt-2-latest")
extract_response = client.extract(
schema=pydantic_to_json_schema(PatientIntake),
markdown=parse_response.markdown,
model="extract-latest"
)
print(extract_response.extraction)Multi-Document Classification and Extraction
Split a batch PDF into document types, then extract type-specific fields from each:
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
# Step 1: Parse the batch
parse_response = client.parse(document=Path("batch.pdf"), model="dpt-2-latest")
# Step 2: Split by document type
split_response = client.split(
markdown=parse_response.markdown,
split_class=[
{"name": "Invoice", "description": "Commercial invoice with line items", "identifier": "Invoice Number"},
{"name": "Receipt", "description": "Payment receipt", "identifier": "Receipt Date"},
{"name": "Bank Statement", "description": "Monthly bank account statement"}
]
)
# Step 3: Extract from each split based on its classification
for split in split_response.splits:
print(f"Type: {split.classification}, Pages: {split.pages}")
if split.classification == "Invoice":
extract_response = client.extract(
schema=pydantic_to_json_schema(Invoice),
markdown=split.markdowns[0],
model="extract-latest"
)
print(f" Invoice #{extract_response.extraction['invoice_number']}")
elif split.classification == "Bank Statement":
# Use bank statement schema
passTable Extraction
from landingai_ade import LandingAIADE
from pathlib import Path
import json
client = LandingAIADE()
# Parse document or spreadsheet
response = client.parse(document=Path("data.xlsx"), model="dpt-2-latest")
# Filter table chunks
tables = [chunk for chunk in response.chunks if chunk.type == "table"]
print(f"Found {len(tables)} tables")
for i, table in enumerate(tables, start=1):
print(f"\nTable {i} on page {table.grounding.page}:")
print(table.markdown) # HTML table — parse with pandas or BeautifulSoup
# Save as CSV using pandas
import pandas as pd
from io import StringIO
for i, table in enumerate(tables, start=1):
try:
dfs = pd.read_html(StringIO(table.markdown))
if dfs:
dfs[0].to_csv(f"table_{i:02d}.csv", index=False)
print(f"Saved table_{i:02d}.csv")
except Exception as e:
print(f"Table {i}: could not parse as HTML ({e})")Multi-page tables: When a table spans multiple pages, ADE emits separate chunks per page
and may represent some pages as plain text instead of table chunks. See
Table Stitching in the
document-workflows skill for three approaches to merge them into a single output.Figure Extraction with Cropping
Extract figures from PDFs as individual PNG files using bounding boxes:
from dotenv import load_dotenv
load_dotenv()
import fitz # PyMuPDF — install with: pip install pymupdf
from landingai_ade import LandingAIADE
from pathlib import Path
client = LandingAIADE()
# Step 1: Parse the PDF
pdf_path = Path("document.pdf")
response = client.parse(document=pdf_path, model="dpt-2-latest")
# Step 2: Filter figure chunks
figure_chunks = [chunk for chunk in response.chunks if chunk.type == "figure"]
print(f"Found {len(figure_chunks)} figures")
# Step 3: Open PDF with PyMuPDF and crop each figure
pdf_doc = fitz.open(pdf_path)
for idx, chunk in enumerate(figure_chunks, start=1):
page_num = chunk.grounding.page
bbox = chunk.grounding.box # Always present — API guarantees grounding on returned chunks
page = pdf_doc[page_num]
# Convert normalized coordinates (0-1) to absolute pixel coordinates
x0 = bbox.left * page.rect.width
y0 = bbox.top * page.rect.height
x1 = bbox.right * page.rect.width
y1 = bbox.bottom * page.rect.height
# Render at 2x zoom for quality
zoom = 2.0
mat = fitz.Matrix(zoom, zoom)
pix = page.get_pixmap(matrix=mat, clip=fitz.Rect(x0, y0, x1, y1))
output_path = f"figure_{idx:02d}_page{page_num + 1}.png"
pix.save(output_path)
print(f"Figure {idx}: saved as {output_path}")
# IMPORTANT: Read back the first output PNG and visually verify it shows the right content
# before continuing. Page indexing bugs are easy to miss without a visual check.
pdf_doc.close()Key Points:
- Bounding boxes use normalized coordinates (0-1); multiply by page dimensions to get pixels
- Every chunk returned by the API is guaranteed to have
grounding.box - Use
zoom=2.0or higher for crisp output - Page numbers are zero-indexed in ADE
- After saving the first PNG, read it back and confirm it shows the expected figure
Related skills
FAQ
What are ADE's three capabilities?
Parse converts documents into structured Markdown, Extract pulls specific structured data using JSON schemas or Pydantic models, and Split classifies and separates multi-document batches by type.
Does it need training or templates?
No, it parses, extracts, and classifies documents without requiring templates or training.