space ocr

APIドキュメント

https://api.space-ocr.com
#

概要

space ocr API は、文書画像を名前付きフィールド・Markdown・プレーンテキストのいずれかとして読み取り、値ごとに読み取り位置の座標と検証フラグを返します。その結果を保存・閲覧するのが MySpace です。spocr_* で始まる API キー 1 本で、すべての REST エンドポイントとイベント webhook を利用できます。

RESTful、JSON、CORS 対応。可変長のバッチや非同期処理は Jobs / Webhooks セクションで案内します。

#

5分クイックスタート

キー発行 → curl をコピペ → JSON。初回呼び出しまで 5 分あれば十分です。無料枠は毎月 100 件。

① Developer → API Keys でキーを発行します(カード不要)。

② 右の curl をそのまま実行 — サンプル画像は実際にホスティングされているので、キーを差し替えるだけで動きます。

③ レスポンスの data.values の値と、data.cells の box / quad / verified、data.review.flagged(要確認リスト)を確認してください。フィールドに型や制約(number / date / pattern / enum / near)を宣言すると、解釈済みの値が data.normalized に、違反が review に載ります — 宣言できる一覧は POST /ocr/fields の fields を参照してください。

④ コードを書く前に試したいときは、MySpace コンソールがそのままプレイグラウンドになります。シートにファイルを置けば API と同じ結果が並び、セルをクリックすると元画像の座標まで確認できます。API からアップロードした文書も同じシートに現れるので、自動処理と目視確認を同じ場所で扱えます。

リクエスト
1
2
3
4
5
6
7
8
curl -X POST https://api.space-ocr.com/ocr/fields \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://space-ocr.com/samples/two-receipts.jpg",
    "imageType": "url",
    "fields": [{ "name": "store_name" }, { "name": "total" }]
  }'
#

認証

すべてのリクエストは Authorization ヘッダーで Bearer <APIキー> を送ります。キーは Developer → API Keys から発行・失効できます。

キー形式は spocr_ で始まる文字列です。漏洩した場合は即時に失効してください。

リクエスト
1
2
curl https://api.space-ocr.com/amount \
  -H "Authorization: Bearer YOUR_API_KEY"
#

Base URL

本番 base URL は単一です。バージョニングはキーとイベントペイロードの apiVersion で管理します。
1
2
3
4
5
# Production
https://api.space-ocr.com

# OpenAPI spec
https://api.space-ocr.com/openapi.json
#

Rate limits

60 req/min/key、600 req/min/uid。超過時は HTTP 429 と Retry-After ヘッダーで待機秒数を返します。

応答には常に X-Request-Id (req_xxx) と X-RateLimit-Remaining(その分に残る呼び出し数)が含まれます。サポート連絡時には X-Request-Id を添えてください。

/ocr/fields・/create・/upload は Idempotency-Key ヘッダーをサポートします。同一キーの再送は 24h キャッシュされ、X-Idempotent-Replay: true で示されます。

#

画像サイズと応答時間

応答時間を決めるのは主に文書の密度と読み取らせる量(宣言したフィールド数)で、画像のピクセル数ではありません。観測分布は p50 7.2 秒 / p90 10.5 秒 — 本番全体の集計で、その大半は数項目の宣言です。数十項目を宣言した高密度の呼び出しはこの分布の上側に来ます(SLA ではありません)。

JSON ボディの上限は 28MB です。base64 はファイルの約 1.33 倍になるため、元画像で約 20MB 相当までを 1 リクエストで送れます。超えた場合は 413 を返し、details.limitBytes に許容サイズ、details.receivedBytes に受信サイズが入ります。

ただし 32MiB を超えるリクエストは、当社のコードに届く前に Google Cloud 側で遮断されます。このときの応答は JSON ではなく text/html(Google Frontend の 413 ページ)なので、応答を必ず JSON として解析する実装は例外で落ちます。上の 28MB を守っていればこの経路には到達しません。

大きな写真は、サーバー内部のエンコード結果によっては長辺 4000px に縮小してから読み取ります(縦横比は保持)。実際に読み取ったサイズは data.image の width / height に入るので、送った画像と異なることがあります。座標は 0–1000 の正規化値なので縮小されても意味は変わりません。クライアント側で縮小するなら長辺 4000px が目安です。

向きも同じです。EXIF の orientation は読み取る前にピクセルへ反映されるため、横向きに撮った写真は正立したページとして読まれ、値も座標もその向きで返ります。data.image の width と height が送信ファイルと入れ替わるのはこのときです(4000×3000 で送って 3000×4000)。枠を描く実装は、送信画像のサイズではなく data.image を基準にしてください。

上限を超える画像は imageType: "url" で URL を渡すか、/upload(非同期・ファイルあたり 20MB)+ /jobs ポーリングまたは webhook をご利用ください。同期呼び出しの処理が 180 秒を超えると ocr_engine_timeout になります — 原因は画素数より密度(小さな文字が詰まった多ページ書類)であることが多く、縮小ではなく 1 ページ 1 画像への分割か非同期経路をご検討ください。

#

結果の再現性

確定値として扱う数字は、応答を保存してそれを使ってください。同じ画像を読み直すことは、同じ答えの取得ではなく もう一度の読み取り です。

値の抽出はモデルを通ります。同じ画像でも、実行ごとに出力の構成が揺れることが実測されています(どこまで 1 つの値として返すか、明細の切り方など)。座標の文字照合と normalized のパースは決定論的(追加のモデル呼び出しなし)ですが、その入力になる抽出そのものはそうではありません。

会計・監査のように「あとで同じ数字を出せること」が要件になる用途では、応答 JSON をそのまま保管し、確認は保存した値に対して行ってください。読み直しは、内容が変わったときや、レビュー結果を破棄してよいときだけで十分です。

Idempotency-Key は 24 時間だけ同じ応答を返す再送の安全装置であって、保管の仕組みではありません(保管については データの取り扱い を参照)。

#

エラー

4xx / 5xx は共通エンベロープで返却されます。requestId はサポート連絡時の手がかりです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "error": {
    "code": "validation_failed",
    "message": "imageType is required",
    "requestId": "req_xxx"
  },
  "details": {
    /* optional, endpoint-specific context (e.g. /upload returns processable count) */
  }
}

// error.code: validation_failed | bad_request | invalid_image | invalid_api_key
//           | key_inactive | unauthorized | forbidden | not_found
//           | insufficient_balance | rate_limited | ocr_engine_error
//           | ocr_engine_timeout | storage_error | internal_error

HTTP ステータス

200
正常
400
リクエスト不正 / バリデーション失敗。画像 URL を取得できない・base64 が壊れている場合も同じ 400(code: invalid_image、課金されません)。再試行しても結果は変わりません
401
API キーが無効または欠落
402
残高不足。/upload は details.requested / processable / breakdown を含む
403
スコープ外のリソース(例: 他のキーが作成した job)
404
path 不存在 / api.space-ocr.com 以外でアクセス
413
ボディが 28MB 超 / ファイルが 20MB 超。details.limitBytes に許容サイズ、details.receivedBytes に受信サイズが入ります(Content-Length が無い場合は limitBytes のみ)。32MiB 超のリクエストはインフラ側で遮断されるため、その 413 だけは JSON ではなく HTML です
429
レート制限。Retry-After に秒数
500
内部エラー
502
OCR エンジン側エラー(自動返金済み)。message に実際の理由が入ります。画像入力そのものの問題は 400 invalid_image に分かれるので、502 は再試行する価値があります
504
同期 OCR が上限の 180 秒以内に完了しませんでした(課金されません)。原因は画素数より密度であることがほとんどなので、縮小ではなく 1 ページ 1 画像に分割するか、処理時間に余裕のある非同期 /upload をご利用ください
#
POST/ocr/fieldsBearer¥10(税込)

構造化 OCR

画像から名前付きフィールドを抽出します。fields で抽出スキーマを指定するか、autoFields で自動提案させます。

同期呼び出しなので、応答が返るまで接続を保つ必要があります。処理は 最長 180 秒、超えると 504 ocr_engine_timeout(課金されません)。実測では 1 枚あたり数秒〜数十秒に収まりますが、クライアント側のタイムアウトは余裕をもって設定してください。この上限に当たるのは小さな文字が詰まった多ページ書類がほとんどで、原因は画素数ではなく密度です — 縮小すると読めなくなるので、1 ページ 1 画像に分割するか、処理時間に余裕のある非同期 POST /upload をご利用ください。

ボディパラメータ

imagestringrequired

Base64 文字列または画像 URL。JSON ボディの上限は 28MB で、base64 はファイルの約 1.33 倍になるので、元画像で約 20MB 相当までです。超えると 413(details.limitBytes / receivedBytes)を返します。それより大きい場合は URL 指定か /upload(非同期・ファイルあたり 20MB)をご利用ください。

長辺 4000px を超える画像はサーバー側で自動縮小してから読みます — 座標も縮小後のページ(data.image)基準で返るので、上限に合わせて事前に画質を落として送る必要はありません。

imageType"base64" | "url"required
image の型を明示。旧名 image_type も後方互換で動作(非推奨)。
fieldsarray<FieldSpec>optional

抽出スキーマ配列。autoFields を使う場合は省略可。値はページに書かれている読みで返り、要約や言い換えはしません — だから座標に結び付けて検証できます。

ただしバイト単位の複製ではありません: values はモデルが読んだ文字列で、文字照合は全角・括弧・空白を畳んでから比べるため、(税抜)が (税抜) になるような書き直しは通ります。完全一致で 突合 する場合は cells[path].evidence.printed_text(その座標で OCR が読んだ文字)を使ってください。

namestringrequired
レスポンス JSON のキーになります。
type"string" | "array" | "object" | "number" | "integer" | "date"optional

既定は string。number / integer / date を宣言しても values は変わりません — 型がモデルに渡ることはありません(型を伝えると、その形の値を作ってしまいます)。宣言した型が生むのは normalized という第二の層で、同じ読みをその型に解釈した値が values と同じ形で並びます("¥13,220" → 13220、"令和8年8月16日" → "2026-08-16"、"3袋" → 3)。解釈は決定的で追加のモデル呼び出しはありません。解釈できなかった値は normalized で null になり、reason "type_mismatch" が付きます — 多くの場合それは誤読の合図です。

逆に、宣言しない方がよい項目もあります。数量欄の「一式」、支払期限の「翌月末払い」のように 値ではない書き方が正規に印字される 項目です。型を宣言すると、書類として正しいのに毎回 type_mismatch として要確認に上がります(error は conventional_token / relative_date と種類までは言いますが、リストには載ります)。常に数値・日付が入る項目にだけ型を宣言し、それ以外は string のままで受けて、そちらの業務ルールで解釈してください。

descriptionstringoptional
その値がどこにあるかのヒント(例: 「合計の右」)。
childrenarray<FieldSpec>optional
type が array / object のときの子フィールド。同じ FieldSpec が再帰的に入ります。明細行は行数を数えずに type: "array" + children で宣言するのが推奨で、返る行数はページ次第です。子の座標は行ごとに解決されるので、同じ列見出し(数量・金額)が全行で繰り返されていても取り違えません。cells のキーと review.flagged[].path は items[0].amount のような添字付きパスになります。
requiredbooleanoptional
true の項目が空で返るか、レスポンスから丸ごと欠けた場合は review.flagged に reason "missing" として記録されます — 返ってこなかった値には照合する相手が無いため、文字照合が原理的に見られない唯一のクラスです。モデルには渡らないので抽出の動きは変わりません(必須だと伝えると、印字されていない値を推測しないという指示と衝突します)。無くて当然の項目にまで付けると合図が埋もれるので、必ず印字される値だけに付けてください。
labelstring | string[]optional

