Paxa Labs

สกัดฟิลด์จากเอกสาร

Document Extraction อ่านไฟล์ PDF หรือรูปภาพ แล้วเติมค่าลงสคีมาของฟิลด์ที่คุณกำหนดชนิดไว้ แต่ละค่าคือข้อความที่พิมพ์อยู่ในเอกสาร หรือ null พร้อมเหตุผล คิดเครดิตรายหน้าตามขนาดของสคีมา ตัดก่อนอ่านและคืนให้อัตโนมัติเมื่อล้มเหลว

ดูเป็น Markdown

POST /v1/extract อ่านไฟล์ PDF, PNG, JPEG หรือ WebP แล้วเติมค่าลงสคีมาที่คุณส่งมา ส่งไฟล์เป็น base64 ในฟิลด์ document พร้อมรหัสโมเดล paxa-doc-extract-v1 และ schema ของฟิลด์ที่ระบุชนิด ทุกค่าที่ส่งกลับคือข้อความที่พิมพ์อยู่ในเอกสาร อ่านตามชนิดของฟิลด์ หรือเป็น null พร้อมเหตุผล ระบบไม่อนุมานและไม่ค้นหาข้อมูลจากที่อื่น เช่นเดียวกับทุกปลายทาง ระบบตัดเครดิตก่อนประมวลผลและคืนให้อัตโนมัติเมื่อประมวลผลไม่สำเร็จ (เครดิต)

# 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/extract \
  --max-time 300 \
  -H "Authorization: Bearer $PAXA_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "document": "$DOC",
  "model": "paxa-doc-extract-v1",
  "schema": {
    "fields": {
      "seller": {
        "type": "string",
        "required": true,
        "description": "The shop name as printed at the top"
      },
      "total": {
        "type": "number",
        "required": true
      },
      "issued_on": {
        "type": "date"
      },
      "items": {
        "type": "array",
        "max_items": 5,
        "items": {
          "type": "object",
          "fields": {
            "name": {
              "type": "string"
            },
            "amount": {
              "type": "number"
            }
          }
        }
      }
    }
  }
}
EOF
Response
{
  "status": "complete",
  "fields": {
    "seller": "ร้านข้าวแกงบ้านสวน สาขาสีลม",
    "total": 70,
    "issued_on": null,
    "items": [
      {
        "name": "ข้าวแกงเขียวหวานไก่",
        "amount": 60
      },
      {
        "name": "น้ำเปล่า",
        "amount": 10
      }
    ]
  },
  "missing": [],
  "unverified": [],
  "truncated": [],
  "assumed": [],
  "usage": {
    "pages": 1,
    "leaves": 13,
    "credits": 13
  }
}

การคิดเครดิต#

หนึ่งหน้าคิด 13 เครดิตเมื่อสคีมานับได้ไม่เกิน 50 ฟิลด์ย่อย และ 19.5 เครดิตเมื่อนับได้ 51 ถึง 100 ฟิลด์ย่อย คิดเป็นสองเท่าและสามเท่าของอัตรา OCR ที่ 6.5 เครดิต เพราะรวมการอ่านเอกสารไว้แล้ว หนึ่งหน้าคือหนึ่งหน้าของ PDF หรือรูปภาพหนึ่งไฟล์ ระบบอ่านจำนวนหน้าจากเอกสารและนับฟิลด์ย่อยจากสคีมาก่อนตัดเครดิตทั้งคู่ จึงรู้ค่าใช้จ่ายก่อนเริ่มอ่านเสมอ หลักฐานและข้อความที่อ่านได้ไม่เปลี่ยนค่าใช้จ่าย

