◉CLAUDE LABEN
●THREADS — Messages API に message threads がベータで登場しました(11/2)。会話をサーバーに保持し、以降は新しいメッセージだけを送ります●CONSOLE — Console の IP 許可リストがベータで登場しました(11/5)。許可外のアドレスからのリクエストは拒否されます●SONNET 4.5 — Claude API での提供終了は 11/30、残り24日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST — Opus 4.8 の fast mode は非推奨になり、11/30 に除去されます。移行先は Opus 5.5●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●THREADS — Messages API に message threads がベータで登場しました(11/2)。会話をサーバーに保持し、以降は新しいメッセージだけを送ります●CONSOLE — Console の IP 許可リストがベータで登場しました(11/5)。許可外のアドレスからのリクエストは拒否されます●SONNET 4.5 — Claude API での提供終了は 11/30、残り24日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST — Opus 4.8 の fast mode は非推奨になり、11/30 に除去されます。移行先は Opus 5.5●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/API & SDK
⬡ API & SDK/2026-11-07中級

キーは正しいのに 400 と 401 が返る — 作り直す前に見る、ワークスペース ID とブラウザ直呼びの切り分け

API キーは有効なのに 400 や 401 が返るとき、キーを作り直す前に疑う二つの原因を整理します。ワークスペース ID ヘッダーの欠落と、ブラウザからの直接呼び出しの禁止です。エラー文言ごとの切り分け表と、手元で動かせる判定スクリプト付きです。

Claude API144APIキートラブルシューティング68ワークスペースCORS

土曜の午後、iMac の前で Anthropic の認証ページを読み返していました。API キーの種類が整理し直されていて、「キーがワークスペースに紐づいていないと、リクエストにヘッダーを足さなければ 400 になる」という一節が目に留まりました。

読みながら気づいたのは、私の頭の中で「キーまわりの不具合は 401、直し方はキーの再発行」という図式が出来上がっていたことです。400 が返ってくる可能性を、それまで一度も想定していませんでした。

公式の Authentication ページに書かれている範囲を手がかりに、「キーは正しいのに失敗する」二つのパターンを切り分けてみます。なお、ここに載せるエラー文言は公式の記載から引いたものです。私自身の環境で組織設定を変えて再現した出力ではない点は、先にお伝えしておきます。

症状でまず二つに分けます

最初に見るのは、HTTP ステータスとエラーメッセージの冒頭だけです。ここを読まずにキーを作り直すと、原因が別のところにあるときに、新しいキーでも同じエラーが返ってきます。

ステータスメッセージの特徴疑う場所キーは有効か
400This API key is not scoped to a workspace … must include the anthropic-workspace-id headerリクエストのヘッダー有効
400anthropic-workspace-id header must be a valid workspace IDヘッダーの値の書式有効
404Workspace … not foundID の写し間違い、または権限有効
401CORS requests are not allowed for this Organization呼び出し元がブラウザ有効
401上記以外の authentication_errorキーそのもの(期限・削除・失効)無効の可能性

「有効」と書いた行は、キーを作り直しても直りません。ここが、再発行に走る前に立ち止まっていただきたい理由です。

パターン1: 400「ワークスペースに紐づいていない」

公式の説明では、ワークスペースを指定して作ったキーは、そのワークスペースでだけ動き、リクエストにワークスペース ID を書かなくても通ります。一方、ワークスペースを指定せずに作ったキーは、リクエストのたびに anthropic-workspace-id ヘッダーが必要です。ヘッダーを省くと、先ほどの表の一行目の 400 が返ります。

直し方は二通りあります。

一つ目は、ヘッダーを足す方法です。Python の SDK なら、クライアントを作るときに一度だけ指定できます。

from anthropic import Anthropic
 
# 解決したいこと: ワークスペース未指定のキーで、毎回ヘッダーを書かずに呼ぶ
client = Anthropic(
    default_headers={"anthropic-workspace-id": "wrkspc_YOUR_WORKSPACE_ID"},
)
 
message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=256,
    messages=[{"role": "user", "content": "接続確認です。一言だけ返してください。"}],
)
print(message.content[0].text)

ここでヘッダーをクライアント側に寄せているのは、呼び出しごとに足すと、一か所書き忘れた関数だけが 400 を返すからです。原因が「キー」でなく「書き忘れ」になると、ログを見ても気づきにくくなります。

二つ目は、キーを作り直す方法です。Console の API keys ページで「Create key」を押し、ワークスペースを指定して作ります。ただし、この方法は既存のキーを差し替える手間がかかります。単独の小さなスクリプトならヘッダーを足すほうが速く、複数の場所に配った鍵なら、作り直しの前に配布先の一覧を作っておくほうが安全です。

ID の確認先は、Console のワークスペース一覧の「ID」列です。公式の例では wrkspc_ で始まる形をしています。値が違うと 400(書式不正)、存在しない、または自分に権限がないと 404 になります。404 のメッセージは、どちらの場合も同じ文面になると書かれていました。写し間違いと権限不足を、メッセージだけでは区別できないということです。

パターン2: 401「CORS requests are not allowed for this Organization」

こちらは、キーが正しいのに 401 が返るという、いちばん紛らわしい症状です。

呼び出し元がブラウザ上の JavaScript のとき、リクエストには Origin ヘッダーが付きます。公式の説明では、ゼロデータ保持(ZDR)や Compliance API が有効な組織では、このヘッダーを持つリクエストは、直接呼び出し用のヘッダーを付けても拒否されます。Enterprise の親組織に紐づく Console 組織は、親の設定を引き継ぐため、自分の Console に Compliance API の項目が見えなくても対象になることがある、とも書かれていました。

