Free text in, your schema out
Contracts, emails, job posts, product descriptions: most business data is prose with a database schema trapped inside. A batch extraction job turns fifty thousand documents into fifty thousand rows.
- Schema in the prompt
- JSON out per record
- Chain with OCR jobs
- 50% off realtime
Built for this shape of work.
Independent records, no user waiting, and a real budget: the profile the 50% batch discount is priced for.
Extraction is ETL, and ETL is batch
Structured extraction almost always feeds a table, an index, or a training set. The consumers are pipelines with nightly cadences, not users watching a spinner.
The schema is the prompt
Field names, types, allowed values, and null rules go in the system message once and govern every record in the file. Schema changes are file diffs, not code deploys.
Chains cleanly from OCR
Scanned documents run OCR as job one and extraction as job two, both JSONL, both on the same API key. custom_id carries the document key through the whole chain.
One JSONL file, one job.
Each line is a complete OpenAI-compatible request with a custom_id that is echoed on the matching output row. Up to 50,000 records and 200MB per job.
{"custom_id":"lease-0071","method":"POST","url":"/v1/chat/completions","body":{"model":"openrelay/gpt-oss-120b","messages":[{"role":"system","content":"Extract from the lease as JSON: {\"tenant\", \"landlord\", \"address\", \"term_months\": int, \"monthly_rent_usd\": number, \"renewal_option\": bool, \"notice_days\": int|null}. Use null when absent. No prose."},{"role":"user","content":"This Lease Agreement is entered into by..."}]}}
{"custom_id":"job-ad-5518","method":"POST","url":"/v1/chat/completions","body":{"model":"openrelay/gpt-oss-20b","messages":[{"role":"system","content":"Extract as JSON: {\"title\", \"company\", \"location\", \"remote\": bool, \"salary_min\": int|null, \"salary_max\": int|null, \"skills\": []}."},{"role":"user","content":"Senior Platform Engineer - Acme (Denver or remote, $185k-$215k)..."}]}}from openai import OpenAI
client = OpenAI(
base_url="https://inference.openrelay.inc/v1",
api_key="vl_••••••••", # same SDK, new base URL
)
# extract lease terms from 50,000 contracts into rows
f = client.files.create(file=open("batch.jsonl", "rb"), purpose="batch")
batch = client.batches.create(
input_file_id=f.id,
endpoint="/v1/chat/completions",
completion_window="24h", # billed at 50% of realtime
)
# poll: validating → in_progress → completed
batch = client.batches.retrieve(batch.id)
print(batch.status, batch.request_counts)
# JSONL of {custom_id, response}; failures land in error_file_id
results = client.files.content(batch.output_file_id)The right models for this job.
Batch rates are 50% off the realtime per-token catalog rates, per 1M tokens.
See the full catalogGPT-OSS 120B
OpenAI · 128K context
openrelay/gpt-oss-120b
$0.075 / $0.30
batch input / output per 1M
The extraction default: structured output support and enough headroom for gnarly source text and strict schemas.
GPT-OSS 20B
OpenAI · 128K context
openrelay/gpt-oss-20b
$0.025 / $0.10
batch input / output per 1M
Simple schemas over short text (contact info, order fields) at one-third the batch price.
DeepSeek-OCR 2
DeepSeek · 8K context
openrelay/deepseek-ocr-2
$0.019 / $0.019
batch input / output per 1M
Upstream stage for scans and PDFs: transcribe or extract directly from page images.
What separates a good run from a re-run.
Prompt and file patterns learned from real jobs, so the first submission is the one that counts.
Type every field and define null
term_months: int and 'use null when the document does not state it' prevent the two classic failures: numbers as prose ('twelve months') and hallucinated values for missing fields.
Forbid prose explicitly
End the system prompt with 'Reply with the JSON object only.' A single markdown fence or apology in 2% of rows is the difference between json.loads and a cleanup regex.
Validate the output file mechanically
Parse every row against your schema (pydantic or JSON Schema) and re-submit rejects as a small follow-up batch. Two-pass extraction routinely lands above 99% clean.
Include one worked example for messy sources
For OCR'd or inconsistent text, a single input-to-JSON example in the prompt buys more accuracy than any amount of instruction prose.
Common questions.
How do I get reliable structured output from an LLM?
Pin the schema in the system prompt with explicit types and null semantics, forbid prose, use a model with structured output support (the GPT-OSS family here), and validate mechanically after the run. In batch, add a second small job that retries the rows that failed validation; the combination is well above 99% parseable in practice.
Can this replace a data extraction API or template-based parser?
For variable-layout sources (contracts, emails, listings, resumes) prompt-defined extraction breaks far less often than templates when layouts shift, and changing it is a prompt edit instead of a template release. Fixed-layout forms at extreme volume can still favor a template engine; many teams run templates for the fixed 20% and LLM extraction for the long tail.
What does extraction cost at scale?
A 2,000-token contract with a 150-token JSON result on GPT-OSS 120B at batch rates costs about $0.0002 per document, so 50,000 contracts run around $10. Short-text extraction on GPT-OSS 20B is several times cheaper still.
How do I extract from PDFs and scans?
Chain two batch jobs: DeepSeek-OCR 2 turns page images into text (or extracts fields directly from the image), then the extraction pass applies your schema. Keep the document key in custom_id and the join is free.
More batch workloads
OCR the whole archive, priced per token instead of per page
OCR thousands of PDFs, scans, invoices, and receipts with DeepSeek-OCR 2 through a batch API.
Classify a million documents with a prompt, not a training run
Classify documents, tickets, and text at scale with an LLM batch API.
Synthetic data and distillation, a million examples per night
Generate synthetic training data and distill models with batch LLM jobs.
Running agent pipelines instead of flat request files? See the agentic batch API.
Ship the first job today.
Grab an API key, upload a JSONL file, and run open models at half the realtime cost. No contract, no minimums. Deposit $5 to get $10.