จำนวนฟิลด์ย่อยคือฟิลด์ย่อยทุกตัวนับหนึ่งครั้ง บวกฟิลด์ย่อยในอาร์เรย์นับหนึ่งครั้งต่อหนึ่งสมาชิกที่อาร์เรย์กำหนดขนาดไว้ สคีมาที่มีฟิลด์ส่วนหัว 15 ฟิลด์ กับสามฟิลด์ต่อรายการสินค้าสูงสุดสิบรายการ นับได้ 45 ฟิลด์ย่อย สคีมาเดียวกันที่ยี่สิบรายการนับได้ 75 คำตอบรายงานจำนวนนี้ใน usage.leaves คู่กับ usage.pages และ usage.credits

ยอดที่ตัดรายงานอยู่ใน usage.credits ของเนื้อหาคำตอบเท่านั้น ปลายทางนี้ไม่ส่งเฮดเดอร์ x-credits-charged เมื่อการสกัดล้มเหลวหลังตัดเครดิต ระบบคืนเครดิตอัตโนมัติและตอบ 502 provider_error เอกสารที่ไม่มีฟิลด์ใดเลยถือเป็นการส่งมอบผลลัพธ์ที่ทุกฟิลด์เป็น null และคิดเครดิตตามปกติ

เขียนสคีมา#

schema เป็นออบเจกต์ที่มีคีย์เดียวคือ fields ซึ่งจับคู่ชื่อฟิลด์กับนิยามของมัน นิยามหนึ่งมี type แฟล็ก required (ไม่บังคับ) description ยาวไม่เกิน 200 ตัวอักษร (ไม่บังคับ) และคีย์ที่ชนิดนั้นรองรับ description คือข้อความเดียวของคุณที่โมเดลอ่าน จึงเป็นที่อธิบายความหมายของฟิลด์ เช่น "ชื่อผู้ขายตามที่พิมพ์ด้านบน" ชื่อฟิลด์ยาว 1 ถึง 64 ตัวอักษร ใช้ได้เฉพาะตัวอักษรละติน ตัวเลข และขีดล่าง

คอนเทนเนอร์สองแบบใช้ซ้อนฟิลด์ object มี fields ของตัวเอง array มี items ที่เป็นฟิลด์ย่อยหรือออบเจกต์ และต้องมี max_items ตั้งแต่ 1 ถึง 200 ซ้อนคอนเทนเนอร์ใต้รากได้ไม่เกิน 3 ชั้น ดังนั้น items[].tax.rate ใช้ได้ แต่ชั้นที่สี่จะถูกปฏิเสธ อาร์เรย์เก็บฟิลด์ย่อยหรือออบเจกต์ อาร์เรย์ซ้อนอาร์เรย์จะถูกปฏิเสธ

ชนิดคีย์ค่าที่ส่งกลับ
stringpattern, min_length, max_lengthข้อความตามที่พิมพ์ คัดลอกมาตรง ๆ ใช้กับรหัสที่อาจเริ่มด้วยเลขศูนย์
integermin, maxจำนวนเต็มที่พิมพ์อยู่ในช่วงข้อความ
numbermin, max, decimalsจำนวนเงินหรือตัวเลขที่พิมพ์อยู่ในช่วงข้อความ
datemin, max, not_future, yearวันที่แบบ ISO YYYY-MM-DD อ่านจากช่วงข้อความตามธรรมเนียมเอกสารไทย วันมาก่อน ปีเป็น พ.ศ. หรือ ค.ศ. ก็ได้
timeเวลาแบบ 24 ชั่วโมง HH:MM รวมวินาทีเมื่อพิมพ์ไว้
enumvalues (1 ถึง 50), aliases, strictค่าหนึ่งใน values แปลงจากช่วงข้อความที่พิมพ์ หรือเลือกเมื่อไม่มีสิ่งใดพิมพ์ไว้
iddigits, checkตัวเลข ตรวจจำนวนหลักและเลขตรวจสอบแล้ว
thai_idformatเลข 13 หลักของเลขประจำตัวประชาชน นิติบุคคล หรือผู้เสียภาษี ตรวจเลขตรวจสอบแล้ว
emailที่อยู่อีเมล ส่วนโดเมนเป็นตัวพิมพ์เล็ก
phoneformatเบอร์โทรไทยแบบ E.164 หรือแบบในประเทศ
postal_codeรหัสไปรษณีย์ห้าหลักที่ใช้อยู่ในประเทศไทย
provinceformatหนึ่งใน 77 จังหวัดของไทย
bankformatธนาคารในประเทศไทย
insurerformatบริษัทประกันที่ได้รับใบอนุญาตในประเทศไทย
card_schemeformatเครือข่ายบัตร เช่น Visa, Mastercard, JCB หรือ UnionPay
payment_methodformatเงินสด บัตร โอน พร้อมเพย์ หรือกระเป๋าเงิน
legal_formformatรูปแบบนิติบุคคลไทย เช่น บจก., บมจ., หจก. หรือ ร้าน
currencyformatรหัส ISO 4217 จากสัญลักษณ์หรือชื่อสกุลเงิน
unitformatหน่วยนับจากรายการสินค้า
amount_wordsจำนวนเงินที่เขียนเป็นตัวหนังสือ ไทยหรือภาษาอื่น ส่งกลับเป็นตัวเลข
objectfieldsออบเจกต์ของฟิลด์ภายใน
arrayitems, max_itemsสมาชิก max_items ตัวแรกที่พิมพ์ไว้ ตามลำดับการอ่าน

