space ocr
指南文章價格文件

用 API 從發票中擷取資料

space-ocr 發票資料擷取 API 的開發者指南:用 curl 與 Python 呼叫 POST /ocr/fields、發票範本、自訂欄位,以及帶有來源座標(bounding box)的擷取結果。

從發票上抓出結構化資料 —— 廠商、發票號碼、日期、明細金額、稅額 —— 是最常見的文件自動化工作之一,卻也是最費工、最難自己手刻的一種。對 OCR 文字跑 regex,只要某家廠商一改版面就立刻失效。而範本比對工具又要你為每一家供應商手動框格子。你真正想要的,是一個能讀懂任何版面、回傳乾淨且有型別欄位的發票資料擷取 API —— 而且關鍵在於,它還要告訴你每個值在頁面上的哪個位置被讀出來的,這樣你才敢相信結果。

最後這一點才是整件事的重點。一個只回傳 total: 2,045、卻沒有任何來源依據的發票擷取端點,放進應付帳款流程裡就是個隱患。本文將帶你走過 space-ocr 的 POST /ocr/fields 端點:一次同步呼叫,接收一張發票影像,套用內建的 invoice 範本(或你自己的欄位結構),並讓每個值都帶著經過驗證的 bounding box 回傳。

還沒寫半行程式碼,先看輸出長什麼樣

下面是一張真實解析出來的收據。把游標移到任一欄位上,影像上對應的方框就會亮起 —— 那個方框正是該值被讀取出來的位置,而且每個欄位都帶有自己的 match ratio。發票的運作方式一模一樣:你擷取的每個欄位,都會回到它原本所在的像素上。

Invoice with extracted-field bounding boxes
Verified fields
Invoice

Each value with a box carries a verified on-page location — bbox + 4-point vertices + match_ratio — on a 0–1000 normalized grid (0,0 top-left → 1000,1000 bottom-right), the same shape the live API returns. Hover a field to trace it back to the pixels it came from.

驗證與 base URL

公開 API 只有單一基底位址 —— https://api.space-ocr.com —— 沒有 /v1 這類路徑版本。每次請求都以 HTTP Bearer token 驗證,金鑰以 spocr_ 為前綴:

Authorization: Bearer spocr_xxxxxxxxxxxxxxxx

標頭缺漏或格式錯誤會回傳 401;無法辨識的金鑰會回傳 403。每個回應都帶有 X-Request-Id 標頭(格式為 req_xxx),建議記錄下來以便支援追蹤。完整規格以 OpenAPI 3.1 發佈於 GET /openapi.json,若你想直接產生用戶端可以參考。

最簡單的呼叫:內建發票範本

最快的路徑是 templateId: "invoice" —— 一個預先定義好的結構,它知道發票長什麼樣子,所以你不必自己描述欄位。把影像以 URL 或純 base64 傳入(imageType 會依 http(s):// 前綴自動判斷),就能拿到有型別的欄位回傳。

用內建發票範本呼叫 POST /ocr/fields
1
2
3
4
5
6
7
8
curl -X POST https://api.space-ocr.com/ocr/fields \
  -H "Authorization: Bearer spocr_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/invoices/inv-4471.jpg",
    "imageType": "url",
    "templateId": "invoice"
  }'
Why it matters

駝峰式命名才是正式形式。 參數為 imageTypetemplateIdautoFields。舊的 snake_case 別名(image_typetemplate_idauto_fields)仍可運作,但已棄用 —— 新程式碼請優先採用駝峰式名稱。

回應的結構

成功的呼叫會回傳 { status: "success", data: { ... } }。每個擷取出來的值都帶有自己的來源依據,而 field_bboxes 對應表提供每個欄位的座標:

  • bbox —— 一個軸對齊矩形 { xmin, ymin, xmax, ymax },落在 0–1000 normalized 的格線上(0,0 = 左上角,1000,1000 = 右下角),與影像的像素尺寸無關。換算成像素:pixel_x = bbox_x / 1000 × image_width
  • vertices —— 四個依序排列的點 {x, y}(左上 → 右上 → 右下 → 左下),構成一個會隨文件傾斜的定向方框,因此就算是用手機歪斜拍下的發票,框線也能貼合。
  • match_ratio —— 該值的字元實際在頁面上被定位到的比例(0–1)。當比例 ≥ 0.85 時,視為高度匹配;1.0 代表每個字元都被找到。
  • bbox_source —— 座標的取得方式:vision_symbol_match(常見的字元比對路徑,帶有其真實的 match_ratio)、token_id / token_id_hybrid(用上了 word-token 提示)、low_confidence(匹配較弱 —— 值得複核),或 shared_value(自合併儲存格傳遞而來)。
POST /ocr/fields → 回應(節錄)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "status": "success",
  "data": {
    "total": "2,045",
    "field_bboxes": {
      "total": {
        "bbox": { "xmin": 595, "ymin": 974, "xmax": 781, "ymax": 1000 },
        "vertices": [
          { "x": 594, "y": 975 }, { "x": 781, "y": 972 },
          { "x": 781, "y": 998 }, { "x": 595, "y": 1000 }
        ],
        "match_ratio": 0.93,
        "bbox_source": "vision_symbol_match"
      }
    }
  }
}
✓ Verified

