Quickstart
Parse your first document with the API.
Before you start, create an API key in OpenParser Studio and choose a PDF, PNG, or JPEG file.
Parse a document
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.pdfconst 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"),
)Replace YOUR_API_KEY and ./document.pdf. Direct uploads can be up to 50 MiB.
The request is multipart:
requestis the parse configuration, sent asapplication/json.fileis the document.
ocr_model is required. The openparser@1 output format is versioned, so its response shape remains stable as the underlying OCR model changes.
Handle the response
Most requests finish with 200 OK and a ParsedDocument:
{
"output_format": "openparser@1",
"document_id": "doc_01h2example",
"provenance": {
"provider": "paddle",
"model": "paddleocr-vl-1.6"
},
"text": "# Invoice\n\nTotal due: $1,234.56",
"markdown": "# Invoice\n\nTotal due: $1,234.56\n",
"pages": [
{
"number": 1,
"width": 1700,
"height": 2200,
"unit": "pixel",
"element_ids": ["elem-title", "elem-total"],
"reading_order": ["elem-title", "elem-total"]
}
],
"elements": [
{
"id": "elem-title",
"kind": "text",
"role": "heading",
"text": "# Invoice",
"spans": [{ "start": 0, "end": 9 }]
},
{
"id": "elem-total",
"kind": "text",
"role": "paragraph",
"text": "Total due: $1,234.56",
"spans": [{ "start": 11, "end": 31 }]
}
],
"relations": [],
"text_annotations": [],
"assets": []
}Longer requests return 202 Accepted instead. Read the job ID from the response body or follow the Location header:
curl -s 'https://api.openparser.dev/jobs/JOB_ID' \
-H 'Authorization: Bearer YOUR_API_KEY'openparser jobs get JOB_ID --jsonconst job = await client.jobs.get('JOB_ID');job = client.jobs.get("JOB_ID")Polling does not control the job. Once accepted, it continues if your client disconnects.
Extract fields
Use /extract when you want a specific JSON shape instead of the full parsed document:
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-6-luna","schema":{"type":"object","properties":{"invoice_number":{"type":"string"}},"required":["invoice_number"],"additionalProperties":false}};type=application/json' \
-F 'file=@./document.pdf'openparser extract sync ./document.pdf \
--ocr-model paddleocr-vl-1.6 \
--llm-model openai/gpt-6-luna \
--schema-json '{"type":"object","properties":{"invoice_number":{"type":"string"}},"required":["invoice_number"],"additionalProperties":false}'const extracted = await client.extract.sync(
{
ocr_model: 'paddleocr-vl-1.6',
llm_model: 'openai/gpt-6-luna',
schema: {
type: 'object',
properties: {
invoice_number: { type: 'string' },
},
required: ['invoice_number'],
additionalProperties: false,
},
},
file,
);from pathlib import Path
extracted = client.extract.sync(
{
"ocr_model": "paddleocr-vl-1.6",
"llm_model": "openai/gpt-6-luna",
"schema": {
"type": "object",
"properties": {
"invoice_number": {"type": "string"},
},
"required": ["invoice_number"],
"additionalProperties": False,
},
},
file=Path("document.pdf"),
)Call GET /models/llm to choose a current extraction model. The default response is a short list of suggested models.
Retry safely
Generate a new Idempotency-Key for each new request. When a network error makes the outcome unclear, retry the same request with the same key. OpenParser returns the original job instead of starting duplicate work.
Do not reuse a key with different input. The API rejects that request with 409 Conflict.