เงื่อนไขของฟิลด์#

เงื่อนไขบอกว่าฟิลด์นั้นคืออะไร ค่าที่พิมพ์ไว้แต่ผิดเงื่อนไขจะส่งกลับเป็น null พร้อมเหตุผล และช่วงข้อความยังอยู่ใน evidence เมื่อคุณขอไว้ จำกัด number ด้วย min max และ decimals กำหนดรูป string ด้วย pattern ซึ่งเป็น regular expression ยาวไม่เกิน 200 ตัวอักษรที่ต้องตรงกับค่าทั้งค่า และด้วย min_length กับ max_length จำกัด date ด้วย min และ max เป็นวันที่แบบ ISO และด้วย not_future ซึ่งเทียบกับวันนี้ตามเวลาประเทศไทย id รับ digits เป็นจำนวนหลักหรือคู่ [min, max] ตั้งแต่ 1 ถึง 64 และ check เป็น none, thai_mod11 หรือ luhn การตรวจสอบจับตัวเลขที่ผิดได้แต่จะไม่แก้ให้

year กำหนดว่า date อ่านปีสองหลักอย่างไร ค่าเริ่มต้นคือ recent ซึ่งเลือกศักราชที่ทำให้วันที่ตกในอดีตไม่นานนี้ เลข 69 บนใบเสร็จจึงเป็น พ.ศ. 2569 ส่วน strict จะส่งปีกลับเมื่อมีศักราชเดียวที่เข้ากันได้ be และ ce ระบุศักราชที่คุณทราบอยู่แล้ว ไม่ว่าใช้กฎใด ปีสองหลักทุกตัวที่อ่านจะถูกระบุใน assumed ของคำตอบพร้อมตัวเลขตามที่พิมพ์

enum และชุดค่าในตัว#

enum ระบุ values ของตัวเอง และอาจมี aliases ซึ่งจับคู่ค่าหนึ่งกับการสะกดแบบอื่นได้ไม่เกิน 20 แบบ เช่น "bangkok" กับ กรุงเทพมหานคร กทม. และ BKK ระบบเทียบช่วงข้อความที่พิมพ์กับชุดค่า ค่าที่ตรงทั้งหมดโดยไม่แยกตัวพิมพ์เล็กใหญ่ชนะก่อน ถัดมาคือช่วงข้อความที่มีการสะกดยาวตั้งแต่สี่ตัวอักษรของค่าเพียงค่าเดียว ถัดมาคือช่วงข้อความที่ต่างจากค่าเดียวเพียงหนึ่งตัวอักษร โดยไม่มีค่าอื่นอยู่ในระยะสองตัวอักษร กรณีเสมอกันจะไม่เลือกค่าใด และฟิลด์เป็น null พร้อม ambiguous_match ตั้ง strict เป็น true เพื่อรับเฉพาะค่าที่ตรงทั้งหมด enum ยังเป็นฟิลด์เดียวที่อาจเป็นการตัดสิน เช่น ประเภทของเอกสารซึ่งไม่ได้พิมพ์ไว้ที่ใด จะถูกเลือกจากค่าที่ระบุ

