API のしくみ

つなぎ方

あなたのサービスを HANAUTA へつなぐための API です。 経路は3本あり、それぞれ鍵が違います — あなたのサーバーから叩くもの、 利用者の端末から叩くもの、HANAUTA から届くもの。 取り違えると危ないので、口ごとに「誰が叩くか」を貼ってあります。

あなたに書いていただくのは、第2節と第4節だけです。 第3節は撮影画面が自分で叩くもので、実装は要りません。

13本の経路

どの口も同じ土台(https://api.humatch.vows-match.com)にありますが、 認可の材料が違います。API キーを端末へ配らないこと、 撮影のトークンをサーバー間の用途に使わないこと — この2つが守れていれば、 経路は混ざりません。

経路叩くのはできること
API キー Authorization: Bearer <あなたの API キー> あなたのサーバー セッションを作る/状態を引く/画像を取る
セッショントークン URL に載っている1本(/s/{token} 利用者の端末(撮影画面) 撮影画面を開く/画像を上げる/提出する
署名 X-Kyc-Signature(HANAUTA が署名する) HANAUTA → あなたのサーバー 結果を届ける
API キーは端末へ配らないでください。 画像を取る口も、状態を引く口も、API キーでしか開きません。 だから利用者の端末からは、自分の審査結果にも他人の画像にも届きません。

2あなたのサーバーから叩く

POST /v1/verification_sessions API キー 本人確認をひとつ始める。撮影画面の URL が返る
POST /v1/verification_sessions
Authorization: Bearer <あなたの API キー>
Idempotency-Key: <任意>

{
  "assurance_level":     "document",      // age_only / document / identity_match
  "client_reference_id": "user_abc123",   // 256文字以内・個人情報は入れない
  "minimum_age":         18,              // 省略時 18
  "expires_in":          900,             // 秒。60〜604800(7日)・省略時 900
  "metadata":            { "plan": "take" }  // 任意。10キー・値は256文字以内
}
201 Created

{
  "id":         "vs_01JQ…",
  "object":     "verification_session",
  "status":     "requires_input",
  "url":        "https://…/s/<トークン>",   // これをアプリ内で開く
  "expires_at": "2026-09-15T12:15:00.000Z"
}

url に載るトークンは、平文で存在する唯一の場所です。 HANAUTA 側にはハッシュしか残りません。控えておかないと二度と出せません (そのときは新しいセッションを作り直します)。

返るもの

401 unauthorizedAPI キーが無い・不正
403 assurance_level_not_allowedご契約で許していない水準
400 invalid_request入力が不正(error.param がどの入力かを指す)
200Idempotency-Key の再送。同じセッションが返るが、URL のトークンは振り直される
409 idempotency_conflict予約だけ残ってセッションが消えている

expires_in が上限を超えたときは、黙って15分に丸めず 400 を返します。「7日のリンクを送ったつもり」が静かに壊れるのを避けるためです。

GET /v1/verification_sessions/{id} API キー いまの状態を引く。結果が届かなかったときの回復手段
200 OK

{
  "id":                  "vs_01JQ…",
  "object":              "verification_session",
  "status":              "verified",
  "assurance_level":     "document",
  "minimum_age":          18,
  "client_reference_id": "user_abc123",
  "created_at":          "2026-09-15T12:00:00.000Z",
  "expires_at":          "2026-09-15T12:15:00.000Z",
  "metadata":            { "plan": "take" },
  "first_opened_at":     "2026-09-15T12:01:10.000Z",  // 撮影画面を開いた時刻
  "submitted_at":        "2026-09-15T12:03:42.000Z",
  "reviewed_at":         "2026-09-15T15:42:00.000Z",
  "verified_outputs":    ["age_over_18", "document_valid"]
}

status は4つ

requires_inputまだ撮っていない(却下されて撮り直しに戻ったときも、ここへ戻る)
processing出し終わって、人の承認を待っている
verified人が承認した。verified_outputs が付く
canceled撮らないまま期限が切れた

却下されたときは requires_input に戻り、last_error に理由(category / code / message)が付きます。 他社のセッションは 404 です (403 にすると ID が存在することだけ漏れるため)。

機械の判定案はここに出ません。 読み取った生年月日・年齢・OCR の生テキスト・機械の指摘は、 そもそも外へ出す型に存在しません。出るのは人が承認した結果だけです。
POST /v1/verification_sessions/{id}/artifacts/{slot}/download_url API キー 提出された画像を取りに行く(front / back / selfie
200 OK

{
  "object":       "artifact_download",
  "slot":         "front",
  "url":          "https://<取得用のホスト>/…?Expires=…&Signature=…",
  "expires_at":   "2026-09-15T15:44:00.000Z",   // 120秒
  "content_type": "image/jpeg"
}

画像は結果に添えて送りません。取りに来てもらいます。 送りつけると、受け取るつもりの無いサーバーにも届いてしまい、 経路上のログにも乗るためです。

403 ip_not_allowed許可リストに無い IP(契約ごとに設定。空なら制限なし)
429 rate_limit_exceeded契約ごとのレート制限(既定 60回/分)
403 output_not_retrievable契約で取得を許していない種別
410 artifact_expired保持期間を過ぎて消えた
404 not_found他社のもの・無いセッション・一度も提出されていない

呼び出しは成功も失敗も1行ずつ監査ログに残ります(1年)。 記録が書けなければ URL を出しません。

3利用者の端末から叩く

ここはあなたが実装するものではありません。 HANAUTA が返した url をアプリ内の Web 画面で開けば、 以下は撮影画面が自分で叩きます。載せてあるのは、 何が起きているかを把握していただくためです。 API キーは要りません — 認可は URL に載っているトークン1本だけです。

GET /s/{token} トークン 撮影画面そのもの(HTML)
期限内で未提出200 撮影画面
期限切れ200「有効期限が切れました」
提出済み200「審査中です」
審査済み・取り消し済み200「この手続きは終了しています」
トークンが不正404(理由を書き分けない)

「一回限り」は1セッションにつき1本の意味で、 「1回開いたら無効」ではありません。撮り直しもリロードもあるので、 期限内なら何度でも開けます。

POST /v1/capture/{token}/upload-url トークン 画像の置き場への切符をもらう
{ "slot": "front", "content_length": 123456, "document_type": "residence_card" }

→ 署名付きの PUT 先(5分で切れる)と、付けるべきヘッダが返る

画像はサーバーのプログラムを通りません。 返した URL でブラウザが置き場へ直接送ります。 置き場の鍵と長さ・型は署名に含まれているので、 宣言と違うものを送ると弾かれます

種別ごとに、撮る面が決まっています

運転免許証表面のみ
マイナンバーカード表面のみ(裏面は個人番号なので受け付けません
在留カード表面 + 裏面(在留期間の更新履歴があるため)
パスポート顔写真ページのみ

この表は画面ではなくサーバーが持っています。2箇所に分けると、 画面には枠が出るのに切符が降りてこないという直しようのない状態になります。 その種別で撮らない面を求めると 403 slot_not_allowed です。

POST /v1/capture/{token}/detect トークン 自撮り中、枠に顔が収まっているかをその場で返す
{ "faces": [{ "x": 0.31, "y": 0.22, "w": 0.38, "h": 0.44, "score": 0.98 }] }

位置と大きさは画像に対する比率です。「枠に収まっているか」を決めるのは画面のほうで、 サーバーは事実だけ返します。画像は保存しませんし、この口は保存する権限を持ちません。 顔写真の照合が要る水準のときだけ動きます。

POST /v1/capture/{token}/submit トークン 提出する。ここから人の審査へ回る
202 Accepted

{ "object": "capture_submission", "status": "processing", … }
判定は返しません。 返すのは「受け付けた」だけです。 結果を利用者の端末経由であなたへ返す経路を、HANAUTA は持ちません — 端末を通した結果は、端末の持ち主に書き換えられるからです。

提出した時点でトークンは無効になります(撮影画面もアップロードも自動的に閉じます)。 必要な画像が置き場に無ければ 400 documents_missing で、 どの面が足りないかを返します。

4結果が届く(Webhook)

判定があなたへ渡る唯一の経路です。 登録していただいた webhook_url へ、HANAUTA から POST します。

POST <登録した webhook_url>
X-Kyc-Signature: t=1757308800,v1=5f2b8c…,v1=9a1d…
X-Kyc-Event-Id:  evt_01JQ8F5N2P3Q4R5S6T7U8V

{
  "id":         "evt_01JQ8F5N2P3Q4R5S6T7U8V",
  "type":       "verification_session.verified",
  "created_at": "2026-09-15T15:42:00.000Z",
  "data": { "object": { /* GET で返るものと同じ形 */ } }
}

届くのは4種

verification_session.processing出し終わって審査待ちになった
verification_session.verified人が承認した
verification_session.requires_input却下された、または HANAUTA 側の不調で撮り直しに戻った
verification_session.canceled撮らないまま期限が切れた

署名鍵の受け取り方

運用者が Slack で /ekyc-config →「署名鍵を生成」を押すと、10分・1回だけ開ける受け取りリンクが本人にだけ出ます。 鍵の値は Slack を通りません。2本まで並行して持てるので、入れ替えは止めずにできます(導入マニュアルの四)。

署名の確かめ方

署名の対象   "{t}.{受け取った本文の生バイト列}"
計算         HMAC-SHA256(あなたの署名鍵)の16進
ヘッダ       X-Kyc-Signature: t=<epoch秒>,v1=<hex>[,v1=<hex>]

届かなかったとき

直後・1分・5分・30分・2時間・6時間・24時間の7回、送り直します。 2xx を返せば成功です。

408 / 429送り直す(「今は無理」と読む)
それ以外の 4xx送り直さない(何度送っても直らないため)
3xx送り直さない(署名付きの本文を別のホストへ運ばないため)
5xx・応答なし・タイムアウト送り直す(1回あたり10秒で切ります)

7回使い切ったら諦めます。そのときの回復手段が GET /v1/verification_sessions/{id} です — だから両者は同じ形を返すように、1つの部品から作っています

署名鍵が1本も無い届け先と、https: でない届け先へは送りません。 署名は本文が途中で書き換えられていないことしか守らないので、 盗み見られる経路そのものは塞げないためです。

5守ってほしいこと

client_reference_idmetadata に個人情報を入れないでください。 氏名・メールアドレス・電話番号・住所のいずれも入れてはいけません。 HANAUTA が「持たない」と決めた個人情報が、参照 ID の形で恒久的に残ってしまいます (セッションは判定の記録として残り続けます)。 あなたの中でだけ意味を持つ、それ自体では何も分からない識別子をお渡しください。

機械では個人情報かどうかを見分けられないので、HANAUTA は長さで抑えるだけを行います (client_reference_id は256文字・metadata は10キー×256文字)。 守られる前提で、破ったときの被害の上限だけ切ってあります。

エラーはどれも同じ形で返ります

{ "error": { "code": "invalid_request", "message": "…", "param": "expires_in" } }

param は、どの入力が悪いかを指します。 存在しないものと、権限が無いものは書き分けません(どちらも 404) — 書き分けると、ID が存在することだけが漏れるためです。