AI エージェントがあなたの API のエラーを読んでいる

シェア

3 月、Mintlify は自社がホストするドキュメントサイトへの 1 か月分のトラフィックを集計しました。約 7 億 9,000 万件の リクエストのうち、45.3% が AI コーディングエージェントによるもので、ブラウザは 45.8% でした。Claude Code だけで全体の 4 分の 1 を送っていました(Mintlify の数字)。

これは API ではなくドキュメントへのトラフィックです。でも、ドキュメントを読むエージェントは、その後リクエストを書き、 実行し、返ってきたものを読むのと同じエージェントです。Postman の2025 State of the API 調査では、開発者の 24% がすでにエージェントを念頭に API を設計していると答えました。発表当時は気が早く聞こえましたが、今は そうではありません。

ここで作っているもののほとんどは失敗にまつわるものです。500 を返す、止まる、レスポンスの途中で切れるエンドポイント。 だから私たちが問い続けたのはごく狭い問いでした。エージェントがあなたの API からエラーを受け取ったとき、次に正しいことを するだけの情報があるでしょうか。

エージェントはエラーメッセージを文字どおりに受け取る

{"error":"E4021"} を受け取った開発者はコードを検索し、何も見つからず、同僚に聞きます。エージェントは誰にも聞けません。あなたが送ったボディを 頼りに動き、ボディに役立つことが書いていなければ推測します。同じリクエストをリトライしたり、問題のなかったフィールドを 変えたりするのです。

ID が存在しないとき、私たちの商品エンドポイントはこう返します。

Terminal
$ curl -i https://api.testingapis.com/api/Products/99999
HTTP/1.1 404 Not Found
content-type: application/json; charset=utf-8

{"status":404,"error":"Not Found","message":"Product with ID 99999 does not exist."}

面白みのない一文ですが、それで構いません。リソースと ID を名指ししているので、読むのが人でもプログラムでも、問題は ID であり、送り直しても意味がないとわかります。

エラーの標準的な形がほしいなら、RFC 9457(Problem Details for HTTP APIs)が type、title、status、detail、instance を定義し、application/problem+json として返します。2023 年に RFC 7807 を置き換えたものです。正確な形式はあまり重要ではない と私たちは考えています。大事なのは、どこかに平易な一文があることです。

どれだけ待てばいいかを伝える

エージェントはリトライします。429 や 503 がどれだけ待つべきかを伝えないと、クライアントは数字を選ぶしかなく、ループで 作業を進めるエージェントは選び続けます。Retry-After は何十年も前から HTTP の一部で、ヘッダー 1 つでこれを解決します。

Terminal
$ curl -i https://api.testingapis.com/api/status/429
HTTP/1.1 429 Too Many Requests
content-type: application/json; charset=utf-8
retry-after: 5

{"status":429,"statusText":"Too Many Requests","category":"client-error","message":"This is a 429 response. Use /api/status/{code} to request any status."}
GET/api/status/429

IETF は RateLimit と RateLimit-Policy ヘッダーにも取り組んでいます。クライアントがクォータを使い切った後ではなく、使い切る前に確認できるようにするものです。ドラフト 11が 5 月に出ました。構文はこうです。

ヘッダー
RateLimit-Policy: "default";q=100;w=60
RateLimit: "default";r=50;t=30

これは「60 秒あたり 100 リクエスト、残り 50、ウィンドウは 30 秒後にリセット」と読みます。まだドラフトなので、クライアントが すでに読んでいる X-RateLimit-* ヘッダーをやめることはしません。両方送ってもコストはごくわずかです。

リトライは重複を生む

GET のリトライは無害です。POST のリトライはそうではありません。最初の試行がサーバーに届き、レスポンスだけが失われた場合です。 注文が 2 つできてしまいます。人なら二重請求にたいてい気づきますが、エージェントは 2 回目の試行がうまくいったのを見て次に進みます。

よくある解決策は冪等キーです。クライアントは書き込みと一緒に一意の Idempotency-Key ヘッダーを送り、同じキーがもう一度届いたら、サーバーは処理を二重に行わずに最初の結果を返します。Stripe は何年もこの方式で 動いており、ヘッダーのIETF ドラフトもありますが、まだ RFC にはなっていません。支払いを受け付ける API や、片付けが面倒なものを作る API なら、エージェントが 呼び始める前に追加する価値があります。

MCP サーバーは普通の HTTP API になりつつある

多くのエージェントは REST API を直接呼ばず、それを包む MCP サーバーを経由します。7 月 28 日に公開された MCP の仕様では、セッションのハンドシェイクと Mcp-Session-Id ヘッダーがなくなりました。各リクエストが自分のプロトコルバージョンとクライアント情報を持ち、メソッド名とツール名は HTTP ヘッダーで運ばれるので、ゲートウェイはボディを解析せずにリクエストのルーティングやレート制限ができます。

結果として、MCP サーバーは今や他のステートレスな HTTP サービスとほぼ同じように振る舞います。普通のロードバランサーの後ろに 置けますし、上のアドバイスもそのまま当てはまります。ツール呼び出しが失敗したとき、エージェントが MCP サーバーに必要とする のは、API に対してと同じく、理由と待ち時間です。

手早く確かめる

この記事を書きながら実行したスクリプトです。失敗する 4 つのリクエストを送り、それぞれについて 2 つのことを確認します。 プログラムが読める理由があるか、そして Retry-After があるかです。

check-errors.mjs
// For each failure: is there a reason a program can read, and a time to wait?
const BASE = "https://api.testingapis.com";

const cases = [
  ["missing product", "/api/Products/99999"],
  ["rate limited", "/api/status/429"],
  ["server down", "/api/Products/1?error=503"],
  ["bad request", "/api/status/400"],
];

for (const [name, path] of cases) {
  const res = await fetch(BASE + path);
  const body = await res.json().catch(() => ({}));
  const reason = body.message ?? body.detail ?? body.title;
  const wait = res.headers.get("retry-after");
  console.log(
    name.padEnd(16),
    res.status,
    reason ? "reason: yes" : "reason: NO ",
    wait ? `retry-after: ${wait}s` : "retry-after: -",
  );
}
出力(Node 24、2026 年 10 月 9 日)
missing product  404 reason: yes retry-after: -
rate limited     429 reason: yes retry-after: 5s
server down      503 reason: yes retry-after: -
bad request      400 reason: yes retry-after: -

どのレスポンスにも理由があるのは良いことです。でも 3 行目を見てください。/api/status/503 は Retry-After: 5 を送るのに、実際のエンドポイントに ?error=503 を付けると何も送りません。これに対してテストしたクライアントは、いつリトライすべきかを学べません。これは対応予定のリストに 入っています。

同じスクリプトを、あなたの API の失敗するパスに向けてみてください。API を好きなときに失敗させられないなら、エラーレスポンスのガイドで、私たちの API から 500、429、タイムアウト、途中で切れたボディを受け取る方法を紹介しています。

どれも新しいアドバイスではありません。わかりやすいエラーメッセージと Retry-After は、誰かがエージェントを作るずっと前から良い考えでした。変わったのは、困った人はいずれメールをくれるのに対して、エージェントは ただもう一度試すということです。