ชนิดชุดค่าปิด ได้แก่ province, bank, insurer, postal_code, card_scheme, payment_method, legal_form, currency และ unit คือ enum ที่บริการมีตารางค่าให้เอง รวมถึงตัวย่อที่เอกสารพิมพ์ ชื่อเดิมของธนาคารที่รวมกิจการแล้วแปลงเป็นธนาคารปัจจุบัน สัญลักษณ์สกุลเงินที่กำกวมอย่าง $ ใช้กับหลายสกุลเงิน จึงส่งกลับเป็นสัญลักษณ์นั้นเอง format เลือกรูปแบบที่ส่งกลับ และเป็นรูปแบบที่ไม่สูญเสียข้อมูลเสมอ

ชนิดรูปแบบ ค่าเริ่มต้นมาก่อน
provincename_th, name_en, code (10), iso (TH-10)
bankshort (KBANK), code (004), name_th, name_en
insurershort, code (2037), name_th, name_en
thai_iddigits (1234567890121), grouped (1-2345-67890-12-1)
phonee164 (+66812345678), national (0812345678)
card_schemecode (MASTERCARD), name
payment_method, legal_form, currency, unitcode, name_th, name_en

การปฏิเสธสคีมา#

สคีมาที่อยู่นอกภาษาสคีมาตอบ 400 schema_invalid ก่อนอ่านเอกสาร โดยไม่คิดเครดิต เนื้อหาปัญหามีสองฟิลด์เพิ่มจากรูปแบบขั้นต่ำ path ระบุฟิลด์ที่มีปัญหาแบบจุด (ว่างสำหรับราก) และ reason เป็นค่าหนึ่งจากตารางด้านล่าง สคีมาที่ใหญ่กว่า 128 KB ในรูป JSON ตอบโค้ดเดียวกันพร้อมเหตุผล too_large เหตุผลคงเป็นภาษาอังกฤษ เพราะเป็นค่าบนสาย

reasonMeaning
not_an_objectThe schema, or the field definition at this path, is not a JSON object.
unknown_keyA key at this path is not part of the dialect. A field takes type, required, description, and the keys its type lists.
missing_fieldsThe object at this path has no fields key.
invalid_fieldsThe fields key at this path is not an object with at least one field.
invalid_nameThe field name at this path is not 1 to 64 characters of letters, digits, and underscores.
missing_typeThe field at this path has no type.
unknown_typeThe type at this path is not one of the dialect's types.
too_deepThe field at this path sits under more than 3 containers.
missing_itemsThe array at this path has no items definition.
nested_arrayThe array at this path holds arrays. An array holds leaves or objects.
invalid_max_itemsThe array at this path has no max_items, or one outside 1 to 200.
too_many_leavesThe schema counts more leaf fields than the model's ceiling, arrays counted once per element they are sized for.
invalid_flagThe required, strict, or not_future key at this path is not a boolean.
invalid_formatThe format at this path is not one the type offers.
invalid_boundA min or max at this path is not a number, or not an ISO date on a date field.
min_exceeds_maxThe min at this path is greater than its max.
invalid_countA decimals, min_length, or max_length at this path is not a whole number in range.
invalid_patternThe pattern at this path is not a valid regular expression of up to 200 characters.
invalid_digitsThe digits at this path is not a count from 1 to 64, or a [min, max] pair in that range.
invalid_checkThe check at this path is not none, thai_mod11, or luhn.
invalid_yearThe year at this path is not recent, strict, be, or ce.
invalid_descriptionThe description at this path is not a string of up to 200 characters.
invalid_enum_valuesThe values at this path is not a list of 1 to 50 strings.
duplicate_enum_valueTwo values at this path are the same once case is folded.
invalid_aliasesThe aliases at this path is not a map from a listed value to up to 20 spellings.
conflicting_aliasAn alias at this path spells a listed value, or could name two of them.
too_largeThe schema is larger than 131,072 bytes as JSON.

