ข้ามไปยังเนื้อหาหลัก
Thinker Docs
ดาวน์โหลด .md/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/fields
  • identity-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 (รองรับปฏิทินพุทธ)
imageData 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 Discovery
  • options (วัตถุ 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 OK
    • Content-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 Requestbad_requestโครงสร้าง JSON ผิดพลาด, ส่งค่าตัวเลือกไม่ตรงตามเงื่อนไข (เช่น ไม่ระบุรหัสผ่านในฟังก์ชัน protection หรือระบุ mode ไม่ถูกต้อง)
400 Bad Requestinvalid_dataข้อมูลตัวแปรไม่เป็นไปตามข้อตกลงของฟิลด์ คืนข้อมูลรายการฟิลด์ที่บกพร่องในตัวแปร fields เช่น [{"field": "loanAmount", "reason": "expected a number"}]
401 Unauthorizedunauthenticatedไม่พบหรือรหัสกุญแจ API คีย์ผิดพลาดในส่วนหัว X-Api-Key
403 Forbiddeninsufficient_scopeกุญแจ API คีย์ไม่มีสิทธิ์ตามขอบเขตที่เรียกใช้
403 Forbiddendraft_requires_userมีความพยายามดึงข้อมูลหรือสร้างงานแบบร่างโดยใช้กุญแจ API (ระบบอนุญาตสิทธิ์นี้เฉพาะผู้ใช้ผ่านเว็บเบราว์เซอร์เท่านั้น)
403 Forbiddenforbiddenสิทธิ์ระดับบุคคลไม่เพียงพอ (เช่น บัญชีประเภท Viewer พยายามขอดึงข้อมูลแบบร่าง)
404 Not Foundnot_foundไม่พบเทมเพลตหรือเอกสาร หรือไฟล์ดังกล่าวอยู่ในความครอบครองของทีมอื่น
410 Gonegoneเอกสารที่ระบุหมดอายุการเก็บรักษาและถูกทำลายออกจากระบบออนเพรมของสถาบันการเงินแล้ว
413 Payload Too Largepayload_too_largeขนาดคำขอ HTTP เกินความจุที่อนุญาตไว้คือ 1 MB
422 Unprocessablerender_failedตัวประมวลผลเทมเพลตล้มเหลว (เกิดปัญหาจากโครงสร้างของตัวเทมเพลต ไม่ใช่ปัญหาเรื่องตัวแปรคำขอส่งเข้ามา) — รวมถึงกรณีการเรนเดอร์ใช้เวลานานเกินกำหนดแล้วถูกตัดจบ (timeout)
429 Too Many Requestsrate_limitedเรียก generate ถี่เกินเพดาน (ค่าเริ่มต้น 120 คำขอ/นาที ต่อกุญแจ API) — คืนส่วนหัว Retry-After (วินาที) ให้รอแล้วค่อยลองใหม่
501 Not Implementednot_implementedส่งคำขอใช้งานความสามารถที่สเปกระบุแต่ระบบยังไม่ได้พัฒนาขึ้นมาในรุ่นนี้ (เช่น การพิมพ์แบบกลุ่ม bulkOutput)
503 Service Unavailableprotection_unavailableมีคำขอเข้ารหัสผ่าน PDF แต่เซิร์ฟเวอร์ไม่มีการติดตั้งเครื่องมือ qpdf เอาไว้ (เป็นภาวะถาวรของเครื่องนั้น — ไม่ควรลองใหม่)
503 Service Unavailableoverloadedเซิร์ฟเวอร์กำลังเรนเดอร์ 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: 124559

3) 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.pdf

5) ตั้งชื่อเอกสาร (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.