値の横に印字されているラベル(例: "合計")。同じ値がページに複数回印字されているとき、座標をそのラベルの隣の出現にアンカーします。ラベルがページに正確に 1 回だけ印字されているときのみ働き、見つからなければ通常の検索に戻ったうえで、その事実が review.notes に issue: "label_unresolved" として載ります(値は返るので、この知らせが無いと宣言が効いていないことに気づけません)。「消費税(8%)」「10%対象 小計」のように複数の語にまたがる表記もそのまま書けます。required と同じくモデルには渡りません — 抽出テキストは変わらず、座標のアンカーだけが変わります。候補が複数あるときは配列で(例: ["発行日", "発行年月日"])。

効くのはトップレベルの string / number / integer / date フィールドだけです。array・object 本体と、その children に付けた label は読まれずに無視されます(ページ全体で 1 回きりのラベルは、繰り返す行のどれを指すのか原理的に言えないため)。明細表の「数量」「金額」のように列見出しが全行で共有される場合は label を付ける必要がありません — children の座標は行ごとに解決されます。行内で位置を伝えたいときは description(例: 「単価の右」)を使ってください。

nearstring | string[] | { terms, match }optional

値の そば に印字されているはずの語彙です(例: 宛先の会社名なら ["御中", "様"]、発行元なら ["登録番号", "〒", "TEL"])。抽出後、宣言した語をページ上で探し、値がそのどれの近傍にも無ければ reason "near_mismatch" が立ちます。

これは「完璧に読めたのに 別の場所の値 を選んだ」クラスへの唯一の手立てです。帳票には会社名が 2 社印字されていて、反対側を選んでも文字照合は一致するので verified: true のまま通過します。enum も、どちらも正当なマスタ値なら区別できません。near は選択を正しくするのではなく、間違った選択を 見える ようにします。

判定は値の すべての出現 に対して行います(v85)。どの出現も語彙のそばに無ければ near_mismatch(どこにあっても違う値)、そばにある出現はあるのに座標が付いたのが別の複製なら near_ambiguous(どちらを指すか未定 — 値自体は正しいかもしれません)。同じ値が 2 か所に印字される帳票で、正しい値が座標の付き方だけで通ったり落ちたりしていたためです。判定の内訳は cells[path].evidence.near にそのまま入ります。

match で「語が印字の どこ に付くか」を指定できます: boundary(既定 — 語そのもの、または語の先頭/末尾)· suffix(御中・様・宛)· prefix(〒・TEL・登録番号)· standalone(くっつかない語のみ)· anywhere(v81 の動作)。v85 で既定が変わりました: v81 は長い語の内側ならどこでも認めたため、工事名「中野様邸増築工事」の 様 が、御中 を一度も印字しない書式で 宛先 判定の証人になっていました。v81 の動作が要る場合は { "match": "anywhere" } と明示してください。語が 単独の語として 印字されている通常のケースは、どのモードでも影響を受けません。

宣言した語がページのどこにも印字されていないときは判定を保留し、review.notes に issue: "near_unresolved" として載ります(御中を印字しない書式を罰しないため)。その書式こそ取り違えが起きる場所である場合は not_near の担当です。label と同じくモデルには渡りません。近傍の窓はエンジン規定で、セルの高さを単位に横 ±6 倍・縦 ±3 倍です。

not_nearstring | string[] | { terms, match }optional

near の鏡 — 値の そばにあってはならない 語彙です(宛先の会社名に ["登録番号", "TEL", "〒"])。値がそのどれかの近傍にあれば reason "near_conflict" が立ち、隣にあった語と距離が cells[path].evidence.not_near に載ります。形も match の指定も near と同じです(v85)。

なぜ両方が要るのか: near は識別の目印が 印字されているとき にしか話せません。ところが当事者が実際に取り違えられるのは 宛名行を持たない事務用フォーム で、そこには 御中 がそもそも印字されず、near は保留するしかありません。一方で発行元ブロックは必ず何かを印字します(登録番号 / TEL / 〒)。だから届く言い方は否定形になります — 発行元ブロックの中に座っている宛先は、発行元です。

語彙が印字されていないことは違反ではないので、near と違って保留せず、そのまま通ります(review.notes にも載りません)。モデルには渡りません。

patternstring | string[]optional
正規化後の値が満たすべき正規表現。JSON Schema と同じ部分一致なので、値全体を見るときは ^…$ を付けてください。配列を渡すと「どれか 1 つ満たせば通過」になります。string 型専用。照合は全角を半角に畳んだ値に対して行うので、ページが全角で印字されていても素の ASCII パターンが効きます(T12… は T12… として照合)。破ると reason "pattern_mismatch"。モデルには渡りません — 形を教えると、その形の値を作ってしまいます。
min / maxnumberoptional
number / integer 型の値の範囲(両端を含む)。正規化後の数値に対して判定し、外れると reason "out_of_range" が付きます。
enumstring[]optional
業務側がすでに持っている値の集合を API に渡す口です — 取引先マスタの会社名一覧、品目マスタ、単位の一覧(袋・本・個)など。正規化後の値がその集合に無ければ reason "pattern_mismatch" が立ちます。あわせて、両エンジンが同じ誤読で一致してしまうクラス(冊 を 申 と読むなど)に効く唯一の手立てでもあります — 文字どうしの突き合わせは、両方が同じ間違いをしたときに構造的に何も言えません。なお、集合に入っている正当な値どうし(登録済みの別の取引先を返した場合)は区別できません — その持ち場は near です。
review"normal" | "off"optional
"off" にすると、そのフィールドについてエンジンが推定した検討理由(text_mismatch・low_ratio・ambiguous_occurrence など)を出しません。証跡はすべて残り、判定だけを控えます。品名や備考のような自由記述の列に付けておくと、登録番号や合計に立った印が読めるようになります。宣言したルール(required の missing、pattern・min/max・enum 違反)は消せません — 自分で書いた規則が、自分で書いた別のキーで取り消せてしまうのは危険だからです。
autoFieldsbooleanoptional
true で fields 不指定時に LLM がスキーマを自動提案。旧名 auto_fields も後方互換で動作(非推奨)。
promptstringoptional
自由記述の指示(任意)。
リクエスト
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
curl -X POST https://api.space-ocr.com/ocr/fields \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/receipt.jpg",
    "imageType": "url",
    "fields": [
      { "name": "store_name", "type": "string",
        "description": "店舗名" },
      { "name": "date",       "type": "string",
        "description": "取引日" },
      { "name": "payment_method", "type": "string",
        "enum": ["現金", "クレジット", "電子マネー"],
        "description": "支払方法" },
      { "name": "invoice_no", "type": "string", "required": true,
        "description": "伝票番号" },
      { "name": "items",      "type": "array",
        "description": "購入品目",
        "children": [
          { "name": "name",  "type": "string" },
          { "name": "qty",   "type": "string" },
          { "name": "price", "type": "string" }
        ]
      },
      { "name": "total",      "type": "number", "required": true,
        "label": "合計",
        "description": "合計" }
    ]
  }'

レスポンスフィールド

status"success"
成功時は常に "success"。エラーは HTTP 4xx/5xx と共通エラー envelope(Errors 参照)で返り、このボディにはなりません。
data.valuesobject

リクエストのスキーマそのままの純粋なユーザーデータ。予約キーが混ざらないので、そのまま DB に保存できます。値はモデルがページから読んだ文字列で、文字照合で印字と突き合わせています — バイト単位の複製ではないので、完全一致で照合するなら cells[path].evidence.printed_text を使ってください。

値には半角 ¥(U+00A5)のような文字がそのまま入ります。cp932 / Shift_JIS でエンコードすると(CSV 出力も含め)その 1 文字だけで例外になり得るので、UTF-8 のまま扱ってください。

data.cellsmap<path, Cell>
パスをキーにしたフラットな座標・検証マップ。キーは review.flagged[].path と同じ文法(items[0].price)なので、flagged のパスでそのまま O(1) 参照できます。items[0] のような行パスは行全体のユニオンボックスです。
box{ xmin, ymin, xmax, ymax }
軸平行の矩形。0〜1000 正規化です(data.image でピクセルに戻せます)。並べ替えや領域の判定のように軸で計算したいときに向きます。傾いた写真では文字より広く取られることがあり、その度合いが過ぎると reason overwide_box が立ちます。
quad[{ x, y } × 4]

傾いたスキャンに追従する 4 点。box と常に両方付きます。画面に枠を描くならこちらを使ってください。基準は送信したファイルではなく data.image が示す読み取り後のページです — EXIF で横倒しの写真は正立させてから読むため、width と height が入れ替わることがあります(4000×3000 で送って 3000×4000 が返る)。ピクセル換算も data.image で行ってください。

傾き補正(デスキュー)は行いません — ページを回して座標を作り直すことはなく、返る座標は傾いたまま読んだ入力画像の座標系です。傾いた写真では quad がその傾きに沿います。

verifiedboolean | null

このセルの判定です。review の鏡なので、二つが食い違うことはありません — false は review に理由が入っているとき(宣言したルール違反も含め、理由の種類を問いません)、true は何も立たず、かつ照合が実際に走ったとき、null は何も立たなかったが照合できるものが無かったとき(行ユニオンなど幾何のみの項目)です。ゲートに使うのはこの 1 個で足ります。

文字照合そのもの(値がこの座標の OCR 原文と一致したか、2 つの独立エンジンの合意)は evidence.text_match にあります。その不一致には元から専用の理由 text_mismatch があるので、判定から失われるものはありません。

true でも「求めた意味の値である」ことは意味しません。モデルがページ上の別の箇所(近くの補助見出しなど)を拾った場合、座標はその拾った文字に付き、照合も一致し、何も立たなければ true で返ります。座標が答えるのは「この値はどこから来たか」で、「これは正しい項目か」ではありません — そこは label / near / enum の担当です。

review{ reasons } | null

null なら通過、値が入っていれば人の確認を推奨。reasons: type_mismatch | out_of_range | pattern_mismatch | near_mismatch | near_ambiguous | near_conflict | nobox | text_mismatch | crop_mismatch | low_ratio | weak_source | low_ocr_confidence | ambiguous_occurrence | overwide_box | missing。reasons は破られたルール全部を ランク順 に並べた配列で、0 番が代表です(長さ 1 でも必ず配列)。前の 6 つは呼び出し側が宣言したルール違反なので、エンジンの推定より上位にランクされます。

near_mismatch と near_ambiguous は別々の質問への答えです: 前者は この値の どの出現も 宣言語のそばに無い(どこにあっても違う値)、後者は そばにある出現は あるが座標が付いたのはそれではない(どの複製を指すか未定 — 値自体は正しいかもしれません)。同じ値が 2 か所に印字される帳票で、正しい値が座標の付き方だけで通ったり落ちたりしていたのを分けたものです(v85)。near_ambiguous が立つとき ambiguous_occurrence は併記しません — 同じ事実を二つの語彙で言うことになるからです。

画面を作る場合は、ここに並んだ 全種(と今後追加され得るコード)に表示を割り当て、1 セルに複数の理由が同時に立つ配列を前提にしてください — 一部だけ対応すると未対応コードで表示が壊れます。未知のコードは汎用の「要確認」に落とすのが安全です。