อ่านคำตอบ#

fields คือต้นไม้ของสคีมาที่เติมค่าธรรมดา และเป็น null ตรงที่อ่านค่าไม่ได้ status เป็น "complete" เมื่อฟิลด์ที่ required ทุกตัวมีค่า และเป็น "incomplete" ในกรณีอื่น ค่านี้คำนวณจากฟิลด์เท่านั้น ไฟล์ที่ไม่ใช่เอกสารตามที่สคีมาบรรยายจะกลับมาเป็น incomplete โดยฟิลด์ required ส่วนใหญ่อยู่ใน missing ซึ่งเป็นวิธีตรวจจับเอกสารผิดประเภท คำตอบไม่มีคะแนนความมั่นใจ เพราะการคัดลอกตัวเลขที่อ่านผิดมาอย่างซื่อตรงเป็นสิ่งที่คะแนนใดมองไม่เห็น ชนิดที่มีเลขตรวจสอบคือวิธีจับกรณีนั้น

  • missing ระบุฟิลด์ required ทุกตัวที่เป็น null ไม่ว่าด้วยเหตุผลใด
  • unverified ระบุทุกฟิลด์ที่ช่วงข้อความอ่านเป็นชนิดของฟิลด์ไม่ได้ หรือที่ข้อความที่อ่านได้ไม่มีค่านั้น พร้อม reason จากตารางด้านล่าง ฟิลด์เหล่านี้เป็น null
  • truncated ระบุอาร์เรย์ทุกตัวที่เอกสารมีสมาชิกมากกว่าขนาดที่กำหนด สมาชิก max_items ตัวแรกอยู่ใน fields ตามลำดับการอ่าน เพิ่ม max_items เพื่ออ่านส่วนที่เหลือ
  • assumed ระบุวันที่ทุกตัวที่พิมพ์ปีเป็นสองหลัก พร้อม printed และ read_as คีย์นี้มีเสมอ และว่างเมื่อไม่มีการตีความ
  • evidence เมื่อ include_evidence เป็น true จับคู่พาธของแต่ละฟิลด์กับช่วงข้อความที่พิมพ์ซึ่งใช้อ่านค่า ไม่ว่าจะอ่านค่าได้หรือไม่
  • pages เมื่อ include_pages เป็น true คือข้อความที่อ่านได้จากแต่ละหน้าในรูป Markdown เป็นการอ่านเดียวกับที่ POST /v1/ocr คืนให้ ฟิลด์ทั้งหมดสกัดมาจากข้อความนี้

พาธเขียนแบบจุด และสมาชิกอาร์เรย์ระบุดัชนี เช่น "items[2].amount" เหตุผลใน unverified มีดังนี้

