請求書OCR API/納品書OCRでCSVへ ── 請求書データ抽出APIの実装ガイド
請求書・納品書を手入力やExcel文字化けから解放する開発者向けガイド。POST /ocr/fields に画像を投げると、取引先・日付・合計・明細が構造化データで返り、各値に元画像の座標(bbox)とmatch_ratioが付く。curl・Pythonコード、CSV出力、Webhook、料金まで。
請求書や納品書を、いまだに手で Excel に打ち込んでいませんか。日付、取引先、税抜・税込、そして明細の一行一行 ── 月末になると山積みの紙とにらめっこして、数字を1セルずつ転記する。途中で一桁ずれて、合計が合わなくて、また最初から照合する。あの時間を、なくしたい。
スキャンした PDF をコピーしようとしたら文字が選択できない。OCR にかけたら、明細がぜんぶ1つのセルに潰れて改行も列も失われる。CSV を Excel で開いたら文字化けして品名が読めない。会計ソフトに取り込みたいだけなのに、その手前で毎回つまずく ── これは、書類を扱う現場の「あるある」です。
この記事は、その作業を API 1本 に置き換えるための開発者向けガイドです。POST /ocr/fields に請求書・納品書の画像を投げると、取引先・日付・合計といった項目と、明細の各行が型のついた構造化データで返ってきます。しかも返ってくるすべての値に、元画像のどこから読み取ったかの座標(bbox)が付くので、抽出結果を鵜呑みにせず原本と突き合わせて検証できます。curl と Python のコード付きで、最短経路から本番運用までひと通り見ていきます。
まずは触ってみる ── アップロード不要、10秒で体験
コードを書く前に、実際の出力を見てください。下は本物のレシートを解析した結果です。項目にカーソルを合わせると、その値が画像のどこから読み取られたかがハイライトされ、各項目の**マッチ率(match_ratio)**も確認できます。請求書・納品書もまったく同じ挙動です ── 抽出した1つ1つの値が、読み取り元のピクセルに紐づきます。

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.
「元画像 → 抽出シート → 該当箇所の強調 → CSV出力」の流れ
space ocr の使い方は、突きつめると4ステップです。(1) 領収書・請求書・納品書の画像を投げる → (2) 列の決まったシートに1枚=1行で抽出される → (3) 値をクリックすると元画像の該当箇所が点灯して原本照合できる → (4) そのまま CSV で書き出して会計ソフトに取り込む。まずは1枚を投げて項目が埋まる様子から。
認証とベース URL
公開 API のベースは https://api.space-ocr.com の1つだけ ── /v1 のようなパスバージョニングはありません。各リクエストは、spocr_ で始まるキーを使った HTTP Bearer トークンで認証します。
Authorization: Bearer spocr_xxxxxxxxxxxxxxxx
ヘッダが欠落・不正なら 401、未登録のキーなら 403 が返ります。すべてのレスポンスに X-Request-Id(形式 req_xxx)ヘッダが付くので、サポート問い合わせ用にログへ残しておくと安心です。クライアントを自動生成したい場合は、GET /openapi.json に OpenAPI 3.1 の仕様が公開されています。
最短経路 ── ビルトインの請求書・納品書テンプレート
一番速いのは、templateId に組み込みテンプレートを指定する方法です。請求書なら templateId: "invoice"、納品書なら templateId: "delivery"。「請求書とはどんな項目を持つか」をエンジン側が知っているので、こちらで項目を1つずつ定義する必要はありません。画像は 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/docs/delivery-0831.jpg",
"imageType": "url",
"templateId": "delivery"
}'パラメータはキャメルケースが正式名です。 imageType / templateId / autoFields を使ってください。旧来のスネークケース(image_type / template_id / auto_fields)も動きますが非推奨です。新しいコードではキャメルケースを優先してください。なお請求書なら templateId: "invoice"、納品書なら templateId: "delivery" です。
レスポンスの形 ── 値ごとに「出どころ」が付く
成功すると { status: "success", data: { ... } } が返ります。抽出された各値はそれぞれ出どころ情報を持ち、field_bboxes マップに項目ごとの座標がまとまっています。
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 が返したワードトークンのヒントで Vision の語トークンを引いた経路)、low_confidence(文字マッチが 0.85 未満 ── 要確認)、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"
}
}
}
}座標は AI の言い分を鵜呑みにしていません。 言語モデルが返すのは各値のテキストと、使ったワードトークンのヒント(wid)だけで、座標そのものは返しません。エンジンはまずそのテキストを、Vision OCR がページ上で実際に検出したシンボルと1文字ずつ照合します ── だから矩形は、その文字が本当に見つかったピクセルに着地し、各値には「どれだけ一致したか」を示す match_ratio が付きます。LLM がワードトークンのヒントを返したときは、そのトークンの座標で一部の項目を上書きすることもありますが、このヒントはノイズを含むことがあり(繰り返し行で隣の行と取り違える、いわゆる stochastic な揺れ)、盲信せず列・行の整合性で検証・補正してから採用します。要点は「AI が間違えない」ではなく、どの値もページ側に照合し直され、どれだけ一致したかのスコアが残ること。詳しくはバウンディングボックスで OCR を監査可能にする仕組みを参照してください。
テンプレートで足りないとき ── カスタム項目
実務の請求書には、汎用テンプレートが名前を持たない項目があります ── 発注番号、支払条件コード、プロジェクトタグなど。そんなときは templateId の代わりに(または併用して)、FieldSpec の配列 fields を渡します。各 FieldSpec は { name, type, description?, children? }。fields と templateId を両方送った場合は fields が優先されます。
description がモデルを誘導する場所です ── 何をどう拾うかを日本語の指示文で書けます。そして type: "array" と children の組み合わせが、繰り返す明細行を引き出す方法です。子スキーマを1つ定義すれば、行が何本でも返ります。
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": "明細1行につき1要素",
"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"]])値は原文のまま(verbatim)保持されます。 合計 7,855 は文字列 "7,855" として返り、カンマ区切り・小数点・全角文字もそのまま。description で明示的に頼んだときだけ正規化します。UI に見える ¥ は装飾で、値の一部ではありません。CSV の文字化け対策として、Excel で開く CSV は UTF-8 BOM(utf-8-sig)で書き出すのがコツ ── 上のコードもそうしています。なお、明細が「1セルに潰れる」のを防ぐ鍵が type: "array" + children で、これにより1明細=1行に展開されます。
文字をクリックして、元の箇所へジャンプ
シートに蓄積した後は、値をクリックすると元画像の該当箇所が点灯します。これがバッチのスポットチェックで一番速い方法です ── 書類全体を見渡す代わりに、視線がそのまま該当箇所へ飛びます。match_ratio が低い項目だけを優先して確認する、といった運用もできます。
非同期で大量に ── バッチアップロード・ジョブ・Webhook
POST /ocr/fields は同期で、リクエスト/レスポンスのループに置く1枚処理に最適です。請求書・納品書のフォルダをまとめて処理するなら、シートに対して POST /upload(multipart の files を繰り返し)で投げます。既定では即座にジョブ配列が返ります。
{ "path": "...", "jobs": [ { "uniqueKey": "...", "jobId": "...", "status": "pending" } ] }
結果の受け取り方は2通り。GET /jobs/{jobId} をポーリングするか、Webhook を登録します。Webhook はスペースごとに1 URL、すべてのイベントが X-Spaceocr-Signature ヘッダで HMAC-SHA256 署名されます。注目すべきイベントは upload.received・item.created・ocr.completed(data.result に抽出結果)・ocr.failed。ペイロードを信頼する前に、必ず署名を検証してください。
冪等性・リクエスト追跡・レート制限
本番パイプラインを安全にリトライ可能にするための、いくつかのヘッダがあります。
| ヘッダ | 役割 |
|---|---|
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 ヘッダではありません。バックオフはボディの値を見て行ってください。
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"requestId": "req_8fa2c1"
},
"details": { "retryAfterSec": 12 }
}抽出から、クエリできるシートへ
請求書をシートに抽出したら、読み戻すために OCR を再実行する必要はありません。GET /view が蓄積済みの行に対してサーバ側クエリ ── where・sort・select・limit・offset ── を実行します。OCR 再実行も課金もなし。座標は既定で一緒に返り、軽くしたいときだけ boxes=0 を付けます。例えば where=total>=40000 で高額の請求書だけ、sort=-invoice_date で新しい順に。そこから CSV で書き出せば(UTF-8 BOM なので Excel と CJK もきれいに開く)、会計ソフトへの取り込みに使えます ── 詳しくはスキャン書類を CSV にするとレシートを CSV に変換するを参照してください。なお全エンドポイントの仕様は API ドキュメントにまとまっています。
PDF はページを画像に変換してから送ります。 OCR エンジンが直接解析するのはラスター画像(JPEG・PNG・GIF・BMP・TIFF・WebP)です。API を直接叩く場合は、PDF の各ページを PNG などにレンダリングしてから送ってください(Web アプリにドロップする場合は、ページの画像化をアプリが自動で行うので、そのまま PDF を投げられます)。freee・マネーフォワード・弥生・kintone への連携は公式 API 連携ではなく、書き出した CSV の取り込みで行う前提です。また、インボイス制度や電子帳簿保存法への対応可否は、各社の運用・要件にあわせてご確認ください(本サービスが法要件の充足を保証するものではありません)。
料金
POST /ocr/fields は 1コール ¥10、POST /upload は ¥10 × N 枚です。失敗時は無課金 ── OCR が結果を返さなければ返金され、502 エンジンエラーや ocr.failed イベントは自動で返金されます。読み取り専用エンドポイント(GET /space・/view・/amount・/health)は無料。無料枠はクレカ不要で 月 100 枚、Pro は $39/月、Business は問い合わせ(お見積り)です。
請求書・納品書を API で抽出する手順
- API キーを用意するログインして spocr_ で始まる API キーを発行し、各リクエストに Authorization: Bearer spocr_... を付けます。ベース URL は https://api.space-ocr.com です。
- 画像を用意する(PDF はページを画像化)請求書・納品書を JPEG/PNG などのラスター画像として用意します。API を直接叩く場合、PDF は各ページを PNG にレンダリングしてから送ります(Web アプリにドロップする場合はアプリが自動で画像化します)。画像は URL または純粋な base64 で渡し、imageType を url / base64 で指定します。
- POST /ocr/fields を叩く請求書なら templateId: "invoice"、納品書なら templateId: "delivery" を指定します。テンプレートで足りない項目は fields[](FieldSpec {name,type,description,children})で定義し、明細は type:"array" + children で1行ずつ展開します。
- レスポンスを検証する返ってきた各値の bbox・vertices・match_ratio・bbox_source を確認します。match_ratio が 0.85 未満(low_confidence)の項目は原本と突き合わせて確認します。
- CSV にして会計ソフトへ抽出結果を UTF-8 BOM 付きの CSV に書き出し(明細は配列行として展開)、freee・マネーフォワード・弥生などの CSV 取り込みに渡します。蓄積後は GET /view で再 OCR・無課金のままクエリできます。