第16巻 API契約定義(2) ― クロスボーダー送金・照会・継続収納
目次
第16巻 API契約定義(2) ― クロスボーダー送金・照会・継続収納
本巻は
docs/specs/32_api_contracts.mdのクロスボーダー送金〜OpenAPI仕様書まで(ZC Core APIの残り)を収める。前巻→第15巻の続き、続きは→第17巻。
クロスボーダー送金
POST /api/cross-border/send
クロスボーダー送金開始
Request:
{
"direction": "OUTBOUND",
"foreign_fps_id": "SGPAYNOW",
"foreign_bank_bic": "DBSSSGSG",
"foreign_account_id": "1234567890",
"foreign_currency": "SGD",
"foreign_amount": 100,
"domestic_amount": 10000,
"exchange_rate": 100.0,
"settlement_bank_id": "001",
"fatf_data": {
"originator": { "name": "...", "account_id": "...", "address": "optional" },
"beneficiary": { "name": "...", "account_id": "..." },
"ordering_institution": { "bank_id": "001", "bank_name": "...", "country": "JP" },
"beneficiary_institution": { "bank_id": "...", "bank_name": "...", "country": "SG" },
"is_cross_border": true,
"fatf16_applicable": true
},
"domestic_txid": "TX-..."
}
fatf_data の構造は src/types/primitives.ts#FatfR16Data を正とする(当事者は{name, account_id, address?, national_id?, date_of_birth?, place_of_birth?} の入れ子。
平坦な originator_name / beneficiary_name は受理しない)。検証はsrc/shared/fatf_validator.ts で、不備は水際で FATF_VALIDATION_FAILED / FATF_DATA_REQUIRED。
GET /api/cross-border/:cbTxid
クロスボーダー送金照会
POST /api/cross-border/:cbTxid/callback
外国FPSからのステータス更新
Request: { "status": "SETTLED|FAILED", "foreign_ref": "..." }
照会
GET /api/transactions/:txid
QueryResponse(30_internal_design.md §13.6 準拠)
Response:
{
"txid": "TX-...",
"state": "TxState",
"reason_code": "optional",
"decision": { "status": "NONE|DECIDED_TO_SETTLE|DECIDED_CANCEL", "decision_proof_ref": "optional" },
"execution": { "a": "NONE|OK|NG", "b": "NONE|OK|NG", "payer_bank_proof_ref": "optional", "payee_bank_proof_ref": "optional" },
"case": { "case_id": "optional", "status": "optional" },
"as_of": "RFC3339",
"watermark": 12345,
"watermark_detail": { "shards": { "TX:TX-2026-0001": 12345, "GT:GTID-7": 67890 } },
"freshness_level": "GREEN|YELLOW|RED",
"next_action_hint": "WAIT|RETRY_LATER|CONTACT_PAYER_BANK|OPEN_CASE",
"next_retry_at": "RFC3339 | null",
"public_message_id": "IGS_HOLD_YYYY-MM-DD (optional)",
"dns_settlement_status": "HOLD_ACTIVE (optional)",
"external_settlement": { "status": "IgsStatus", "retriable": true }
}
next_action_hintは閉じた 4 値(実装の正:src/types/api/transfers.tsのQueryResponse.next_action_hint:WAIT|RETRY_LATER|CONTACT_PAYER_BANK|OPEN_CASE)。事象固有の含意はreason_codeが担い、事象ごとに新しい hint 値を作らない(30_internal_design.md§13.6)。freshness_levelの閾値・測定基準および現行実装との差分は30_internal_design.md§13.6 を正とする。watermarkはwatermark_detail.shardsの最大値、watermark_detail.shardsは当該取引に関する事実を
載せている各チェーンのMAX(event_seq)。キー書式(TX:/GT:/DNS:)と「参加していて未記帳の
チェーンは 0 を返す」規範は30_internal_design.md§13.6 を正とする。public_message_id/dns_settlement_statusは清算サイクルが HOLD 中のときだけ付与される
(20_method_design.md§9.4.4.1)。2 つの場合を混同しないこと:
(A) IGS リングフェンスで止まっている HIGH_VALUE は取引自体が未完了なのでpublic_message_id
(IGS_HOLD_{business_date})を伴う。
(B) 通常レーンの取引が既にSETTLEDで、行間のネット清算だけが HOLD の場合はdns_settlement_status: "HOLD_ACTIVE"を参考情報として付すのみで、取引未完了として表示しては
ならない。external_settlementは中銀決済まで到達した取引にのみ付く(external_settlement_status != NONE)。statusの値域(実装の正:src/types/states.ts#IgsStatus:REQUESTED|SETTLED|FAILED|HOLD|TIMEOUT)。retriableは「待てば進みうるか」で、HOLD/TIMEOUTがtrue。reason_codeは HOLD と不成立を区別しないため、窓口の分岐はこのフィールドで行う
(§状態 reason_code の注意、20_method_design.md§9.4.4.1 (A))。中銀の生の失敗理由は
相手方の不足を名指しし得るので運ばない(閉域はhold_detail)。- 応答には上記のほか、UI 補助フィールド(
lane/amount_value/ 当事者 ID 等)が付随する。これらは QueryResponse 契約の一部ではなく、契約の対象は上記フィールドのみである。
GET /api/transactions
取引一覧
Query: ?state=...&payer_bank_id=...&payee_bank_id=...&lane=...&limit=50&offset=0
GET /api/transactions/:txid/events
取引イベントログ照会
GET /api/transactions/:txid/explain
人間可読な状態遷移サマリー + 改ざん検知付き。FinalityLog を辿って
イベントごとに日本語の reason / actors を付与し、integrity.chain_verified
で同 TX のハッシュチェーン健全性を返す。
Response(抜粋):
{
"txid": "TX-...",
"lane": "EXPRESS",
"current_state": "SETTLED",
"summary": "送金は正常に最終確定しました",
"timeline": [
{ "seq": 1, "at": "...", "event": "PaymentInitiated",
"state_from": null, "state_to": "RECEIVED",
"reason": "送金リクエストを受け付けました", "actors": ["ZC"], "payload": {} },
...
],
"integrity": {
"chain_verified": true,
"entries_checked": 7,
"break_at_seq": null,
"break_reason": null,
"algorithm": "SHA-256 hash-chain v2"
},
"proofs": { "decision_proof_ref": "...", "payer_bank_proof_ref": "...",
"payee_bank_proof_ref": "...", "finality_log_ref": "..." }
}
GET /api/transactions/:txid/story
/explain の構造化データに加えて、ナラティブ段落 + Mermaid sequenceDiagram +
健全性ヴァーディクト(OK | WATCH | STUCK | TERMINAL)を返す。オペレータが
1 件の TX を画面でレビューする用途。
Response(抜粋):
{
"txid": "TX-...",
"headline": "[EXPRESS] 001 → 002 の ¥5,000 は最終確定済み",
"narrative": "09:00:01 JST に 001 から 002 への ¥5,000 の取引(EXPRESS)が動き出しました。...",
"mermaid_sequence": "sequenceDiagram\n autonumber\n ...",
"pacing": {
"started_at": "...", "last_event_at": "...", "elapsed_ms": 123,
"longest_gap": { "from_event": "...", "to_event": "...", "gap_ms": 80 }
},
"health": { "status": "TERMINAL", "message": "...", "next_expected": [] },
"integrity": { "chain_verified": true, "entries_checked": 7 }
}
health.status = STUCK(最後のイベントから 60 秒以上経過し、まだ終端状態に
到達していない)が返ったら運用調査の合図。
GET /api/transactions/:txid/verify
TX チェーンのハッシュチェーン全件検証+副署必須化ポリシーの充足判定。
Response:
{
"chain_id": "TX-...",
"valid": true,
"entries_checked": 7,
"break_at_seq": null,
"break_reason": null,
"algorithm": "SHA-256 hash-chain v2",
"cosign": {
"chain_id": "TX-...",
"chain_kind": "TX",
"required": true,
"min_cosigners": 2,
"cosign_count": 2,
"satisfied": true,
"basis_kind": "IRREVERSIBILITY",
"basis_entry_hash": "…"
},
"finality_confirmed": true
}
valid: false のとき break_reason はLEGACY_UNCHAINED_ENTRY | PREV_HASH_MISMATCH | ENTRY_HASH_MISMATCH のいずれか。finality_confirmed は ハッシュチェーン健全(valid)かつ副署必須化ポリシー充足
(cosign.satisfied) のときだけ true。当該チェーン種別(TX/GTID/DNS)に
mandatory ポリシーが設定されていなければ cosign.required=false で常に充足扱い。
規範(副署は「基準エントリ」に対して行う。現 tip に対して行ってはならない)
副署の対象は動かないエントリでなければならない。定足数とは「相異なる k 者が
同一のハッシュに署名した」ことであり、tip は通常の業務追記のたびに動くから、
tip に署名させると 2 人目は別のハッシュに署名することになり、どの単一ハッシュに対する
計数も 1 を超えられない。すなわちmin_cosigners >= 2の mandatory ポリシーは原理的に充足不能になる。
したがって基準エントリ(basis entry)を次の優先で決める(
resolveCosignBasis,
src/zc/finality/finality_anchor.ts)。
- 当該チェーンの不可逆点を記録したエントリ——TX は
PAYEE_EXEC_CONFIRMED(b)、
GTID はGT_SETTLED、DNS はサイクルのSETTLED。- 無ければ、直近アンカーが固定した当該チェーンの tip(
FinalityAnchor.chain_tips_json)。- どちらも無ければ
COSIGN_BASIS_NOT_FOUND。tip へフォールバックしてはならない
——上の欠陥をそのまま呼び戻すためである。応答の
cosignはbasis_kind(IRREVERSIBILITY/ANCHOR)とbasis_entry_hashを併せて返す。参加行が何に署名すべきかは、この値が唯一の出所である。
この帰結として
finality_confirmedは単調である。 基準エントリは動かないので、k 者が署名し終えた後にチェーンへ追記があっても
cosign_countは 0 に戻らない。本節はかつて「tip が動けば
cosign_countは 0 に戻る。これは実装上の欠落ではなく意味論の帰結である」と書いていたが、これは誤りであった——「副署は署名した時点までの
履歴を覆う」という意味論は正しく、そこから導かれるのは「署名対象を履歴上の固定点に
取る」ことであって、「毎回 0 に戻る」ことではない。どの固定点を制度上の「外部確定」と
みなすか(不可逆点のエントリか日次アンカーか)は上記 1/2 の優先として本節が定める。
finality_confirmed=falseを「ファイナリティが取り消された」と読んではならない。決済の不可逆性(b)を表すのは取引の
stateであり、本フィールドは「外部検証者に提示できる副署が、基準エントリに対して揃っているか」を表す。
GET /api/gtid/:gtid/verify
GET /api/dns/:cycle_id/verify
GTID/DNS チェーン専用の検証。Response 形は /transactions/:txid/verify
と同形(chain_id に gtid/cycle_id が入る)。
POST /api/finality/cosign
当該チェーンの当事者参加行(TX=payer/payee、GTID=leg 銀行、DNS=ネットポジション
銀行)が、チェーンの基準エントリ(上記 /verify の規範。cosign.basis_entry_hash
で取得する)に副署する。署名は KeyRegistry(owner_type='PARTICIPANT')で検証。Body:
{ "chainId": "DNS-2026-06-30", "participantId": "002", "signerKeyId": "KEY-002",
"nonce": "n-002", "occurredAt": "2026-06-30T07:30:00.000Z", "signatureB64": "..." }
署名対象は {chain_id, entry_hash}(entry_hash = 基準エントリ)。ZC 側でもresolveCosignBasis で同じ値を解決するため、tip に対する署名は検証に通らない。
失敗時は COSIGN_NOT_APPLICABLE | COSIGN_ENTRY_NOT_FOUND | COSIGN_BASIS_NOT_FOUND | COSIGN_PARTICIPANT_MISMATCH ほか KEY_* / EXTERNAL_SIGNATURE_INVALID /SIGNATURE_REPLAYED / TIMESTAMP_SKEW。
PUT/GET /internal/cosign-policy/:kind (:kind = TX | GTID | DNS)
副署必須化ポリシーの設定/参照(運用 API、X-Cron-Secret 必須)。
PUT Body: { "min_cosigners": 2, "is_mandatory": true }。mandatory を設定すると、
その種別のチェーンは required 数の相異なる参加行が基準エントリに副署するまで/verify の finality_confirmed が false になる。
GET /api/events
全体イベントログ(最近N件)
Query: ?limit=100&offset=0
Index: idx_fl_occurred_at。
GET /api/gtid/:gtid
GTID照会
GET /api/gtid
GTID一覧
Query: ?limit=20&offset=0
GET /api/gtid/:gtid/events
GTIDイベントログ照会
GET /api/htlc/:htlc_id
HTLC照会
GET /api/htlc
HTLC一覧
Query: ?limit=50&offset=0
GET /api/dns/:business_date/status
DNS状態照会
→ { "state": "OPEN|KICKED|SETTLED|HOLD_ACTIVE", "igs_mode": "NORMAL|STOP|RINGFENCED|RINGFENCED_PLUS", "cycle_id": "...", "business_date": "YYYY-MM-DD", "public_message_id": "DNS_HOLD_YYYY-MM-DD|null" }
stateの値域(実装の正:src/types/states.ts#DnsState:OPEN|KICKED|SETTLED|HOLD_ACTIVE)。- 当該営業日のサイクルがまだ生成されていない場合のみ
{ "state": "NOT_STARTED", "business_date": "..." }を 200 で返す(NOT_STARTEDはDnsStateの値ではなく、「行が無い」ことを 404 ではなく状態として返すための応答専用の特例値。実装src/zc/query/query.ts#handleGetDnsStatus)。 igs_modeの値域(実装の正:src/types/states.ts#IgsMode:NORMAL|STOP|RINGFENCED|RINGFENCED_PLUS)。RECALC_EXCLUDING_BANKは本応答の値ではない(中央銀行が返す清算結果の種別。20_method_design.md§9.4.4 の規範)。public_message_idは HOLD 中のみ非 NULL(DNS_HOLD_{business_date})。参加行はこの ID に
対応する事前承認テンプレにのみ顧客表示を限定する(10_requirements.md§3.3.1-3/-4)。
本応答は全参加主体向けであり、原因行・不足額は決して含めない——含まれるのは
「HOLD である」という公式ステータスと、それに対応するテンプレ ID だけである。
GET /api/dns/:business_date/position
参加行ネットポジション照会
→ { "business_date": "YYYY-MM-DD", "positions": [ { "cycle_id": "DNS-...", "bank_id": "001", "net_position": -1200000, "gross_send": ..., "gross_receive": ..., "is_settled": 0 }, ... ] }
(net_position は +受取/−支払)
フィールド名の正(規範):ネットポジションの項目名は 本節を正とする(
net_position/gross_send/gross_receive)。20_method_design.md§9.4.4 は「ネットポジション」「グロス送信/受信」という業務語で規範を述べ、項目名は本節を参照する形に整理してある。未充足:
20_method_design.md§9.4.4 (A) は本照会にas_of/watermark(鮮度)の付与を規範として求めているが、本契約は付与していない。同節の規範は「事務が回るための必須要件」として書かれているため、この差分を充足済みとして扱ってはならない(30_internal_design.md第10章 Roadmap で追跡)。
GET /api/dns/:business_date/hold_detail
DNS_HOLD の閉域詳細照会。閉域認可を持つ主体(当該の負け参加行・監督当局・中央銀行・ZC運営)
のみが呼べる(20_method_design.md §9.4.4 (B) が「事務が回るための必須要件」として規範化)。
リクエストヘッダ:
X-Purpose-Code: P01|P02|P03|P04|P05|P06|P07 # 必須(`10_requirements.md` §3.3.2.2.1)
X-Bank-Id: 001 # 参加行スコープ(当該の負け参加行のみ)
X-Cron-Secret: <CRON_SECRET> # 運営スコープ(ZC運営・監督当局・中央銀行)
- 認可が無い呼び出しは
403ではなく404 NOT_FOUNDを返す。HOLD が発生しているか
どうか自体が閉域情報であり、403は「無い」と「見せない」を区別してしまう——
「あの行は今日ショートしているのか」は取り付けの引き金そのものである。 - 次の 4 つはすべて同一の 404 本文を返し、応答から HOLD の有無を推測できないようにする:
目的コード欠落/呼び出し主体不明/当該営業日に HOLD 無し/当事者でない参加行。 - 目的コードの無い呼び出しは実時間で遮断し、
DataAccessViolationDetectedを GLOBAL チェーンへ
記録する(10_requirements.md§3.3.2.2.1.1-2。事後監査ではなく遮断が規範)。 - 認可された読み取りも
ClosedDomainAccessGrantedとして記録する。ZC が提供する中で最も機微な
読み取り(原因行の特定情報と不足額)であるため、拒否だけでなく許可も監査対象とする。 - スコープ差: 参加行は自行の不足額のみ、運営スコープはサイクル全体の合計を得る。
collateral_call_amountは不足額に復旧リザーブと同じバッファ率
(DNS_RECOVERY_RESERVE_BUFFER_RATE)を乗じた値。「何を差し入れるべきか」と
「ZC がこの HOLD に対して見込むリザーブ」を 1 つのバッファ率から導き、二重管理を避ける。- 実装:
src/zc/query/query.ts#handleGetDnsHoldDetail・src/zc/settlement/dns/query.ts#getDnsHoldDetail、
認可プリミティブはsrc/zc/platform/purpose.ts。
Response:
{
"business_date": "2026-06-30",
"cycle_id": "DNS-JPY-20260630-01",
"shortfall_amount": 1200000000,
"collateral_call_amount": 1500000000,
"recommended_actions": ["MARKET_FUNDING", "LENDING_REQUEST", "COLLATERAL_PLEDGE"],
"contact_channel": "...",
"as_of": "RFC3339"
}
規範: 本エンドポイントの応答は閉域情報であり、
GET /api/dns/:business_date/status(全参加主体向け・公式ステータスと完全一致)には決して含めない
(
10_requirements.md§3.2.5.1・20_method_design.md§9.4.4)。
GET /api/boj/positions
各参加行の日銀預け金勘定(BOJ)残高照会(公開API・プリファンド型RTGSの残高モニタ、
プリファンド型RTGSの残高可視化)。
→ { "positions": [ { "bank_id": "001", "boj_balance": 100000000000 }, ... ], "as_of": "RFC3339" }
(同等データの運用内部版は GET /internal/boj-positions。そちらは as_of を付けない)
GET /api/cases/:case_id
CASE照会
POST /api/cases/:case_id/update
CASE状態更新
Request: { "state": "IN_PROGRESS|RESOLVED|ESCALATED" }
- 値域の正は実装の
src/zc/cases/case.ts#CASE_UPDATE_STATES。それ以外は400 INVALID_STATE。 OPENは受け付けない。CASE が生まれる状態であり、そこへ戻すと「いつ開いたか」が曖昧になる——
解決後に再び作業が要る事象は、同じ txid に紐づく新しい CASE として起票する
(b 成立後の救済を別取引で行うのと同じ理由。10_requirements.md§4.3)。
GET /api/system-mode
ZC全体の運用モード照会(縮退モード)
→ { "mode": "NORMAL|BCP_READONLY|QUORUM_LOSS_READONLY", "reason": "...|null", "activated_at": "...|null", "updated_at": "..." }
mode の値域(実装の正: src/types/states.ts#SystemModeValue: NORMAL | BCP_READONLY | QUORUM_LOSS_READONLY)。
縮退モードは2種類。いずれも新規の資金移動(状態確定)を拒否し、照会系は影響を受けない。
BCP_READONLY:運用者が宣言するベンダー障害縮退(可搬性と縮退)。bcp-activate/bcp-deactivateで操作。QUORUM_LOSS_READONLY:設計原則10の自動縮退。単一正本性を担保する合意ログが quorum を喪失した際、誤決定を避けるため自動的に read-only へ縮退し、quorum 回復で自動復帰する。運用者トグルでは解除できない(bcp-deactivateは当モード中はSYSTEM_QUORUM_LOSS_READ_ONLYで拒否)。
POST /internal/system-mode/bcp-activate
BCP_READONLY(ベンダー障害縮退モード)へ移行。冪等。
Request: { "reason": "Cloudflare regional outage" }
POST /internal/system-mode/bcp-deactivate
NORMAL へ復帰。冪等。QUORUM_LOSS_READONLY 中は拒否(quorum 回復まで縮退を維持)。
POST /internal/system-mode/quorum-report
合意ログのレプリカ到達性を報告し、ZC のモードを quorum 健全性と整合させる(設計原則10)。
運用の健全性監視がレプリカ集合の到達状況を POST し、ZC は quorum 喪失で QUORUM_LOSS_READONLY へ縮退、回復で NORMAL へ復帰する。運用者宣言の BCP_READONLY は上書きしない。
Request: { "reachable": ["tokyo", "osaka"] }(省略時はメンバ全到達=健全とみなす)
→ { "health": { "total": 3, "reachable": 2, "required": 2, "hasQuorum": true, ... }, "mode": {...}, "action": "DEGRADED|RESTORED|NO_CHANGE" }
メンバ集合は ZC_QUORUM_REPLICAS(カンマ区切り、未設定時は3レプリカ既定)。quorum は厳密過半数(floor(N/2)+1)。
GET /internal/system-mode
現在の運用モード照会(運用ダッシュボード用)。
SSE(Server-Sent Events)
GET /api/sse/events/:bankId
銀行宛リアルタイムイベントストリーム
レスポンス: text/event-stream 形式。EventStreamテーブルから未配信イベントをポーリング。
IGS
POST /api/igs/callback
日銀ネット即時グロス清算のコールバック
Request:
{
"ext_instruction_id": "...",
"status": "SETTLED|FAILED",
"boj_settle_ref": "optional",
"failed_reason": "optional"
}
先進的アーキテクチャ実験(Advanced Features)
GET /api/stream/connect
Rafiki風 ストリーミング・マイクロ決済 (WebSocket)
接続確立後、{ "type": "START", "gtid": "..." } を送信し、{ "type": "PACKET", "amount": 10 } 等を複数回送信可能。
一定間隔のDO AlarmによってD1にまとめてStateが記録される。
GET /api/als/lookup
Mojaloop風 O(1) エイリアス解決ディレクトリキャッシュ
Query: ?alias=phone:090xxxx
Response: { "bank_id": "001", "account_hash": "...", "pspr_ref": "..." }
Reversal(救済取引)
SETTLED 済み TX に対する苦情・誤送金・二重送金などへの補償フロー。
起票可否の判定は 10_requirements.md §4.3.0(Reversal の三層ゲート)、API と reason 区分は同 §4.3.1、データ構造は 31_schema.md § ReversalRecords。
POST /api/reversals
補償取引を起票する。一部の reason は approval_ref 必須(社内統制の事前
承認チケット番号など)。受理されると lane=STANDARD, purpose=REFUND の
新規 TX が生成される。
Request:
{
"original_txid": "TX-...",
"amount": 5000,
"reason": "ReversalReason",
"requested_by": "001 (bank_id) | OPS",
"approval_ref": "string (required when reason ∈ APPROVAL_REQUIRED_REASONS)",
"description": "optional"
}
reason の値域(実装の正: src/zc/cases/reversal.ts#ReversalReason: CUSTOMER_DISPUTE |DUPLICATE_PAYMENT | INCORRECT_AMOUNT | INCORRECT_PAYEE | FRAUD | OPERATIONAL_ERROR)。
意味は 10_requirements.md §4.3.1 を正とする。値をここで散文的に例示しない——かつて本節はWRONG_RECIPIENT という実在しない値を例に挙げ続けており(10_requirements.md 側では既に
除去済みだった)、宣言の書式に寄せることで CI の値域照合の対象になる。
Response 201:
{
"result": "REVERSAL_CREATED",
"reversal_id": "REV-...",
"reversal_txid": "TX-...",
"status": "TX_CREATED"
}
Response 422 の reason_code:
CREDIT_FAILED_PROOF_REQUIRED— 第1層未充足。POST /api/transfers/:txid/credit-failed-proof
が未提出。この場合、要求は握り潰さず CASE として受理し、応答にcase_idを返す
(10_requirements.md§4.3.0-1「満たさない顧客異議は Reversal ではなく CASE として受理し、
当事者間の解決へ接続する」)。顧客異議は実在する事象であって、それ自体は巻き戻しの根拠ではない。APPROVAL_REF_REQUIRED— 第2層(内部統制版)未充足。APPROVAL_REQUIRED_REASONSに該当するreasonにapproval_refが無い。ORIGINAL_NOT_FOUND/ORIGINAL_NOT_SETTLED/INVALID_REVERSAL_AMOUNT/OVER_REVERSAL。
検査順(規範): 金額の妥当性・累計超過は第1層ゲートより前に評価する。逆順にすると、
単なる入力ミスのたびに CASE が起票され、誰かがそれを閉じる仕事が増える。ゲートは AND なので
順序は結果を変えないが、運用負荷は変える。
GET /api/reversals/:reversal_id
特定 Reversal の状態と関連 TX を返す。
GET /api/transactions/:txid/reversals
ある TX に紐づく Reversal 一覧。
Circuit Breaker(参加行疎通監視)
ZC→Bank 呼び出しの連続失敗に対するブレーカー。状態は CLOSED → OPEN → HALF_OPEN → CLOSED。
詳細は 31_schema.md § CircuitBreakerState / 10_requirements.md(制度・ガバナンス要件)。
GET /api/circuit-breaker
全行のサーキットブレーカー状態とメトリクスを一覧。
Response:
{ "circuit_breakers": [
{ "bank_id": "001", "state": "CLOSED", "consecutive_failures": 0,
"total_requests": 1234, "total_successes": 1200, "total_failures": 34,
"total_denied": 0, "half_open_inflight": 0,
"last_success_at": "...", "last_failure_at": "..." },
...
] }
GET /api/circuit-breaker/:bank_id
特定行のサーキットブレーカー状態。未登録(メトリクスがまだ無い)行はstate: CLOSED の初期値が返る(404 ではない)。
POST /api/circuit-breaker/:bank_id/reset
強制 CLOSED へリセット(運用オペレーション)。
Response: { "result": "RESET", "bank_id": "..." }
継続収納(口座振替)
制度要件は 10_requirements.md §3.2.8、処理方式は 20_method_design.md §2.2.7、スキーマは 31_schema.md § ZC テーブル(継続収納)。
呼び出し主体は常に参加行である。 受取人(収納事業者)は ZC の参加者ではなく受取行の顧客であり、収納の起票も結果の受領も受取行を経由する(10_requirements.md §3.2.8.8-4)。ZC が受取人と直接やり取りする経路は設けない。
POST /api/debit-mandates
継続収納契約の登録。顧客が buildMandatePayload の正規ペイロードに署名し、KeyRegistry で検証する(registerMandate と同じ経路)。
Request:
{
"payer_bank_id": "002",
"payer_account_alias": "tel:+81-90-...",
"payee_bank_id": "001",
"payee_account_hash": "h:...",
"product_ref": "◯◯ゴールドカード ****1234",
"charge_mode": "PERIODIC",
"period_cycle": "MONTHLY",
"collection_mode": "SCHEDULED",
"notice_days_min": 14,
"amend_freeze_hours": 33,
"ladder_max": 3,
"caps": {
"per_collection": 30000, "month_amount": 30000, "month_count": 2,
"two_month_amount": 50000, "lifetime_amount": null, "lifetime_count": null,
"pending_amount": 60000, "pending_count": 4,
"latefee_month": 500, "latefee_rate_max": 0.146,
"realtime_month_count": 0, "variance_ratio_max": 1.5
},
"eligibility_attestation": { "...": "受取行が署名した適格性証明" },
"mandate": { "principal_key_id": "KEY-...", "nonce": "...", "occurred_at": "RFC3339",
"signature": "base64", "valid_from": "RFC3339", "valid_to": "RFC3339" },
"idempotency_key": "string"
}
Response: { "result": "REGISTERED", "dd_mandate_id": "DDM-...", "mandate_id": "MANDATE-...", "effective_collection_mode": "SCHEDULED", "demoted": false }
effective_collection_modeは払出行のプロファイルから導出する。 要求モードを提供できない行(Tier 1 等)では自動降格し、demoted: trueとdemotion_reasonを返す(10_requirements.md§3.2.8.2-4)。黙って挙動を変えない。- 認可期限は必須ではない。 無期限は
valid_toの番兵値で表現する(10_requirements.md§3.2.8.1-4)。 - 宣言した上限が制度上限(
PR-DD-*)を超える場合は422 CAP_EXCEEDS_POLICY。 collection_mode='REALTIME'は既定で不許可(422 REALTIME_NOT_PERMITTED)。開放は制度判断による。
エラー: 401 MISSING_SIGNATURE / INVALID_SIGNATURE、409 KEY_REVOKED、422 CAP_EXCEEDS_POLICY / LADDER_MAX_EXCEEDS_POLICY / REALTIME_NOT_PERMITTED / MODE_UNSUPPORTED_BY_PAYER_BANK。
PATCH /api/debit-mandates/:dd_mandate_id/caps
上限の変更。方向により要件が非対称である(10_requirements.md §3.2.8.4-10)。
| 方向 | 顧客署名 |
|---|---|
| 引き下げ | 不要 |
| 引き上げ | 必要(実質的に新たな委任であるため) |
Request: { "caps": { ... }, "mandate"?: { 引き上げ時のみ必須の署名一式 }, "idempotency_key": "string" }
Response: { "result": "CAPS_UPDATED", "dd_mandate_id": "DDM-...", "raised": ["month_amount"], "lowered": ["month_count"] }
- 消費済みカウンタはリセットしない(引き上げ→引き下げの往復による消費の洗浄を防ぐ)。
- 引き上げに署名がなければ
401 SIGNATURE_REQUIRED_FOR_RAISE。
DELETE /api/debit-mandates/:dd_mandate_id
契約の失効。顧客・受取行のいずれからも可能(顧客に不利益が生じないため)。冪等。
Response: { "result": "REVOKED", "dd_mandate_id": "DDM-...", "revoked_at": "RFC3339", "superseded_collections": 2, "already": false }
失効は以後の解錠を全滅させる。未確定の予告は SUPERSEDED ではなく LAPSED として終端し、件数を返す。
GET /api/debit-mandates/:dd_mandate_id
契約単位の照会。未来の予定を含む(10_requirements.md §3.2.8.8、G6)。
Response: 契約内容・累計枠の消費状況(残枠つき)・過去の収納履歴・次回以降の予定(ラダー全段)。当事者判定と目的コードは照会の認可に従う。
GET /api/debit-mandates?payer_account_alias=...
顧客の「私が許可している引き落とし一覧」。実物の口座振替が提供しない可視性であり、本制度の固有の価値にあたる。
POST /api/collections
収納予告の登録。ラダーの全段を一括で登録する。予告を経ない収納は存在しない。
Request:
{
"dd_mandate_id": "DDM-...",
"charge_ref": "2026年4月分",
"edi_ref": "EDI-...",
"rungs": [
{ "ladder_seq": 1, "amount": 9800, "latefee": 0, "due_date": "2026-04-27" },
{ "ladder_seq": 2, "amount": 9800, "latefee": 50, "due_date": "2026-04-28" },
{ "ladder_seq": 3, "amount": 9800, "latefee": 150, "due_date": "2026-05-13" }
],
"idempotency_key": "string"
}
Response:
{ "result": "COLLECTION_NOTICED",
"collections": [ { "collection_id": "COL-...", "ladder_seq": 1, "state": "SCHEDULED",
"due_date": "2026-04-27", "amend_freeze_at": "2026-04-26T15:00:00+09:00",
"confirm_deadline_at": "2026-04-28T00:00:00+09:00" } ],
"budget_reserved": true }
- 全段が予告時点で顧客に開示される(
10_requirements.md§3.2.8.5-5)。再請求の時期と金額を顧客が事前に知り得ることが本制度の要件である。 - 遅延損害金は元本と分離して登録する(
latefee)。総額への溶かし込みは受理しない。段ごとに固定額であり、計算式は受け付けない。 - 累計枠はこの時点で予約される(
10_requirements.md§3.2.8.4-1)。枠は資金ではなく認可の配分であるため、予告時点で押さえても顧客は何も失わない。 - 第 2 段以降を
REALTIMEにできない(10_requirements.md§3.2.8.5-7)→422 LADDER_RUNG_CANNOT_BE_REALTIME。 - スコープ超過は却下せず、当該段を
AWAITING_ADDITIONAL_AUTHとして返す(stateを見ること)。
エラー: 404 DD_MANDATE_NOT_FOUND、409 CHARGE_REF_ALREADY_COLLECTED(当該費目は既に CONFIRMED_OK)、422 BUDGET_RATE_EXCEEDED / BUDGET_EXHAUSTED / NOTICE_PERIOD_TOO_SHORT / LADDER_MAX_EXCEEDED / LATEFEE_EXCEEDS_POLICY / CHARGE_REF_INVALID(PERIODIC の構造検証違反・PR-DD-PERIOD-AHEAD-MAX 超過)。
PATCH /api/collections/:collection_id
予告の変更。凍結が止めるのは不利益変更のみである(10_requirements.md §3.2.8.5-3)。
| 操作 | 凍結前 | 凍結後 |
|---|---|---|
| 減額・予定日の後ろ倒し | 可 | 可 |
| 増額・予定日の前倒し | 可 | 不可(422 FROZEN_UNFAVOURABLE_CHANGE) |
変更は上書きせず追記する(CollectionAmended を FinalityLog へ)。「先月いくらで予告されていたか」が消えてはならない。
DELETE /api/collections/:collection_id
予告の取下げ(WITHDRAWN)。顧客に有利な変更であるため凍結後も常に可能。他手段での入金があった場合の正当な運用経路である。予約済みの累計枠は解放する。
GET /api/collections/:collection_id
収納の状態照会。
Response:
{ "collection_id": "COL-...", "charge_ref": "2026年4月分", "ladder_seq": 1,
"state": "FIRED", "settlement_status": "ACCEPTED", "result": null,
"confirmed": false, "confirm_deadline_at": "2026-04-28T00:00:00+09:00",
"retriable_today": true,
"attempts": [ { "attempt_no": 1, "observed_at": "...", "result": "NG",
"reason_code": "INSUFFICIENT_FUNDS", "retriable_today": true } ],
"txid": "TX-..." }
settlement_statusの語義(規範):ACCEPTEDは受理・照合済みであって収納完了ではない。この値で売掛の消し込みを行ってはならない。confirmed: falseを必ず併せて返すのは、ACCEPTEDを成功と読み違えた実装が消し込みを走らせる事故を防ぐためである。確定はCONFIRMED_OK/CONFIRMED_NGのみが表す(10_requirements.md§3.2.8.6-8)。他手段との二重収納の防止は受取人の責任であり、ZC の債務はこの信号の品質に限られる。
retriable_today は当日中の再挑戦余地の有無。ACCOUNT_NOT_FOUND 等は当日の入金では解決しないため、受取人が督促対象を絞れるようにする。督促そのものは ZC の外側で行われる。
POST /api/collections/:collection_id/additional-auth
追加認可(再許諾)。スコープ超過で AWAITING_ADDITIONAL_AUTH にある収納を、顧客の署名により通す。
Request: { "decision": "APPROVE" | "DECLINE", "mandate"?: { 単発認可の署名一式 }, "idempotency_key": "string" }
Response: { "result": "APPROVED"|"DECLINED", "collection_id": "COL-...", "state": "SCHEDULED"|"DECLINED_BY_PAYER", "extra_mandate_id": "MANDATE-..." }
- 既定は単発認可(当該
charge_ref・当該金額・1 回限りのスコープ)。恒久的な引き上げを望む場合はPATCH /api/debit-mandates/:id/capsを用いる。 - 単発認可は枠を迂回せず、枠に加算する(
10_requirements.md§3.2.8.7-4)。 - 無応答は拒否である。 振替日までに応答がなければ
LAPSEDとなり、ラダー全体が終了する(後続段は発火しない)。この既定を反転させてはならない——沈黙を同意とみなすと本機構自体が過大請求の経路となる。
GET /api/directory/banks/:bankId/collection-profile
払出行の勘定系プロファイルの公開ビュー。公知情報(銀行の取扱時間は約款等で公表される運用事実)であり、受取人が「この行の顧客なら何時までに入金してもらえば間に合うか」を自ら算定できるようにする。
Response: { "bank_id": "002", "supported_modes": ["SCHEDULED","SCHEDULED_LONG"], "center_cut_schedule": ["00:00","12:00","20:00"], "realtime_name_check": true, "last_attempt_guidance": "20:00" }
収納ごとの状態ではなく静的な参照データである。LegacyProfile の window_open_hour / window_close_hour / realtime_name_check から導出する。
管理・設定
POST /api/pspr/register
PSPR登録
Request: { "pspr_ref": "...", "payee_bank_id": "001", "account_hash": "h:...", "expires_at": "RFC3339" }
POST /api/participants/register
参加行登録(初期投入用)
Request: { "bank_id": "001", "bank_name": "長岡銀行", "ingress_base_url": "/bank/001", "h_limit": 100000000 }
GET /api/banks
参加行一覧
POST /api/banks/add
参加行追加(シミュレーター用: 銀行+システム勘定を一括作成)
Request: { "bank_id": "003", "bank_name": "加賀銀行", "h_limit": 100000000, "participant_type": "GOVERNMENT" }
participant_type(省略可):"BANK"(既定)|"GOVERNMENT"。給付発起参加者
の類型化。不正な値は400 INVALID_INPUT。
DELETE /api/banks/:bankId
参加行削除
GET /api/banks/:bankId/accounts
参加行の口座一覧
GET /api/accounts/:accountId/name
口座名義照会
OpenAPI仕様書
GET /api/openapi/zc.yaml
ZC APIのOpenAPI仕様書(YAML)
GET /api/openapi/bank.yaml
Bank APIのOpenAPI仕様書(YAML)