用 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。發票的運作方式一模一樣:你擷取的每個欄位,都會回到它原本所在的像素上。

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):// 前綴自動判斷),就能拿到有型別的欄位回傳。
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"
}'駝峰式命名才是正式形式。 參數為 imageType、templateId、autoFields。舊的 snake_case 別名(image_type、template_id、auto_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(自合併儲存格傳遞而來)。
{
"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"
}
}
}
}座標並不是照模型的說法照單全收。 語言模型會回傳每個值的文字 —— 以及它用了哪些 word token 的提示 —— 但從不直接回傳方框本身。引擎接著把這段文字,拿去和視覺 OCR 在頁面上實際偵測到的符號做字元層級的比對;match_ratio 就是有多少比例被找到,而方框會落在這些字元真正來自的像素上。模型的 token 提示可能帶有雜訊(在重複的列之間,它有時會把提示張冠李戴),因此會以欄、列的一致性檢查來驗證它們,而不是盲目採信。換句話說,一個值的座標會回頭對照發票加以查核,並附上一個分數,說明它們匹配得有多好。完整推論請見 為什麼 bounding box 讓 OCR 可稽核。
當範本不夠用時:自訂欄位
真實的發票上常有通用範本不會命名的欄位 —— 採購單參照(PO reference)、付款條件代碼、專案標籤。這時改傳一個 fields 陣列,內含 FieldSpec 物件,可取代範本或與其並用。每個 FieldSpec 是 { name, type, description?, children? }。如果你同時送出 fields 與 templateId,以 fields 為準。
description 是你引導模型的地方:用淺白的文字說明要擷取什麼、怎麼擷取。而 type: "array" 搭配 children,正是用來抓出重複的明細項目 —— 一份子結構,對應多列。(我們在 從發票擷取明細項目 一文中有深入說明。)
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"])值會原封不動地保留。 總額 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.received、item.created、ocr.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)。
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"requestId": "req_8fa2c1"
},
"details": { "retryAfterSec": 12 }
}從擷取到一張可查詢的 sheet
發票一旦被擷取進 sheet,你就不必為了讀回資料而重跑 OCR。GET /view 會在伺服器端對已儲存的列執行查詢 —— where、sort、select、limit、offset —— 不收費、也不重新擷取。bounding box 預設會一併帶上;想要更精簡的 payload 可加上 boxes=0。從這裡你還能匯出成 CSV(UTF-8 BOM,所以 Excel 與 CJK 文字都能正常打開)—— 詳見 把掃描文件轉成 CSV。
計費
POST /ocr/fields 每次呼叫 ¥10,而 POST /upload 是 ¥10 × N 張影像。失敗不收費 —— 若 OCR 沒有回傳結果會退款,而 502 引擎錯誤或 ocr.failed 事件會自動退款。唯讀端點(GET /space、/view、/amount、/health)免費。免費方案為每月 100 次掃描、無需信用卡;Pro 為 ¥7,980/月;Business 則需洽詢業務。
如何用 API 從發票擷取資料
- 取得 API 金鑰並設定授權建立一把以 spocr_ 為前綴的金鑰,並透過 HTTP Bearer token 向 https://api.space-ocr.com 驗證。每次請求都帶上 Authorization: Bearer spocr_xxxx 標頭;缺漏或格式錯誤的標頭會回傳 401,無法辨識的金鑰會回傳 403。
- 送出發票影像對 POST /ocr/fields 送出一張發票影像,影像可用 URL 或純 base64 提供(imageType 會依 http(s):// 前綴自動判斷)。引擎只接受點陣影像 —— JPEG、PNG、GIF、BMP、TIFF、WebP。
- 選擇範本或自訂欄位最快的方式是設定 templateId: 'invoice',使用內建的發票範本。若需要範本未涵蓋的欄位,改傳 fields 陣列,內含 FieldSpec 物件 { name, type, description?, children? }。若同時傳入 fields 與 templateId,以 fields 為準。
- 讀取帶有座標的回傳結果成功的呼叫會回傳 { status: "success", data: { ... } }。每個值都帶有來源依據,field_bboxes 對應表提供每個欄位的座標:bbox(0–1000 normalized 的軸對齊矩形)、vertices(定向頂點)、match_ratio(值的字元在頁面上被定位到的比例),以及 bbox_source(座標的取得方式)。
- 依 match_ratio 驗證並後續處理用 match_ratio 判斷每個值的可信度(≥ 0.85 視為高度匹配),對較弱的匹配進行人工複核。要批次處理一整個資料夾時,改用 POST /upload,並透過輪詢 GET /jobs/{jobId} 或註冊 webhook 取得結果。