space ocr
文章文件
指南

發票 OCR API/送貨單 OCR 轉 CSV ── 發票資料擷取 API 實作指南

讓開發者擺脫發票、送貨單手動輸入與 Excel 亂碼的指南。把影像 POST 到 /ocr/fields,即可取得結構化的客戶、日期、合計與明細,每個值都附帶原始影像座標(bbox)與 match_ratio。內含 curl、Python 程式碼、CSV 輸出、Webhook 與收費說明。

9 分鐘閱讀· 2026-06-25

你是不是還在用手把發票、送貨單一格一格敲進 Excel?日期、客戶、未稅、含稅,還有明細的每一行 ── 一到月底就得對著堆積如山的紙張,把數字一格一格謄寫過去。中途差了一位數,合計兜不攏,又得從頭核對一遍。那段時間,真的很想省下來。

想把掃描好的 PDF 複製出來,卻發現文字根本選不起來。丟去 OCR 跑一遍,明細又全部擠進同一個儲存格,換行和欄位都不見了。CSV 用 Excel 一打開又亂碼,品名完全看不懂。明明只是想匯進會計軟體,卻每次都卡在這道關卡前 ── 這就是天天和文件打交道的人最熟悉的「日常」。

這篇文章,就是要教開發者怎麼把這些作業換成一支 API。把發票、送貨單的影像 POST 到 POST /ocr/fields,就能拿到客戶、日期、合計這些欄位,連明細的每一行都會以帶型別的結構化資料回傳。而且回傳的每一個值,都附帶它是從原始影像哪個位置讀出來的座標(bbox),所以你不必照單全收,可以拿去和原稿逐一比對驗證。文中附上 curl 與 Python 程式碼,從最短路徑一路看到正式上線運維。

先動手玩玩看 ── 免上傳,10 秒體驗

寫程式之前,先看看實際的輸出。下面是一張真實收據的解析結果。把游標移到欄位上,就會highlight出這個值是從影像的哪裡讀出來的,還能看到每個欄位的符合率(match_ratio)。發票、送貨單的行為完全一樣 ── 擷取出的每一個值,都會對應到讀取來源的像素。

Source receipts with extracted-field bounding boxes
Verified fields
KINSHO · 合計 2,045
ライフ · 合計 4,286

Every value 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.

Demo擷取出的值全都會伴隨 <b>bbox(座標)</b>、支援旋轉的 vertices、<b>符合率</b> 一併回傳 ── 是可以描繪、可以引用的「有出處」資料。
擷取出的值全都會伴隨 bbox(座標)、支援旋轉的 vertices、符合率 一併回傳 ── 是可以描繪、可以引用的「有出處」資料。

「原始影像 → 擷取表單 → 標示對應位置 → 輸出 CSV」的流程

space ocr 的用法,歸結起來就是 4 個步驟。(1) 丟進收據、發票、送貨單的影像 → (2) 在欄位固定的表單裡,以 1 張=1 列的方式擷取出來 → (3) 點一下值,原始影像的對應位置就會亮起,方便和原稿核對 → (4) 直接輸出成 CSV 匯進會計軟體。先從丟一張進去、看欄位被自動填滿開始吧。

Demo把一張發票拖進去,帶型別的欄位就會自動填滿 ── 和 <b>API</b> 回傳的相同資料,直接呈現在 UI 上。
把一張發票拖進去,帶型別的欄位就會自動填滿 ── 和 API 回傳的相同資料,直接呈現在 UI 上。

驗證與基礎 URL

公開 API 只有 https://api.space-ocr.com 這一個基礎位址 ── 沒有 /v1 這類的路徑版本管理。每一次請求,都用以 spocr_ 開頭的金鑰、透過 HTTP Bearer token 進行驗證。

Authorization: Bearer spocr_xxxxxxxxxxxxxxxx

標頭缺漏或不正確會回傳 401,未註冊的金鑰則回傳 403。所有回應都會帶上 X-Request-Id(格式 req_xxx)標頭,記進 log 裡,日後向客服詢問時會比較安心。如果想自動產生用戶端,GET /openapi.json 提供了 OpenAPI 3.1 的規格。

最短路徑 ── 內建的發票、送貨單範本