evidenceobject
判定の生データ — text_match(文字照合そのもの: 値がこの座標の OCR 原文と一致したか。照合が走ったときだけキーがあり、走らなければ verified も null です。判定が false でも text_match: true はあり得ます — 文字は合っていて、宣言したルールの方が引っ掛けた場合です)/ source(座標の出どころ: vision_symbol_match / token_id …)/ match_ratio(文字照合の一致率) / printed_text(その座標で OCR が読んだ文字そのもの。values はモデルが書いた文字列で、上の文字照合は全角・括弧・空白を畳んでから比べるため、モデルが書き直した表記でも通ります — 台帳と 突合 するような 完全一致 が要る場面ではこちらを使ってください。values を上書きするものではありません: OCR 側にも誤読があり、だから二つを突き合わせています。グリフだけを繋ぎます — 単語の間の空白は復元されないので、空白を無視して比べてください。ここに空白が無いことは、ページに空白が無い証拠ではありません) / near(near を宣言した項目が near_mismatch・near_ambiguous で立ったときだけ: occurrences = この値がページに印字されている回数、satisfied = そのうち宣言語の近傍にあるもの、anchored = 座標が付いた方がその中に入るか、nearest_term と nearest = 最も近い宣言語と、そこまでの距離。距離は 許された窓の倍数 で、1 以下なら通っていた距離です — 窓は横 6・縦 3 と非等方なので、絶対距離ひとつでは閾値と比べられません)/ not_near(near_conflict のときだけ: matched = 隣にあった語、distance = 同じ倍数) / ocr_confidence(一致グリフに対する OCR 自身の信頼度の最小値、無ければキー無し)/ crop_verified(切り出し再読の結果、実行時のみ)/ multiline(値が折り返していて、box がその複数行のユニオンであることを示す。true のときだけキーがあります)。
normalized{ value, type, method, error? }

スカラー型(number / integer / date、あるいは pattern・enum を付けた string)を宣言したフィールドにだけ付きます。data.normalized のその葉が null だった理由がここにあります。method は現在つねに "deterministic"(追加のモデル呼び出しはありません)。

error は読み取れなかった理由を種類で言います: not_numeric / not_an_integer / not_a_date は こちらの誤読の可能性(l510 のような)、no_year は年が印字されていない日付(8/16・9月末日)、conventional_token は書類がそう印字している慣用表記(一式・各・別途・ダッシュだけの欄)、relative_date は他の項目に依存する支払条件(翌月末払い・締日から60日)です。後ろの 2 つは 読み直しても値は出ません — 確認に回すのではなく、そちらの業務ルールで処理する対象です。どれでも reasons は type_mismatch のままなので、件数の集計は変わりません。

data.reviewobject
文書 1 枚分の検証サマリー。フィールドごとの判定は cells 側、ここは集計と要確認リストです。
unit"field"
集計単位。
declaredinteger
空の値・返ってこなかった required も数えた全スロット(固定分母)。
returnedinteger
空でない値の数。
boxedinteger
座標が付いたセルの数。
verifiedinteger
verified: true のセルの数 = 検討に上がらず、かつ照合が走ったセル。フラグの立ったセルはここに入りません(flagged.length がその数です)。
flagged[{ path, reasons }]
要確認リスト。件数はこの配列の長さそのもの(別カウンタなし)、path は cells のキーと同じ文法。値はあるのに座標が無いフィールド(nobox)と、返ってこなかった required(missing)はセルが無く、ここにだけ現れます。missing が立つのは required を宣言したフィールドだけです — 宣言していないフィールドの欠落にはフラグは立ちません。reasons は破られたルール全部を ランク順 に並べた配列で、0 番が代表です(長さ 1 でも必ず配列)。
by_reasonobject
理由別の内訳(例: { "pattern_mismatch": 1, "text_mismatch": 1 })。1 つのセルが宣言したルールとエンジンの疑いの両方に触れることがあるため、reasons に載った理由を すべて 数えます。したがって合計は flagged の件数以上になります(検討の 件数 は flagged.length のままです)。
notesarray

宣言がそのまま実行されなかったときだけ付くお知らせです。各項目は path / issue / description を持ち、issue で分岐してください。

issue: "type_coerced" は この API がサポートしない型を宣言した場合(declared_type / applied_type も付きます)。サポートするのは string / number / integer / date / array / object で、スカラー型は黙って処理され、解釈された値は normalized に返ります。

issue: "label_unresolved" は 宣言した label がどこにもアンカーできなかった場合です(印字されていない・2 回以上ある・隣に確信できる値が無い)。値そのものは通常の検索で返るため、これが無いと 宣言が効いていないことに気づけません。

data.normalizedobject
スカラー型(number / integer / date、あるいは pattern・enum を付けた string)を宣言したフィールドがあるときだけ付きます。values と まったく同じ形 のツリーで、葉だけがその型に解釈された値です(normalized.items[0].qty が values.items[0].qty の隣に並びます)。宣言した葉だけの 疎な ツリーで、解釈できなかった葉は null — 理由は cells[path].normalized.error にあります。解釈は決定的なので、同じページなら毎回同じ値です。values 側はそのままで、この層が隣に足されるだけです — 座標と検証が付いているのは values の方です。
data.image{ width, height }
読み取ったページのピクセルサイズ。すべての座標がこの基準です。0〜1000 の正規化座標をピクセルに戻すのに使います(pixel_x = box.xmin / 1000 × width)。EXIF による正立と、必要に応じた縮小のあとの値なので、送信したファイルの width / height と異なることがあります。
レスポンス
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{
  "status": "success",
  "data": {
    "values": {
      "store_name": "スーパー ABC",
      "date": "2025-04-10",
      "invoice_no": "",
      "items": [
        { "name": "牛乳", "qty": "1", "price": "¥198" }
      ],
      "total": "¥548"
    },
    "cells": {
      "store_name":     { "box": { "xmin": 14, "ymin": 36, "xmax": 210, "ymax": 58 },
                          "quad": [{"x":14,"y":36},{"x":210,"y":36},{"x":210,"y":58},{"x":14,"y":58}],
                          "verified": true, "review": null,
                          "evidence": { "text_match": true, "source": "vision_symbol_match", "match_ratio": 0.98, "ocr_confidence": 0.96 } },
      "date":           { "box": { "xmin": 14, "ymin": 80, "xmax": 180, "ymax": 102 },
                          "quad": [{"x":14,"y":80},{"x":180,"y":80},{"x":180,"y":102},{"x":14,"y":102}],
                          "verified": true, "review": null,
                          "evidence": { "text_match": true, "source": "token_id", "match_ratio": 1.0, "ocr_confidence": 0.99 } },
      "items[0]":       { "box": { "xmin": 263, "ymin": 460, "xmax": 738, "ymax": 523 },
                          "quad": [{"x":263,"y":460},{"x":738,"y":460},{"x":738,"y":523},{"x":263,"y":523}],
                          "verified": null, "review": null,
                          "evidence": { "source": "vision_symbol_match", "match_ratio": 1.0 } },
      "items[0].name":  { "box": { "xmin": 263, "ymin": 460, "xmax": 503, "ymax": 492 },
                          "quad": [{"x":263,"y":460},{"x":503,"y":460},{"x":503,"y":492},{"x":263,"y":492}],
                          "verified": true, "review": null,
                          "evidence": { "text_match": true, "source": "token_id", "match_ratio": 1.0, "ocr_confidence": 0.97 } },
      "items[0].qty":   { "box": { "xmin": 333, "ymin": 460, "xmax": 338, "ymax": 490 },
                          "quad": [{"x":333,"y":460},{"x":338,"y":460},{"x":338,"y":490},{"x":333,"y":490}],
                          "verified": true, "review": null,
                          "evidence": { "text_match": true, "source": "vision_symbol_match", "match_ratio": 1.0, "ocr_confidence": 0.94 } },
      "items[0].price": { "box": { "xmin": 693, "ymin": 460, "xmax": 738, "ymax": 488 },
                          "quad": [{"x":693,"y":460},{"x":738,"y":460},{"x":738,"y":488},{"x":693,"y":488}],
                          "verified": false,
                          "review": { "reasons": ["text_mismatch"] },
                          "evidence": { "text_match": false, "source": "vision_symbol_match", "match_ratio": 0.62, "ocr_confidence": 0.88 } },
      "total":          { "box": { "xmin": 380, "ymin": 720, "xmax": 530, "ymax": 742 },
                          "quad": [{"x":380,"y":720},{"x":530,"y":720},{"x":530,"y":742},{"x":380,"y":742}],
                          "verified": true, "review": null,
                          "evidence": { "text_match": true, "source": "vision_symbol_match", "match_ratio": 1.0, "ocr_confidence": 0.98 },
                          "normalized": { "value": 548, "type": "number", "method": "deterministic" } }
    },
    "review": {
      "unit": "field",
      "declared": 7,
      "returned": 6,
      "boxed": 6,
      "verified": 5,
      "flagged": [
        { "path": "items[0].price", "reasons": ["text_mismatch"] },
        { "path": "invoice_no", "reasons": ["missing"] }
      ],
      "by_reason": { "text_mismatch": 1, "missing": 1 }
    },
    // 宣言した型は values を書き換えず、この層に出ます
    "normalized": { "total": 548 },
    "image": { "width": 1654, "height": 2339 }
  }
}
#
POST/ocr/markdownBearer¥10(税込)

Markdown 変換

レイアウトを保ったまま画像を Markdown に変換します。見出し・段落・リスト・表が要素として返り、要素ごとに座標が付きます。

同期呼び出しなので、応答が返るまで接続を保つ必要があります。処理は 最長 180 秒、超えると 504 ocr_engine_timeout(課金されません)。実測では 1 枚あたり数秒〜数十秒に収まりますが、クライアント側のタイムアウトは余裕をもって設定してください。この上限に当たるのは小さな文字が詰まった多ページ書類がほとんどで、原因は画素数ではなく密度です — 縮小すると読めなくなるので、1 ページ 1 画像に分割するか、処理時間に余裕のある非同期 POST /upload をご利用ください。

ボディパラメータ

imagestringrequired

Base64 文字列または画像 URL。JSON ボディの上限は 28MB で、base64 はファイルの約 1.33 倍になるので、元画像で約 20MB 相当までです。超えると 413(details.limitBytes / receivedBytes)を返します。それより大きい場合は URL 指定か /upload(非同期・ファイルあたり 20MB)をご利用ください。

長辺 4000px を超える画像はサーバー側で自動縮小してから読みます — 座標も縮小後のページ(data.image)基準で返るので、上限に合わせて事前に画質を落として送る必要はありません。

imageType"base64" | "url"required
image の型を明示。旧名 image_type も後方互換で動作(非推奨)。
promptstringoptional
レイアウト解釈への自由記述の追加指示(任意)。
includeElementsbooleanoptional
既定 true — values.elements(内容)と cells(要素ごとの座標・検証)を返します。false にすると組み立て済みの Markdown 文字列のみ。
リクエスト
1
2
3
4
5
6
7
curl -X POST https://api.space-ocr.com/ocr/markdown \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/report.jpg",
    "imageType": "url"
  }'

レスポンスフィールド

status"success"
成功時は常に "success"。エラーは HTTP 4xx/5xx と共通エラー envelope(Errors 参照)で返り、このボディにはなりません。
data.values.markdownstring
組み立て済みの Markdown 文字列。
data.values.elementsarray<Element>
内容だけの要素配列。座標と検証フラグは cells 側に分離されています。どの要素も請求しなかった OCR トークンは paragraph(evidence.source: unclaimed_tokens)として末尾に回収され、落ちた段落が消えません。
type"heading" | "paragraph" | "list_item" | "blockquote" | "code_block" | "thematic_break" | "table"
要素の種類。
textstring
要素の本文(table 以外)。
levelinteger
heading の見出しレベル。
rowsinteger
table の行数。
colsinteger
table の列数。
cells[{ row, col, header, text }]
table のセル配列。
data.cellsmap<path, Cell>
パスをキーにしたフラットな座標・検証マップ — elements[3] が要素、elements[2].cells[1] が表のセル。review.flagged[].path と同じ文法なので flagged からそのまま引けます。includeElements: false のときは付きません。
box{ xmin, ymin, xmax, ymax }
軸平行の矩形。0〜1000 正規化です(data.image でピクセルに戻せます)。並べ替えや領域の判定のように軸で計算したいときに向きます。傾いた写真では文字より広く取られることがあり、その度合いが過ぎると reason overwide_box が立ちます。
quad[{ x, y } × 4]