座標並不是照模型的說法照單全收。 語言模型會回傳每個值的文字 —— 以及它用了哪些 word token 的提示 —— 但從不直接回傳方框本身。引擎接著把這段文字,拿去和視覺 OCR 在頁面上實際偵測到的符號做字元層級的比對;match_ratio 就是有多少比例被找到,而方框會落在這些字元真正來自的像素上。模型的 token 提示可能帶有雜訊(在重複的列之間,它有時會把提示張冠李戴),因此會以欄、列的一致性檢查來驗證它們,而不是盲目採信。換句話說,一個值的座標會回頭對照發票加以查核,並附上一個分數,說明它們匹配得有多好。完整推論請見 為什麼 bounding box 讓 OCR 可稽核

當範本不夠用時:自訂欄位

真實的發票上常有通用範本不會命名的欄位 —— 採購單參照(PO reference)、付款條件代碼、專案標籤。這時改傳一個 fields 陣列,內含 FieldSpec 物件,可取代範本或與其並用。每個 FieldSpec 是 { name, type, description?, children? }。如果你同時送出 fieldstemplateId,以 fields 為準。

description 是你引導模型的地方:用淺白的文字說明要擷取什麼、怎麼擷取。而 type: "array" 搭配 children,正是用來抓出重複的明細項目 —— 一份子結構,對應多列。(我們在 從發票擷取明細項目 一文中有深入說明。)

帶有巢狀明細項目的自訂 FieldSpec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests, base64

with open("invoice.jpg", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

resp = requests.post(
    "https://api.space-ocr.com/ocr/fields",
    headers={"Authorization": "Bearer spocr_xxxxxxxxxxxxxxxx"},
    json={
        "image": b64,
        "imageType": "base64",
        "fields": [
            {"name": "vendor", "type": "string",
             "description": "Supplier / billing company name"},
            {"name": "invoice_no", "type": "string",
             "description": "Invoice number, verbatim"},
            {"name": "invoice_date", "type": "string"},
            {"name": "total", "type": "string",
             "description": "Grand total, keep comma separators"},
            {"name": "line_items", "type": "array",
             "description": "One row per line on the invoice",
             "children": [
                 {"name": "description", "type": "string"},
                 {"name": "qty", "type": "number"},
                 {"name": "unit_price", "type": "number"},
             ]},
        ],
    },
    timeout=60,
)

data = resp.json()["data"]
print(data["total"], data["field_bboxes"]["total"]["match_ratio"])
Why it matters

值會原封不動地保留。 總額 7,855 會以字串 "7,855" 的形式回傳 —— 千分位逗號、小數點與全形字元都完整保留。引擎只有在某欄位的 description 明確要求時,才會做正規化。你在網頁 UI 上看到的 ¥ 只是裝飾,並不屬於值的一部分。引擎只接受點陣影像 —— JPEG、PNG、GIF、BMP、TIFF、WebP —— 並會自動轉成 RGB。

走非同步路線:批次上傳、工作(jobs)與 webhook

POST /ocr/fields 是同步的,非常適合在請求/回應的迴圈裡處理單張發票。若是一整個資料夾的發票,就用 POST /upload(multipart 的 files,可重複)把它們送進一張 sheet。預設會立刻回傳一個 jobs 陣列:

{ "path": "...", "jobs": [ { "uniqueKey": "...", "jobId": "...", "status": "pending" } ] }

接著你有兩種方式得知結果:輪詢 GET /jobs/{jobId},或註冊一個 webhook。Webhook 是每個 space 一個 URL,透過 X-Spaceocr-Signature 標頭以 HMAC-SHA256 簽章。你會在意的事件有 upload.receiveditem.createdocr.completed(其 data.result 帶有擷取結果)以及 ocr.failed。在信任任何 payload 之前,務必先驗證簽章。

冪等性、請求追蹤與速率限制

有幾個標頭能讓正式環境的流程安全地重試:

標頭用途
Idempotency-Key/upload/create 上,以相同金鑰重試會在 24 小時內重播快取的回應(X-Idempotent-Replay: true)—— 安全重試,不會重複扣費。
X-Request-Id每個回應都會回傳(req_xxx);請記錄下來以便追查問題。

速率限制為每把金鑰每分鐘 60 次請求每個 uid 每分鐘 600 次請求,以固定 60 秒的時間窗計算。一旦超出,你會得到 429,且 error.code: "rate_limited"。等待時間放在 JSON 主體的 details.retryAfterSec 中 —— 沒有 Retry-After HTTP 標頭,所以請依主體裡的值來退避(back off)。

429 回應主體
1
2
3
4
5
6
7
8
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded",
    "requestId": "req_8fa2c1"
  },
  "details": { "retryAfterSec": 12 }
}

從擷取到一張可查詢的 sheet