最快的方式,是在 templateId 指定內建範本。發票用 templateId: "invoice",送貨單用 templateId: "delivery"。「發票會有哪些欄位」引擎這邊已經知道,所以你不必自己一個一個定義欄位。影像可以用 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/docs/delivery-0831.jpg",
    "imageType": "url",
    "templateId": "delivery"
  }'
Why it matters

參數的正式名稱是駝峰式命名。 請使用 imageType / templateId / autoFields。舊有的蛇形命名(image_type / template_id / auto_fields)雖然也能運作,但已不建議使用。新的程式碼請優先採用駝峰式命名。另外,發票用 templateId: "invoice",送貨單用 templateId: "delivery"

回應的格式 ── 每個值都附帶「出處」

成功時會回傳 { status: "success", data: { ... } }。擷取出的每個值都各自帶有出處資訊,各欄位的座標則統整在 field_bboxes map 裡。

  • bbox ── { xmin, ymin, xmax, ymax } 的軸對齊矩形。是在正規化為 0〜1000 的網格上的整數座標(0,0 為左上,1000,1000 為右下)。和影像的像素尺寸無關。換算成像素為 pixel_x = bbox_x / 1000 × image_width
  • vertices ── 依左上 → 右上 → 右下 → 左下順序排列的 4 個點 {x, y}。是沿著文件傾斜方向的**支援旋轉(oriented)**矩形,所以即使是斜著拍的手機照片也能漂亮地框住。
  • match_ratio ── 該值的字元中,實際在頁面上找到的比例(0〜1)。0.85 以上視為確信符合,1.0 代表所有字元都在頁面上找到了。
  • bbox_source ── 座標導出方式的標籤。vision_symbol_match(字元符合度達 0.85 以上而落地的一般路徑 ── 會伴隨實際的 match_ratio)、token_id / token_id_hybrid(用 LLM 回傳的 word token 提示去查 Vision 的詞 token 的路徑)、low_confidence(字元符合度低於 0.85 ── 需確認)、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

座標並不是照單全收 AI 的說法。 語言模型回傳的只有各值的文字,以及它用到的 word token 的提示(wid),並不會回傳座標本身。引擎會先把那段文字,拿去和 Vision OCR 在頁面上實際偵測到的符號逐字比對 ── 所以矩形會落在那些字元真正被找到的像素上,而每個值也都會附上代表「符合了多少」的 match_ratio。當 LLM 回傳 word token 的提示時,引擎也可能用該 token 的座標去覆寫部分欄位,但這個提示有可能夾帶雜訊(在重複的列裡和鄰列搞混,也就是所謂 stochastic 的擺動),所以不會盲信,而是先用欄、列的一致性驗證、修正後才採用。重點不在於「AI 不會出錯」,而在於每一個值都會重新比對回頁面,並留下符合了多少的分數。詳情請參閱用邊界框讓 OCR 可稽核的機制

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

實務上的發票,常有通用範本沒有命名的欄位 ── 訂單號、付款條件代碼、專案標籤等等。這種時候,就用 FieldSpec 的陣列 fields 來取代 templateId(或兩者併用)。每一個 FieldSpec 是 { name, type, description?, children? }。如果同時送了 fieldstemplateId,會以 fields 為優先。

description 就是引導模型的地方 ── 你可以用中文的指示句寫明要抓什麼、怎麼抓。而 type: "array" 搭配 children 的組合,正是抽出重複明細列的方法。只要定義一個子 schema,無論有幾列都會回傳。

自訂 FieldSpec ── 巢狀擷取明細(送貨單轉 CSV)
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
34
35
36
37
38
39
40
import requests, base64, csv

with open("delivery.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": "送貨方(客戶)的公司名稱"},
            {"name": "delivery_no", "type": "string",
             "description": "送貨單號。保留原文"},
            {"name": "delivery_date", "type": "string",
             "description": "送貨日期。日本年號/西元維持原文"},
            {"name": "total", "type": "string",
             "description": "合計金額。保留千分位逗號"},
            {"name": "items", "type": "array",
             "description": "明細每一列對應一個元素",
             "children": [
                 {"name": "name", "type": "string", "description": "品名"},
                 {"name": "qty", "type": "number", "description": "數量"},
                 {"name": "unit_price", "type": "number", "description": "單價"},
             ]},
        ],
    },
    timeout=60,
)