傾いたスキャンに追従する 4 点。box と常に両方付きます。画面に枠を描くならこちらを使ってください。基準は送信したファイルではなく data.image が示す読み取り後のページです — EXIF で横倒しの写真は正立させてから読むため、width と height が入れ替わることがあります(4000×3000 で送って 3000×4000 が返る)。ピクセル換算も data.image で行ってください。

傾き補正(デスキュー)は行いません — ページを回して座標を作り直すことはなく、返る座標は傾いたまま読んだ入力画像の座標系です。傾いた写真では quad がその傾きに沿います。

verifiedboolean | null

このセルの判定です。review の鏡なので、二つが食い違うことはありません — false は review に理由が入っているとき(宣言したルール違反も含め、理由の種類を問いません)、true は何も立たず、かつ照合が実際に走ったとき、null は何も立たなかったが照合できるものが無かったとき(表要素そのもの — セル各々は検証されます)です。ゲートに使うのはこの 1 個で足ります。

文字照合そのもの(値がこの座標の OCR 原文と一致したか、2 つの独立エンジンの合意)は evidence.text_match にあります。その不一致には元から専用の理由 text_mismatch があるので、判定から失われるものはありません。

true でも「求めた意味の値である」ことは意味しません。モデルがページ上の別の箇所(近くの補助見出しなど)を拾った場合、座標はその拾った文字に付き、照合も一致し、何も立たなければ true で返ります。座標が答えるのは「この値はどこから来たか」で、「これは正しい項目か」ではありません — そこは label / near / enum の担当です。

review{ reasons } | null

null なら通過、値が入っていれば人の確認を推奨。reasons: type_mismatch | out_of_range | pattern_mismatch | near_mismatch | near_ambiguous | near_conflict | nobox | text_mismatch | crop_mismatch | low_ratio | weak_source | low_ocr_confidence | ambiguous_occurrence | overwide_box | missing。reasons は破られたルール全部を ランク順 に並べた配列で、0 番が代表です(長さ 1 でも必ず配列)。前の 6 つは呼び出し側が宣言したルール違反なので、エンジンの推定より上位にランクされます。

near_mismatch と near_ambiguous は別々の質問への答えです: 前者は この値の どの出現も 宣言語のそばに無い(どこにあっても違う値)、後者は そばにある出現は あるが座標が付いたのはそれではない(どの複製を指すか未定 — 値自体は正しいかもしれません)。同じ値が 2 か所に印字される帳票で、正しい値が座標の付き方だけで通ったり落ちたりしていたのを分けたものです(v85)。near_ambiguous が立つとき ambiguous_occurrence は併記しません — 同じ事実を二つの語彙で言うことになるからです。

画面を作る場合は、ここに並んだ 全種(と今後追加され得るコード)に表示を割り当て、1 セルに複数の理由が同時に立つ配列を前提にしてください — 一部だけ対応すると未対応コードで表示が壊れます。未知のコードは汎用の「要確認」に落とすのが安全です。

evidenceobject
判定の生データ — text_match(文字照合そのもの: 値がこの座標の OCR 原文と一致したか。照合が走ったときだけキーがあり、走らなければ verified も null です。判定が false でも text_match: true はあり得ます — 文字は合っていて、宣言したルールの方が引っ掛けた場合です)/ source(座標の出どころ: token_id / char_matcher_fallback / unclaimed_tokens)/ match_ratio(文字照合の一致率) / ocr_confidence(一致グリフに対する OCR 自身の信頼度の最小値、無ければキー無し)/ crop_verified(切り出し再読の結果、実行時のみ)/ multiline(値が折り返していて、box がその複数行のユニオンであることを示す。true のときだけキーがあります)。
data.reviewobject
文書 1 枚分の検証サマリー。要素ごとの判定は cells 側、ここは集計と要確認リストです。
unit"element"
集計単位。
totalinteger
セル単位の総数(表はセル各々を数えます)。
boxedinteger
座標が付いたセルの数。
verifiedinteger
verified: true のセルの数 = 検討に上がらず、かつ照合が走ったセル。フラグの立ったセルはここに入りません(flagged.length がその数です)。
flagged[{ path, reasons }]
要確認リスト。件数はこの配列の長さそのもの(別カウンタはありません)。path は cells のキーと同じ文法なのでそのまま引けます。reasons は破られたルール全部を ランク順 に並べた配列で、0 番が代表です(長さ 1 でも必ず配列)。
by_reasonobject
理由別の内訳(例: { "pattern_mismatch": 1, "text_mismatch": 1 })。1 つのセルが宣言したルールとエンジンの疑いの両方に触れることがあるため、reasons に載った理由を すべて 数えます。したがって合計は flagged の件数以上になります(検討の 件数 は flagged.length のままです)。
coverageobject
recovered_blocks / vision_tokens / tokens_claimed / token_coverage — ページをどれだけ拾えたか。
data.image{ width, height }
読み取ったページのピクセルサイズ。すべての座標がこの基準です。0〜1000 の正規化座標をピクセルに戻すのに使います(pixel_x = box.xmin / 1000 × width)。EXIF による正立と、必要に応じた縮小のあとの値なので、送信したファイルの width / height と異なることがあります。
レスポンス
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
  "status": "success",
  "data": {
    "values": {
      "markdown": "# 四半期レポート\n\n売上は前年同期比で増加した。\n\n| 項目 | 金額 |\n| --- | --- |\n| 売上 | 12,000 |",
      "elements": [
        { "type": "heading", "level": 1, "text": "四半期レポート" },
        { "type": "paragraph", "text": "売上は前年同期比で増加した。" },
        { "type": "table", "rows": 2, "cols": 2, "cells": [
          { "row": 0, "col": 0, "header": true,  "text": "項目" },
          { "row": 0, "col": 1, "header": true,  "text": "金額" },
          { "row": 1, "col": 0, "header": false, "text": "売上" },
          { "row": 1, "col": 1, "header": false, "text": "12,000" }
        ] }
      ]
    },
    "cells": {
      "elements[0]": { "box": { "xmin": 60, "ymin": 48, "xmax": 520, "ymax": 92 },
                       "quad": [{"x":60,"y":48},{"x":520,"y":48},{"x":520,"y":92},{"x":60,"y":92}],
                       "verified": true, "review": null,
                       "evidence": { "text_match": true, "source": "token_id", "ocr_confidence": 0.98 } },
      "elements[1]": { "box": { "xmin": 60, "ymin": 120, "xmax": 900, "ymax": 160 },
                       "quad": [{"x":60,"y":120},{"x":900,"y":120},{"x":900,"y":160},{"x":60,"y":160}],
                       "verified": true, "review": null,
                       "evidence": { "text_match": true, "source": "token_id" } },
      "elements[2]": { "box": { "xmin": 60, "ymin": 200, "xmax": 640, "ymax": 320 },
                       "quad": [{"x":60,"y":200},{"x":640,"y":200},{"x":640,"y":320},{"x":60,"y":320}],
                       "verified": null, "review": null, "evidence": {} },
      "elements[2].cells[0]": { "box": { "xmin": 60,  "ymin": 200, "xmax": 350, "ymax": 260 },
                                "quad": [{"x":60,"y":200},{"x":350,"y":200},{"x":350,"y":260},{"x":60,"y":260}],
                                "verified": true, "review": null, "evidence": { "text_match": true, "source": "token_id" } },
      "elements[2].cells[1]": { "box": { "xmin": 350, "ymin": 200, "xmax": 640, "ymax": 260 },
                                "quad": [{"x":350,"y":200},{"x":640,"y":200},{"x":640,"y":260},{"x":350,"y":260}],
                                "verified": false,
                                "review": { "reasons": ["text_mismatch"] },
                                "evidence": { "text_match": false, "source": "token_id", "ocr_confidence": 0.71 } },
      "elements[2].cells[2]": { "box": { "xmin": 60,  "ymin": 260, "xmax": 350, "ymax": 320 },
                                "quad": [{"x":60,"y":260},{"x":350,"y":260},{"x":350,"y":320},{"x":60,"y":320}],
                                "verified": true, "review": null, "evidence": { "text_match": true, "source": "token_id" } },
      "elements[2].cells[3]": { "box": { "xmin": 350, "ymin": 260, "xmax": 640, "ymax": 320 },
                                "quad": [{"x":350,"y":260},{"x":640,"y":260},{"x":640,"y":320},{"x":350,"y":320}],
                                "verified": true, "review": null, "evidence": { "text_match": true, "source": "token_id" } }
    },
    "review": {
      "unit": "element",
      "total": 6,
      "boxed": 6,
      "verified": 5,
      "flagged": [{ "path": "elements[2].cells[1]", "reasons": ["text_mismatch"] }],
      "by_reason": { "text_mismatch": 1 },
      "coverage": { "recovered_blocks": 0, "vision_tokens": 40, "tokens_claimed": 40, "token_coverage": 1.0 }
    },
    "image": { "width": 1654, "height": 2339 }
  }
}
#
POST/ocr/textBearer¥10(税込)

プレーンテキスト OCR

スキーマも Markdown 文法もなしに、文書の文字だけを全部返します。モデルが画像を見て本当の読み順にブロックを並べ替えるので、多段組みや傾いたスキャンでも文が入り混じりません。

同期呼び出しなので、応答が返るまで接続を保つ必要があります。処理は 最長 180 秒、超えると 504 ocr_engine_timeout(課金されません)。実測では 1 枚あたり数秒〜数十秒に収まりますが、クライアント側のタイムアウトは余裕をもって設定してください。この上限に当たるのは小さな文字が詰まった多ページ書類がほとんどで、原因は画素数ではなく密度です — 縮小すると読めなくなるので、1 ページ 1 画像に分割するか、処理時間に余裕のある非同期 POST /upload をご利用ください。

ボディパラメータ

imagestringrequired

Base64 文字列または画像 URL。JSON ボディの上限は 28MB で、base64 はファイルの約 1.33 倍になるので、元画像で約 20MB 相当までです。超えると 413(details.limitBytes / receivedBytes)を返します。それより大きい場合は URL 指定か /upload(非同期・ファイルあたり 20MB)をご利用ください。

長辺 4000px を超える画像はサーバー側で自動縮小してから読みます — 座標も縮小後のページ(data.image)基準で返るので、上限に合わせて事前に画質を落として送る必要はありません。

imageType"base64" | "url"required
image の型を明示。旧名 image_type も後方互換で動作(非推奨)。
useLlmbooleanoptional
既定 true — 読み順の並べ替えと折り返し行の再結合を行います。false にすると Vision のみの転写(即時・LLM 費用ゼロ、ただし raw OCR 順)。
includeBlocksbooleanoptional
true で values.blocks(内容)と cells(ブロックごとの box / quad / verified / review)も返します。既定 false。
promptstringoptional
転写プロンプトの差し替え(任意)。
リクエスト
1
2
3
4
5
curl -X POST https://api.space-ocr.com/ocr/text   -H "Authorization: Bearer YOUR_API_KEY"   -H "Content-Type: application/json"   -d '{
    "image": "https://example.com/note.jpg",
    "imageType": "url",
    "includeBlocks": true
  }'

レスポンスフィールド

