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}' \
  --json
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,
);
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_... --json
const 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.pdf
const 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" \
  --json
const 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}' \
  --json
const 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: field requests source locations for extracted values. Any compatible extraction model from GET /models/llm may be used; certified_grounding marks 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 \
  --json
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' } },
      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.

On this page