space ocr
ガイド記事料金ドキュメント

請求書からデータを抽出するAPI

space-ocrの請求書データ抽出APIの開発者向けガイド。curlとPythonでのPOST /ocr/fields、請求書テンプレート、カスタムフィールド、検証済みバウンディングボックスまで解説します。

請求書から構造化データ(取引先、請求書番号、日付、明細の金額、税額)を取り出す処理は、ドキュメント自動化のなかでもっともよくある仕事のひとつであり、同時に手作業で組むのがもっとも面倒な仕事のひとつでもあります。OCRテキストに正規表現をかける方法は、取引先がレイアウトを変えた瞬間に壊れます。テンプレートマッチング系のツールは、仕入先ごとに枠を引かされます。本当に欲しいのは、どんなレイアウトでも読み取り、きれいに型付けされたフィールドを返し、しかも――ここが肝心ですが――各値がページ上のどこから来たのかを教えてくれて結果を信頼できる、そんな請求書からデータを抽出するAPIでしょう。

この最後の部分こそがすべてです。total: 2,045 を根拠なしで返してくるだけの抽出エンドポイントは、買掛金(AP)のパイプラインにおいてはむしろリスクになります。本ガイドでは、space-ocrの POST /ocr/fields エンドポイントを解説します。1枚の請求書画像を受け取り、組み込みの invoice テンプレート(または独自のフィールドスキーマ)を適用し、すべての値を検証済みのバウンディングボックス付きで返す、単一の同期呼び出しです。

コードを1行も書く前に、まず出力を確認する

以下は実際に解析したレシートです。任意のフィールドにカーソルを合わせると、画像上のボックスが光ります――そのボックスは、まさにその値が読み取られた場所であり、各フィールドはそれぞれ固有のマッチ率を持っています。請求書もまったく同じ挙動です。抽出したすべてのフィールドが、元になったピクセルの上に戻ってきます。

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.

