請求書からデータを抽出するAPI
space-ocrの請求書データ抽出APIの開発者向けガイド。curlとPythonでのPOST /ocr/fields、請求書テンプレート、カスタムフィールド、検証済みバウンディングボックスまで解説します。
請求書から構造化データ(取引先、請求書番号、日付、明細の金額、税額)を取り出す処理は、ドキュメント自動化のなかでもっともよくある仕事のひとつであり、同時に手作業で組むのがもっとも面倒な仕事のひとつでもあります。OCRテキストに正規表現をかける方法は、取引先がレイアウトを変えた瞬間に壊れます。テンプレートマッチング系のツールは、仕入先ごとに枠を引かされます。本当に欲しいのは、どんなレイアウトでも読み取り、きれいに型付けされたフィールドを返し、しかも――ここが肝心ですが――各値がページ上のどこから来たのかを教えてくれて結果を信頼できる、そんな請求書からデータを抽出するAPIでしょう。
この最後の部分こそがすべてです。total: 2,045 を根拠なしで返してくるだけの抽出エンドポイントは、買掛金(AP)のパイプラインにおいてはむしろリスクになります。本ガイドでは、space-ocrの POST /ocr/fields エンドポイントを解説します。1枚の請求書画像を受け取り、組み込みの invoice テンプレート(または独自のフィールドスキーマ)を適用し、すべての値を検証済みのバウンディングボックス付きで返す、単一の同期呼び出しです。
コードを1行も書く前に、まず出力を確認する
以下は実際に解析したレシートです。任意のフィールドにカーソルを合わせると、画像上のボックスが光ります――そのボックスは、まさにその値が読み取られた場所であり、各フィールドはそれぞれ固有のマッチ率を持っています。請求書もまったく同じ挙動です。抽出したすべてのフィールドが、元になったピクセルの上に戻ってきます。

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.
認証とベースURL
公開APIは単一のベース https://api.space-ocr.com で提供され、/v1 のようなパスバージョニングはありません。すべてのリクエストは、spocr_ で始まるキーを使ったHTTP Bearerトークンで認証します。
Authorization: Bearer spocr_xxxxxxxxxxxxxxxx
ヘッダーが欠落していたり不正な形式だと 401 が返り、認識できないキーは 403 を返します。すべてのレスポンスには X-Request-Id ヘッダー(形式 req_xxx)が付与されるので、サポート時のトレース用にログへ残しておきましょう。クライアントを自動生成したい場合は、GET /openapi.json でOpenAPI 3.1として完全な仕様が公開されています。
最もシンプルな呼び出し:組み込みの請求書テンプレート
最短ルートは 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 です。従来のスネークケースの別名(image_type、template_id、auto_fields)も依然として動作しますが、非推奨(deprecated) です。新しいコードではキャメルケースの名前を使ってください。
レスポンスの構造
呼び出しが成功すると { status: "success", data: { ... } } が返ります。抽出された各値はそれぞれ固有の根拠を持ち、field_bboxes マップがフィールドごとの座標を示します。
bbox— 0〜1000に正規化されたグリッド上の軸並行な矩形{ xmin, ymin, xmax, ymax }(0,0 = 左上、1000,1000 = 右下)で、画像のピクセルサイズには依存しません。ピクセルへの変換はpixel_x = bbox_x / 1000 × image_widthです。vertices— 順序付けされた4点{x, y}(左上 → 右上 → 右下 → 左下)で、ドキュメントの傾きに沿った方向付きボックスを形成します。そのため、傾いたスマホ撮影の請求書でもきれいに囲めます。match_ratio— その値の文字のうち、実際にページ上で見つかった割合(0〜1)です。0.85以上で確信を持ってマッチしたとみなし、1.0はすべての文字が見つかったことを意味します。bbox_source— 座標がどう導出されたかを示します。vision_symbol_match(通常の文字マッチ経路で、実際のmatch_ratioを伴う)、token_id/token_id_hybrid(単語トークンのヒントを使用)、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"
}
}
}
}座標はモデルの言い分を鵜呑みにして決めていません。 言語モデルは各値のテキストと――どの単語トークンを使ったかのヒントを返しますが、ボックスそのものは決して返しません。エンジンはそのテキストを、ビジョンOCRがページ上で実際に検出したシンボルに対して文字単位で照合します。match_ratio はそのうちどれだけが見つかったかを表し、ボックスはそれらの文字が由来する実際のピクセルの上に置かれます。モデルのトークンヒントはノイズを含むことがあるため(繰り返し行の間で取り違えることがあります)、列・行の一貫性チェックでヒントを盲信せずに検証します。つまり、ある値の座標は請求書に照らし合わせて確認され、どれだけよくマッチしたかを示すスコアが付くわけです。詳しい仕組みはなぜバウンディングボックスがOCRを監査可能にするのかをご覧ください。
テンプレートでは足りないときのカスタムフィールド
実際の請求書には、汎用テンプレートが名前を付けてくれないフィールドがあります――発注(PO)参照番号、支払条件コード、プロジェクトタグなどです。そうした場合は、テンプレートの代わりに(または併用して)FieldSpecオブジェクトの fields 配列を渡します。各FieldSpecは { name, type, description?, children? } です。fields と templateId の両方を送った場合は fields が優先されます。
モデルを誘導するのが description です。これは、何をどう取得するかを平易な日本語(自然言語)で記述した指示文です。そして type: "array" と children の組み合わせが、繰り返し現れる明細行を取り出す方法です――1つの子スキーマで、多数の行を扱います。(この点は請求書からの明細行抽出で詳しく掘り下げます。)
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"])値はそのまま(verbatim)保持されます。 合計が 7,855 なら、文字列 "7,855" としてそのまま返ってきます――カンマ区切り、小数点、全角文字もそのままです。エンジンが正規化を行うのは、フィールドの description で明示的に指示した場合だけです。WebのUIで見える ¥ は装飾であって、値の一部ではありません。エンジンが受け付けるのはラスター画像のみ――JPEG、PNG、GIF、BMP、TIFF、WebP――で、自動的にRGBへ変換されます。
非同期処理へ:バッチアップロード、ジョブ、Webhook
POST /ocr/fields は同期処理で、リクエスト/レスポンスのループで1枚の請求書を扱うのに最適です。請求書のフォルダーをまとめて処理したい場合は、POST /upload(multipartの files を繰り返し指定)でシートに投入します。デフォルトでは即座にjobs配列を返します。
{ "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秒ウィンドウで適用されます。超過すると、error.code: "rate_limited" とともに 429 が返ります。待機時間は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 を、課金なし・再抽出なしで使えます。バウンディングボックスはデフォルトで一緒に返ってきます。ペイロードを軽くしたい場合は boxes=0 を追加してください。そこからCSVへエクスポートできます(UTF-8 BOM付きなので、ExcelでもCJKテキストでも文字化けせずに開けます)――詳しくはスキャン文書をCSVに変換するをご覧ください。
料金
POST /ocr/fields は1回の呼び出しにつき¥10、POST /upload は ¥10 × N枚(画像枚数)です。失敗時の課金はありません――OCRが結果を返さなかった場合は返金され、502 のエンジンエラーや ocr.failed イベントも自動的に返金されます。読み取り専用のエンドポイント(GET /space、/view、/amount、/health)は無料です。無料プランはクレジットカード不要で月100スキャン、Proは月額¥7,980、Businessは営業へのお問い合わせとなります。
APIで請求書からデータを抽出する手順
- APIキーを取得するサインアップしてspocr_で始まるAPIキーを作成します。すべてのリクエストはAuthorization: Bearerヘッダーでこのキーを使って認証します。
- 請求書画像を用意する請求書をラスター画像(JPEG、PNG、GIF、BMP、TIFF、WebP)として用意します。URLとして、または純粋なbase64として渡し、imageType を 'url' または 'base64' に設定します。
- POST /ocr/fields を呼び出すhttps://api.space-ocr.com/ocr/fields にPOSTします。組み込みテンプレートを使うなら templateId: 'invoice' を、取得項目を自分で定義するなら fields[] 配列を指定します。
- フィールドと根拠を読み取るdata 内の型付けされたフィールドと、field_bboxes 内のフィールドごとのバウンディングボックスを読み取ります。各値の match_ratio を確認し、信頼できるマッチ(0.85以上)か、レビューが必要かを判断します。
- 規模に応じて拡張する多数の請求書を扱う場合は POST /upload でシートにバッチ投入し、GET /jobs/{jobId} のポーリングまたはWebhookで結果を受け取ります。その後は GET /view でクエリし、CSVへエクスポートします。