Thai OCR for archives and digitization
Turn scanned Thai books, periodicals, and records into searchable text, page by page, so a collection stops being a folder of images.
What comes back
## บทที่ ๔ การค้าทางเรือ
ในสมัยนั้นเรือสำเภาจากเมืองจีนเข้ามาจอดที่ปากน้ำ
เป็นประจำทุกฤดูลมมรสุม บรรทุกสินค้าจำพวกผ้าไหม
เครื่องถ้วยชาม และเครื่องเทศ
> เชิงอรรถ ๑ ดูรายละเอียดในบทที่ ๗# ข่าวประจำสัปดาห์
## การประชุมสภา
ที่ประชุมมีมติรับหลักการในวาระที่หนึ่ง
## ราคาสินค้าเกษตร
| สินค้า | ราคา/ตัน |
| --- | ---: |
| ข้าวเปลือก | ๙,๘๐๐ |heading ทะเบียนรับหนังสือ พ.ศ. ๒๕๐๘
table 28 rows x 5 columns
paragraph หมายเหตุ เล่มที่ ๓ ต่อจากเล่มที่ ๒The reads above are illustrations of shape, not captured responses. The OCR page carries the model's own witnessed read of a real specimen, with its capture date.
How a run works
A digitized collection is often only half digitized. The pages have been photographed, the files are safe, and not one word of them can be found by searching. What is missing is the reading step, and for Thai it is the step that has been hardest to buy: a scan of an older Thai page carries a script that stacks vertically, a typeface whose letters are told apart by a loop that reproduction wears away, and no spaces between words to help a reader recover.
Sources:ADR (2017)(opens in a new tab)
- Send in page batchesSplit each volume into requests of up to 50 pages and 10 MB. The response keeps its pages separate, so a batch reassembles in order.
- Keep the imagesThe read is a text layer beside the scan, never a replacement for it. Preservation stays with the images; search, quotation, and reuse move to the text.
- Re-read when the model improvesBecause billing is per page with no minimum beyond one, a second pass over a collection costs the same as the first. A run is repeatable rather than final.
A digitization run, priced
| Workload | Pages | Credits |
|---|---|---|
| One volume (320 pages) | 320 | 2,080 |
| One request, at the 50-page ceiling | 50 | 325 |
| A 50-volume run | 104,000 credits | |
At 6.5 credits a page the arithmetic is linear, so a run can be budgeted from its page count before any of it is sent.
What reproduction takes away
ฅ ค ต ศ ด 35 readers, blur-degraded presentation. These letters merged into one another. The feature that separates them is the loop, and loopless faces dominate modern Thai print.
This is a finding about human legibility, from a peer-reviewed study of isolated characters. It is quoted here because it names the feature a scan of an aged page loses first, not as a claim about any OCR system's error rate.
Sources:ADR (2017)(opens in a new tab)
For developers
One request per batch of pages; the response is page by page, so a volume reassembles in order without any bookkeeping of your own.
# Encode without line wrapping: wrapped base64 breaks the JSON string.
DOC=$(base64 < invoice.pdf | tr -d '\n')
# --max-time covers a multi-page document; curl defaults to no limit.
curl -X POST https://api.paxalabs.com/v1/ocr \
--max-time 300 \
-H "Authorization: Bearer $PAXA_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"document\": \"$DOC\", \"model\": \"paxa-ocr-lite-v1\"}"import base64
import os
import requests
with open("invoice.pdf", "rb") as file:
document = base64.b64encode(file.read()).decode()
response = requests.post(
"https://api.paxalabs.com/v1/ocr",
headers={"Authorization": f"Bearer {os.environ['PAXA_API_KEY']}"},
json={"document": document, "model": "paxa-ocr-lite-v1"},
# A multi-page document can run for minutes; give it room.
timeout=300,
)
response.raise_for_status()
body = response.json()
print(body["pages"][0]["markdown"], body["usage"]["credits"], "credits")import { readFile } from "node:fs/promises";
const document = (await readFile("invoice.pdf")).toString("base64");
const response = await fetch("https://api.paxalabs.com/v1/ocr", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAXA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ document, model: "paxa-ocr-lite-v1" }),
// A multi-page document can run for minutes; give it room.
signal: AbortSignal.timeout(300_000),
});
if (!response.ok) throw new Error(`OCR failed: ${response.status}`);
const { pages, usage } = await response.json();
console.log(pages[0].markdown, `${usage.credits} credits`);The loop is the feature a scan loses first
Several Thai letters differ mainly by the small loop at their start, and modern Thai printing largely uses faces that have dropped it. In a peer-reviewed legibility test, 35 readers shown blur-degraded type merged exactly those letters into one another. An archival scan is that experiment by accident: aged paper, imperfect ink, and a photograph of both. It is the reason a Thai reading model has to be built around the script rather than adapted to it.
Common questions
- How well does it read old or degraded print?
- We publish no accuracy figures, for old print or new, and will not until the lab publishes its evaluations. Send a representative sample of your own worst pages through the free credits and judge it against what you actually hold.
- Does it handle multi-column layouts?
- Yes. The read follows reading order rather than scanning straight across the page, so a two-column periodical does not come back interleaved.
- Can we re-run a collection later?
- Yes, and the cost is the same. Billing is per page with no per-request minimum beyond a single page, so a second pass over the same volumes prices identically to the first.
- What about handwriting in older records?
- Printed pages are the strongest case. Handwritten records vary enormously by hand and by era, so treat them as something to test rather than something to assume.
Start with free credits
Sign in with Google, GitHub, or Hugging Face and spend 100 one-time free credits on your own text.