Extract
Extract schema-validated JSON with every OpenParser client.
Extraction runs parsing (unless you reuse a parse job), calls a language model with your JSON Schema, and validates the result. Request and response schemas are in the API reference; this page shows how to call extract from each client.
Inline schema vs pipeline
Send extraction configuration inline on every request, or save it once as a pipeline and pass pipeline_id. Do not combine pipeline_id with inline ocr_model, llm_model, schema, or extraction options.
Inline fits one-off experiments and per-document schemas. Pipelines fit stable production configs you reuse across many documents.
# Inline schema
curl -X POST 'https://api.openparser.dev/extract' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"ocr_model":"paddleocr-vl-1.6","llm_model":"openai/gpt-4.1-mini","schema":{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}};type=application/json' \
-F 'file=@./invoice.pdf'
# Saved pipeline
curl -X POST 'https://api.openparser.dev/extract/async' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"pipeline_id":"oppl_..."};type=application/json' \
-F 'file=@./invoice.pdf'openparser extract sync ./invoice.pdf \
--ocr-model paddleocr-vl-1.6 \
--llm-model openai/gpt-4.1-mini \
--schema-json '{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}'
openparser extract async ./invoice.pdf --pipeline-id oppl_...const result = await client.extract.sync(
{
ocr_model: 'paddleocr-vl-1.6',
llm_model: 'openai/gpt-4.1-mini',
schema: {
type: 'object',
properties: {
invoice_number: { type: 'string' },
total: { type: 'number' },
},
required: ['invoice_number', 'total'],
additionalProperties: false,
},
},
file,
);
const accepted = await client.extract.async({ pipeline_id: 'oppl_...' }, file);from pathlib import Path
result = client.extract.sync(
{
"ocr_model": "paddleocr-vl-1.6",
"llm_model": "openai/gpt-4.1-mini",
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"total": {"type": "number"},
},
"required": ["invoice_number", "total"],
"additionalProperties": False,
},
},
file=Path("invoice.pdf"),
)
accepted = client.extract.async_(
{"pipeline_id": "oppl_..."},
file=Path("invoice.pdf"),
)Create pipelines with POST /pipelines. Choose llm_model from GET /models/llm.
Extract synchronously
POST /extract waits for a terminal ExtractionTerminalResult or returns 202 when the sync wait window expires. File-backed requests parse first; see Reuse a parse job to skip OCR.
curl -X POST 'https://api.openparser.dev/extract' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"ocr_model":"paddleocr-vl-1.6","llm_model":"openai/gpt-4.1-mini","schema":{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}};type=application/json' \
-F 'file=@./invoice.pdf'openparser extract sync ./invoice.pdf \
--ocr-model paddleocr-vl-1.6 \
--llm-model openai/gpt-4.1-mini \
--schema-json '{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}' \
--jsonconst result = await client.extract.sync(
{
ocr_model: 'paddleocr-vl-1.6',
llm_model: 'openai/gpt-4.1-mini',
schema: {
type: 'object',
properties: {
invoice_number: { type: 'string' },
total: { type: 'number' },
},
required: ['invoice_number', 'total'],
additionalProperties: false,
},
},
file,
);from pathlib import Path
result = client.extract.sync(
{
"ocr_model": "paddleocr-vl-1.6",
"llm_model": "openai/gpt-4.1-mini",
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"total": {"type": "number"},
},
"required": ["invoice_number", "total"],
"additionalProperties": False,
},
},
file=Path("invoice.pdf"),
)Extract asynchronously
POST /extract/async admits a durable job and returns 202. Poll GET /jobs/{id} and read the extraction body from the job record or GET /jobs/{id}/result. See Jobs.
curl -X POST 'https://api.openparser.dev/extract/async' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"ocr_model":"paddleocr-vl-1.6","llm_model":"openai/gpt-4.1-mini","schema":{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}};type=application/json' \
-F 'file=@./invoice.pdf'openparser extract async ./invoice.pdf \
--ocr-model paddleocr-vl-1.6 \
--llm-model openai/gpt-4.1-mini \
--schema-json '{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}' \
--json
openparser jobs get opj_... --jsonconst accepted = await client.extract.async(
{
ocr_model: 'paddleocr-vl-1.6',
llm_model: 'openai/gpt-4.1-mini',
schema: {
type: 'object',
properties: {
invoice_number: { type: 'string' },
total: { type: 'number' },
},
required: ['invoice_number', 'total'],
additionalProperties: false,
},
},
file,
);
const job = await client.jobs.get(accepted.id);from pathlib import Path
accepted = client.extract.async_(
{
"ocr_model": "paddleocr-vl-1.6",
"llm_model": "openai/gpt-4.1-mini",
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"total": {"type": "number"},
},
"required": ["invoice_number", "total"],
"additionalProperties": False,
},
},
file=Path("invoice.pdf"),
)
job = client.jobs.get(accepted.id)Extract a batch
POST /extract/batch admits up to 100 file-backed extraction children. Each item carries its own inline config or pipeline_id. parse_job_id is not supported in batch items. See Batching.
curl -X POST 'https://api.openparser.dev/extract/batch' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"items":[{"client_item_id":"invoice-001","file_index":0,"ocr_model":"paddleocr-vl-1.6","llm_model":"openai/gpt-4.1-mini","schema":{"type":"object","properties":{"invoice_number":{"type":"string"}},"required":["invoice_number"],"additionalProperties":false}}]};type=application/json' \
-F 'files=@./invoice-001.pdf'# extract-batch.json contains the ExtractBatchRequest object
openparser extract batch --request extract-batch.json ./invoice-001.pdfconst accepted = await client.extract.batch({
body: {
request: {
items: [
{
client_item_id: 'invoice-001',
file_index: 0,
ocr_model: 'paddleocr-vl-1.6',
llm_model: 'openai/gpt-4.1-mini',
schema: {
type: 'object',
properties: { invoice_number: { type: 'string' } },
required: ['invoice_number'],
additionalProperties: false,
},
},
],
},
files: [file],
},
});from pathlib import Path
accepted = client.extract.batch(
{
"items": [
{
"client_item_id": "invoice-001",
"file_index": 0,
"ocr_model": "paddleocr-vl-1.6",
"llm_model": "openai/gpt-4.1-mini",
"schema": {
"type": "object",
"properties": {"invoice_number": {"type": "string"}},
"required": ["invoice_number"],
"additionalProperties": False,
},
}
]
},
files=[Path("invoice-001.pdf")],
)Suggest a schema
POST /suggest-schema proposes a JSON Schema from a tenant-owned succeeded parse job. Optional hint is capped at 500 characters. This is a separate JSON endpoint — it does not accept file uploads.
curl -X POST 'https://api.openparser.dev/suggest-schema' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"parse_job_id":"opj_...","hint":"Invoice header fields only"}'openparser extract suggest-schema \
--parse-job-id opj_... \
--hint "Invoice header fields only" \
--jsonconst suggestion = await client.extract.suggestSchema({
parse_job_id: 'opj_...',
hint: 'Invoice header fields only',
});suggestion = client.extract.suggest_schema(
{
"parse_job_id": "opj_...",
"hint": "Invoice header fields only",
}
)Reuse a parse job
Pass parse_job_id from a succeeded parse job instead of file or file_id. OpenParser reuses the stored ParsedDocument, skips OCR, and does not add another page charge. Language-model usage is still billed. parse_job_id is available on single-document extract only — not in batch items.
curl -X POST 'https://api.openparser.dev/extract' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"parse_job_id":"opj_...","llm_model":"openai/gpt-4.1-mini","schema":{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}};type=application/json'openparser extract sync \
--parse-job-id opj_... \
--llm-model openai/gpt-4.1-mini \
--schema-json '{"type":"object","properties":{"invoice_number":{"type":"string"},"total":{"type":"number"}},"required":["invoice_number","total"],"additionalProperties":false}' \
--jsonconst result = await client.extract.sync({
parse_job_id: 'opj_...',
llm_model: 'openai/gpt-4.1-mini',
schema: {
type: 'object',
properties: {
invoice_number: { type: 'string' },
total: { type: 'number' },
},
required: ['invoice_number', 'total'],
additionalProperties: false,
},
});result = client.extract.sync(
{
"parse_job_id": "opj_...",
"llm_model": "openai/gpt-4.1-mini",
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
"total": {"type": "number"},
},
"required": ["invoice_number", "total"],
"additionalProperties": False,
},
}
)Pair with Parse or openparser parse async when you want to parse once and try multiple extraction schemas.
Grounding and repair
repair_attempts(0–2) allows another model attempt when the first output fails schema validation.grounding: fieldrequests source locations for extracted values. Any compatible extraction model fromGET /models/llmmay be used;certified_groundingmarks Eigenpal-tested models.
OpenParser does not silently switch models or exceed the configured repair limit.
curl -X POST 'https://api.openparser.dev/extract' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"ocr_model":"paddleocr-vl-1.6","llm_model":"openai/gpt-4.1-mini","schema":{"type":"object","properties":{"invoice_number":{"type":"string"}},"required":["invoice_number"],"additionalProperties":false},"grounding":"field","repair_attempts":1};type=application/json' \
-F 'file=@./invoice.pdf'openparser extract sync ./invoice.pdf \
--ocr-model paddleocr-vl-1.6 \
--llm-model openai/gpt-4.1-mini \
--schema-json '{"type":"object","properties":{"invoice_number":{"type":"string"}},"required":["invoice_number"],"additionalProperties":false}' \
--grounding field \
--repair-attempts 1 \
--jsonconst result = await client.extract.sync(
{
ocr_model: 'paddleocr-vl-1.6',
llm_model: 'openai/gpt-4.1-mini',
schema: {
type: 'object',
properties: { invoice_number: { type: 'string' } },
required: ['invoice_number'],
additionalProperties: false,
},
grounding: 'field',
repair_attempts: 1,
},
file,
);from pathlib import Path
result = client.extract.sync(
{
"ocr_model": "paddleocr-vl-1.6",
"llm_model": "openai/gpt-4.1-mini",
"schema": {
"type": "object",
"properties": {"invoice_number": {"type": "string"}},
"required": ["invoice_number"],
"additionalProperties": False,
},
"grounding": "field",
"repair_attempts": 1,
},
file=Path("invoice.pdf"),
)Successful grounded extractions include a grounding envelope on the terminal result. Each field in grounding.fields[] may include reason (why the quote is the source — not how it was formatted) and an untrusted structured transform_claim (how the quote became the value, omitted when the value is the quote as written). Token usage and per-attempt detail are documented on ExtractionTerminalResult.