Parse

Parse documents with every OpenParser client.

Turn a PDF, PNG, or JPEG into text, semantic elements, and page metadata. Request and response field definitions live in the API reference; this page shows how to call parse from each client.

Choose a source

Each parse request accepts exactly one source:

  • file — inline multipart upload (up to 50 MiB)
  • file_id — bytes uploaded earlier with POST /files (up to 100 MiB in the pool)

Upload once and reuse file_id when you plan to parse or extract the same document multiple times. See Files for pool lifecycle commands.

# Inline upload
curl -X POST 'https://api.openparser.dev/parse' \
  -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","output_format":"openparser@1"};type=application/json' \
  -F 'file=@./document.pdf'

# Reuse a pooled file
curl -X POST 'https://api.openparser.dev/parse' \
  -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","file_id":"opf_...","output_format":"openparser@1"};type=application/json'
openparser parse sync ./document.pdf
openparser files upload ./contract.pdf --json
openparser parse sync --file-id opf_...
const parsed = await client.parse.sync(
  { ocr_model: 'paddleocr-vl-1.6', output_format: 'openparser@1' },
  file,
);

const uploaded = await client.files.upload(file);
await client.parse.sync({
  ocr_model: 'paddleocr-vl-1.6',
  file_id: uploaded.id,
  output_format: 'openparser@1',
});
from pathlib import Path

parsed = client.parse.sync(
    {"ocr_model": "paddleocr-vl-1.6", "output_format": "openparser@1"},
    file=Path("document.pdf"),
)

uploaded = client.files.upload(Path("contract.pdf"))
client.parse.sync(
    {
        "ocr_model": "paddleocr-vl-1.6",
        "file_id": uploaded.id,
        "output_format": "openparser@1",
    }
)

Set ocr_model to an ID from GET /models/ocr. The default output is openparser@1; use raw only when you need the OCR provider envelope. See Parsing for model options and output trade-offs.

Parse synchronously

POST /parse admits a durable job, waits up to the server sync limit (typically 300 seconds), and returns the terminal parse body when ready. If the wait window expires first, the response is 202 Accepted with a Location header — poll that job as described in Jobs.

curl -X POST 'https://api.openparser.dev/parse' \
  -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","output_format":"openparser@1"};type=application/json' \
  -F 'file=@./document.pdf'
openparser parse sync ./document.pdf --json
const parsed = await client.parse.sync(
  { ocr_model: 'paddleocr-vl-1.6', output_format: 'openparser@1' },
  file,
);
from pathlib import Path

parsed = client.parse.sync(
    {"ocr_model": "paddleocr-vl-1.6", "output_format": "openparser@1"},
    file=Path("document.pdf"),
)

Parse asynchronously

POST /parse/async returns 202 as soon as the job is admitted. Poll GET /jobs/{id} until status is terminal, then read the parse body from GET /jobs/{id}/result.

# Admit
curl -X POST 'https://api.openparser.dev/parse/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","output_format":"openparser@1"};type=application/json' \
  -F 'file=@./document.pdf'
# Poll and fetch
curl -s 'https://api.openparser.dev/jobs/opj_...' \
  -H 'Authorization: Bearer YOUR_API_KEY'

curl -s 'https://api.openparser.dev/jobs/opj_.../result?format=openparser@1' \
  -H 'Authorization: Bearer YOUR_API_KEY'
openparser parse async ./document.pdf --json
openparser jobs get opj_... --json
openparser jobs result opj_... --format openparser@1 --json
const accepted = await client.parse.async(
  { ocr_model: 'paddleocr-vl-1.6', output_format: 'openparser@1' },
  file,
);

const job = await client.jobs.get(accepted.id);
const result = await client.jobs.result(accepted.id, { format: 'openparser@1' });
from pathlib import Path

accepted = client.parse.async_(
    {"ocr_model": "paddleocr-vl-1.6", "output_format": "openparser@1"},
    file=Path("document.pdf"),
)

job = client.jobs.get(accepted.id)
result = client.jobs.result(accepted.id, format="openparser@1")

# Or poll with the helper
job = client.wait_for_job(accepted.id)

Parse a batch

POST /parse/batch admits 1–100 child parse jobs behind one parent job. Each item needs a client_item_id and either file_index (into the ordered multipart files fields) or file_id. The parent returns 202; poll it and paginate children until every item is terminal. See Batching for mixed outcomes and billing.

curl -X POST 'https://api.openparser.dev/parse/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"},{"client_item_id":"invoice-002","file_index":1,"ocr_model":"paddleocr-vl-1.6"}],"output_format":"openparser@1"};type=application/json' \
  -F 'files=@./invoice-001.pdf' \
  -F 'files=@./invoice-002.pdf'
# parse-batch.json contains the request object (items + output_format)
openparser parse batch --request parse-batch.json ./invoice-001.pdf ./invoice-002.pdf
const accepted = await client.parse.batch({
  body: {
    request: {
      items: [
        { client_item_id: 'invoice-001', file_index: 0, ocr_model: 'paddleocr-vl-1.6' },
        { client_item_id: 'invoice-002', file_index: 1, ocr_model: 'paddleocr-vl-1.6' },
      ],
      output_format: 'openparser@1',
    },
    files: [file1, file2],
  },
});
from pathlib import Path

accepted = client.parse.batch(
    {
        "items": [
            {
                "client_item_id": "invoice-001",
                "file_index": 0,
                "ocr_model": "paddleocr-vl-1.6",
            },
            {
                "client_item_id": "invoice-002",
                "file_index": 1,
                "ocr_model": "paddleocr-vl-1.6",
            },
        ],
        "output_format": "openparser@1",
    },
    files=[Path("invoice-001.pdf"), Path("invoice-002.pdf")],
)

Outputs and idempotency

Outputs. A succeeded parse returns openparser@1 (plain text, Markdown, pages, elements, relations, annotations, assets) or raw (provider envelope). Field definitions are in the OpenParser schema. Async and timed-out sync responses expose the same body through GET /jobs/{id}/result with ?format=openparser@1 or ?format=raw.

Idempotency. Every parse admission endpoint requires an Idempotency-Key. Generate a new key per distinct request; reuse the same key only when retrying the identical admission after a network error. Reusing a key with different input returns 409 idempotency_conflict. See Errors and retries.

KEY="parse-invoice-2026-04-01"

curl -X POST 'https://api.openparser.dev/parse' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $KEY" \
  -F 'request={"ocr_model":"paddleocr-vl-1.6","output_format":"openparser@1"};type=application/json' \
  -F 'file=@./document.pdf'
openparser parse sync ./document.pdf --idempotency-key "parse-invoice-2026-04-01" --json
const parsed = await client.parse.sync(
  { ocr_model: 'paddleocr-vl-1.6', output_format: 'openparser@1' },
  file,
  { idempotencyKey: 'parse-invoice-2026-04-01' },
);
from pathlib import Path

parsed = client.parse.sync(
    {"ocr_model": "paddleocr-vl-1.6", "output_format": "openparser@1"},
    file=Path("document.pdf"),
    idempotency_key="parse-invoice-2026-04-01",
)

On this page