Developer Guide · ฝัง API สแกน + สกัดข้อมูลเอกสารไทยในระบบของคุณ
DocIntel รับไฟล์เอกสาร (PDF/รูป) → OCR ภาษาไทย → สกัดข้อมูลเป็น JSON (เช่น เลขบัญชี, กำไร/ขาดทุน, วันหมดอายุบัตร) พร้อมตรวจความถูกต้องและปิดบัง PII. ออกแบบสำหรับเอกสารการเงิน/ราชการไทยโดยเฉพาะ.
/schemas| doc_type | เอกสาร | doc_type | เอกสาร |
|---|---|---|---|
thai_id_card | บัตรประชาชน | full_fs | งบการเงินเต็ม |
passport | หนังสือเดินทาง | 5yr_summary | งบย้อนหลัง 5 ปี |
bookbank | สมุดบัญชีธนาคาร | trade_reg | ทะเบียนการค้า/พาณิชย์ |
pnd50 | ภ.ง.ด.50 | affidavit | หนังสือรับรองบริษัท |
boj5 | บ.อ.จ.5 (บัญชีผู้ถือหุ้น) | power_of_attorney | หนังสือมอบอำนาจ |
pp30_summary | ภ.พ.30 | simple_ledger | บัญชีรายรับรายจ่าย |
income_exp | งบรายได้-ค่าใช้จ่าย | community | เอกสารกลุ่ม/ชุมชน |
ไม่รู้ประเภท? ไม่ต้องส่ง — ระบบจับเอง. ถ้าจับไม่ได้จะได้ unknown.
https://docintel-demo.kiatdamrong.shopjob_id แล้ว poll / รับ webhookทุก request (ยกเว้น /api/v1/_meta) ต้องมี header
Authorization: Bearer dci_live_xxxxx · key รับจากผู้ดูแลระบบ.
| เรื่อง | รายละเอียด |
|---|---|
| Key format | dci_live_ ตามด้วยสตริงสุ่ม · ผูกกับ 1 tenant |
| Scope | key จำกัดสิทธิ์ได้ (extract · jobs · applicants · meta · cost · webhooks) · admin/MCP แยกต่างหาก |
| Rate limit | 60 req/นาที ต่อ IP (burst 60) สำหรับ /api/v1/* · + โควตาต่อชั่วโมงต่อ tenant |
| เกิน limit | ได้ 429 (rate) หรือ 503 preflight_busy (โหลดพุ่ง) → retry |
| CORS | เปิดทุก origin · fetch จาก browser ตรงได้ (Bearer header ไม่ใช่ cookie) |
# เมตาดาต้า API (ไม่ต้อง auth) curl https://docintel-demo.kiatdamrong.shop/api/v1/_meta # ตรวจว่า key ใช้ได้ (ควรได้ 200) curl https://docintel-demo.kiatdamrong.shop/api/v1/jobs \ -H "Authorization: Bearer dci_live_xxxxx"
const API = 'https://docintel-demo.kiatdamrong.shop'; const KEY = 'dci_live_xxxxx'; const H = { 'Authorization': `Bearer ${KEY}` }; const jobs = await fetch(`${API}/api/v1/jobs`, { headers: H }).then(r => r.json()); console.log(jobs);
import requests API = "https://docintel-demo.kiatdamrong.shop" H = {"Authorization": "Bearer dci_live_xxxxx"} print(requests.get(f"{API}/api/v1/jobs", headers=H).json())
ตรวจ คุณภาพรูป + (ถ้าต้องการ) ยืนยันประเภทเอกสาร โดย ไม่ต้อง OCR — ตอบ sync ~0.2s. ใช้กรองรูปก่อนเก็บ/ก่อนส่ง Extract.
doc_type_match) — แม่นกว่าปล่อยให้เดาเองcurl -X POST https://docintel-demo.kiatdamrong.shop/api/v1/preflight \ -H "Authorization: Bearer dci_live_xxxxx" \ -F "[email protected]" \ -F "expected_doc_type=boj5"
const fd = new FormData(); fd.append('file', fileInput.files[0]); fd.append('expected_doc_type', 'boj5'); const r = await fetch(`${API}/api/v1/preflight`, { method: 'POST', headers: H, body: fd }).then(r => r.json()); if (r.next_action === 'proceed') { /* เก็บรูป */ } else if (r.next_action === 'review') { /* เตือน: ดูเหมือน r.detected */ } else { /* retry_with_better_image */ }
r = requests.post(f"{API}/api/v1/preflight", headers=H, files={"file": open("slip.jpg", "rb")}, data={"expected_doc_type": "boj5"}).json() print(r["next_action"], r["detected"], r["doc_type_match"])
vision = ระบุประเภทได้เร็ว · needs_ocr = ต้องให้ Extract ตัดสินประเภท (รูปยังใช้ได้)method: "ocr_page1" + detected/doc_type_match เมื่อความมั่นใจ ≥ 0.5 (วัดจริง: ครอบ ~79% แม่น ~96%) · ต่ำกว่าเกณฑ์จะไม่ฟันธง (รายงานใน reasons แทน) · แลกกับเวลา ~10-20 วิ+ ขึ้นกับเครื่อง — ใช้เฉพาะตอนจำเป็น อย่ายิงถี่ · ผล mismatch เป็นคำเตือนให้ review ไม่ใช่การปฏิเสธcode / severity (error=ภาพใช้ไม่ได้ · warning=เตือนให้ตรวจทาน) / message (ไทย พร้อมวิธีแก้) / detail · รหัสที่มี: image_blurry image_too_dark image_overexposed image_low_contrast image_too_small out_of_scope_hint type_mismatch · ว่าง = ไม่พบปัญหา · branch จาก code ได้เลย ไม่ต้อง parse ข้อความใน reasonsscope_warning: true + nearest_type / distance) — คำเตือน ไม่ใช่คำตัดสิน next_action ยังเป็น proceed ตามเดิม · ถ้าฝืนส่งต่อมีโอกาสสูงที่จะได้ผล unknown| next_action | แปลว่า | ควรทำ |
|---|---|---|
proceed | รูปใช้ได้ · ประเภทยืนยันแล้ว/รอ OCR | เก็บรูป → ส่ง Extract เมื่อพร้อม |
review | รูปใช้ได้ แต่ vision เห็น detected ≠ ที่คุณคาด | เตือน user ("ดูเหมือน {detected}") — รับ / ให้อัพใหม่ / ส่ง Extract ให้ OCR ตัดสิน |
retry_with_better_image | คุณภาพต่ำ (เบลอ/มืด/สว่างเกิน) | ให้ user ถ่าย/อัพใหม่ |
สแกน + สกัดข้อมูลเต็ม (OCR). เป็น async — คืน job_id
ทันที (สถานะ 202) แล้วประมวลผลเบื้องหลัง. ดูผลด้วยขั้น 4.
[A-Za-z0-9_-] · ห้ามซ้ำใน tenant ภายใน 90 วัน (ซ้ำ = 409)["loan","urgent"] · สูงสุด 20 · kebab-caseauto (ใช้ในเครื่องก่อน · cloud สำรอง) · local · cloudcurl -X POST https://docintel-demo.kiatdamrong.shop/api/v1/extract \ -H "Authorization: Bearer dci_live_xxxxx" \ -F "[email protected]" \ -F "external_id=loan-2024-00123" \ -F "applicant_id=usr_8f3a1c" \ -F 'tags=["loan","urgent"]' # → { "job_id": "...", "status": "queued", "poll_url": "/api/v1/jobs/..." }
const fd = new FormData(); fd.append('file', fileInput.files[0]); fd.append('external_id', 'loan-2024-00123'); fd.append('applicant_id', 'usr_8f3a1c'); const { job_id, poll_url } = await fetch(`${API}/api/v1/extract`, { method: 'POST', headers: H, body: fd }).then(r => r.json());
r = requests.post(f"{API}/api/v1/extract", headers=H, files={"file": open("statement.pdf", "rb")}, data={"external_id": "loan-2024-00123", "applicant_id": "usr_8f3a1c"}).json() job_id = r["job_id"]
X-Force-Reextract: true เพื่อสแกนซ้ำ.
Extract คืน job_id — poll endpoint นี้จนสถานะเป็น done.
GET /api/v1/jobs/{job_id}/extraction คืนเฉพาะ extraction · _status · validation (+ job_id, external_id, doc_type, extracted_at, tier_used) ขนาด ~2 KB · ส่วน GET /api/v1/jobs/{job_id} เต็มๆ มี pages[] พร้อมรูปหน้าเอกสารแบบ base64 (~90 KB) เหมาะกับหน้า review เท่านั้น · หรือใช้ ?fields=meta,extraction,validation เพื่อตัด pages ทิ้ง/schemas
.result.
curl https://docintel-demo.kiatdamrong.shop/api/v1/jobs/JOB_ID \
-H "Authorization: Bearer dci_live_xxxxx"
async function waitJob(id) { while (true) { const j = await fetch(`${API}/api/v1/jobs/${id}`, { headers: H }).then(r => r.json()); if (j.status === 'done') return j; if (j.status === 'failed' || j.status === 'error') throw j; await new Promise(res => setTimeout(res, 2000)); // รอ 2s } }
import time def wait_job(job_id): while True: j = requests.get(f"{API}/api/v1/jobs/{job_id}", headers=H).json() if j["status"] == "done": return j if j["status"] in ("failed", "error"): raise Exception(j) time.sleep(2)
doc_type · start_page / end_page · และ extraction ของส่วนนั้นเองextraction ระดับบนสุดคือของส่วนแรกเท่านั้น ถ้าอ่านแค่ตัวนั้น ส่วนที่เหลือจะหายไปเงียบ ๆ (ในคลังตัวอย่างคือ 1,141 ส่วน · 2,119 หน้า · ในนั้นเป็นงบการเงิน 425 และ ภ.ง.ด.50 268)sections = job["classification"].get("composite_sections") or []
for s in sections:
print(s["doc_type"], s["start_page"], "-", s["end_page"], s["extraction"])
อยากได้เฉพาะฟิลด์ที่สกัด → GET /api/v1/jobs/{id}/extraction · รูปหน้าเอกสาร → /jobs/{id}/pages/{n}/image · ข้อความ OCR → /pages/{n}/text
classification._warning อธิบายเพิ่มstreet_address (ที่อยู่ส่วนหน้ารวม ตัดคำฟอร์ม เช่น "ตั้งอยู่เลขที่/สำนักงานแห่งใหญ่" ให้แล้ว) / house_no / moo / village / soi / road / subdistrict / district / province / postal_code · province_source บอกที่มา — "text" อ่านจากเอกสาร · "postal_code" อนุมานจากรหัสไปรษณีย์ · มีทุกประเภทเอกสารที่พบที่อยู่ (ฟิลด์ address เดิมคงอยู่ตามเดิม)net_profit = กำไรสุทธิที่ต้องเสียภาษี (รายการที่ 2 ข้อ 21 · ฐานภาษี) · accounting_net_profit = กำไรสุทธิตามบัญชี (ข้อ 9 · ผลประกอบการตามงบ) · แต่ละตัวมี _..._source บอกบรรทัดที่อ่านมาnull และค่าที่อ่านได้อยู่ที่นี่พร้อมเหตุผล: not_in_document (ตัวเลขไม่ปรากฏในเอกสาร = อ่านมั่ว) · registration_equals_request (เลขทะเบียนไปซ้ำเลขคำขอ) · mod11_failed (เฉพาะกรณีไม่มีข้อความ OCR ให้ตรวจ) · เชื่อ null + ตรวจเองได้จากตรงนี้ · ตั้งแต่ 6/9/2569 เลขที่พิมพ์อยู่ในเอกสารจะไม่ถูกกักอีกต่อไป แม้ไม่ครบ 13 หลัก — ดูหัวข้อ Δreason และ note ภาษาไทยที่แสดงผู้ใช้ได้ทันที · เหตุผลหลัก: digits_over_13 / digits_under_13 (เลขที่ต้องเป็น 13 หลักแต่พิมพ์มาเกิน/ขาด — ส่งตามที่พิมพ์ พร้อม over/under กี่หลัก) · checksum_failed (13 หลักแต่เช็คหลักไม่ผ่าน) · digit_recovered (ระบบเติมหลักที่หายด้วยเช็คหลัก — บอกตำแหน่ง ตัวเลข และ value_as_read) · other_copies_off (ใช้สำเนาที่ครบ แต่จุดอื่นในเอกสารอ่านได้ขาด/เกิน) · เมื่อมี warning ใด ๆ review_reasons จะมี extraction_warningsextraction_warnings · fields_withheld · validation_failed · low_classify_confidence · doc_type_unknown · suspected_hallucination · ocr_runaway (OCR อ่านวนซ้ำ) · ocr_sparse (อ่านได้น้อยผิดปกติ) · ocr_engine_fallback / ocr_cloud_fallback (หน้าใดหน้าหนึ่งถูกอ่านด้วยเอนจินสำรอง คุณภาพอาจต่าง) · ถ้า true ให้ส่งเข้า human reviewfilled[] (เติมสำเร็จ) · empties[] (ที่ถามไป) · dropped_ungrounded[] (คำตอบที่ไม่มีในข้อความ OCR — ทิ้ง) · skipped[] (ฟิลด์ที่ไม่ยอมให้เติม) · ค่าที่เติมทุกตัวต้องปรากฏในเอกสารเท่านั้นocr_retry = หน้านี้ถูกอ่านซ้ำเพราะรอบแรกวนซ้ำ (method, first_chars, second_chars, still_runaway) · ocr_engine_fallback = อ่านด้วยเอนจินสำรอง (from, reason) · text_source = "ocr_structure_retry" = ข้อความมาจากการอ่านซ้ำ"suspected_hallucination" = เคสสงสัยระดับสูง — ต้องให้คนตรวจก่อนใช้ค่าเสมอ{check, passed} — ถ้า validation_passed < validation_total ให้ดูว่าข้อไหนไม่ผ่าน (ขอผ่าน ?fields=validation)แนวทางตรวจทานฝั่งคุณ: ดึงรูปหน้าเอกสาร GET /api/v1/jobs/{id}/pages/{n}/image มาแสดงคู่กับค่าที่สกัดได้ ให้เจ้าหน้าที่เทียบกับต้นฉบับ แล้วแจ้งผลกลับด้วย endpoint ด้านล่าง — ทั้งสองฝั่งจะได้เห็นภาพตรงกันว่าเอกสารใดผ่านการยืนยันแล้ว
ปิดลูปการตรวจทาน: เจ้าหน้าที่ของคุณตรวจแล้ว แจ้งผลกลับมาให้ระบบบันทึกไว้
ผลล่าสุดจะแสดงใน GET /jobs/{id} field review
(หรือขอเฉพาะส่วนนี้ด้วย ?fields=review)
true = ผลสกัดถูกต้องตามต้นฉบับ · false = พบข้อผิดพลาดother/unknown){"account_no": "123-4-56789-0"} (สูงสุด 50 คีย์)curl -X POST https://docintel-demo.kiatdamrong.shop/api/v1/jobs/JOB_ID/review -H "Authorization: Bearer dci_live_xxxxx" -H "Content-Type: application/json" -d '{"verified": false, "corrected_doc_type": "bookbank", "corrected_fields": {"account_no": "123-4-56789-0"}, "note": "เจ้าหน้าที่ตรวจกับต้นฉบับแล้ว"}'
await fetch(`${API}/api/v1/jobs/${jobId}/review`, { method: 'POST', headers: { ...H, 'Content-Type': 'application/json' }, body: JSON.stringify({ verified: false, corrected_doc_type: 'bookbank', corrected_fields: { account_no: '123-4-56789-0' }, note: 'เจ้าหน้าที่ตรวจกับต้นฉบับแล้ว', }), });
requests.post(f"{API}/api/v1/jobs/{job_id}/review", headers=H, json={ "verified": False, "corrected_doc_type": "bookbank", "corrected_fields": {"account_no": "123-4-56789-0"}, "note": "เจ้าหน้าที่ตรวจกับต้นฉบับแล้ว", })
ส่งซ้ำได้ (ระบบเก็บเป็นประวัติ · แสดงรายการล่าสุด) ·
ใช้ได้เฉพาะงานสถานะ done (ไม่เช่นนั้นได้ 409) ·
การส่งผลตรวจถูกบันทึกใน audit log ตามแนวทาง PDPA เช่นเดียวกับการเข้าถึงข้อมูล
ลงทะเบียน URL (HTTPS) เพื่อให้ระบบยิงกลับเมื่องานเสร็จ — แทนการ poll.
curl -X POST https://docintel-demo.kiatdamrong.shop/api/v1/webhooks \ -H "Authorization: Bearer dci_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{"url":"https://yourapp.com/hook","events":["extract.done","extract.failed"]}'
extract.donet=<ts>,v1=<hmac> (Stripe-style) — verify ก่อนเชื่อ{
"event_type": "extract.done",
"job_id": "da78be19-b693-...",
"tenant": "your-tenant",
"status": "done",
"filename": "statement.pdf",
"applicant_id": "usr_8f3a1c",
"duration_s": 12.34,
"completed_at": 1720000000,
"doc_type": "boj5", // เฉพาะ extract.done
"classify_confidence": 0.94,
"needs_review": false, // true = มีสัญญาณไม่มั่นใจ → ส่งเข้าคิวคนตรวจ
"review_reasons": [] // เหตุผล (ว่าง = ไม่มีสัญญาณ)
// extract.failed จะมี "error": "..." แทน
}
review_reasons ที่เป็นไปได้:
doc_type_unknown ·
low_classify_confidence ·
suspected_hallucination ·
extraction_warnings ·
validation_failed ·
fields_withheld (เพิ่ม 26/8 — ระบบกักค่าที่อ่านไม่ได้จริงไว้ใน extraction._rejected แทนที่จะเดา · เปิดดูรายชื่อ field และเหตุผลได้ในนั้น)
— ความหมายแต่ละตัวตรงกับหัวข้อ "เมื่อระบบไม่มั่นใจ" ในข้อ 4
ระบบคุณใช้ needs_review ตัดสินใจได้ทันทีจาก webhook
โดยไม่ต้องดึงผลเต็มก่อน
extract.done
แล้วค่อยเรียก GET /api/v1/jobs/{job_id} เพื่อดึงผลเต็ม.
import hmac, hashlib def verify(secret, sig_header, raw_body): p = dict(x.split("=", 1) for x in sig_header.split(",")) signed = f'{p["t"]}.{raw_body}'.encode() expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, p["v1"]) # raw_body = ตัว body ดิบ (ห้าม re-serialize ก่อน verify)
const crypto = require('crypto'); function verify(secret, sigHeader, rawBody) { const p = Object.fromEntries(sigHeader.split(',').map(x => x.split('='))); const signed = `${p.t}.${rawBody}`; const exp = crypto.createHmac('sha256', secret).update(signed).digest('hex'); return crypto.timingSafeEqual(Buffer.from(exp), Buffer.from(p.v1)); }
หมุน secret: /webhooks/{id}/rotate-secret · จัดการ: GET /webhooks · /webhooks/{id}/disable · /delete
jobsส่งหลาย job_id ของงบต่างปี → ได้ผลต่างทีละฟิลด์ + เทียบ trend/อัตราส่วน.
หรือเทียบเอกสารของ applicant คนเดียวผ่าน /applicants/{id}/compare.
writerตัวช่วย เจน/เรียบเรียงข้อความภาษาไทย ให้เป็นทางการขึ้น ตามอินพุตที่ frontend กำหนด. ไม่เกี่ยวกับเอกสารที่สแกน — ไม่ได้ดึงข้อมูลจาก extract. ระบบจะไม่แต่งข้อเท็จจริงใหม่ (ปรับภาษา/โครงสร้างเท่านั้น).
Request (JSON body):
curl -X POST https://docintel-demo.kiatdamrong.shop/api/v1/improve-objective \ -H "Authorization: Bearer dci_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "draft": "โครงการนี้จะช่วยเกษตรกรในชุมชนให้ดีขึ้น", "keywords": ["ความยั่งยืน","รายได้","เทคโนโลยี"], "project_context": "ทุนพัฒนาชุมชนเกษตร" }' # → { "improved": "...", "original_length": .., "improved_length": .., "_model": ".." }
r = requests.post(f"{API}/api/v1/improve-objective", headers=H, json={ "draft": "โครงการนี้จะช่วยเกษตรกรในชุมชนให้ดีขึ้น", "keywords": ["ความยั่งยืน", "รายได้", "เทคโนโลยี"], }).json() print(r["improved"])
Response: improved · original_length ·
improved_length · _model · _tokens · _duration_ms
เพิ่มใหม่ 25/8 — Writer อีก 2 หัวข้อ ·
request body / response / error เหมือน improve-objective ทุกฟิลด์
(คีย์ scope writer เดิมใช้ได้เลย) ต่างแค่ URL ตามช่องในฟอร์ม:
response ของ 2 เส้นใหม่เพิ่มฟิลด์ field_type
(expected_results / problem_background)
บอกว่าหัวข้อไหนตอบ · ตัวอย่างผลจริง + รายละเอียดเต็มอยู่ในคู่มือ AI Writer หัวข้อ 8
ลบงานและข้อมูลที่เกี่ยวข้องถาวร (ตามสิทธิ PDPA ของเจ้าของข้อมูล).
/applicants/search · /applicants/{id}/timeline · /applicants/{id}/documents · ค่าใช้จ่าย /cost-summary
/changes · Reference ทุก field regenerate แล้วที่ /schemasใช้กับ trade_reg.registration_number · full_fs.company.tax_id · pnd50.tax_id · boj5.company_tid — ระบบไม่ตัดสินแทนว่าเลขถูกหรือผิด แต่บอกว่าอ่านได้กี่หลัก ขาดหรือเกิน แล้วให้ผู้ใช้ตรวจกับต้นทาง
_warnings.<field>.reason = checksum_failed · <field>_checksum_pass = falsedigits_over_13 + over: 1digits_under_13 + under: 1digit_recovered + position digit value_as_readother_copies_off + variants[]_rejected reason not_in_document_warnings.<field> ประกอบ · note ทุกตัวเป็นภาษาไทยพร้อมแสดงผู้ใช้needs_review / review_reasons ใน GET (เดิมเฉพาะ webhook) · extraction._fill · pages[].ocr_retry · pages[].ocr_engine_fallback · pages[].text_source = "ocr_structure_retry" · เหตุผล review ใหม่ ocr_runaway ocr_sparse ocr_engine_fallback ocr_cloud_fallback extraction_warnings fields_withheld — รายละเอียดในหัวข้อ 4 "สัญญาณที่ต้องอ่าน"
model_used · pages[].ocr_model · extraction._model เป็นชื่อกลาง (เช่น "AI Gateway OCR") — อย่าผูกโค้ดกับค่าเหล่านี้
_fill และจากข้อ 1) — ทุกกรณีมี _fill หรือ _warnings กำกับ200 พร้อมผลเก่าแทน 202 เหมือนเดิมtrade_reg.issuing_office ตอบชื่อสำนักงานจริง (เช่น เทศบาลตำบลปะโค) ไม่ใช่ป้าย "สำนักงานทะเบียนพาณิชย์"error หน้าตาเดียวกัน
เสมอ · ฟิลด์ detail เดิมยังอยู่ (เผื่อ client เก่า) แต่ ให้ผูก model
กับ error อย่างเดียว
{
"error": {
"code": "external_id_conflict", // token คงที่ snake_case — ใช้ switch/case
"message": "external_id 'x' already used within 90-day window",
"status": 409, // = HTTP status
"details": { "existing_job_id": "...", "hint": "..." }, // หรือ null
"request_id": "cid_4df968c8d21e" // ส่งให้เราเวลาแจ้งปัญหา
},
"detail": ... // legacy · จะเลิกใช้ในอนาคต
}
422 validation จะมี details.fields[] บอกชื่อ field ที่ผิดทีละตัว:
{"field": "applicant_id", "message": "Field required", "type": "missing"}
| code | status | หมายถึง |
|---|---|---|
customer_key_required · not_a_customer_key · invalid_customer_key · key_expired · key_revoked | 401 | ปัญหา API key |
forbidden | 403 | key ไม่มี scope / ข้าม tenant |
not_found · webhook_not_found | 404 | ไม่พบ job / webhook |
external_id_conflict | 409 | external_id ซ้ำใน 90 วัน · details.existing_job_id ชี้งานเดิม |
webhook_not_found_or_version_mismatch · webhook_not_found_or_already_disabled | 409 | webhook ถูกแก้ไปแล้ว · ส่ง expected_version ล่าสุด |
payload_too_large | 413 | ไฟล์เกิน 30 MB |
unsupported_media_type | 415 | ชนิดไฟล์ไม่รองรับ |
validation_error | 422 | body/form ไม่ผ่าน · ดู details.fields หรือ message |
expected_version_required · url_required | 400 | ขาด field บังคับของ webhook API |
per_key_rate_limit_exceeded · tenant_rate_limit_exceeded · tenant_concurrency_limit | 429 | เกินโควตา · รอตาม Retry-After |
preflight_busy · memory_pressure · service_draining · audit_unavailable | 503 | ระบบไม่ว่างชั่วคราว · retry สั้นๆ |
internal_error | 500 | ข้อผิดพลาดฝั่งเรา · แจ้ง request_id มาได้เลย |
กติกา: code ที่เป็น token เฉพาะจะคงที่ตลอด (ผูก logic ได้) · ถ้าไม่มี token เฉพาะจะได้
code กลางตาม status (bad_request / unauthorized / not_found /
conflict / validation_error / rate_limited / internal_error ฯลฯ)
และ message อธิบายเป็นข้อความเสมอ
| Status | หมายถึง | วิธีแก้ |
|---|---|---|
401 | ไม่มี/ผิด key | ใส่ header Authorization: Bearer ... ให้ถูก |
403 | key ไม่มีสิทธิ์ (scope) หรือข้าม tenant | ใช้ key ที่มี scope นั้น |
409 | external_id ซ้ำใน 90 วัน | ใช้ค่าใหม่ หรือ X-Force-Reextract: true |
413 | ไฟล์ใหญ่เกิน 30 MB | ย่อ/บีบไฟล์ |
415 | ชนิดไฟล์ไม่รองรับ | ใช้ PDF/PNG/JPG/JPEG/WebP |
422 | body ไม่ผ่าน validation (เช่น external_id ผิดรูปแบบ) | อ่าน detail — บอกฟิลด์ที่ผิด |
429 | เกิน rate limit | รอตาม Retry-After แล้วลองใหม่ |
503 | preflight_busy — โหลดพุ่งชั่วคราว | retry สั้นๆ (ไม่ใช่ error ที่ request คุณ) |
500 | server error | ลองใหม่ · ถ้าซ้ำแจ้งผู้ดูแล |
/docs) แสดง ทุก endpoint รวม admin —
ลูกค้าใช้แค่เส้น /api/v1/* ที่อยู่ในหน้านี้. ที่เหลือเป็นของผู้ดูแลระบบ.
GET /api/v1/samples
คืนรายชื่อประเภทที่มีไฟล์ตัวอย่าง และ GET /api/v1/sample/{doc_type}
ดึงไฟล์นั้นมาได้เลย. ทั้งสองเส้นเปิดสาธารณะ ไม่ต้องใช้ API key —
เอาไปลอง /preflight หรือ /extract
ได้ทันทีโดยไม่ต้องเตรียมไฟล์เอง. เอกสารชุดนี้ผ่านการคัดกรองข้อมูลส่วนบุคคลแล้ว.