อ่านเอกสารด้วย OCR
ปลายทาง OCR อ่านไฟล์ PDF และรูปภาพ แล้วคืนข้อความเป็น Markdown หรือบล็อกโครงสร้าง คิดเครดิตเป็นรายหน้า ตัดเครดิตก่อนอ่านและคืนให้อัตโนมัติเมื่ออ่านไม่สำเร็จ
POST /v1/ocr อ่านไฟล์ PDF, PNG, JPEG หรือ WebP แล้วคืนข้อความในไฟล์ ส่งไฟล์เป็น base64 ในฟิลด์ document พร้อมรหัสโมเดล paxa-ocr-lite-v1 คำตอบมีหนึ่งรายการต่อหนึ่งหน้า เช่นเดียวกับทุกปลายทาง ระบบตัดเครดิตก่อนประมวลผลและคืนให้อัตโนมัติเมื่อประมวลผลไม่สำเร็จ (เครดิต)
# 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 { 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`);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"){
"pages": [
{
"page": 1,
"markdown": "# ใบเสร็จรับเงิน\n\nร้านข้าวแกงบ้านสวน สาขาสีลม\n\n| รายการ | จำนวน | ราคา |\n| --- | --- | --- |\n| ข้าวแกงเขียวหวานไก่ | 1 | 60 |\n| น้ำเปล่า | 1 | 10 |\n\nรวมทั้งสิ้น 70 บาท"
}
],
"usage": {
"pages": 1,
"credits": 6.5
}
}การคิดเครดิต#
แต่ละหน้าคิด 6.5 เครดิตเท่ากัน ไม่ว่าบนหน้านั้นจะมีอะไร หนึ่งหน้าคือหนึ่งหน้าของ PDF หรือรูปภาพหนึ่งไฟล์ ระบบอ่านจำนวนหน้าจากตัวเอกสารก่อนตัดเครดิต จึงรู้ค่าใช้จ่ายก่อนเริ่มอ่านเสมอ
ยอดที่ตัดรายงานอยู่ใน usage.credits ของเนื้อหาคำตอบเท่านั้น ปลายทางนี้ไม่ส่งเฮดเดอร์ x-credits-charged คำตอบที่เร็วและช้าจึงมีรูปแบบเดียวกัน เมื่ออ่านไม่สำเร็จหลังตัดเครดิต ระบบคืนเครดิตอัตโนมัติและตอบ 502 provider_error
เอกสารและรูปแบบไฟล์#
ฟิลด์ document คือไบต์ของไฟล์ในรูป base64 ระบบอ่านสี่รูปแบบ ได้แก่ PDF, PNG, JPEG และ WebP โดยตรวจรูปแบบจากไบต์ของไฟล์เอง และรูปภาพนับเป็นหนึ่งหน้าเสมอ ไฟล์ที่อ่านเป็นสี่รูปแบบนี้ไม่ได้จะตอบ 400 document_invalid ส่วน PDF ที่ต้องใช้รหัสผ่านในการเปิดจะตอบ 400 document_password_required ทั้งนี้ PDF ที่เข้ารหัสเพื่อจำกัดสิทธิ์อย่างเดียว ซึ่งเปิดได้โดยไม่ต้องกรอกรหัสผ่าน ระบบอ่านได้ตามปกติ และทุกกรณีข้างต้นไม่ตัดเครดิต
base64 ทำให้ขนาดข้อมูลโตขึ้นราวหนึ่งในสาม โปรดเผื่อขีดจำกัดขนาดคำขอของไคลเอนต์เมื่อส่งไฟล์สแกนขนาดใหญ่ และถอดรหัสผ่านของ PDF ก่อนเข้ารหัส base64
ขีดจำกัดจำนวนหน้าและขนาด#
หนึ่งคำขออ่านได้ไม่เกิน 50 หน้า เอกสารที่ยาวกว่านั้นตอบ 400 too_many_pages ไฟล์หลังถอดรหัส base64 ต้องไม่เกิน 10 MiB ไฟล์ที่ใหญ่กว่าตอบ 413 document_too_large การปฏิเสธทั้งสองแบบไม่ตัดเครดิต เอกสารที่ใหญ่กว่านี้ให้แบ่งส่งเป็นหลายคำขอ
เลือกรูปแบบผลลัพธ์#
ฟิลด์ output เลือกรูปแบบผลลัพธ์ ค่าเริ่มต้นคือ "markdown" ทั้งสองรูปแบบให้เนื้อหาการอ่านเดียวกัน ต่างกันที่ปริมาณโครงสร้างที่ติดมาด้วย
Markdown#
ค่าเริ่มต้นคืนแต่ละหน้าเป็น Markdown แบบ GitHub ตามลำดับการอ่าน หัวเรื่อง รายการ และตารางมาเป็นโครงสร้าง Markdown จึงนำไปวางในวิกิ พรอมป์ต หรือดัชนีค้นหาได้ทันที ต่อแต่ละหน้าด้วยบรรทัดว่างเพื่อประกอบเป็นเอกสารเต็ม
บล็อกโครงสร้าง#
ตั้ง output เป็น "structured" แล้วแต่ละหน้าจะมาเป็นบล็อกแยกตามชนิด เรียงตามลำดับการอ่าน แต่ละบล็อกบอกชนิดและเนื้อหาในฟิลด์ที่ระบุชนิดไว้ชัดเจน ไปป์ไลน์จึงแยกหัวเรื่อง ตาราง และย่อหน้าได้โดยไม่ต้องแกะ Markdown เอง
| บล็อก | ฟิลด์ | ความหมาย |
|---|---|---|
| heading | text, level | หัวเรื่องของส่วน level เริ่มที่ 1 สำหรับหัวเรื่องเด่นที่สุด |
| paragraph | text | ย่อหน้าข้อความ |
| list | items | รายการเรียงตามลำดับ |
| table | rows | เซลล์ของตารางเป็นแถวของค่าคอลัมน์ แถวแรกมาก่อน |
| figure | text | รูปภาพหรือแผนภูมิ text คือคำบรรยายหรือป้ายกำกับใกล้เคียง |
DOC=$(base64 < invoice.pdf | tr -d '\n')
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\", \"output\": \"structured\"}"{
"pages": [
{
"page": 1,
"blocks": [
{
"type": "heading",
"level": 1,
"text": "ใบเสร็จรับเงิน"
},
{
"type": "paragraph",
"text": "ร้านข้าวแกงบ้านสวน สาขาสีลม"
},
{
"type": "table",
"rows": [
[
"รายการ",
"จำนวน",
"ราคา"
],
[
"ข้าวแกงเขียวหวานไก่",
"1",
"60"
],
[
"น้ำเปล่า",
"1",
"10"
]
]
},
{
"type": "paragraph",
"text": "รวมทั้งสิ้น 70 บาท"
}
]
}
],
"usage": {
"pages": 1,
"credits": 6.5
}
}ความเสถียรและการส่งซ้ำ#
คำตอบเป็น JSON ก้อนเดียว เอกสารหลายหน้าอาจใช้เวลาหลายนาที จึงควรตั้งไทม์เอาต์ของไคลเอนต์ให้เผื่อไว้ ตัวอย่างด้านบนเผื่อ 300 วินาที
คำขอที่ใช้เวลานานพอจะเขียนส่วนหัวของคำตอบออกไปก่อนที่จะรู้ผลลัพธ์ เพื่อไม่ให้พร็อกซีระหว่างทางตัดการเชื่อมต่อที่ยังทำงานอยู่ คำตอบลักษณะนี้มีสถานะ 200 เสมอ และรายงานความล้มเหลวไว้ในเนื้อคำตอบด้วยเอกสารปัญหาชุดเดียวกับที่คำขอล้มเหลวได้รับ คือมี title และ status แทนที่ตำแหน่งของ pages เอกสารยาวคือกรณีที่ไปถึงจุดนี้ ให้ถือว่าเนื้อคำตอบที่มี title คือข้อผิดพลาดตามที่ระบุ ส่วนเอกสารที่สั้นกว่านั้นแจ้งสถานะไว้ที่บรรทัดสถานะเสมอ
ส่งซ้ำได้อย่างปลอดภัยด้วย Idempotency-Key การส่งซ้ำของคำขอที่ส่งมอบแล้วจะได้ผลการอ่านเดิมโดยไม่ตัดเครดิตซ้ำ เมื่ออ่านไม่สำเร็จ ระบบคืนเครดิตให้ และคำขอใหม่ต้องใช้คีย์ใหม่