data = resp.json()["data"]

# 把明細寫進 CSV。為了 Excel 與 CJK,用 UTF-8 BOM 輸出
with open("delivery.csv", "w", encoding="utf-8-sig", newline="") as out:
    w = csv.writer(out)
    w.writerow(["品名", "數量", "單價"])
    for row in data.get("items", []):
        w.writerow([row["name"], row["qty"], row["unit_price"]])
Why it matters

值會原文(verbatim)保留。 合計 7,855 會以字串 "7,855" 回傳,千分位逗號、小數點、全形字元也都原封不動。只有在你用 description 明確要求時才會做正規化。UI 上看到的 ¥ 是裝飾,並不是值的一部分。對付 CSV 亂碼的訣竅是:要用 Excel 開的 CSV,請以 UTF-8 BOMutf-8-sig)輸出 ── 上面的程式碼就是這麼做的。另外,防止明細「擠成一格」的關鍵就是 type: "array" + children,靠它把 1 筆明細展開成 1 列。

點一下文字,跳到原始位置

累積進表單之後,點一下值,原始影像的對應位置就會亮起。這是批次抽查時最快的方法 ── 不必把整份文件看一遍,視線會直接飛到對應位置。也可以採取只優先確認 match_ratio 偏低的欄位這種運維方式。

Demo橫跨已擷取的發票進行搜尋,一口氣跳到命中的儲存格 ── 以及它<b>在原始影像中的對應位置</b>。
橫跨已擷取的發票進行搜尋,一口氣跳到命中的儲存格 ── 以及它在原始影像中的對應位置

非同步大量處理 ── 批次上傳、Job、Webhook

POST /ocr/fields 是同步的,最適合放在 request/response 迴圈裡的單張處理。如果要整批處理一整個發票、送貨單的資料夾,就對表單用 POST /upload(重複 multipart 的 files)丟進去。預設會立刻回傳 job 陣列。