status"success"
成功時は常に "success"。エラーは HTTP 4xx/5xx と共通エラー envelope(Errors 参照)で返り、このボディにはなりません。
data.values.textstring
全文。ブロックを読み順に結合した文字列です。
data.values.blocks[{ text }]
内容だけのブロック配列(includeBlocks: true のとき)。座標と検証フラグは cells 側です。どのブロックも請求しなかった OCR トークンは回収ブロック(evidence.source: unclaimed_tokens)として末尾に付き、落ちた段落が静かに消えません。
data.cellsmap<path, Cell>
パスをキーにしたフラットな座標・検証マップ(blocks[7])。review.flagged[].path と同じ文法なので flagged からそのまま引けます。includeBlocks: true のとき付きます。
box{ xmin, ymin, xmax, ymax }
軸平行の矩形。0〜1000 正規化です(data.image でピクセルに戻せます)。並べ替えや領域の判定のように軸で計算したいときに向きます。傾いた写真では文字より広く取られることがあり、その度合いが過ぎると reason overwide_box が立ちます。
quad[{ x, y } × 4]

傾いたスキャンに追従する 4 点。box と常に両方付きます。画面に枠を描くならこちらを使ってください。基準は送信したファイルではなく data.image が示す読み取り後のページです — EXIF で横倒しの写真は正立させてから読むため、width と height が入れ替わることがあります(4000×3000 で送って 3000×4000 が返る)。ピクセル換算も data.image で行ってください。

傾き補正(デスキュー)は行いません — ページを回して座標を作り直すことはなく、返る座標は傾いたまま読んだ入力画像の座標系です。傾いた写真では quad がその傾きに沿います。

verifiedboolean | null

このセルの判定です。review の鏡なので、二つが食い違うことはありません — false は review に理由が入っているとき(宣言したルール違反も含め、理由の種類を問いません)、true は何も立たず、かつ照合が実際に走ったとき、null は何も立たなかったが照合できるものが無かったとき(幾何のみの項目)です。ゲートに使うのはこの 1 個で足ります。

文字照合そのもの(値がこの座標の OCR 原文と一致したか、2 つの独立エンジンの合意)は evidence.text_match にあります。その不一致には元から専用の理由 text_mismatch があるので、判定から失われるものはありません。

true でも「求めた意味の値である」ことは意味しません。モデルがページ上の別の箇所(近くの補助見出しなど)を拾った場合、座標はその拾った文字に付き、照合も一致し、何も立たなければ true で返ります。座標が答えるのは「この値はどこから来たか」で、「これは正しい項目か」ではありません — そこは label / near / enum の担当です。

review{ reasons } | null

null なら通過、値が入っていれば人の確認を推奨。reasons: type_mismatch | out_of_range | pattern_mismatch | near_mismatch | near_ambiguous | near_conflict | nobox | text_mismatch | crop_mismatch | low_ratio | weak_source | low_ocr_confidence | ambiguous_occurrence | overwide_box | missing。reasons は破られたルール全部を ランク順 に並べた配列で、0 番が代表です(長さ 1 でも必ず配列)。前の 6 つは呼び出し側が宣言したルール違反なので、エンジンの推定より上位にランクされます。

near_mismatch と near_ambiguous は別々の質問への答えです: 前者は この値の どの出現も 宣言語のそばに無い(どこにあっても違う値)、後者は そばにある出現は あるが座標が付いたのはそれではない(どの複製を指すか未定 — 値自体は正しいかもしれません)。同じ値が 2 か所に印字される帳票で、正しい値が座標の付き方だけで通ったり落ちたりしていたのを分けたものです(v85)。near_ambiguous が立つとき ambiguous_occurrence は併記しません — 同じ事実を二つの語彙で言うことになるからです。

画面を作る場合は、ここに並んだ 全種(と今後追加され得るコード)に表示を割り当て、1 セルに複数の理由が同時に立つ配列を前提にしてください — 一部だけ対応すると未対応コードで表示が壊れます。未知のコードは汎用の「要確認」に落とすのが安全です。

evidenceobject
判定の生データ — text_match(文字照合そのもの: 値がこの座標の OCR 原文と一致したか。照合が走ったときだけキーがあり、走らなければ verified も null です。判定が false でも text_match: true はあり得ます — 文字は合っていて、宣言したルールの方が引っ掛けた場合です)/ source(座標の出どころ: token_id / char_matcher_fallback / unclaimed_tokens / vision_paragraph)/ match_ratio(文字照合の一致率) / ocr_confidence(一致グリフに対する OCR 自身の信頼度の最小値、無ければキー無し)/ crop_verified(切り出し再読の結果、実行時のみ)/ multiline(値が折り返していて、box がその複数行のユニオンであることを示す。true のときだけキーがあります)。
data.reviewobject
文書 1 枚分の検証サマリー。Vision 専用経路(useLlm: false)でも常に付きます。
unit"block"
集計単位。
totalinteger
ブロックの総数。
boxedinteger
座標が付いたセルの数。
verifiedinteger
verified: true のセルの数 = 検討に上がらず、かつ照合が走ったセル。フラグの立ったセルはここに入りません(flagged.length がその数です)。
flagged[{ path, reasons }]
要確認リスト。件数はこの配列の長さそのもの(別カウンタはありません)。path は cells のキーと同じ文法なのでそのまま引けます。reasons は破られたルール全部を ランク順 に並べた配列で、0 番が代表です(長さ 1 でも必ず配列)。
by_reasonobject
理由別の内訳(例: { "pattern_mismatch": 1, "text_mismatch": 1 })。1 つのセルが宣言したルールとエンジンの疑いの両方に触れることがあるため、reasons に載った理由を すべて 数えます。したがって合計は flagged の件数以上になります(検討の 件数 は flagged.length のままです)。
coverageobject
recovered_blocks / vision_tokens / tokens_claimed / token_coverage — ページをどれだけ拾えたか(useLlm: true の LLM 経路のみ)。
data.image{ width, height }
読み取ったページのピクセルサイズ。すべての座標がこの基準です。0〜1000 の正規化座標をピクセルに戻すのに使います(pixel_x = box.xmin / 1000 × width)。EXIF による正立と、必要に応じた縮小のあとの値なので、送信したファイルの width / height と異なることがあります。
data.source"llm" | "vision"
"llm" は読み順を並べ替えた経路、"vision" は LLM 失敗時の自動フォールバック(warning に理由が入ります)。
レスポンス
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
{
  "status": "success",
  "data": {
    "values": {
      "text": "株式会社サクラ商事\n請求書\n合計 1,451円",
      "blocks": [
        { "text": "株式会社サクラ商事" }
      ]
    },
    "cells": {
      "blocks[0]": { "box": { "xmin": 60, "ymin": 48, "xmax": 470, "ymax": 92 },
                     "quad": [{"x":60,"y":48},{"x":470,"y":48},{"x":470,"y":92},{"x":60,"y":92}],
                     "verified": true, "review": null,
                     "evidence": { "text_match": true, "source": "token_id", "ocr_confidence": 0.98 } }
    },
    "review": {
      "unit": "block",
      "total": 12,
      "boxed": 12,
      "verified": 11,
      "flagged": [{ "path": "blocks[7]", "reasons": ["text_mismatch"] }],
      "by_reason": { "text_mismatch": 1 },
      "coverage": { "recovered_blocks": 0, "vision_tokens": 96, "tokens_claimed": 96, "token_coverage": 1.0 }
    },
    "image": { "width": 1654, "height": 2339 },
    "source": "llm"
  }
}
#
GET/spaceBearer

ツリー閲覧

MySpace のフォルダ/シート/メモを一覧します。path と depth でスコープを絞れます。

クエリパラメータ

pathstringoptional
"/" 区切り。フォルダは名前、シートは名前 (親内で一意) または /create が返した uniqueKey。既定は "/"。
depthinteger 1..10optional
再帰深さ。既定 1。
リクエスト
1
2
curl https://api.space-ocr.com/space?path=/&depth=1 \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

pathstring
リクエストで指定した起点パス。
depthinteger
適用された再帰深さ。
itemsarray<Item>
配下のアイテム一覧。
pathstring
アイテムのフルパス。/view・/upload・/remove にそのまま渡せます。
namestring
表示名。
type"folder" | "sheet" | "doc" | "memo" | "img"
アイテムの種類。doc はドキュメント束(.md / .txt)です。
uniqueKeystring
名前と無関係な安定キー(folder 以外)。パスのセグメントとしても使えます。
createdAtinteger (epoch ms)
作成時刻。
extensionsobject | null
付加メタデータ(無ければ null、folder 以外)。
レスポンス
1
2
3
4
5
6
7
8
9
10
11
{
  "path": "/",
  "depth": 1,
  "items": [
    { "path": "/invoices", "name": "invoices", "type": "folder", "createdAt": 1716700000000 },
    { "path": "/memo_2024", "name": "メモ", "type": "memo",
      "uniqueKey": "...", "createdAt": 1716700000000, "extensions": null }
  ]
}

// type: folder | sheet | doc | memo | img。folder 以外は uniqueKey / extensions を持ちます。
#
GET/viewBearer

中身を見る

フォルダ/シート/ドキュメント束/メモ/画像、すべての種類の中身を返します。ドキュメント束は pages 配列、シートは rows 配列です。クエリ(where / sort / select / limit / offset / boxes)はシート専用 — 他の種類では無視され、全件がそのまま返ります。

シートの行は アップロード時刻(createdAt)の昇順で返ります。これは POST /edit・POST /remove の row: N と同じ順番なので、レスポンスの N 番目の行がそのまま row: N です。

クエリパラメータ

pathstringrequired
対象パス。
wherestring | string[]optional
【シート専用】行フィルタ。例: total>=40000 / vendor~ABC。繰り返し可(AND)。演算子: = != > >= < <= ~(~ は部分一致)。シートのカラム + name / ocrStatus / createdAt を対象。保存されている値はモデルがページから読んだ文字列なので、比較の前に数値化を試みます: 全角を半角に正規化し、通貨記号(¥ ¥ $ ₩ € £ 円 元 원 など)・桁区切り・会計式のマイナス表記((1,200) / △1,200 / 末尾ハイフン)を取り除いて、純粋な十進数になれば数値比較。ならなければ文字列比較です(両辺が数値のときだけ数値比較)。日付・電話番号・12% などは数値扱いされません。
sortstring | string[]optional
【シート専用】並べ替え。例: total:desc / -invoice_date。繰り返しで tie-break。where と同じ規則で数値化を試み、両方が数値なら数値順、そうでなければ文字列順です。
selectstringoptional
【シート専用】カンマ区切りの返却カラム名。例: vendor,total。
limitinteger 1..500optional
【シート専用】返却行数の上限。ドキュメント束の pages には効きません。
offsetintegeroptional
【シート専用】ページング用スキップ数。レスポンスの nextOffset と組み合わせて使用。
boxes"0" | "1" | "true" | "false"optional
【シート専用】0 / false で行の cells(座標・検証マップ)を省略した軽量レスポンス。values / review / image は残ります。select= は values のキーと、先頭セグメントが一致する cells のパスに適用されます。
リクエスト
1
2
3
4
5
6
7
8
# 複数 where (AND) + ソート + 投影 + ページネーション
curl "https://api.space-ocr.com/view?path=/invoices/sheet1\
&where=total>=10000\
&where=vendor~ABC\
&sort=-invoice_date\
&select=vendor,total,invoice_date\
&limit=20&offset=0" \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

