Datalab in Practice: Marker, Surya, Chandra and the API
Datalab ships three open-source projects and a hosted API, and the first decision is which one you are actually installing. This guide takes the path most builders need: run Marker locally on a PDF, read what it produces, then make the same request through the API.
This is part 3 of the Document AI series. Every command below comes from the official READMEs and docs, read on September 26, 2026. None of the commands were run for this article. Where an output is shown, it is the shape the documentation describes, labeled as expected output from the docs.
Pick the right repository first
The three projects overlap, and the READMEs are clear about the division of labor.
| Project | What it is | Use it when | Install |
|---|---|---|---|
| Marker | A conversion pipeline: text layer first, Surya VLM where needed, tables, images, metadata | You want markdown, HTML, JSON, or RAG chunks out of whole documents | pip install marker-pdf |
| Surya | A 650M-parameter OCR model with layout, reading order, and table recognition | You want raw OCR lines, layout boxes, or table cells to build your own pipeline | pip install surya-ocr |
| Chandra | A full-page document VLM, version 2 released March 2026 | Scans, handwriting, math, and forms where a text-layer pipeline struggles | pip install chandra-ocr |
Marker uses Surya under the hood, so installing Marker gets you both. Chandra is separate. The Marker README points to Chandra when documents need full-page VLM OCR, such as math-heavy pages and scans.
Check the model license before you build on any of them. The code is Apache 2.0 in all three. The model weights use a modified OpenRAIL-M license. The Marker and Surya READMEs make the weights free for research, personal use, and startups under $5M in funding or revenue. The Chandra README sets the threshold at $2M and adds that the weights cannot be used competitively with Datalab's API. Above those lines, commercial use of the weights needs a license from Datalab.
Step 1: install Marker and its inference backend
The README asks for Python 3.10 or newer and PyTorch. Then:
pip install marker-pdf
Add the full extra only if you will convert DOCX, PPTX, XLSX, HTML, or EPUB files as well as PDFs:
pip install marker-pdf[full]
Marker runs its VLM through a local inference server that it starts on first use. On an NVIDIA GPU that means vLLM in Docker, which needs Docker and the NVIDIA Container Toolkit. On a CPU or Apple Silicon machine it means llama.cpp. The README's macOS instruction is:
brew install llama.cpp
This is the step most likely to trip you up, because the Python install succeeds either way. If neither backend is present, the first conversion that needs OCR has no server to call.
Step 2: convert one PDF
Put your file in a working folder. The synthetic Harbor Lane invoice from part 2 is the running example, saved as invoice.pdf. Then run the single-file command from the README:
marker_single invoice.pdf --output_format markdown --output_dir ./out
The options that matter most on a first run, all documented in the README:
--mode fastor--mode balanced. The default depends on the device: balanced on a GPU, fast on CPU and Apple Silicon. Balanced uses the VLM for layout and re-OCRs any page with bad embedded text. Fast keeps VLM calls to equations, garbled blocks, and scanned pages.--disable_ocrturns off every VLM call. It is the pure CPU text-layer path, and it skips equations and scanned pages entirely.--force_ocrOCRs every page even when the PDF has a text layer. The README suggests it when you see garbled text.--use_llmadds an external LLM to merge tables across pages, format tables, and extract form values. It defaults to a Gemini model and needs an API key for whichever service you configure.--page_range "0,5-10"limits the pages. Page numbers are 0-indexed.
For a folder of files, the batch command takes the same options:
marker ./invoices --output_dir ./out
Step 3: read what came back
Expected output from the docs, not from a run: with markdown output, the README says you get the converted text with formatted tables, image links, and LaTeX equations fenced with double dollar signs, and the extracted images are saved in the same folder. Every output format also returns a metadata dictionary.
The metadata is where you check whether the conversion did what you think. The README documents two keys. table_of_contents lists detected headings with their page and position. page_stats records, for each page, the text_extraction_method used and the count of each block type. If a page you expected to be OCR'd shows the embedded text layer as its method, and the text looks wrong, rerun with --force_ocr.
Switch to JSON when you need positions:
marker_single invoice.pdf --output_format json --output_dir ./out
The README describes the JSON as a list of pages, each a tree of blocks. Every block carries an id such as /page/0/Table/3, a block_type such as SectionHeader, Table, Form, or Handwriting, an html rendering, a four-corner polygon in page coordinates, and its children. Child blocks also carry a section_hierarchy. The page-level html uses content-ref placeholders for its children, so you have to resolve those to rebuild full HTML. The README points to a helper in marker/output.py for that.
That block tree is what makes a conversion auditable. A value you later extract from the invoice can be traced back to a block ID, and the block ID gives you a polygon to highlight on the page.
Step 4: go lower with Surya, or heavier with Chandra
If you only want the OCR layer, Surya's CLI commands each write a results.json, according to the Surya README:
surya_ocr invoice.pdf
surya_layout invoice.pdf
surya_table invoice.pdf
surya_ocr returns text with boxes, labels, confidence, and reading order. surya_layout returns labeled regions such as Text, Table, and SectionHeader with reading positions. surya_table returns rows, columns, cells, and HTML for each table.
For scans and handwriting, the Chandra README gives two routes. The recommended one starts a vLLM server in Docker and then converts:
pip install chandra-ocr
chandra_vllm
chandra invoice.pdf ./output
The other runs the model locally through Hugging Face and needs torch:
pip install chandra-ocr[hf]
chandra invoice.pdf ./output --method hf
Expected output from the docs: each file gets its own subdirectory holding a .md file, an .html file, and a _metadata.json file with page information and token counts, with extracted images saved in the output directory.
Step 5: the same conversion through the Datalab API
The hosted API runs Datalab's newer models without a GPU on your side. The quickstart installs the SDK and reads your key from the environment:
pip install datalab-python-sdk
export DATALAB_API_KEY=your_api_key_here
Then convert:
from datalab_sdk import DatalabClient, ConvertOptions
client = DatalabClient()
options = ConvertOptions(output_format="markdown", mode="balanced")
result = client.convert("invoice.pdf", options=options)
print(result.markdown)
result.save_output("output/")
The SDK polls for you. Over plain HTTP, the conversion docs show a two-step pattern: submit, then poll the request_check_url from the response.
curl -X POST https://www.datalab.to/api/v1/convert \
-H "X-API-Key: $DATALAB_API_KEY" \
-F "file=@invoice.pdf" \
-F "output_format=markdown" \
-F "mode=balanced"
Two details in those docs are easy to miss. A job can return status complete with success false and an error, so check both. And requests processed in the EU return a signed result_url to download instead of inline content. The docs warn that Python SDK 0.5.0 does not follow that URL, so an empty result from the SDK does not prove the document was empty.
The API has three modes, fast, balanced, and accurate. The docs recommend balanced for most work and accurate for scans, complex tables, and dense layouts. They also note that the REST default is fast, so pass the mode explicitly.
Step 6: extract fields instead of converting pages
Conversion gives you the whole invoice. To get fields, send a JSON schema to the extract endpoint. This is the quickstart from the structured extraction docs, trimmed to the invoice:
import json
from datalab_sdk import DatalabClient, ExtractOptions
client = DatalabClient()
schema = {
"type": "object",
"properties": {
"invoice_number": {"type": "string", "description": "Invoice ID or number"},
"total_amount": {"type": "number", "description": "Total amount due"},
"vendor_name": {"type": "string", "description": "Company or vendor name"},
},
"required": ["invoice_number", "total_amount"],
}
options = ExtractOptions(page_schema=json.dumps(schema), mode="balanced")
result = client.extract("invoice.pdf", options=options)
print(json.loads(result.extraction_schema_json))
Expected output from the docs: extraction_schema_json holds your fields, and each field gets a companion list of citations, such as total_amount_citations, naming the source block IDs. Balanced and accurate extraction also return _meta fields with an extraction status, reasoning, and verification result.
Watch the two mode settings. extraction_mode chooses turbo, fast, balanced, or accurate extraction. mode chooses parsing quality. When you omit mode, the docs say balanced and accurate extraction parse in accurate mode, which is billed separately. If you will extract more than once from the same file, convert with save_checkpoint=true and pass the checkpoint to extract so the document is not parsed twice.
What it costs, as of September 26, 2026
Datalab's pricing page lists conversion at $4 per 1,000 pages in fast or balanced mode and $10 in accurate mode, and extraction at $6 for turbo or fast, $15 for balanced, and $20 for accurate, with possible compute fees on the last two. The billing docs say processors are additive, describe a free monthly allowance of $20 for work-email accounts and $10 for personal ones, and list a Team plan at $400 a month that includes $400 of usage.
So a balanced extraction on a two-page invoice, with the default accurate parse, costs the extraction rate plus the conversion rate on both pages. The document processing cost calculator carries these Datalab rates as presets and adds the human review time that usually dominates the bill.
The local route has no per-page fee, but it has a GPU or a slow CPU, the inference server to keep running, and the weights license to respect. For a startup under the threshold with a GPU already on hand, Marker locally is a reasonable way to learn the output format before paying for anything.
Where this leaves you
You now know which repository to install, what each output format contains, and how the API maps onto the local tools. The part to test on your own documents is the one this article could not: whether fast mode is good enough on your scans, and whether the extra accuracy of the hosted models is worth the per-page price.
For how Datalab fits a wider vendor shortlist, see where Datalab fits among enterprise tools.
Previous: Parse, extract, classify, split. Next: Extend in practice: schemas, evals and workflows.