認証とベース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として渡し(imageTypehttp(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 です。従来のスネークケースの別名(image_typetemplate_idauto_fields)も依然として動作しますが、非推奨(deprecated) です。新しいコードではキャメルケースの名前を使ってください。

レスポンスの構造

呼び出しが成功すると { status: "success", data: { ... } } が返ります。抽出された各値はそれぞれ固有の根拠を持ち、field_bboxes マップがフィールドごとの座標を示します。

  • bbox0〜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(結合セルから伝播)。
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

座標はモデルの言い分を鵜呑みにして決めていません。 言語モデルは各値のテキストと――どの単語トークンを使ったかのヒントを返しますが、ボックスそのものは決して返しません。エンジンはそのテキストを、ビジョンOCRがページ上で実際に検出したシンボルに対して文字単位で照合します。match_ratio はそのうちどれだけが見つかったかを表し、ボックスはそれらの文字が由来する実際のピクセルの上に置かれます。モデルのトークンヒントはノイズを含むことがあるため(繰り返し行の間で取り違えることがあります)、列・行の一貫性チェックでヒントを盲信せずに検証します。つまり、ある値の座標は請求書に照らし合わせて確認され、どれだけよくマッチしたかを示すスコアが付くわけです。詳しい仕組みはなぜバウンディングボックスがOCRを監査可能にするのかをご覧ください。

テンプレートでは足りないときのカスタムフィールド

実際の請求書には、汎用テンプレートが名前を付けてくれないフィールドがあります――発注(PO)参照番号、支払条件コード、プロジェクトタグなどです。そうした場合は、テンプレートの代わりに(または併用して)FieldSpecオブジェクトの fields 配列を渡します。各FieldSpecは { name, type, description?, children? } です。fieldstemplateId の両方を送った場合は fields が優先されます。

モデルを誘導するのが description です。これは、何をどう取得するかを平易な日本語(自然言語)で記述した指示文です。そして type: "array"children の組み合わせが、繰り返し現れる明細行を取り出す方法です――1つの子スキーマで、多数の行を扱います。(この点は請求書からの明細行抽出で詳しく掘り下げます。)

明細行をネストしたカスタム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

値はそのまま(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.receiveditem.createdocr.completeddata.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ヘッダーは存在しないので、ボディの値を使ってバックオフしてください。

429レスポンスのボディ
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 を、課金なし・再抽出なしで使えます。バウンディングボックスはデフォルトで一緒に返ってきます。ペイロードを軽くしたい場合は boxes=0 を追加してください。そこからCSVへエクスポートできます(UTF-8 BOM付きなので、ExcelでもCJKテキストでも文字化けせずに開けます)――詳しくはスキャン文書をCSVに変換するをご覧ください。

請求書をドロップすると、型付けされたフィールドが埋まります――APIが返すのと同じデータを、UI上で確認できます。

料金

POST /ocr/fields1回の呼び出しにつき¥10POST /upload は ¥10 × N枚(画像枚数)です。失敗時の課金はありません――OCRが結果を返さなかった場合は返金され、502 のエンジンエラーや ocr.failed イベントも自動的に返金されます。読み取り専用のエンドポイント(GET /space/view/amount/health)は無料です。無料プランはクレジットカード不要で月100スキャン、Proは月額¥7,980、Businessは営業へのお問い合わせとなります。

抽出済みの請求書を横断検索し、該当セル――そしてその元データのボックスへ――直接ジャンプできます。

APIで請求書からデータを抽出する手順

  1. APIキーを取得する
    サインアップしてspocr_で始まるAPIキーを作成します。すべてのリクエストはAuthorization: Bearerヘッダーでこのキーを使って認証します。
  2. 請求書画像を用意する
    請求書をラスター画像(JPEG、PNG、GIF、BMP、TIFF、WebP)として用意します。URLとして、または純粋なbase64として渡し、imageType を 'url' または 'base64' に設定します。
  3. POST /ocr/fields を呼び出す
    https://api.space-ocr.com/ocr/fields にPOSTします。組み込みテンプレートを使うなら templateId: 'invoice' を、取得項目を自分で定義するなら fields[] 配列を指定します。
  4. フィールドと根拠を読み取る
    data 内の型付けされたフィールドと、field_bboxes 内のフィールドごとのバウンディングボックスを読み取ります。各値の match_ratio を確認し、信頼できるマッチ(0.85以上)か、レビューが必要かを判断します。
  5. 規模に応じて拡張する
    多数の請求書を扱う場合は POST /upload でシートにバッチ投入し、GET /jobs/{jobId} のポーリングまたはWebhookで結果を受け取ります。その後は GET /view でクエリし、CSVへエクスポートします。
請求書からデータを抽出するのに最適なAPIは?
優れた請求書抽出APIとは、(事前に枠を引いたテンプレートだけでなく)どんなレイアウトでも読み取り、きれいに型付けされたフィールドを返し、各値の根拠を示してくれるものです。space-ocrのPOST /ocr/fieldsは、これを1回の同期呼び出しで実現します。templateId 'invoice' または独自のfields[]スキーマを指定して請求書画像を渡すと、すべての値がバウンディングボックス・方向付き頂点・マッチ率とともに返ってくるので、元の文書と照合して検証できます。
ヘッダー項目だけでなく、請求書の明細行も抽出できますか?
はい。type 'array' のFieldSpecと、1行を記述するchildrenスキーマ(例:description、qty、unit_price)を使います。APIは請求書の明細行ごとに1行を返し、それぞれが固有のバウンディングボックスを持ちます。取引先、請求書番号、日付、合計といったヘッダー項目も、同じ呼び出しのなかで抽出されます。
請求書抽出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 は1回の呼び出しにつき¥10、/upload は1画像あたり¥10です。失敗時の課金はありません――OCRが結果を返さなかった場合は自動的に返金されます。無料プランはクレジットカード不要で月100スキャン、Proは月額¥7,980、Businessは営業へのお問い合わせとなります。

最初の請求書を、たった1回の呼び出しで抽出

無料プラン――月100スキャン、クレジットカード不要。すべてのフィールドが、ページ上の位置情報付きで返ってきます。

関連