type"folder" | "sheet" | "doc" | "memo" | "img"
対象の種類。以下のフィールドは type によって付くものが変わります。
pathstring
対象パス。
namestring
表示名(folder 以外)。
columnsarray<ColumnSpec>
【シート】このシートのカラムスキーマ(POST /create と同じ形)。
totalinteger
【シート】シート全体の行数 /【doc】ページ数。
matchedinteger
【シート】where を通過した行数。
offset / limit / nextOffsetinteger | null
【シート】ページング状態。nextOffset を次の offset に渡します(終端は null)。
rowsarray<Row>
【シート】行の配列。createdAt 昇順 — POST /edit・POST /remove の row: N と同じ順番です。
rowKeystring
行の安定キー。POST /edit の row にそのまま渡せます。
namestring
元ファイル名。
createdAtinteger (epoch ms)
アップロード時刻 — 既定の行順の根拠。
imageUrlstring | null
元画像の URL。
ocrStatus"pending" | "done" | "failed"
OCR 状態。
valuesobject | null
抽出された値(カラム名がキー)。POST /ocr/fields の data.values と同じ純粋なユーザーデータです。OCR 前は null。
cellsmap<path, Cell>
POST /ocr/fields と同じ座標・検証マップ。boxes=0 なら省略されます。
reviewobject
POST /ocr/fields と同じ検証サマリー(unit: "field")。
image{ width, height }
読み取ったページのピクセルサイズ(座標の基準)。送信したファイルとは異なることがあります。
mode"markdown" | "text"
【doc】束の変換モード。
pagesarray<Page>
【doc】ページの配列(アップロード順)。各ページは values(mode に応じて { markdown, elements } または { text, blocks })+ cells + review + image — POST /ocr/markdown・/ocr/text と同じ v2 構造です。OCR 前のページは values: null のみ。
pageKeystring
ページの安定キー。
namestring
元ファイル名。
imageUrlstring | null
元画像の URL。
ocrStatus"pending" | "done" | "failed"
OCR 状態。
valuesobject | null
mode=markdown なら { markdown, elements }、mode=text なら { text, blocks }。
cellsmap<path, Cell>
POST /ocr/markdown・/ocr/text と同じ座標・検証マップ(elements[3] / blocks[7] …)。
reviewobject
同エンドポイントと同じ検証サマリー(unit: "element" / "block")。
image{ width, height }
読み取ったページのピクセルサイズ(座標の基準)。送信したファイルとは異なることがあります。
itemsarray
【folder】子アイテムの一覧({ path, name, type, uniqueKey? })。
textstring
【memo】メモの本文。
imageUrl / ocrStatusstring | null
【img】元画像の URL と OCR 状態。
レスポンス
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// type=sheet
{
  "type": "sheet",
  "path": "/invoices/sheet1",
  "name": "sheet1",
  "columns": [ /* ... */ ],
  "total": 128,        // シート全体の行数
  "matched": 12,       // where 通過行数
  "offset": 0,
  "limit": 20,
  "nextOffset": 20,    // 次ページ用 / 終端は null
  "rows": [
    {
      "rowKey": "img_abc",
      "name": "invoice_2025_04_10.jpg",
      "createdAt": 1744243200000,   // アップロード時刻 / 既定の行順
      "imageUrl": "https://...",
      "ocrStatus": "done",
      "values": { "vendor": "ABC Corp", "total": "12000", "invoice_date": "2025-04-10" },
      "cells": { /* POST /ocr/fields と同じ box / quad / verified / review — boxes=0 で省略 */ },
      "review": { "unit": "field" /* ... */ },
      "image": { "width": 1654, "height": 2339 }
    }
  ]
}

// type=folder
{
  "type": "folder",
  "path": "/invoices",
  "items": [
    { "path": "/invoices/2024", "name": "2024", "type": "folder" },
    { "path": "/invoices/Kvho45OXMKw…", "name": "sheet1", "type": "sheet", "uniqueKey": "Kvho45OXMKw…" }
  ]
}

// type=doc — ページ全件がそのまま返ります(where / sort / limit / offset / select / boxes は無視)。
// type=doc (mode=markdown)
{
  "type": "doc",
  "path": "/reports/quarterly",
  "name": "quarterly",
  "mode": "markdown",
  "total": 2,
  "pages": [
    {
      "pageKey": "img_abc",
      "name": "page1.jpg",
      "imageUrl": "https://...",
      "ocrStatus": "done",
      "values": { "markdown": "# ...", "elements": [ /* ... */ ] },
      "cells": { /* POST /ocr/markdown と同じ box / quad / verified / review */ },
      "review": { "unit": "element" /* ... */ },
      "image": { "width": 1654, "height": 2339 }
    }
  ]
}

// type=doc (mode=text)
{
  "type": "doc",
  "path": "/notes/scan",
  "name": "scan",
  "mode": "text",
  "total": 1,
  "pages": [
    {
      "pageKey": "img_def",
      "name": "note.jpg",
      "imageUrl": "https://...",
      "ocrStatus": "done",
      "values": { "text": "...", "blocks": [ /* ... */ ] },
      "cells": { /* POST /ocr/text と同じ box / quad / verified / review */ },
      "review": { "unit": "block" /* ... */ },
      "image": { "width": 1654, "height": 2339 }
    }
  ]
}

// type=memo
{ "type": "memo", "path": "...", "name": "todo", "text": "..." }

// type=img
{ "type": "img", "path": "...", "name": "...", "imageUrl": "...", "ocrStatus": "done" }
#
POST/createBearer

作成

親フォルダの下に folder / sheet / doc / memo を作成します。シートは OCR スキーマ (columns) と prompt を、doc(ドキュメント束)は mode を持ちます。

ボディパラメータ

pathstringrequired
親フォルダの path。
type"folder" | "sheet" | "doc" | "memo"required
作成するアイテムの種類。doc はドキュメント束(.md / .txt)です。
namestringrequired
表示名。
textstringoptional
memo の本文(type=memo の場合のみ)。
columnsarray<ColumnSpec>optional
シートの OCR スキーマ(type=sheet 用)。以後このシートへのアップロードは、毎回このスキーマで抽出されます。
idstringoptional
列の安定 ID。省略すると自動生成されます。
namestringrequired
列名。抽出された値はこの名前で行に入ります。
type"string" | "number" | "integer" | "date" | "array"required
値の形。単一の値は string、明細行のような繰り返しは array にして children で行の中身を宣言します。number / integer / date を宣言すると、アップロードごとに各セルへ解釈済みの normalized 値が付き、解釈できなかった値には reason "type_mismatch" が立ちます — 意味論は /ocr/fields の FieldSpec.type と同じで、型がモデルに渡ることはありません。
descriptionstringoptional
その値がどこにあるかのヒント(例: 「合計の右」)。
childrenarray<ColumnSpec>optional
type が array の列の子フィールド。明細行の各セルになります。
requiredbooleanoptional
true の列は、その値が空で返るか読み取れなかったときに、その行の review.flagged に reason "missing" として現れます — アップロードのたびに適用され、抽出の動きは変わりません。必ず印字される値だけに付けてください。
labelstring | string[]optional
値の横に印字されているラベル(例: "合計")。同じ値がページに複数回印字されているとき、座標をそのラベルの隣の出現にアンカーします(v64)。string 列専用・ラベルがページに正確に 1 回のときだけ働き、モデルには渡りません — 抽出テキストは変わらず座標だけが変わります。「消費税(8%)」のように複数の語にまたがる表記も書けます。
nearstring | string[] | { terms, match }optional
値の そば に印字されているはずの語彙(例: 宛先なら ["御中", "様"])。アップロードごとに、値の すべての出現 を見て reason "near_mismatch"(どの出現も語彙のそばに無い)または "near_ambiguous"(そばにある出現はあるが座標が付いたのは別の複製)が立ち、語がページのどこにも無ければ判定を保留して review.notes に issue: "near_unresolved" が載ります。{ terms, match } で語の付き方(boundary 既定 / suffix / prefix / standalone / anywhere)も指定できます(v85)。意味論は FieldSpec.near と同じで、モデルには渡りません。
not_nearstring | string[] | { terms, match }optional
near の鏡 — 値の そばにあってはならない 語彙(宛先の列に ["登録番号", "TEL", "〒"])。隣にあれば reason "near_conflict" が立ちます。宛名行を持たない書式では 御中 が印字されず near は保留するしかないので、そこに届くのはこちらです(v85)。意味論は FieldSpec.not_near と同じで、モデルには渡りません。
patternstring | string[]optional
値が満たすべき正規表現(string 列専用、JSON Schema と同じ部分一致 — 値全体は ^…$)。配列は「どれか 1 つ満たせば通過」。照合は全角を半角に畳んだ値に対して行い、破ると reason "pattern_mismatch"。モデルには渡りません。
min / maxnumberoptional
number / integer 列の値の範囲(両端を含む)。正規化後の数値に対して判定し、外れると reason "out_of_range" が付きます。
enumstring[]optional
許容する値の集合です(string 列専用) — 取引先マスタの会社名一覧、単位の一覧(袋・本・個)など。集合に無い値には reason "pattern_mismatch" が立ちます。両エンジンが同じ誤読で一致するクラス(冊→申)に効く唯一の手立てです。
review"normal" | "off"optional
"off" にすると、その列についてエンジンが推定した検討理由(text_mismatch など)を出しません。宣言したルール(missing・pattern・min/max・enum・near 違反)は消せません。
promptstringoptional
シートの抽出指示プロンプト(任意)。
mode"markdown" | "text"optional
doc 束の変換モード(type=doc 用、既定 markdown)。markdown はレイアウト保持、text は原文そのまま。
リクエスト
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# sheet
curl -X POST https://api.space-ocr.com/create \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "/invoices",
    "type": "sheet",
    "name": "sheet1",
    "columns": [
      { "id": "amount", "name": "amount", "type": "string", "required": true },
      { "id": "date",   "name": "date",   "type": "string" }
    ],
    "prompt": "請求書から金額と日付を抽出"
  }'

レスポンスフィールド

pathstring
作成されたアイテムのパス。sheet / doc / memo は uniqueKey がパスに組み込まれて返ります。
type"folder" | "sheet" | "doc" | "memo"
作成された種類。
uniqueKeystring
名前と無関係な安定キー(folder 以外)。以後 /view・/upload の path セグメントとしてそのまま使えます。
レスポンス
1
2
3
4
5
6
7
8
// HTTP 201 Created
// sheet/memo は uniqueKey が path に組み込まれて返却される
{ "path": "/invoices/Kvho45OXMKw…", "type": "sheet", "uniqueKey": "Kvho45OXMKw…" }

// 作成と同時に item.created Webhook が発火します。Idempotency-Key ヘッダーを
// 付けると 24h 以内の再送は同じレスポンスをそのまま返します。
// required: true の列(上の amount)は、以後のアップロードで値が空だと
// その行の review.flagged に reason "missing" として現れます。
#
POST/uploadBearer¥10(税込)× N

画像アップロード

シートまたはドキュメント束に画像を 1 枚以上アップロードします。multipart/form-data。既定は非同期 (jobs を返却 → webhook で完了通知)。

フォームフィールド (multipart)

pathstringrequired
アップロード先のシート/ドキュメント束の path。束のモード(markdown / text)で変換方法が決まります。
filesfile (repeatable)required
画像ファイル。複数枚は files を繰り返し送信。1 リクエスト最大 20 ファイル / 1 ファイル最大 20MB、リクエスト全体では 28MB までです(超過は 413)。
waitbooleanoptional
true で同期実行(1 枚あたり最大 30s 待機、超過分は status:"pending" で返却)。1 枚アップロード用。
リクエスト
1
2
3
4
5
curl -X POST https://api.space-ocr.com/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "path=/invoices/sheet1" \
  -F "files=@invoice1.jpg" \
  -F "files=@invoice2.jpg"

レスポンスフィールド

pathstring
アップロード先のパス。
jobsarray<Job>
非同期(既定)のとき。1 ファイル 1 ジョブで、完了は ocr.completed Webhook か GET /jobs/{jobId} のポーリングで受け取ります。
uniqueKeystring
作成された行/ページの安定キー。
originalNamestring
元ファイル名。
jobIdstring
GET /jobs/{jobId} に渡すジョブ ID。
status"pending"
受付時点では常に pending。
resultsarray<Result>
wait=true のとき jobs の代わりに返ります。Job の属性に加えて、終わった分は mode と result を持ちます — result は GET /jobs と同じ v2 構造({ values, cells, review, image })。30s 以内に終わらなかった分は status: "pending" のままなので /jobs でポーリングしてください。
レスポンス
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
41
42
43
44
// async (default)
{
  "path": "/invoices/sheet1",
  "jobs": [
    { "uniqueKey": "...", "originalName": "invoice1.jpg", "jobId": "job_...", "status": "pending" },
    { "uniqueKey": "...", "originalName": "invoice2.jpg", "jobId": "job_...", "status": "pending" }
  ]
}

