/docs/api.th.mdข้อมูลอ้างอิง API (API REFERENCE)
แผนภาพระบบ (Service Diagram)#
ลำดับการทำงาน (Sequence Diagram)#
sync — ได้รับไฟล์ PDF กลับมาทันที#
async — ได้ลิงก์ (ไฟล์ถูกเก็บไว้ให้)#
attach — รวมเอกสาร แล้วเลขหน้าถูกคำนวณใหม่#
1. การพิสูจน์ตัวตน (Authentication), ขอบเขต (Scopes) และการแยกข้อมูลตามทีม (Team Isolation)#
การเข้าถึง API ทุกส่วนใช้รหัสกุญแจ API (API Key) ส่งผ่านส่วนหัว (Header) ของคำขอ HTTP:
X-Api-Key: tdk_XXXXXXXXXXXXXXXXXXXXXXXXขอบเขตการใช้งาน (Scopes)#
กุญแจ API แต่ละชุดจะถูกจำกัดสิทธิ์ตามขอบเขตความปลอดภัย (Least Privilege) ดังนี้:
generate: อนุญาตให้เรียกสร้างเอกสารที่/v1/generateเท่านั้นdocuments:read: อนุญาตให้เรียกดูหรือดาวน์โหลดเอกสารจาก/v1/documents/:idเท่านั้นtemplates:read: อนุญาตให้ดึงโครงสร้างตัวแปรข้อตกลงผ่าน/v1/templates/:id/fieldsidentity-sync: อนุญาตเฉพาะสำหรับ API ซิงค์ข้อมูลทีมและผู้ใช้ที่/v1/sync/identitiesเท่านั้น (ห้ามรวมขอบเขตนี้ร่วมกับขอบเขตอื่นๆ)
การแยกข้อมูลตามทีม (Team Isolation)#
กุญแจ API ทุกชุดจะผูกมัดเข้ากับหน่วยงานหรือทีม (teamId) อย่างเข้มงวด:
- หากมีการพยายามดึงข้อมูลเทมเพลตหรือเอกสารของทีมอื่น ระบบจะส่งรหัสตอบกลับ
404 Not Foundเสมอ (ไม่ใช้403 Forbiddenเพื่อป้องกันผู้ใช้งานสุ่มหาไอดีเอกสารของต่างทีม) - คำขอที่เข้าข่ายข้ามสิทธิ์ผู้ใช้จะลงบันทึกในตารางตรวจสอบเป็นสถานะ
outcome: deniedเพื่อประโยชน์ด้านความมั่นคงปลอดภัย - กุญแจ API ทำหน้าที่เป็นอัตลักษณ์ของเครื่องทำงาน (Machine Identity) ที่ผูกติดกับทีม ไม่ได้ขึ้นอยู่กับสถานะของตัวบุคคลผู้สร้างคีย์ แม้พนักงานลาออกก็ไม่ส่งผลกระทบต่อระบบงานที่ผสานการทำงานอยู่
2. ข้อมูลตัวแปรของเทมเพลต (Field Discovery)#
ค้นหาข้อมูลรายละเอียดของฟิลด์ตัวแปรที่เทมเพลตต้องการเพื่อนำมาใช้กรอกข้อมูล
- สิทธิ์ที่ต้องใช้: ขอบเขต
templates:read(หรือล็อกอินด้วยสิทธิ์ Editor/Admin) - เส้นทาง (Route):
GET /v1/templates/:id/fields
พารามิเตอร์คิวรี (Query Parameters)#
version(ตัวเลข, ทางเลือก): ระบุเวอร์ชันเทมเพลตที่เผยแพร่แล้ว หากละเว้นจะแสดงข้อมูลของเวอร์ชันเผยแพร่ล่าสุดdraft(ค่าจริง/เท็จ, ทางเลือก): ส่งtrueเพื่อดึงข้อมูลของรุ่นร่างปัจจุบัน (ใช้งานได้เฉพาะเซสชันคุกกี้ของผู้ใช้ออกแบบที่มีบทบาท Editor/Admin เท่านั้น กุญแจ API ไม่ได้รับอนุญาตให้เข้าถึงแบบร่าง)
ตัวอย่างผลลัพธ์ (Response Payload)#
{
"templateId": "tpl_7f3a",
"version": 1,
"status": "published",
"fields": [
{
"name": "customerName",
"type": "text",
"required": true
},
{
"name": "loanAmount",
"type": "number",
"required": true,
"format": {
"decimals": 2,
"thousandsSeparator": true,
"prefix": "฿ "
},
"validation": {
"min": 0
}
},
{
"name": "contractDate",
"type": "date",
"required": true,
"format": {
"calendar": "buddhist",
"pattern": "d MMMM yyyy"
}
}
]
}ชนิดฟิลด์ (type) และรูปแบบค่าที่ต้องส่งใน data#
Field Discovery จะบอก type ของแต่ละฟิลด์ ค่าที่ส่งเข้าไปใน data ต้องตรงตามชนิดนั้น หากไม่ตรงจะได้รับ 400 invalid_data พร้อมรายการฟิลด์ที่บกพร่อง:
ชนิด (type) | รูปแบบค่าที่ส่ง | ข้อจำกัด / หมายเหตุ |
|---|---|---|
text | ข้อความ (string) | อักขระควบคุมจะถูกตัดออกอัตโนมัติ |
number | ตัวเลข หรือสตริงตัวเลข เช่น 1250000 หรือ "1250000" | แสดงผลตาม format/validation ของฟิลด์ |
date | สตริงวันที่แบบ ISO เช่น "2026-07-21" | แสดงผลตาม format (รองรับปฏิทินพุทธ) |
image | Data URL เท่านั้น เช่น "data:image/png;base64,iVBORw0KG..." | รองรับ PNG/JPEG/GIF/WebP • ปฏิเสธ URL แบบ http(s) • ขนาดภาพต้องไม่เกิน 30 ล้านพิกเซล (กว้าง×สูง) เกินคืน invalid_data |
qrcode | ข้อความที่จะเข้ารหัสเป็น QR เช่น "https://example.com/verify/abc" | — |
table | อาเรย์ 2 มิติของข้อความ เช่น [["Alice","New York"],["Bob","LA"]] หรือ สตริง JSON ของอาเรย์นั้น | แต่ละอาเรย์ย่อยคือหนึ่งแถว • จำกัดสูงสุด 2,000 แถว และ 20,000 เซลล์รวม เกินคืน invalid_data |
svg | มาร์กอัป SVG แบบอินไลน์ เช่น "<svg xmlns='http://www.w3.org/2000/svg' ...>...</svg>" (ต้องมี <svg และ </svg> — ไม่ใช่ data URL) | เพื่อความปลอดภัย ระบบปฏิเสธ <script>, ตัวจัดการเหตุการณ์ on*=, <!DOCTYPE>/<!ENTITY>/<?xml-stylesheet, และการอ้างอิงทรัพยากรภายนอก (href/xlink:href/src) เกินคืน invalid_data |
ค่าที่ส่งต้องครอบคลุมทุกฟิลด์ที่
required: trueหากฟิลด์ใดชนิดtable/svg/imageปรากฏใน Field Discovery แต่dataส่งค่าผิดชนิด (เช่น ส่งข้อความธรรมดาให้table) จะถูกปฏิเสธด้วยinvalid_data
3. การสร้างเอกสาร (Generate)#
สั่งกรอกข้อมูลลงเทมเพลตเพื่อผลิตไฟล์ PDF
- สิทธิ์ที่ต้องใช้: ขอบเขต
generate - เส้นทาง (Route):
POST /v1/generate - ประเภทข้อมูล:
Content-Type: application/json(ขนาดตัวขอต้องไม่เกิน 1 MB)
โครงสร้างพารามิเตอร์ใน Body#
templateId(ข้อความ, จำเป็น): รหัสประจำตัวเทมเพลตversion(ตัวเลข, ทางเลือก): ระบุเวอร์ชันเผยแพร่ที่ต้องการล็อกการผลิต หากละไว้จะใช้รุ่นล่าสุดของระบบmode(ข้อความ, จำเป็น): ตัวเลือกระหว่าง"sync"(ประสานเวลา) หรือ"async"(ไม่ประสานเวลา)data(วัตถุ JSON, จำเป็น): คีย์-ค่าของตัวแปรตามข้อกำหนดใน Field Discoveryoptions(วัตถุ JSON, ทางเลือก):metadata(วัตถุ JSON, ทางเลือก): ข้อมูลระบุรายละเอียดเอกสารที่จะฝังใน PDF (ห้ามใส่ PII)title: ชื่อเรื่องเอกสาร (จำกัด 200 ตัวอักษร)subject: หัวข้อเอกสาร (จำกัด 200 ตัวอักษร)
protection(วัตถุ JSON, ทางเลือก): ข้อมูลการตั้งรหัสผ่านไฟล์ PDF (ต้องการระบบ qpdf บนเซิร์ฟเวอร์)userPassword: รหัสผ่านสำหรับการเปิดอ่านเอกสาร (จำเป็นต้องกรอกอย่างน้อยหนึ่งรหัสผ่าน)ownerPassword: รหัสผ่านสำหรับจัดการสิทธิ์แก้ไขเอกสาร (ถ้าละไว้จะเท่ากับ userPassword)permissions: ระบุอนุญาตความปลอดภัย{ "print": true, "copy": false, "modify": false }
attach(อาเรย์ข้อความ, ทางเลือก): รายการไอดีdocumentIdของเอกสารของทีมเดียวกันที่สร้างผ่านโหมด Async เพื่อนำมาเชื่อมต่อรวมแฟ้ม PDF (แนบได้สูงสุด 3 ฉบับ)watermark(วัตถุ JSON, ทางเลือก): ข้อความลายน้ำพิมพ์ทับบนเอกสารtext: ข้อความลายน้ำ เช่น "ฉบับร่าง" (จำกัดไม่เกิน 30 ตัวอักษร)opacity: ระดับความโปร่งแสง (ค่า 0 ถึง 1)
bulkOutput(ข้อความ, ทางเลือก): กำหนดการส่งออกแบบกลุ่ม (ไม่รองรับในรุ่นนี้ ส่งค่าเข้ามาจะคืนข้อผิดพลาด501 not_implementedทันที)
รูปแบบคำตอบรับ (Responses)#
- โหมด Sync (
mode: "sync"): คืนค่าตอบกลับเป็นไบนารีไฟล์ PDF ดิบพร้อมรหัสสถานะ200 OKContent-Type: application/pdf
- โหมด Async (
mode: "async"): คืนค่าคำรับเรื่องสร้างเอกสารเป็นรหัสสถานะ201 Created{ "documentId": "8108147a-9f5b-426c-8e01-c092d8f921ab", "url": "https://docs.thinker.internal/v1/documents/8108147a-9f5b-426c-8e01-c092d8f921ab?download=1", "sha256": "c092d8f921ab4cd9f257a627e31b40283c7dfa2c2c0183b9cfad83c0f829f0e1", "expiresAt": "2026-08-12T17:00:00.000Z" }
4. การดึงข้อมูลเอกสาร (Retrieve Async Document)#
เรียกดูข้อมูลหรือดาวน์โหลดไฟล์ที่สั่งสร้างไว้ผ่านโหมด Async
- สิทธิ์ที่ต้องใช้: ขอบเขต
documents:read - เส้นทาง (Route):
GET /v1/documents/:id
พารามิเตอร์คิวรี (Query Parameters)#
download(ตัวเลข, ทางเลือก): หากระบุเป็น1ระบบจะทำการสตรีมส่งไฟล์ PDF ไบนารีกลับมาโดยตรง (รหัสสถานะ200 OKและระบุContent-Type: application/pdf)
ตัวอย่างผลลัพธ์กรณีปกติ (ไม่ใช่ดาวน์โหลด)#
{
"documentId": "8108147a-9f5b-426c-8e01-c092d8f921ab",
"templateId": "tpl_7f3a",
"version": 1,
"sha256": "c092d8f921ab4cd9f257a627e31b40283c7dfa2c2c0183b9cfad83c0f829f0e1",
"createdAt": "2026-07-14T00:07:05.000Z",
"expiresAt": "2026-08-14T00:07:05.000Z",
"url": "https://docs.thinker.internal/v1/storage/documents/8108147a-9f5b-426c-8e01-c092d8f921ab?token=..."
}5. รหัสข้อผิดพลาดและ HTTP สถานะ (Error Codes & HTTP Statuses)#
| HTTP สถานะ | รหัสข้อผิดพลาด (code) | คำอธิบายสาเหตุและแนวทางแก้ไข |
|---|---|---|
| 400 Bad Request | bad_request | โครงสร้าง JSON ผิดพลาด, ส่งค่าตัวเลือกไม่ตรงตามเงื่อนไข (เช่น ไม่ระบุรหัสผ่านในฟังก์ชัน protection หรือระบุ mode ไม่ถูกต้อง) |
| 400 Bad Request | invalid_data | ข้อมูลตัวแปรไม่เป็นไปตามข้อตกลงของฟิลด์ คืนข้อมูลรายการฟิลด์ที่บกพร่องในตัวแปร fields เช่น [{"field": "loanAmount", "reason": "expected a number"}] |
| 401 Unauthorized | unauthenticated | ไม่พบหรือรหัสกุญแจ API คีย์ผิดพลาดในส่วนหัว X-Api-Key |
| 403 Forbidden | insufficient_scope | กุญแจ API คีย์ไม่มีสิทธิ์ตามขอบเขตที่เรียกใช้ |
| 403 Forbidden | draft_requires_user | มีความพยายามดึงข้อมูลหรือสร้างงานแบบร่างโดยใช้กุญแจ API (ระบบอนุญาตสิทธิ์นี้เฉพาะผู้ใช้ผ่านเว็บเบราว์เซอร์เท่านั้น) |
| 403 Forbidden | forbidden | สิทธิ์ระดับบุคคลไม่เพียงพอ (เช่น บัญชีประเภท Viewer พยายามขอดึงข้อมูลแบบร่าง) |
| 404 Not Found | not_found | ไม่พบเทมเพลตหรือเอกสาร หรือไฟล์ดังกล่าวอยู่ในความครอบครองของทีมอื่น |
| 410 Gone | gone | เอกสารที่ระบุหมดอายุการเก็บรักษาและถูกทำลายออกจากระบบออนเพรมของสถาบันการเงินแล้ว |
| 413 Payload Too Large | payload_too_large | ขนาดคำขอ HTTP เกินความจุที่อนุญาตไว้คือ 1 MB |
| 422 Unprocessable | render_failed | ตัวประมวลผลเทมเพลตล้มเหลว (เกิดปัญหาจากโครงสร้างของตัวเทมเพลต ไม่ใช่ปัญหาเรื่องตัวแปรคำขอส่งเข้ามา) — รวมถึงกรณีการเรนเดอร์ใช้เวลานานเกินกำหนดแล้วถูกตัดจบ (timeout) |
| 429 Too Many Requests | rate_limited | เรียก generate ถี่เกินเพดาน (ค่าเริ่มต้น 120 คำขอ/นาที ต่อกุญแจ API) — คืนส่วนหัว Retry-After (วินาที) ให้รอแล้วค่อยลองใหม่ |
| 501 Not Implemented | not_implemented | ส่งคำขอใช้งานความสามารถที่สเปกระบุแต่ระบบยังไม่ได้พัฒนาขึ้นมาในรุ่นนี้ (เช่น การพิมพ์แบบกลุ่ม bulkOutput) |
| 503 Service Unavailable | protection_unavailable | มีคำขอเข้ารหัสผ่าน PDF แต่เซิร์ฟเวอร์ไม่มีการติดตั้งเครื่องมือ qpdf เอาไว้ (เป็นภาวะถาวรของเครื่องนั้น — ไม่ควรลองใหม่) |
| 503 Service Unavailable | overloaded | เซิร์ฟเวอร์กำลังเรนเดอร์ PDF เต็มกำลัง (ถึงเพดานงานที่ทำพร้อมกัน) — เป็นภาวะ ชั่วคราว คืนส่วนหัว Retry-After: 2 ให้ลองใหม่หลังหน่วงเวลา แนะนำใช้ exponential backoff แยกให้ออกจาก protection_unavailable: ดูที่ค่า code ไม่ใช่แค่สถานะ 503 |
6. ตัวอย่างการใช้คำสั่ง Curl สำหรับทดสอบเชื่อมต่อ (Curl Integration Example)#
นักพัฒนาสามารถคัดลอกชุดคำสั่งด้านล่างไปใช้รันเพื่อทดสอบเชื่อมระบบเบื้องต้น โดยเปลี่ยนค่าตัวแปรในระบบตามความเหมาะสม:
# 1. การเรียกตรวจสอบข้อมูลฟิลด์ที่จำเป็น (Field Discovery)
curl -s "https://docs.thinker.internal/v1/templates/tpl_7f3a/fields?version=1" \
-H "X-Api-Key: $THINKER_DOCS_API_KEY"
# 2. การสั่งสร้างเอกสารแบบไม่ประสานเวลา (Async Document Generation)
# จะได้รับข้อมูล documentId และเส้นทางการดึงไฟล์
curl -s -X POST "https://docs.thinker.internal/v1/generate" \
-H "X-Api-Key: $THINKER_DOCS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "tpl_7f3a",
"version": 1,
"mode": "async",
"data": {
"customerName": "นายสมชาย ใจดี",
"loanAmount": 1250000,
"contractDate": "2026-07-14",
"refCode": "LN-2569-004182"
},
"options": {
"metadata": {
"title": "สัญญาเงินกู้รายย่อย LN-004182",
"subject": "loan_ref=LN-2569-004182"
},
"protection": {
"userPassword": "$PDF_PASSWORD",
"permissions": {
"print": true,
"copy": false,
"modify": false
}
}
}
}'
# 3. การดาวน์โหลดไฟล์ PDF ไบนารีด้วย documentId
curl -s "https://docs.thinker.internal/v1/documents/8108147a-9f5b-426c-8e01-c092d8f921ab?download=1" \
-H "X-Api-Key: $THINKER_DOCS_API_KEY" \
-o contract.pdfตัวอย่างคำขอครบทุกแบบ (Request / curl)#
ทุกตัวอย่างด้านล่างถูกทดสอบจริงกับระบบที่รันอยู่ ไม่ได้เขียนขึ้นจากการคาดเดา
BASE=https://docs.thinker.internal
TID=50e3a8aa-d4da-4c06-9d40-afeae1ad9c9b
# ห้ามใส่คีย์จริงลงในสคริปต์ที่ commit — อ่านจาก environment เท่านั้น
export THINKER_DOCS_API_KEY=...1) ถามสัญญาฟิลด์ — ส่งอะไรบ้าง#
curl -s "$BASE/v1/templates/$TID/fields?version=2" -H "X-Api-Key: $THINKER_DOCS_API_KEY"2) sync — ได้รับไฟล์ PDF กลับมาทันที#
curl -s -X POST "$BASE/v1/generate" -H "X-Api-Key: $THINKER_DOCS_API_KEY" -H 'Content-Type: application/json' -d '{
"templateId": "'"$TID"'",
"version": 2,
"mode": "sync",
"data": {
"case_id": "LN-2569-001",
"fullName": "นายหนึ่ง ในใจ",
"nationalId": "1579940596522",
"age": "40",
"branch": "สำนักงานใหญ่"
}
}' --output document.pdfสิ่งที่ได้กลับ
HTTP/1.1 200 OK
content-type: application/pdf
content-length: 1245593) async — ได้ลิงก์ (ไฟล์ถูกเก็บไว้ให้)#
curl -s -X POST "$BASE/v1/generate" -H "X-Api-Key: $THINKER_DOCS_API_KEY" -H 'Content-Type: application/json' -d '{
"templateId": "'"$TID"'",
"version": 2,
"mode": "async",
"data": { "case_id": "LN-2569-001", "fullName": "นายหนึ่ง ในใจ" }
}'สิ่งที่ได้กลับ
{
"documentId": "7ee7f983-d06c-49bf-bad8-d7168051a503",
"url": "https://storage.internal/thinker-docs/documents/7ee7f983-….pdf?X-Amz-Signature=…",
"sha256": "e4aec0e72ba031a731983d771b1714882eeb9dcbdb900bcf042a0c42087d3615",
"expiresAt": "2026-08-12T17:15:21.009Z"
}4) ดึงเอกสารที่เก็บไว้ (ขอลิงก์ใหม่ได้เสมอ)#
DOCID=7ee7f983-d06c-49bf-bad8-d7168051a503
# แบบที่ 1 — รับ JSON พร้อม url ที่ออกใหม่
curl -s "$BASE/v1/documents/$DOCID" -H "X-Api-Key: $THINKER_DOCS_API_KEY"
# แบบที่ 2 — ให้ส่งไฟล์กลับมาโดยตรง
curl -s "$BASE/v1/documents/$DOCID?download=1" -H "X-Api-Key: $THINKER_DOCS_API_KEY" -o out.pdf5) ตั้งชื่อเอกสาร (metadata)#
curl -s -X POST "$BASE/v1/generate" -H "X-Api-Key: $THINKER_DOCS_API_KEY" -H 'Content-Type: application/json' -d '{
"templateId": "'"$TID"'", "version": 2, "mode": "async",
"data": { "case_id": "LN-2569-001" },
"options": {
"metadata": { "title": "สัญญาเงินกู้ LN-2569-001", "subject": "case=LN-2569-001" }
}
}'⚠️ metadata ไม่เคยถูกเข้ารหัส ใส่ได้แค่ ตัวอ้างอิง ห้ามใส่ชื่อลูกค้า เลขบัตร หรือจำนวนเงิน
6) ใส่รหัสผ่านให้ไฟล์ของผู้รับ (protection)#
# รหัสผ่านมาจาก environment เท่านั้น — อย่าพิมพ์ลงคำสั่ง อย่าเก็บลงไฟล์
curl -s -X POST "$BASE/v1/generate" -H "X-Api-Key: $THINKER_DOCS_API_KEY" -H 'Content-Type: application/json' -d '{
"templateId": "'"$TID"'", "version": 2, "mode": "sync",
"data": { "case_id": "LN-2569-001" },
"options": {
"protection": {
"userPassword": "'"$PDF_PASSWORD"'",
"permissions": { "print": true, "copy": false, "modify": false }
}
}
}' --output protected.pdfถ้าเซิร์ฟเวอร์ไม่มี qpdf จะได้ 503 protection_unavailable — ไม่ใช่ไฟล์ที่ไม่ได้ล็อก
7) แนบเอกสารอื่นต่อท้าย + ลายน้ำ#
curl -s -X POST "$BASE/v1/generate" -H "X-Api-Key: $THINKER_DOCS_API_KEY" -H 'Content-Type: application/json' -d '{
"templateId": "'"$TID"'", "version": 2, "mode": "async",
"data": { "case_id": "LN-2569-001" },
"options": {
"attach": ["7ee7f983-d06c-49bf-bad8-d7168051a503"],
"watermark": { "text": "ฉบับร่าง", "opacity": 0.15 }
}
}'attach รับได้เฉพาะ documentId ของทีมคุณเอง id ของทีมอื่น → 404 เหมือน id ที่ไม่มีอยู่จริง
และ เลขหน้าจะถูกคำนวณใหม่ตามเอกสารที่รวมแล้ว — สัญญา 2 หน้า + สำเนา 2 หน้า → ท้ายหน้า 3 เขียนว่า "หน้า 3 จาก 4"
8) เมื่อข้อมูลผิดสัญญา — ถูกจับที่ชายแดนก่อนสร้าง PDF#
curl -s -X POST "$BASE/v1/generate" -H "X-Api-Key: $THINKER_DOCS_API_KEY" -H 'Content-Type: application/json' -d '{
"templateId": "'"$TID"'", "version": 2, "mode": "sync",
"data": { "age": "สี่สิบ" }
}'400 Bad Request
{
"error": "data does not match the template field contract",
"code": "invalid_data",
"fields": [
{ "field": "age", "reason": "expected a number" },
{ "field": "case_id", "reason": "required" }
]
}9) พิสูจน์ว่าไฟล์ที่คุณถืออยู่คือไฟล์ที่ระบบออกให้#
shasum -a 256 out.pdf
# e4aec0e72ba031a731983d771b1714882eeb9dcbdb900bcf042a0c42087d3615
# ต้องตรงกับ sha256 ที่ API ตอบกลับมา — ไม่ต้องเก็บ PII ใดๆ เพื่อพิสูจน์เรื่องนี้/docs/api.th.md — this page renders that file. It does not keep a second copy, so the two can never disagree.