{ "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 之前,請務必驗證簽章。

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

為了讓正式環境的 pipeline 能安全地重試,有幾個標頭可以用。

標頭作用
Idempotency-Key/upload/create,相同金鑰的重送會重播 24 小時內的快取回應(X-Idempotent-Replay: true)── 不會重複計費,可安心重試。
X-Request-Id所有回應都會帶(req_xxx)。記進 log 供客服使用。

速率限制為每把金鑰 60 次/分每個 uid 600 次/分(固定 60 秒視窗)。超過時會回傳 429error.code: "rate_limited"。要等待的秒數放在 JSON body 的 details.retryAfterSec ── 不是 Retry-After HTTP 標頭。請依 body 裡的值來做退避(backoff)。

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

從擷取,到可查詢的表單

把發票擷取進表單之後,要回頭讀取並不需要重跑 OCR。GET /view 會對已累積的列執行伺服器端查詢 ── wheresortselectlimitoffset。不重跑 OCR,也不計費。座標預設會一併回傳,只有想讓回應輕一點時才加上 boxes=0。例如用 where=total>=40000 只要高額的發票,用 sort=-invoice_date 依新到舊排序。從這裡再輸出成 CSV(因為是 UTF-8 BOM,用 Excel 與 CJK 也能漂亮地打開),就能拿去匯進會計軟體 ── 詳情請參閱把掃描文件轉成 CSV把收據轉成 CSV。所有端點的規格則統整在 API 文件裡。

Note

PDF 要先把頁面轉成影像再送。 OCR 引擎直接解析的是點陣影像(JPEG、PNG、GIF、BMP、TIFF、WebP)。如果你直接呼叫 API,請先把 PDF 的每一頁算繪成 PNG 等格式再送(若是拖進 Web 應用程式,頁面影像化會由應用程式自動完成,所以可以直接丟 PDF)。和 freee、マネーフォワード(Money Forward)、弥生(彌生)、kintone 的串接並非官方 API 整合,而是以匯入輸出的 CSV 為前提。此外,是否符合發票(インボイス)制度或電子帳簿保存法等要求,請依各公司的運維與需求自行確認(本服務並不保證滿足法規要求)。

收費

POST /ocr/fields一次呼叫 ¥10POST /upload 為 ¥10 × N 張。失敗不計費 ── OCR 若沒有回傳結果就會退款,502 引擎錯誤或 ocr.failed 事件都會自動退款。唯讀端點(GET /space/view/amount/health)免費。免費額度免信用卡、每月 100 張,Pro 為 $39/月,Business 請洽詢(報價)。

用 API 擷取發票、送貨單的步驟

  1. 準備 API 金鑰
    登入後發行以 spocr_ 開頭的 API 金鑰,並在每一次請求加上 Authorization: Bearer spocr_...。基礎 URL 為 https://api.space-ocr.com。
  2. 準備影像(PDF 要把頁面影像化)
    把發票、送貨單準備成 JPEG/PNG 等點陣影像。直接呼叫 API 時,PDF 要先把每一頁算繪成 PNG 再送(拖進 Web 應用程式則由應用程式自動影像化)。影像用 URL 或純 base64 傳入,並以 imageType 指定 url / base64。
  3. 呼叫 POST /ocr/fields
    發票指定 templateId: "invoice",送貨單指定 templateId: "delivery"。範本不夠的欄位用 fields[](FieldSpec {name,type,description,children})定義,明細用 type:"array" + children 一列一列展開。
  4. 驗證回應
    確認回傳的每個值的 bbox、vertices、match_ratio、bbox_source。對 match_ratio 低於 0.85(low_confidence)的欄位,拿去和原稿比對確認。
  5. 轉成 CSV 匯進會計軟體
    把擷取結果寫成帶 UTF-8 BOM 的 CSV(明細展開成陣列列),交給 freee、Money Forward(マネーフォワード)、彌生(弥生)等的 CSV 匯入。累積之後可用 GET /view 在不重跑 OCR、不計費的情況下查詢。
發票、送貨單 OCR API 支援日文嗎?
支援。語言完全自動,不需要指定提示。日文、英文、中文、韓文都用同一個引擎處理,並會正規化全形/半形、各種連字號變體、括號、CJK 的空白、直書的漢字,以及多種文字系統的混用。日本年號的日期與品名等,只要不在 description 裡明確指定,都會原文(verbatim)回傳。
PDF 的發票、送貨單有支援嗎?
有支援。不過 OCR 引擎直接解析的是點陣影像(JPEG、PNG、GIF、BMP、TIFF、WebP)。如果你直接呼叫 API,請先把 PDF 的每一頁算繪成 PNG 等格式再送。若是把 PDF 拖進 Web 應用程式,頁面影像化會由應用程式自動完成,所以可以直接丟 PDF 進去做 OCR。
擷取出的資料能匯進 freee、Money Forward(マネーフォワード)或彌生(弥生)嗎?
可以透過 CSV 匯入。表單可以匯出成 CSV(為了 Excel 與 CJK 帶有 UTF-8 BOM),明細會展開成陣列列,所以能交給各會計軟體的 CSV 匯入功能。這些並非官方 API 整合,而是以匯入輸出的 CSV 為前提的運維方式。
擷取的準確度如何保證?結果可以信任嗎?
每一個值都會附上它從原始影像哪裡讀出來的 bbox(0〜1000 正規化座標)與 vertices,以及 match_ratio。match_ratio 是該值的字元中實際在頁面上找到的比例,0.85 以上為確信符合,1.0 為所有字元都符合。座標不是 AI 生出來的,而是把擷取出的文字拿去和 Vision OCR 的真實符號逐字比對導出。LLM 回傳的 word token 提示可能夾帶雜訊,所以會先用欄、列的一致性驗證後才採用。因此你可以採取只優先目視確認低分欄位這種稽核運維方式。
個資與資料的處理,以及失敗時的計費怎麼算?
失敗不計費。OCR 若沒有回傳結果就會退款,502 引擎錯誤或 ocr.failed 事件都會自動退款。Webhook 每個 space 一個 URL,所有事件都會用 X-Spaceocr-Signature 標頭做 HMAC-SHA256 簽章,所以接收端可以先驗證簽章再處理。也可以用冪等金鑰(Idempotency-Key)防止重試時的重複計費。

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

免費額度 ── 每月 100 張,免信用卡。每一個值,都附帶它是從原始影像哪裡讀出來的座標一併回傳。

相關文章