// ドキュメント束にアップロードした場合 — jobs の形は同じで、束の mode で変換方法が決まります
{
  "path": "/reports/quarterly",
  "jobs": [
    { "uniqueKey": "...", "originalName": "page1.jpg", "jobId": "job_...", "status": "pending" }
  ]
}

// wait=true — jobs ではなく results が返ります
{
  "path": "/invoices/sheet1",
  "results": [
    { "uniqueKey": "...", "originalName": "invoice1.jpg", "jobId": "job_...",
      "status": "done", "mode": "sheet",
      "result": { /* { values, cells, review, image } — GET /jobs と同じ v2 構造 */ } },
    { "uniqueKey": "...", "originalName": "invoice2.jpg", "jobId": "job_...",
      "status": "pending" }   // 30s 以内に終わらなかった分は /jobs でポーリング
  ]
}

// 402 — 残高不足
{
  "error": { "code": "insufficient_balance", "message": "...", "requestId": "req_..." },
  "details": {
    "requested": 5,
    "processable": 3,
    "breakdown": {
      "freeRemaining": 0,
      "flatfeeRemaining": 3,
      "balance": 0,
      "perCallCost": 1,
      "currency": "scans"
    }
  }
}
#
POST/editBearer

シート行/メモを編集

シートのセル値、またはメモの本文を上書きします。anyOf: (path, row, column, value) または (path, text)。編集できるのはシートとメモだけ — ドキュメント束(.md / .txt)は OCR 判読結果そのものなので 400 で拒否されます。

ボディパラメータ

pathstringrequired
対象シート/メモの path。
rowinteger | stringoptional
整数(インデックス、負数は末尾起算)または rowKey(文字列)。シート編集時に必須。
columninteger | stringoptional
整数(カラムインデックス)または カラム名/id。シート編集時に必須。
valueanyoptional
上書きする値。シート編集時に必須。
textstringoptional
メモ編集時の新しい本文。メモ編集時に必須。
リクエスト
1
2
3
4
5
6
7
8
9
10
11
# sheet
curl -X POST https://api.space-ocr.com/edit \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"/invoices/sheet1","row":"img_abc","column":"amount","value":"12000"}'

# memo
curl -X POST https://api.space-ocr.com/edit \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"/todo","text":"新しい本文"}'

レスポンスフィールド

okboolean
true なら反映済みです。
patchedobject
実際に適用された変更。シート編集なら { row, column, value }。
レスポンス
1
{ "ok": true, "patched": { "row": "img_abc", "column": "amount", "value": "12000" } }
#
POST/removeBearer

削除(cascade)

フォルダ/シート/メモ/画像を削除します。フォルダ削除は配下メタデータ・flat エントリ・Storage を全てカスケード。

ボディパラメータ

pathstringrequired
削除対象の path。
リクエスト
1
2
3
4
curl -X POST https://api.space-ocr.com/remove \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"/invoices/2024"}'

レスポンスフィールド

okboolean
true なら削除完了です(フォルダは配下までカスケード済み)。
レスポンス
1
{ "ok": true }
#
GET/jobs/{jobId}Bearer

OCR ジョブのポーリング

POST /upload(非同期)が返した jobId の状態を確認します。webhook を使わない場合に使用。

パスパラメータ

jobIdstringrequired
/upload 応答内の jobs[].jobId。
リクエスト
1
2
curl https://api.space-ocr.com/jobs/job_xxx \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

jobIdstring
ジョブ ID。
status"pending" | "done" | "failed"
処理状態。failed は自動返金済みです。
uniqueKeystring
作成された行/ページの安定キー。
pathstring
アイテムのパス。
sheetRef / docRefstring | null
アップロード先がシートなら sheetRef、ドキュメント束なら docRef に uniqueKey が入ります(もう一方は null)。
mode"sheet" | "markdown" | "text"
アップロード先で決まる出力形式。
resultobject
status が done のときのみ。{ values, cells, review, image } — mode に関わらず OCR エンドポイント(/ocr/fields・/ocr/markdown・/ocr/text)と同じ v2 構造で、values の中身だけが mode に従います。ocr.completed Webhook の data.result も同じ形です。
レスポンス
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "jobId": "job_xxx",
  "status": "done",
  "uniqueKey": "img_abc",
  "path": "/invoices/sheet1/img_abc",
  "sheetRef": "Kvho45OXMKw…",
  "docRef": null,
  "mode": "sheet",
  "result": {
    "values": { "amount": "12000", "date": "2025-04-10" },
    "cells": { /* POST /ocr/fields と同じ box / quad / verified / review */ },
    "review": { "unit": "field" /* ... */ },
    "image": { "width": 1654, "height": 2339 }
  }
}
#
GET/amountBearer

残高・無料枠

現在の残高と利用可能な無料枠を返します。
リクエスト
1
2
curl https://api.space-ocr.com/amount \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

freeobject
毎月の無料枠。
used / limit / remaininginteger
今サイクルの使用量・上限・残り。
cycleStartinteger (epoch ms)
サイクル開始時刻。
cycleEndinteger (epoch ms)
サイクル終了時刻(ここでリセット)。
flatfeeobject
定額プラン。未加入なら enabled: false で、他の属性は付きません。
enabledboolean
加入中かどうか。
used / limit / remaininginteger
今サイクルの使用量・上限・残り。
cycleStart / cycleEndinteger (epoch ms)
サイクルの期間。
nextBillingAtinteger (epoch ms)
次回請求時刻。
interval"monthly"
請求間隔。
renewalboolean
自動更新かどうか。
planstring
プラン名(例: "pro")。
balanceinteger

チャージ済み残高。単位は通貨ではなくスキャン数です。

消費は 無料枠 → 定額プラン → 残高 の順で、無料枠が残っている間この値は減りません。残量表示を作る場合は free.remaining と balance の両方を見せてください — balance だけだと、無料枠を使っている間ずっと減らない数字になります。

currency"scans"
残高の単位。
perCallCostinteger
1 コールあたりの消費スキャン数(1)。
レスポンス
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
{
  "free": {                    // 毎月の無料枠
    "used": 12,
    "limit": 100,
    "remaining": 88,
    "cycleStart": 1716700000000,
    "cycleEnd": 1719378400000
  },
  "flatfee": {                 // 定額プラン(未加入なら enabled:false)
    "enabled": true,
    "used": 340,
    "limit": 3000,
    "remaining": 2660,
    "cycleStart": 1716700000000,
    "cycleEnd": 1719378400000,
    "nextBillingAt": 1719378400000,
    "interval": "monthly",
    "renewal": true,
    "plan": "pro"
  },
  "balance": 1240,             // チャージ済み残高(スキャン数)
  "currency": "scans",         // 残高の単位は通貨ではなくスキャン数
  "perCallCost": 1             // 1 スキャン = 1 コール
}

// 処理可能枚数 = free.remaining + (flatfee.enabled ? flatfee.remaining : 0) + balance。
// この順で消費されます(無料枠 → 定額 → 残高)。
#
GET/health

ヘルスチェック

認証不要のヘルスチェック。あわせて 二つの変更履歴 を返します — この API 自体のもの(最上位の version・changelog)と、いま動いているエンジンのもの(engine 以下のバージョン・デプロイ時刻・changelog)です。エラー率や検討フラグの件数、再実行時の安定性を測っておられる場合は、測定値の横に両方のバージョンを記録しておくと、後日数値が動いたときに自分の変更かこちらの更新かを切り分けられます。リクエスト/レスポンスの形式(構造)は安定した契約で、changelog に載るのは追加キーと内容レベルの変化だけです。
リクエスト
1
curl https://api.space-ocr.com/health

レスポンスフィールド

status"ok"
サービスが応答できていれば ok。
versionstring
公開 API の契約バージョン(現在 v2.8)。エンジンのバージョンは engine.version です。
timeinteger (epoch ms)
サーバー時刻。
changelogarray

この API 自体の変更履歴です。新しい順、エントリごとに version・date・changes[]。

engine.changelog と紛らわしいので一度だけ整理させてください。最上位の changelog は version(v2.x — エンドポイント・パラメータ・レスポンスキーの契約)と対になり、engine.changelog は engine.version(vNN — 読み取りの中身)と対になります。軸が別なので、片方だけが動くことがあります。

engineobject | null
いま稼働中の OCR エンジンの正体です。version(エンジンのバージョン、例 v81)/ build.sha・build.deployed_at(デプロイされたコミットと時刻)/ changelog(呼び出し側から観測できる変更の履歴、新しい順 — 各エントリは version・date・changes[])。5 分キャッシュで返すので、デプロイ直後は最大 5 分古い値になることがあります。エンジンに一時的に届かないときは null になりますが、API 自体の稼働とは別問題なので status は ok のままです。
レスポンス
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "status": "ok",
  "version": "v2.8",
  "time": 1787298502910,
  "changelog": [
    { "version": "v2.8", "date": "2026-08-21",
      "changes": ["Field extraction responses carry a new evidence key, `printed_text` …", "…"] }
  ],
  "engine": {
    "version": "v83",
    "build": { "sha": "52b9a92", "deployed_at": "2026-08-21T06:59:16Z" },
    "changelog": [
      { "version": "v83", "date": "2026-08-21",
        "changes": ["New evidence key `cells[path].evidence.printed_text` …", "…"] }
    ]
  }
}
#

概要

スペース全体に 1 つの Webhook URL を登録すると、すべてのイベントが HMAC 署名付きで配信されます。設定は Developer → Webhooks か、後述の Webhook 管理エンドポイントから行います。
#

イベント

全イベントは同じ envelope(event / deliveryId / occurredAt / apiVersion / data)を持ちます。
item.createdeventoptional
/create で folder / sheet / memo が作成
upload.receivedeventoptional
/upload で画像が受信
ocr.completedeventoptional
OCR 完了。data.mode が出力形式(sheet / markdown / text)、data.result に結果
ocr.failedeventoptional
OCR 失敗(自動返金済み)
webhook.testeventoptional
/webhook/test による手動テスト

ペイロード例 — ocr.completed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "event": "ocr.completed",
  "deliveryId": "dlv_xxx",
  "occurredAt": 1716700000000,
  "apiVersion": "v2.7",
  "data": {
    "uid": "...",
    "path": "/invoices/sheet1/img_abc",
    "parentPath": "/invoices/sheet1",
    "uniqueKey": "img_abc",
    "sheetRef": "sht_xxx",
    "docRef": null,
    "mode": "sheet",
    "result": {
      "values": { "amount": "12000", "date": "2025-04-10" },
      "cells": { /* box / quad / verified / review */ },
      "review": { "unit": "field" /* ... */ },
      "image": { "width": 1654, "height": 2339 }
    }
  }
}
mode はアップロード先で決まります — シートなら "sheet"、ドキュメント束なら "markdown" / "text"。result は GET /jobs と同じ { values, cells, review, image }(v2)で、values の中身だけが mode に従います(sheet: フィールド値 / markdown: { markdown, elements } / text: { text, blocks })。ドキュメント束の場合は sheetRef が null になり、docRef に束の uniqueKey が入ります。
#

配信ヘッダー

受信エンドポイントには下記ヘッダーが付きます。署名検証で必要なものは Signature / Timestamp。
1
2
3
4
5
X-Spaceocr-Signature: t=<unix_ms>,v1=<hex>
X-Spaceocr-Timestamp: <unix_ms>
X-Spaceocr-Event: ocr.completed
X-Spaceocr-Delivery: dlv_<id>
Content-Type: application/json
#