發票一旦被擷取進 sheet,你就不必為了讀回資料而重跑 OCR。GET /view 會在伺服器端對已儲存的列執行查詢 —— wheresortselectlimitoffset —— 不收費、也不重新擷取。bounding box 預設會一併帶上;想要更精簡的 payload 可加上 boxes=0。從這裡你還能匯出成 CSV(UTF-8 BOM,所以 Excel 與 CJK 文字都能正常打開)—— 詳見 把掃描文件轉成 CSV

丟進一張發票,有型別的欄位就自動填好 —— 和 API 回傳的是同一份資料,只是呈現在 UI 上。

計費

POST /ocr/fields 每次呼叫 ¥10,而 POST /upload 是 ¥10 × N 張影像。失敗不收費 —— 若 OCR 沒有回傳結果會退款,而 502 引擎錯誤或 ocr.failed 事件會自動退款。唯讀端點(GET /space/view/amount/health)免費。免費方案為每月 100 次掃描、無需信用卡;Pro 為 ¥7,980/月;Business 則需洽詢業務。

在已擷取的發票之間搜尋,直接跳到符合的儲存格 —— 以及它的來源方框。

如何用 API 從發票擷取資料

  1. 取得 API 金鑰並設定授權
    建立一把以 spocr_ 為前綴的金鑰,並透過 HTTP Bearer token 向 https://api.space-ocr.com 驗證。每次請求都帶上 Authorization: Bearer spocr_xxxx 標頭;缺漏或格式錯誤的標頭會回傳 401,無法辨識的金鑰會回傳 403。
  2. 送出發票影像
    對 POST /ocr/fields 送出一張發票影像,影像可用 URL 或純 base64 提供(imageType 會依 http(s):// 前綴自動判斷)。引擎只接受點陣影像 —— JPEG、PNG、GIF、BMP、TIFF、WebP。
  3. 選擇範本或自訂欄位
    最快的方式是設定 templateId: 'invoice',使用內建的發票範本。若需要範本未涵蓋的欄位,改傳 fields 陣列,內含 FieldSpec 物件 { name, type, description?, children? }。若同時傳入 fields 與 templateId,以 fields 為準。
  4. 讀取帶有座標的回傳結果
    成功的呼叫會回傳 { status: "success", data: { ... } }。每個值都帶有來源依據,field_bboxes 對應表提供每個欄位的座標:bbox(0–1000 normalized 的軸對齊矩形)、vertices(定向頂點)、match_ratio(值的字元在頁面上被定位到的比例),以及 bbox_source(座標的取得方式)。
  5. 依 match_ratio 驗證並後續處理
    用 match_ratio 判斷每個值的可信度(≥ 0.85 視為高度匹配),對較弱的匹配進行人工複核。要批次處理一整個資料夾時,改用 POST /upload,並透過輪詢 GET /jobs/{jobId} 或註冊 webhook 取得結果。
從發票擷取資料最好用的 API 是哪個?
一個好的發票擷取 API 應該能讀懂任何版面(而不只是你事先框好的範本),回傳乾淨、有型別的欄位,並為每個值提供來源依據。space-ocr 的 POST /ocr/fields 一次同步呼叫就能做到:傳入一張發票影像,搭配 templateId 'invoice' 或你自己的 fields[] 結構,每個值都會帶著 bounding box、定向頂點(oriented vertices)與 match ratio 一起回傳,讓你能對照原始文件加以驗證。
我能同時擷取發票的明細項目與表頭欄位嗎?
可以。使用 type 為 'array' 的 FieldSpec,並以 children 結構描述單一列(例如 description、qty、unit_price)。API 會為發票上每一個明細項目回傳一列,每列都帶有自己的 bounding box。而 vendor、發票號碼、日期、總額等表頭欄位會在同一次呼叫中一併擷取。
發票擷取 API 接受 PDF 嗎?
引擎只接受點陣影像 —— JPEG、PNG、GIF、BMP、TIFF 與 WebP —— 並會自動轉成 RGB。請將影像以 URL 或純 base64 的形式放在 'image' 欄位,並把 imageType 設為 'url' 或 'base64'。
發票擷取 API 如何處理錯誤與速率限制?
速率限制為每把金鑰每分鐘 60 次請求、每個 uid 每分鐘 600 次,以固定 60 秒的時間窗計算。超出時會回傳 HTTP 429,error.code 為 'rate_limited',等待時間放在 JSON 主體的 details.retryAfterSec 中 —— 沒有 Retry-After 標頭。請在 /upload 與 /create 上使用 Idempotency-Key,這樣重試會在 24 小時內重播快取的回應,而不會再次扣費。
從一張發票擷取資料要花多少錢?
POST /ocr/fields 每次呼叫 ¥10,/upload 則為每張影像 ¥10。失敗不收費 —— 若 OCR 沒有回傳結果會自動退款。免費方案每月含 100 次掃描、無需信用卡;Pro 為 ¥7,980/月;Business 則需洽詢業務。

一次呼叫,擷取你的第一張發票

免費方案 —— 每月 100 次掃描,無需信用卡。每個欄位都會帶著它在頁面上的位置一起回傳。

相關