もう一点、見落としやすい記述があります。自分のバックエンドが、ブラウザから届いたリクエストをそのまま転送していると、元の Origin ヘッダーも一緒に流れて、同じエラーになるのです。公式は「新しいリクエストを作り直して送る」ことを求めています。

要するに、ブラウザから Claude API を直接呼ぶ構成そのものが、組織によっては成立しません。直すのは、キーでも設定でもなく、構成です。

# 解決したいこと: ブラウザには鍵を渡さず、自分のサーバーだけが API を呼ぶ
# (Flask は一例です。構成の要点は「鍵はサーバーの環境変数にだけ置く」こと)
import os
from flask import Flask, request, jsonify
from anthropic import Anthropic
 
app = Flask(__name__)
client = Anthropic()  # ANTHROPIC_API_KEY を環境変数から読む
 
 
@app.post("/api/ask")
def ask():
    question = request.get_json(force=True).get("question", "")[:2000]
    # ブラウザのリクエストを転送せず、ここで新しい API リクエストを作る
    msg = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=512,
        messages=[{"role": "user", "content": question}],
    )
    return jsonify({"answer": msg.content[0].text})

質問文を 2,000 字で切っているのは、画面側の入力がそのまま課金に直結しないようにするためです。

個人開発で自分の Web サイトを運用している私は、API キーをサーバー側の秘密情報にだけ置いています。画面に出るのは結果の文字列だけです。この線引きは、CORS の話以前に、キーがページのコードから読めてしまうという単純な理由で守っています。今回の 401 は、その線引きが組織の規則としても求められる場面がある、という確認になりました。

手元で動かせる判定スクリプト

エラーを受け取るたびに表を見比べるのは手間なので、ステータスとメッセージから原因の候補を返す関数を書いてみました。メッセージの文言は上の公式記載を前提にしています。文言が将来変わったときは、ここだけ直せば足ります。

import json
 
 
def triage(status: int, body: str) -> str:
    """Claude API のエラー応答を、鍵を作り直す前に切り分ける。"""
    try:
        msg = json.loads(body)["error"]["message"]
    except (ValueError, KeyError, TypeError):
        msg = body
    if status == 400 and "not scoped to a workspace" in msg:
        return "鍵は有効。anthropic-workspace-id ヘッダーが無い"
    if status == 400 and "must be a valid workspace ID" in msg:
        return "ヘッダーの値が ID の書式になっていない"
    if status == 404 and msg.startswith("Workspace `"):
        return "ID の写し間違い、または権限が無い"
    if status == 401 and msg.startswith("CORS requests are not allowed"):
        return "鍵は有効。ブラウザ直呼びが組織設定で禁止。サーバー側へ移す"
    if status == 401:
        return "鍵そのものを疑う: 期限切れ・削除・無効化・退会による失効"
    return "別の原因: ステータスとメッセージを記録して次へ"

手元で五つの例文を通したところ、期待どおりの候補が返りました。ただし、これは私が公式の文面を写して作った例文での確認です。実際の応答で試した結果ではありません。

最後の分岐に「期限切れ」を入れたのは、公式が、期限を過ぎたキーは 401 の authentication_error になり、再有効化できないと書いているからです。期限の設計そのものは、別の記事(APIキーに有効期限を付ける運用)に書いています。

切り分けの順番

私は次の順で見ることにしました。

  1. ステータスの確認。400 と 404 ならキーは有効なので、再発行は見送り
  2. 401 のときは、メッセージが CORS requests are not allowed で始まるかの確認
  3. 始まる場合は、呼び出し元がブラウザか、転送されたブラウザのリクエストかの調査
  4. 始まらない場合は、期限・削除・無効化を Console の API keys ページで確認

この順なら、キーの再発行は最後の一手になります。

キーを疑うのは、ステータスとメッセージを読んだあとです。 今回の読み直しで、この順番だけは崩さないと決めました。

まずは、お手元のエラーログから 400 と 401 を一件ずつ拾い、上の表のどの行に当たるかを確かめるところから始めていただければと思います。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ API & SDK2026-10-28
web fetch が通らないとき、400 と 200 のどちらで返るかで疑う場所が変わります
web fetch を tools に足した途端に 400 が返るのか、200 のまま結果ブロックに error_code が入るのか。失敗の出方で二層に分けて、組織設定・ドメイン制限・URL の出どころを順に疑う切り分け表と、確かめた Python の診断関数をまとめました。
⬡ API & SDK2026-10-24
「Credit balance is too low」の前に見る3つの勘定 — 含まれるクレジットと購入分は別の財布です
Max や Team の API クレジットが残っているはずなのに「Credit balance is too low」と出る。原因は、含まれるクレジットの対象外の呼び出しと、購入分の残高、月間上限が別々に動いているためです。Console で見る順番と、夜間スクリプトに入れる検知コードをまとめました。
⬡ API & SDK2026-10-22
上限に達したのに 400 が返ってきた — 自分で決めた利用上限・階層の月間上限・レート制限を見分ける
Claude API の「上限」系エラーは3種類あり、ステータスも待てば直るかどうかも違います。自分で設定した上限は400、階層の月間上限は429、通常のレート制限も429。見分けるコードと判断表をまとめました。