この記事でできること
前回の記事「Claude Codeで銀行APIを実行する(実践編ー基礎ー)」の最後に、次はこれを試すと書きました。参照専用ポリシーのエージェントに振込を頼むとどう断られるか。金額の上限つきエージェントを作ると、どこまで通るか。 その答え合わせです。
4つの実験で確かめました。同じ「法人口座へ500円振込して」という頼み方で、参照専用エージェントは断り、上限1,000円のエージェントは受け付ける。同じエージェントでも2,000円にすると断る。変えたのは環境変数1行だけです。
読み終えると、AIエージェントに銀行口座を触らせるとき、どこにどう縛りを入れられるかが実行結果つきで分かります。
- 対象読者:準備編・基礎編を読んでトークンとエージェントを作った方(環境の説明は前回までの記事に譲ります)
- 前提:sunabar のアクセストークンとエージェントIDを用意していて、MCP経由でAPIを呼べること
- かかるお金:0円。動かすのは仮想のお金で、振込手数料も0円でした
- 試した日と環境:2026年8月30日〜31日・Claude Code 2.1.226
先に押さえること
| # | 押さえること | 中身 |
|---|---|---|
| 1 | ポリシーで止まると HTTP 403 が返る | 銀行本体のエラーとは別系統です。エラーコードは理由別に分かれておらず、何に引っかかったかは日本語の文で返ります(2章・4章) |
| 2 | エラーメッセージは、自分が書いたポリシーをもとに作られる | ポータルのポリシー欄に書いた文章の語彙と内容が、errorMessage に反映されます。ただし一字一句そのままではありません(2章) |
| 3 | AIが銀行APIを呼んだ理由が銀行側に残る | MCPツールに渡す reasoning の一文が、API実行履歴に「AI実行理由」として記録されていました(5章) |
| 4 | 基礎編で「実行しない」と書いた振込が、MCP経由では素通りした | Bash から curl を打つのは止まるのに、同じ振込をMCPツールから呼ぶと確認も出ずに実行されました(2章) |
1. 何を試すか
使ったエージェントは2つです。
| # | エージェント | ポリシーの中身 |
|---|---|---|
| 1 | 参照専用 | 残高照会と入出金明細照会だけを許可。振込・振替・総合振込は一切受け付けない |
| 2 | 上限1,000円つき | 残高照会・入出金明細照会・振込依頼を許可。ただし振込は1回あたり1,000円まで |
1つ目は準備編で作ったものです。2つ目はこの記事のために新しく作りました。
なお「参照専用」はエージェントに私が付けた名前です。銀行側の言い方は「照会」で、API名も残高照会・入出金明細照会・振込状況照会と揃っています。この記事でも、エージェント名以外は「照会」で書いています。
試したのは4回です。
| # | エージェント | 頼んだこと | 予想 |
|---|---|---|---|
| 1 | 参照専用 | 法人口座へ500円 | 断られる |
| 2 | 上限1,000円つき | 法人口座へ500円 | 通る |
| 3 | 上限1,000円つき | 法人口座へ2,000円 | 断られる |
| 4 | 上限1,000円つき | 法人口座へ1,000円(上限ちょうど) | どちらか分からない |
1と2は、頼む言葉も金額も振込先も同じにしました。違いをエージェントだけに絞るためです。
頼む前に用意したもの
「法人口座へ500円振込して」と打つだけで通ったのは、振込先の情報が作業フォルダに置いてあったからです。振込依頼APIには銀行コード・支店コード・口座番号・名義カナが要るので、これが無いと Claude Code は値を作れません。
置いてあったのは、基礎編で作った振込依頼のリクエストです。
{
"accountId": "<個人口座のID>",
"remitterName": "<依頼人名カナ>",
"transferDesignatedDate": "2026-08-30",
"transfers": [
{
"itemId": "1",
"transferAmount": "1000",
"beneficiaryBankCode": "0310",
"beneficiaryBranchCode": "<支店コード>",
"accountTypeCode": "1",
"accountNumber": "<法人口座の口座番号>",
"beneficiaryName": "<法人名義カナ>"
}
]
}
値の集め方は基礎編の3章に書きました。同じフォルダで作業を続けている読者は、そのまま「法人口座へ500円振込して」で通ります。そうでなければ、頼むときに振込先を一緒に渡してください。
なお transferDesignatedDate は私が指定しませんでした。Claude Code が翌日を入れています。これが6章の「予約中」につながります。
2. 参照専用エージェントに振込を頼む
まず、照会が通ることを確かめます。Claude Code に「残高を確認して」と頼むと、口座一覧照会と残高照会が両方通りました。
{"balances":[{"accountId":"<口座のID>","accountTypeName":"普通預金(有利息)",
"balance":"9000","baseDate":"2026-08-30","baseTime":"21:31:46+09:00",
"withdrawableAmount":"9000","previousDayBalance":"10000","previousMonthBalance":"0"}]}
参照専用ポリシーでも照会は素通りします。ここまでは想定どおりです。
次に、同じエージェントのまま「法人口座へ500円振込して」と頼みました。返ってきたのがこれです。
Forbidden: このリソースへのアクセス権限がありません。
errorCode=AI_ERR_001
errorMessage=ポリシーにより照会系APIのみ許可されており、振込・振替・総合振込は一切受け付けません。
リクエストされた振込依頼APIは禁止対象です。
3つ気づいたことがあります。
エラーコードの系統が違います。 基礎編で個人用トークンから法人口座の残高を照会したときは、HTTP 404 で「ご指定のお取引は、お取り扱いできません。」が返りました。エラーコードは 002015 で、これは銀行APIのものです。今回は AI_ERR_001 で、頭が AI_ です。銀行の勘定系に届く前、エージェントのポリシーを見る層で止まっています。
エラーメッセージが、ポータルで登録したポリシーの日本語をもとに作られています。 準備編で私が入れたポリシーはこれでした。
残高照会と入出金明細照会のみ許可する。振込・振替・総合振込は一切受け付けない。
並べると、そのままのコピーではないことが分かります。違いは3つです。
| # | 違うところ | 私が登録した文 | 返ってきた文 |
|---|---|---|---|
| 1 | 言い換えられた | 残高照会と入出金明細照会のみ許可する | 照会系APIのみ許可されており |
| 2 | 文末が変わった | 一切受け付けない。 | 一切受け付けません。 |
| 3 | 一文が足された | (無し) | リクエストされた振込依頼APIは禁止対象です |
私が書いた語彙と内容をもとにしたエラーメッセージが、その場で組み立てられて返ってくる、という形です。
なお、同じリクエストを2回送って文面が一致するかは試していません。一致するなら決まった文、変わるならその都度作られる文、ということになります。ここは未検証です。
止めた対象を名指しします。 「リクエストされた振込依頼APIは禁止対象です」と、何を拒否したかまで書いてあります。
このとき、お金は1円も動いていません。振込依頼そのものが受け付けられていないので、申請番号(applyNo)も発行されませんでした。
⛔ 基礎編で止まったはずの振込が、今回は止まらなかった
ここで、基礎編に書いたことが崩れました。
基礎編にはこう書きました。Claude Code は振込の送信を実行しない。 「このJSONで振込依頼を送って」と頼んだら curl を実行する手前で止まり、しかも「実行してよいですか」という許可の確認すら出ませんでした。確認より手前で実行そのものが拒否される止まり方です。
ところが今回は、「法人口座へ500円振込して」の一言で、振込依頼APIが呼ばれました。 許可の確認は出ていません。止められもしませんでした。この章で 403 が返ったのは銀行のポリシーが働いたからで、Claude Code 側は何も止めていません。次の4章では同じ呼び出しが 201 まで通ります。
違いは呼び出しの経路です。
| 基礎編 | 今回 | |
|---|---|---|
| 経路 | Bash から curl を実行 |
MCPツール(post_personal_transfer_request)を呼ぶ |
| Claude Code の挙動 | 確認も出さずに実行を拒否 | 確認も出さずに実行 |
| 止めたもの | Claude Code 自身 | 銀行のポリシー(403) |
iMac の設定ファイルは enabledMcpjsonServers に sunabar を書いた1行だけで、個別のツールを許可した覚えはありません。Bash で振込コマンドを打つのは止まるのに、同じ振込をMCPツールから呼ぶのは素通りする、という線の引かれ方でした。
準備編で、縛りを自分のパソコンの中だけに置かないほうがいいと書きました。これがその実物です。iMac の Claude Code に期待していた縛りは、呼び出し方が変われば効きませんでした。銀行側のポリシーだけが、呼び出し方に関係なく効いています。
3. 上限つきのエージェントを作る
sunabarポータルの「API利用管理」から「エージェント管理」を開いて、新しいエージェントを登録します。ポリシー欄に書いたのはこの2行です。
残高照会・入出金明細照会・振込依頼を許可する。
ただし振込は1回あたり1,000円までとし、1,000円を超える振込依頼は受け付けない。
この文面は、2章の結果を見てから決めました。 エラーメッセージがポリシーの日本語をもとに作られると分かったので、機械が読む条件式ではなく「そのまま人が読んでも意味が通る一文」にしています。
⛔ 環境変数を書き換えただけでは切り替わらない
エージェントの切り替えは、MCPの設定ファイルが読む環境変数を差し替えて行います。私の .mcp.json はこうなっています。
{
"mcpServers": {
"sunabar": {
"type": "http",
"url": "https://mcp.sunabar.gmo-aozora.com/mcp",
"headers": {
"x-access-token": "${SUNABAR_TOKEN}",
"x-api-type": "personal",
"x-agent-id": "${SUNABAR_AGENT_ID}"
}
}
}
}
~/.zshrc の SUNABAR_AGENT_ID を新しいエージェントのIDに書き換えます。ここで一度 Claude Code を終了して起動し直す必要があります。 ${SUNABAR_AGENT_ID} の展開は起動時にしか行われないので、開いたままのセッションは古いエージェントのIDをヘッダに載せ続けます。
旧IDは同じファイルの1行上にコメントとして残しておきました。戻したくなったときに探さずに済みます。
# 参照専用エージェント(戻すときはこの行を使う)
# export SUNABAR_AGENT_ID=<旧ID>
export SUNABAR_AGENT_ID=<新ID>
起動し直したあと、残高照会が通ることを確かめました。上限つきポリシーでも照会は通ります。
4. 上限の内と外で頼む
500円(上限の中)
2章とまったく同じ言葉で頼みました。今度は受け付けられました。
{"accountId":"<口座のID>","resultCode":"2","applyNo":"2026083000000003"}
同じコード、同じ金額、同じ振込先。違うのは環境変数1行だけです。参照専用では403で拒否された同じリクエストが、上限つきポリシーでは通りました。
2,000円(上限の外)
同じエージェントのまま、金額だけ2,000円に変えます。
Forbidden: このリソースへのアクセス権限がありません。
errorCode=AI_ERR_001
errorMessage=振込依頼の金額が1,000円を超えているため、ポリシーに違反します。
1回あたり1,000円までの振込のみ受け付けます。
2章のエラーメッセージと読み比べると、この記事でいちばん確かめたかった違いが見えます。
| 参照専用(2章) | 上限1,000円つき(4章) | |
|---|---|---|
| errorCode | AI_ERR_001 |
AI_ERR_001(同じ) |
| 断った理由 | 振込依頼APIは禁止対象=APIの種類で止めた | 金額が1,000円を超えている=リクエストの中身の値で止めた |
| ポリシーの文面 | 登録した日本語をもとにした | 登録した日本語をもとにした |
エラーコードは同じ AI_ERR_001 なのに、エラーメッセージだけが違います。 ポリシー違反はひとつのコードにまとめられていて、何に引っかかったかは日本語の文で伝えてきます。
ここは実装するときに効いてきます。理由ごとに分岐する作りにはできません。 できるのは「ポリシーで止まった」までの判定で、何に引っかかったかは日本語の文でしか分かりません。プログラムからは「コードでポリシー拒否と判定して、理由の文はそのまま人に見せる」形になります。
1,000円ちょうど(境界)
ポリシーには「1回あたり1,000円まで」と書きました。日本語の「まで」を、どちらに解釈するのか。翌日に試しました。
{"accountId":"<口座のID>","resultCode":"2","applyNo":"2026083100000001"}
通りました。「1,000円まで」は「1,000円以下」です。
| 金額 | 結果 |
|---|---|
| 500円 | 受付 |
| 1,000円(上限ちょうど) | 受付 |
| 2,000円 | 拒否 |
この返り値には、もう1つ分かることがあります。applyNo は日付ごとに振り直されます。 前日は 2026083000000005 まで進んでいましたが、翌日の1本目は 2026083100000001 に戻りました。前半8桁が日付、後半8桁がその日の連番です。
5. 断られた記録は銀行側に残る
sunabarポータルの「API実行履歴」を開くと、呼び出しが並んでいました。断られた呼び出しも、消えるのではなく denied という判定つきで残っています。
次の表は、21時50分ごろに見た時点の上から6行です(この日の全件は18件ありました)。6章で出し直す振込は、この時点ではまだ実行していません。
| # | 時刻 | API | HTTPステータス | 実行主体 | ポリシー評価結果 |
|---|---|---|---|---|---|
| 1 | 21:49:10 | 振込依頼 | 403 | AI | ✕ denied |
| 2 | 21:44:32 | 振込依頼 | 201 | AI | ✓ allowed |
| 3 | 21:43:29 | 残高照会 | 200 | AI | ✓ allowed |
| 4 | 21:33:31 | 振込依頼 | 403 | AI | ✕ denied |
| 5 | 21:31:43 | 残高照会 | 200 | AI | ✓ allowed |
| 6 | 21:31:35 | 口座一覧照会 | 200 | AI | ✓ allowed |
AI_ERR_001 の正体は HTTP 403 でした。 200(照会が通った)・201(振込依頼を受け付けた)・403(ポリシーで止めた)の3つに分かれています。認可の失敗として扱われているわけです。
「実行主体」の列が AI になっています。 人が画面から操作した分と、AIがAPIで呼んだ分を、同じ一覧の中で見分けられます。
AI実行理由が日本語で残っていた
行をクリックすると詳細が開きます。ここに「AI実行理由」という欄がありました。
ユーザが法人口座へ2000円を振り込むため、振込依頼を実行します。
これは私が書いた文ではありません。Claude Code が、MCPツールを呼ぶときに reasoning パラメータへ入れた一文がそのまま出ています。
sunabarのMCPサーバは、すべてのツールに reasoning というパラメータを用意しています。必須ではありませんが、書式まで決められていました。
「{主語}が{理由}ため、{ツール名}を実行します。」の形式で100文字以内。
改行・絵文字・個人情報は禁止。主語は「ユーザ」または「エージェント」のいずれか。
AIが銀行APIを呼んだ理由が、銀行側の履歴に残ります。 拒否された呼び出しについても残るので、「AIが上限を超える振込を試みた」という事実が理由つきで記録されることになります。
同じ画面には「ポリシーチェック内容」と「ポリシー違反内容」も出ていました。「ポリシー違反内容」の一文は、APIが返してきた errorMessage と1文字も違いません。 プログラムで受け取った文と、人が画面で読む文が同じなので、突き合わせができます。
なお、画面の案内文には「表示情報は一定期間経過後に削除される場合があります」とも書いてありました。記録を残したいなら自分で控えたほうがよさそうです。
6. おまけ:承認まわりで踏んだ落とし穴(基礎編の続き)
受け付けられた500円を承認するところで、3つつまずきました。どちらも基礎編では見えていなかったものです。
その前に、承認する場所を書いておきます。基礎編では「サービスサイトのお知らせ」とだけ書いて、URLを載せていませんでした。ここで自分が取り違えたので、2つ並べておきます。
| サイト | ドメイン | 何をする場所か |
|---|---|---|
| 銀行本体 | sso.gmo-aozora.com |
sunabar の ID とパスワードを取りに行く先(準備編) |
| sunabarサービスサイト | sso.sunabar.gmo-aozora.com |
仮想口座がある。振込の承認はこちら |
sso. の後ろに sunabar. が入るかどうかだけの違いです。私は本体のほうを開いてしまい、承認画面が見つからずに時間を使いました。sunabarポータルの「sunabarサービスサイトで使える銀行口座」から辿れば、ログインIDが入った状態で開きます。
メールトークンは振込依頼と同時にしか発行されない
承認時に表示されたメッセージ。
認証情報の有効期限が切れています。はじめから再度お手続してください。(S096-019403)
原因はメールトークンの期限切れです。基礎編で「期限は発行から約6分」と書きましたが、トークンは振込依頼を出したその瞬間に1回だけ発行されるもので、あとから出し直すことができません。承認ページに発行ボタンはなく、お知らせから再発行する口もありませんでした。
期限を切らしたら、振込依頼からやり直すしかありません。 私は20分ほど放置してしまい、依頼を出し直す羽目になりました。
読者への教訓の形にすると、振込依頼APIを呼ぶ前に、承認画面を開いて待っておく、です。基礎編の「出した日のうちに承認する」より、さらにきつい6分の縛りがあります。
やり直しはこの順番で通りました。①サービスサイトの「お知らせ」を開いて待つ ②APIで振込依頼を出す ③その場でお知らせを更新して新しい通知を開き、6分以内に承認する。
承認完了画面の受付番号は applyNo とは別の番号
承認すると、画面にこう出ます。
振込を受け付けました。(受付番号 : 2026083000000002)
ところが、APIが返してきた applyNo は 2026083000000005 でした。別の番号です。 振込状況照会の返り値を見ると、はっきり分かれていました。
| 返り値の場所 | 項目 | 値 | 何の連番か |
|---|---|---|---|
transferApplies |
applyNo(振込申請番号) |
2026083000000005 | APIで申請した回数 |
transferAccepts |
acceptNo(振込受付番号) |
2026083000000002 | 承認まで通った回数 |
この日はAPIで5回申請して、承認まで来たのは2回目でした。画面の受付番号を applyNo だと思って照会すると引っかかりません。
基礎編では両方とも 2026082900000001 で一致していたので、この差に気づけませんでした。基礎編で applyNo を「受付番号」と書いたのは、そのためです。
振込指定日を翌日にすると、その場では残高が動かない
承認は通りましたが、残高は9,000円のままでした。振込状況照会を見ると transferStatus が 11(予約中) です。
| 項目 | 値 |
|---|---|
| transferStatus | 11 |
| transferStatusName | 予約中 |
| totalDebitAmount | 500 |
| transferDetailFee | 0 |
| transferDesignatedDate | 2026-08-31 |
基礎編との違いは振込指定日だけです。基礎編は当日を指定したので承認の直後に 20 手続済 になり、残高もその場で動きました。今回は翌日を指定したので予約中で止まっています。
日曜でも当日指定は通ります(基礎編の8月30日は日曜でしたが、当日指定のまま手続済になりました)。試すだけなら当日を指定したほうが結果が早く見えます。
翌日の朝9時半にもう一度見ました。まだ動いていません。 transferStatus は 11 のまま、残高も9,000円のまま、入出金明細にもその日の行はありませんでした。指定日が来た瞬間に実行されるわけではないようです。何時に実行されるかまでは分かっていません。
まとめ
| # | 分かったこと |
|---|---|
| 1 | 参照専用ポリシーでも照会は素通りする |
| 2 | ポリシーで止まると HTTP 403・AI_ERR_001。銀行本体のエラーとは別系統 |
| 3 | エラーメッセージは、ポータルに書いたポリシーの日本語をもとに組み立てられる(そのままのコピーではない) |
| 4 | 同じコード・同じ金額でも、エージェントを差し替えるだけで通る/通らないが変わる |
| 5 | ポリシーはAPIの種類だけでなく、リクエストの中身の値(金額)まで見る |
| 6 | ポリシーに日本語で書いた「1,000円まで」は「1,000円以下」だった(上限ちょうどは通る) |
| 7 | エラーコードは理由別に分かれていない。何に引っかかったかは日本語の文で返る |
| 8 | 拒否された呼び出しも API実行履歴に denied として残る |
| 9 | MCPの reasoning に入れた一文が、銀行側に「AI実行理由」として残る |
| 10 | 画面の「ポリシー違反内容」と APIの errorMessage は同じ一文 |
| 11 | メールトークンは振込依頼と同時に1回だけ発行され、約6分で切れる。再発行の口はない |
| 12 | 承認画面の受付番号(acceptNo)と applyNo は別の採番 |
| 13 | 振込指定日を翌日にすると予約中で止まる。指定日の朝9時半でもまだ実行されていなかった |
| 14 | Claude Code の「振込は実行しない」は Bash 経由の話。MCPツール経由では確認も出ずに実行された |
| 15 | 承認するのは sso.sunabar.gmo-aozora.com。本体の sso.gmo-aozora.com と紛らわしい |
3本書いてきて、いちばん手応えがあったのは4番です。AIに渡すコードを1行も変えずに、環境変数だけで「できること」を狭められる。 しかも狭め方を日本語で書けて、エラーメッセージもその日本語で返ってくる。AIエージェントに銀行口座を触らせるのが怖いという話は、この形なら少し扱いやすくなります。