署名検証

X-Spaceocr-Signature は t=<unix_ms>,v1=<hex> 形式です。canonical 文字列は `${t}.${rawBody}`、アルゴリズムは HMAC-SHA256。リプレイ攻撃を防ぐため timestamp が 5 分以上ずれている場合は拒否してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import crypto from "crypto";

export function verify(secret, headers, rawBody) {
  const sig = headers["x-spaceocr-signature"] || "";
  const m = sig.match(/^t=(\d+),v1=([a-f0-9]+)$/);
  if (!m) return false;
  const [, t, v1] = m;
  if (Math.abs(Date.now() - Number(t)) > 5 * 60 * 1000) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(v1, "hex"),
  );
}
#

再送ポリシー

2xx 以外は exponential backoff (1m → 5m → 30m → 2h) で最大 5 回試行。5xx / 408 / 429 / timeout のみ retry 対象、その他 4xx は即 dead。配信ログは 30 日保存。
↻手動再送は POST /webhooks/deliveries/{deliveryId}/redeliver で。詳細は配信履歴セクション参照。
#
GET/webhookBearer

現在の Webhook 設定

スペース全体に登録されている webhook URL と状態を返します。
リクエスト
1
2
curl https://api.space-ocr.com/webhook \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

configuredboolean
登録があるかどうか。false のときは他のフィールドは付きません。
urlstring
配信先 URL。
activeboolean
配信が有効かどうか。
secretMaskedstring
署名鍵の末尾 4 桁だけを残したマスク表示。平文は登録・回転のその一度しか返りません。
createdAt / updatedAtinteger (epoch ms)
登録・更新時刻。
レスポンス
1
2
3
4
5
6
7
8
9
10
11
{
  "configured": true,
  "url": "https://example.com/hooks/space-ocr",
  "active": true,
  "secretMasked": "••••a1b2",
  "createdAt": 1716700000000,
  "updatedAt": 1716700000000
}

// 未設定のとき
{ "configured": false }
#
PUT/webhookBearer

Webhook 設定の作成・更新

スペース全体の webhook URL を登録または更新します。rotateSecret で署名鍵を再発行できます。

ボディパラメータ

urlstring (uri)required
配信先 URL。
activebooleanoptional
配信を有効化(既定 true)。
rotateSecretbooleanoptional
true で署名鍵を再発行し、新しい secret を返却。初回登録時は指定がなくても secret が発行され、そのときも平文で返ります。
リクエスト
1
2
3
4
curl -X PUT https://api.space-ocr.com/webhook \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/space-ocr","active":true}'

レスポンスフィールド

configured / url / active / secretMasked / createdAt / updatedAt—
GET /webhook と同じフィールドです。
secretstring
新規発行・rotateSecret のときだけ、この一度だけ平文で返ります。後から再取得はできないので、すぐ保管してください。
レスポンス
1
2
3
4
5
6
7
8
9
{
  "configured": true,
  "url": "https://example.com/hooks/space-ocr",
  "active": true,
  "secretMasked": "••••a1b2",
  "secret": "kJ8s…",   // 新規発行・回転時のみ、この一度だけ平文
  "createdAt": 1716700000000,
  "updatedAt": 1716700000000
}
#
DELETE/webhookBearer

Webhook 設定の削除

登録済みの webhook を削除します。以後イベントは配信されません。
リクエスト
1
2
curl -X DELETE https://api.space-ocr.com/webhook \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

okboolean
true なら削除済み。以後イベントは配信されません。
レスポンス
1
{ "ok": true }
#
POST/webhook/testBearer

テストイベントを送信

configured URL に webhook.test イベントを即時送信します。受信側の実装確認用。
リクエスト
1
2
curl -X POST https://api.space-ocr.com/webhook/test \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

okboolean
true なら送信キューに載りました。
deliveryIdstring
発行された配信 ID。/webhooks/deliveries/{deliveryId} で結果を追跡できます。
レスポンス
1
{ "ok": true, "deliveryId": "dlv_xxx" }
#
GET/webhooks/deliveriesBearer

最近の配信履歴

最新の webhook 配信ログを返します。debug 用。

クエリパラメータ

status"pending" | "success" | "dead"optional
配信状態でフィルタします。
limitinteger 1..200optional
返却件数の上限。既定 50。
リクエスト
1
2
curl https://api.space-ocr.com/webhooks/deliveries \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

itemsarray<Delivery>
新しい順の配信ログ。ログは 30 日保存されます。
deliveryIdstring
配信 ID。
eventstring
イベント名(ocr.completed など)。
urlstring
配信先 URL。
path / uniqueKeystring
対象アイテムのパスとキー(該当イベントのみ)。
status"pending" | "success" | "dead"
pending は再試行待ち、dead は全試行が尽きた状態。
attemptsinteger
試行回数。
lastAttemptobject
最後の試行の詳細 — { at, attemptIndex, responseStatus, error, durationMs, responsePreview }。
occurredAtinteger (epoch ms)
イベント発生時刻。
nextAttemptAtinteger | null
次の再試行予定。無ければ null。
completedAtinteger | null
成功時刻。
レスポンス
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "items": [
    {
      "deliveryId": "dlv_xxx",
      "event": "ocr.completed",
      "url": "https://example.com/hooks/space-ocr",
      "path": "/invoices/sheet1/img_abc",
      "uniqueKey": "img_abc",
      "status": "success",          // pending | success | dead
      "attempts": 1,                // 試行回数
      "lastAttempt": {
        "at": 1716700000000,
        "attemptIndex": 0,
        "responseStatus": 200,
        "error": null,
        "durationMs": 143,
        "responsePreview": "ok"
      },
      "occurredAt": 1716700000000,
      "nextAttemptAt": null,
      "completedAt": 1716700000143
    }
  ]
}
#
GET/webhooks/deliveries/{deliveryId}Bearer

配信の詳細

指定配信の完全なペイロードと配信試行履歴を返します。

パスパラメータ

deliveryIdstringrequired
/webhooks/deliveries の deliveryId。
リクエスト
1
2
curl https://api.space-ocr.com/webhooks/deliveries/dlv_xxx \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

deliveryIdstring
配信 ID。
eventstring
イベント名。
occurredAtinteger (epoch ms)
イベント発生時刻。
payloadobject
受信側に送られたイベント本文の全体(envelope の data まで含む)。
attempts[{ at, responseStatus, ok }]
試行履歴。
レスポンス
1
2
3
4
5
6
7
8
9
{
  "deliveryId": "dlv_xxx",
  "event": "ocr.completed",
  "occurredAt": 1716700000000,
  "payload": { /* full event body */ },
  "attempts": [
    { "at": 1716700000000, "responseStatus": 200, "ok": true }
  ]
}
#
POST/webhooks/deliveries/{deliveryId}/redeliverBearer

配信の手動再送

失敗した配信を手動で再送します。

パスパラメータ

deliveryIdstringrequired
再送する deliveryId。
リクエスト
1
2
curl -X POST https://api.space-ocr.com/webhooks/deliveries/dlv_xxx/redeliver \
  -H "Authorization: Bearer YOUR_API_KEY"

レスポンスフィールド

okboolean
true なら再送キューに載りました。
deliveryIdstring
同じ ID をそのまま再利用します(新しい ID は発行されません)。配信ログの status は pending に戻り、attempts に試行が追記されます。
レスポンス
1
2
3
4
{ "ok": true, "deliveryId": "dlv_xxx" }

// 同じ deliveryId を再利用します(新しい ID は発行されません)。配信ログの
// status は pending に戻り、attempts に試行が追記されます。
#

概要

MCP サーバーは、この API をそのまま AI エージェントのツールとして開きます。読み取り 3 種に加えて、フォルダやシートを作り、画像を上げ、溜まった行を条件付きで取り出すところまでエージェントが行えます。中身は同じ REST ルートで、課金も同じです。
#

接続

インストールは不要です。ヘッダーを設定できるクライアントは API キーをそのままベアラートークンとして送ってください。ヘッダーを設定できない Claude デスクトップ/モバイル/claude.ai では、URL をカスタムコネクタとして追加すると OAuth の同意画面が開き、どの API キーで動かすかを選べます。どちらの場合もキーはそのリクエストのみに使われ、サーバー側には保存されません。
1
2
3
# Claude Code
claude mcp add --transport http space-ocr https://mcp.space-ocr.com/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"

Cursor / VS Code / Windsurf (mcp.json)

1
2
3
4
5
6
7
8
{
  "mcpServers": {
    "space-ocr": {
      "url": "https://mcp.space-ocr.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}
上記以外のクライアントでも、標準の Streamable HTTP エンドポイントなので同じ URL とヘッダーで接続できます。画像はサーバー側から取得できる https:// の URL か、base64 / data URI でお送りください。
#

ツール一覧

読み取り 3 つとワークスペース 10 個。課金は対応する REST ルートと同じで、読み取りと画像アップロードだけがクレジットを消費します。公開 URL ではない画像 — 手元のファイル、会話に添付された写真 — は space_inbox から入れます。
ocr_extract1 枚から名前付きフィールドを抽出。fields スキーマを指定するか autoFields で提案させます。1 クレジット
ocr_markdownレイアウトを保った Markdown。要素ごとに座標付き。1 クレジット
ocr_text読み順を復元したプレーンテキスト。ブロックごとに座標付き。1 クレジット
space_guideこのサーバーの使い方ガイドを読む (start / upload / schemas / verification / queries / workflows)。ネットワークもクレジットも使いません。無料
space_listツリーを閲覧 (GET /space)。無料
space_viewアイテムを読む。シートは where / sort / select / limit で問い合わせ (GET /view)。座標は boxes: true のときだけ返ります。無料
space_createフォルダ / シート / ドキュメント束 / メモを作成 (POST /create)。無料
space_inboxシートや束へのアップロード用リンクを発行。公開 URL ではない画像 — 手元のファイル、会話に添付された写真 — を入れる経路がこれです。リンクには有効期限があります。1 クレジット/枚
space_uploadすでに公開 https:// URL になっている画像を、シートや束に最大 20 枚アップロード (POST /upload)。1 クレジット/枚
space_job非同期アップロードのジョブを確認 (GET /jobs)。無料
space_editシートのセル値やメモ本文を修正 (POST /edit)。無料
space_balance残りの無料枠・プラン枠・残高 (GET /amount)。無料
space_deleteアイテムを削除 (POST /remove、フォルダは中身ごと)。2 段階です: confirm なしで呼ぶと何も削除せず、消える中身の件数と署名付きトークンだけを返します。ユーザーに見せて同意を得てから、そのトークンを付けて呼び直すと削除されます。ルートは拒否。無料

削除は 2 段階

space_delete を `confirm` なしで呼ぶと、何も削除せずに「消えるもの」だけが返ります: 対象、その下にあるフォルダー/シート/ドキュメント束/メモ/画像の件数、サンプル、そして `confirm` トークン。トークンは呼び出し側の API キーと対象パスに紐づく署名なのでモデル側で作れず、ユーザーに見せる段階を飛ばせません。同意を得たらそのトークンを付けて呼び直します。有効なのは 10〜20 分、ルートパスは常に拒否されます。
◆削除は元に戻せません。フォルダを消すと中の画像も一緒に消えます。
ツールのほかに、MCP のリソースとプロンプトも公開しています。リソース (space-ocr://guide/schemas · /verification · /queries · /workflows) は、スキーマの組み方・検証フラグの読み方・絞り込みの書き方・一括処理の進め方をまとめたもので、必要になったときだけ読み込まれます。プロンプト (file_documents · review_flagged · ask_documents) は定型の作業手順です。どちらも MCP の任意機能なので、対応はクライアントによります。