reasonMeaning
not_in_readoutThe value does not appear in the text read from the document.
not_as_printedThe value differs from the printed span it was read from.
no_numberThe span carries no number.
ambiguous_numberThe span carries more than one number.
not_an_integerThe number in the span is not whole.
no_dateThe span carries no date.
ambiguous_dateThe span's date reads two ways, or its two-digit year fits no era under the strict rule.
no_timeThe span carries no time of day.
ambiguous_timeThe span carries more than one time.
not_an_enum_valueThe span resolves to none of the listed values.
ambiguous_matchThe span resolves to two listed values equally well.
not_13_digitsThe identifier does not carry exactly thirteen digits.
bad_checksumThe identifier's check digit does not hold.
wrong_digit_countThe identifier's digit count is outside the field's digits.
not_an_emailThe span is not an email address.
not_a_phoneThe span is not a Thai telephone number.
not_a_postal_codeThe span is not a postal code in use in Thailand.
no_amount_wordsThe span is not an amount written in words.
below_minThe number is below the field's min.
above_maxThe number is above the field's max.
too_many_decimalsThe number carries more decimals than the field allows.
too_shortThe text is shorter than the field's min_length.
too_longThe text is longer than the field's max_length.
pattern_mismatchThe text does not match the field's pattern.
before_minThe date is before the field's min.
after_maxThe date is after the field's max.
in_the_futureThe date is after today in Thailand, and the field set not_future.

หมายเหตุ

ภาษาสคีมาไม่มีชนิด boolean ป้ายของช่องติ๊กพิมพ์อยู่บนหน้ากระดาษ แต่รอยติ๊กไม่ได้พิมพ์ ช่องติ๊กจึงจำลองเป็น enum ของป้ายที่พิมพ์ไว้

ขีดจำกัดจำนวนหน้า ขนาด และสคีมา#

หนึ่งคำขออ่านได้ไม่เกิน 20 หน้า เอกสารที่ยาวกว่านั้นตอบ 400 too_many_pages ไฟล์หลังถอดรหัส base64 ต้องไม่เกิน 10 MiB ไฟล์ที่ใหญ่กว่าตอบ 413 document_too_large สคีมานับได้ไม่เกิน 100 ฟิลด์ย่อย และมีขนาดไม่เกิน 128 KB ในรูป JSON กฎเรื่องเอกสารในคู่มือ OCR ใช้เหมือนเดิมทุกข้อ ระบบตรวจรูปแบบจากไบต์ของไฟล์ รูปภาพนับเป็นหนึ่งหน้า และไฟล์ที่อ่านไม่ได้หรือมีรหัสผ่านถูกปฏิเสธก่อนตัดเครดิต หนึ่งคำขอรับหนึ่งเอกสาร ไฟล์ที่มีหลายเอกสารได้ฟิลด์ชุดเดียว

ความเสถียรและการส่งซ้ำ#

คำตอบเป็น JSON ก้อนเดียว เอกสารหลายหน้าอาจใช้เวลาหลายนาที จึงควรตั้งไทม์เอาต์ของไคลเอนต์ให้เผื่อไว้ ตัวอย่างด้านบนเผื่อ 300 วินาที

คำขอที่ใช้เวลานานพอจะเขียนส่วนหัวของคำตอบออกไปก่อนที่จะรู้ผลลัพธ์ เพื่อไม่ให้พร็อกซีระหว่างทางตัดการเชื่อมต่อ คำตอบลักษณะนี้มีสถานะ 200 เสมอ และรายงานความล้มเหลวไว้ในเนื้อคำตอบด้วยเอกสารปัญหาชุดเดียวกับที่คำขอล้มเหลวได้รับ คือมี title และ status แทนที่ตำแหน่งของ fields ให้ถือว่าเนื้อคำตอบที่มี title คือข้อผิดพลาดตามที่ระบุ

ส่งซ้ำได้อย่างปลอดภัยด้วย Idempotency-Key การส่งซ้ำของคำขอที่ส่งมอบแล้วจะได้ฟิลด์ชุดเดิมโดยไม่ตัดเครดิตซ้ำ เมื่อการสกัดล้มเหลว ระบบคืนเครดิตให้ และคำขอใหม่ต้องใช้คีย์ใหม่ รหัสโมเดลคือเวอร์ชันของสัญญา ภาษาสคีมา รูปแบบคำตอบ และกฎการนับฟิลด์ย่อยคงที่ภายใต้ paxa-doc-extract-v1 การเปลี่ยนแปลงจะออกเป็นรหัสใหม่ที่ให้บริการควบคู่กัน