冒頭まとめ

OpenAI API の 401 は、認証に失敗したことを示します。ただし「キーが間違っている」だけを意味するわけではありません。

公式のエラー一覧を見ると、401 は4種類に分けて説明されています。認証情報が無効な場合、送ったキーが正しくない場合、アカウントが組織に所属していない場合、そして要求元の IP が許可リストに一致しない場合です。

重要なのは、このうち3つはキーを作り直しても直らないことです。認証情報が無効な場合の説明には、失効したキーを使っている、要求先の組織やプロジェクトに割り当てられたものとは別のキーを使っている、そして呼び出しているエンドポイントに必要な権限キーが持っていない、という3つの原因が挙げられています。キーそのものは有効でも、宛先や権限が合っていなければ 401 です。

もう1つ、切り分けを一気に進める性質があります。「Incorrect API key provided」の文言には、実際に送られたキーが伏字付きで入ります。これを自分のキーと突き合わせて、一致しないなら、調べるべきはキーではなくそのキーを送っている場所です。

したがって最初にやることは、文言がどの種類かを見分けることです。

エラーの概要

キーが正しくない場合の応答は、この形になります。

{
  "error": {
    "message": "Incorrect API key provided: sk-Eyftb***************************************99vW. You can find your API key at https://platform.openai.com/account/api-keys.",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  }
}

message の中の伏字部分に注目してください。先頭と末尾の数文字が見えています。**これはサーバーが受け取ったキー**なので、自分が設定したつもりのキーと照合できます。

プログラムから呼んでいる場合、公式のソフトウェア開発キットでは AuthenticationError として現れます。公式の説明は、キーまたはトークンが無効・期限切れ・失効している、というものです。

文言は主に次の4種類に分かれます。この判別が切り分けの起点になります。

Invalid Authentication                            → 宛先か権限の不一致
Incorrect API key provided: sk-...                → 送られたキーそのものが違う
You must be a member of an organization to use the API → 組織への所属が無い
IP not authorized                                 → 許可リストとの不一致

まず最初に:文言を4つに振り分ける

第一に、文言に伏字のキーが含まれているかを見ます。含まれていれば、それが実際に送られたキーです。

第二に、そのキーの先頭と末尾を、自分が使っているつもりのキーと突き合わせます。違っていれば、キーの正しさを確認する作業は無意味です。送信元を探す段階に移ります。

第三に、一致しているなら、宛先と権限を疑います。組織やプロジェクトが意図したものか、そのエンドポイントを呼べる権限があるか。

第四に、IP に関する文言であれば、許可リストの設定を確認します。この場合、キーは完全に正しくても通りません。

よくある原因と解決手順

原因1:意図しないキーが送られている

最も多く、そして最も気付きにくい形です。公式にも、古い失効済みのキーが手元に残っている場合がある、と挙げられています。

公式の開発キットは、明示的に指定しなければ環境変数からキーを読みます。したがって、設定ファイルに正しいキーを書いていても、環境変数側に古いキーが残っていればそちらが使われます

Before(設定ファイルだけを確認する):

cat .env.local | grep OPENAI_API_KEY   # 正しいキーが書いてある
python app.py                          # それでも 401

After(実際に読まれている値を確認する):

# シェルの環境変数に古い値が残っていないか
echo "${OPENAI_API_KEY:0:8}...${OPENAI_API_KEY: -4}"

# プログラムから見た値を確認する
python3 -c "
import os
k = os.environ.get('OPENAI_API_KEY', '')
print('env:', k[:8] + '...' + k[-4:] if k else '(未設定)')
"

エラー文言の伏字と、ここで出た先頭・末尾が一致すれば、犯人は環境変数です。一致しなければ、プログラム内の直書き、設定管理の仕組み、あるいは中継のプロキシが別のキーを差し込んでいます。

なお、キーの前後に空白や改行が混ざっている場合も同じエラーになります。公式にも、打ち間違いや余分な空白が原因になり得ると書かれています。設定ファイルからの読み込みでは、末尾の改行が残りやすい点に注意してください。

原因2:キーは有効だが、組織やプロジェクトが違う

公式の説明に、要求先の組織やプロジェクトに割り当てられたものとは別のキーを使っている場合、と明記されています。

現在のキープロジェクトに紐づきます。一方、対象の資源(アシスタント、ファイル、微調整済みモデルなど)も特定のプロジェクトに属します。この2つがずれていると、キーが有効でも 401 です

# 明示的に組織とプロジェクトを指定して切り分ける
curl -sS https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "OpenAI-Organization: org-xxxx" \
  -H "OpenAI-Project: proj_xxxx"

指定を変えて結果が変わるなら、この形です。公式も、キーと組織 ID を要求ヘッダーで正しく指定するよう案内しており、プロジェクト固有のキーは設定画面で対象プロジェクトを選んで確認できると説明しています。

原因3:エンドポイントに必要な権限がない

公式の説明の3つ目です。呼び出そうとしているエンドポイントに対して、キーが必要な権限を持っていない場合も 401 になります。

とくに管理系のAPI で起こります。通常のキーで組織の管理用エンドポイントを呼ぶと、権限不足として拒否されます。この場合、キーの再発行では解決しません。**必要なのは、そのエンドポイント向けの種別のキー**です。

キーには読み取り専用などの制限を設定できるため、書き込み系の操作だけが失敗する、という形でも現れます。読み取りは通るのに作成だけ 401、という状況ならこれを疑ってください。

原因4:IP が許可リストに一致しない

公式に「IP not authorized」として明記されている原因です。要求元の IP が、プロジェクトまたは組織に設定された許可リストと一致しない場合に返ります。

この形は、手元では通るのに配備先で失敗する、という現れ方をします。キーも組織も権限もすべて正しいため、いくら確認しても原因に辿り着きません

疑うべき状況は明確です。配備先を変えた、実行環境の外向き IP が変わった、あるいは組織側で許可リストが新たに設定された。これらの心当たりがあれば、まず設定を確認してください。

自動で台数が増える構成や、外向き IP が固定されていない環境では、許可リストの運用自体を見直す必要があります。

原因5:組織に所属していない

公式が独立した項目として挙げている原因です。アカウントがどの組織にも属していない場合に返ります。

原因として、以前の組織から退出したか外された、プロジェクトから外された、組織自体が削除された、という3つが挙げられています。**利用者側の設定ミスではなく、所属の変化**が原因です。

対処は、組織の管理者に招待してもらうか、新しい組織を用意することです。個人で使っていたキーが、組織の整理をきっかけに突然使えなくなった場合はこれを疑ってください。

補足:似ているが別のもの

利用が許可されていない国や地域からの呼び出しは 403 です。公式では 401 とは別の項目として整理されており、対応するのは対応国の一覧の確認です。

残高や支出の上限に関するエラーは 429 です。公式には、請求関連のエラーでは error.code を見て具体的な原因を特定するよう書かれており、残高切れ、組織の支出上限、プロジェクトの支出上限、承認された利用上限といった識別子が個別に定義されています。認証の問題ではないので、キーを触っても解決しません

開発キットの区分では、401 は認証の例外、403 は権限の例外として別々に定義されています。後者の説明には、正しいキーと組織 ID、資源 ID を使っているかを確認するよう書かれています。

Azure 経由で同じモデルを使う場合、認証の仕組み自体が別です。ヘッダーの名前も鍵の管理も異なるため、本記事の内容はそのままでは当てはまりません(Azure の 401 の記事)。

他の基盤の 401 とも、応答に入る情報の量が違います(GCP の 401 の記事)。

切り分けの順序

  1. 文言に伏字のキーが入っているかを見る。入っていれば、それが実際に送られたキー
  2. 先頭と末尾を自分のキーと突き合わせる。違えば、キーではなく送信元を探す。
  3. 環境変数を確認する。設定ファイルより優先されることがある。
  4. 前後の空白や改行を確認する。読み込み時に混ざりやすい。
  5. 一致しているなら、組織とプロジェクトを明示して切り分ける。
  6. 特定の操作だけ失敗するなら、キー権限を疑う。管理系は別種のキーが要る。
  7. 手元では通るのに配備先で失敗するなら、IP 許可リストを確認する。
  8. 突然使えなくなったなら、組織への所属の変化を疑う。

確認コマンド集

# 1. 実際に送られているキーの先頭と末尾を確認する
python3 -c "
import os
k = os.environ.get('OPENAI_API_KEY', '')
print(repr(k[:8]), '...', repr(k[-4:]), '| 長さ:', len(k))
"

# 2. 前後の空白や改行が混ざっていないかを見る
printf '%s' "$OPENAI_API_KEY" | od -c | tail -3

# 3. 最小の要求で認証だけを確認する
curl -sS -o /dev/null -w "%{http_code}\n" https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY"

# 4. エラーの種別と文言を取り出す
curl -sS https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  | python3 -c "import json,sys; e=json.load(sys.stdin).get('error',{}); print(e.get('code'), '|', e.get('message'))"

# 5. 組織とプロジェクトを明示して切り分ける
curl -sS -o /dev/null -w "%{http_code}\n" https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "OpenAI-Organization: org-xxxx" \
  -H "OpenAI-Project: proj_xxxx"

# 6. 配備先の外向き IP を確認する(許可リストとの照合用)
curl -sS https://api.ipify.org

# 7. プログラムから見えている値を確認する(開発キット経由)
python3 -c "
from openai import OpenAI
c = OpenAI()
print('sdk key:', (c.api_key or '')[:8] + '...' + (c.api_key or '')[-4:])
"

Editor’s Note

このエラーの切り分けを一撃で終わらせる手がかりが、文言の中にあります。それを示す相談が残っています(401 Incorrect API key provided)。

2024年1月、相談者は 401 の文言に伏字のキーが出ているのを見て、こう書いています。「そのキーは使ったことがないし、自分のキー一覧にも存在しない」。試したことも列挙されています。新しいキーの発行、ブラウザキャッシュ削除設定ファイルの値と管理画面のキーの照合、打ち間違いや空白の確認。

どれも真っ当な確認ですが、全部キーの側を疑っています。返答は視点を変えるものでした。キーはまず基本ソフトウェア環境変数から探されるので、そこに古い値が設定されていないか見てほしい、という指摘です。相談者は数時間後に解決を報告しています。

構造はこうです。文言に出ているキーは、サーバーが受け取ったキーです。それが自分の知らないキーだということは、知らない場所から別のキーが送られているという意味になります。この時点で、正しいキーを何度確認しても無駄だと分かります。探すべきは値ではなく経路です。

同じ相談の後半で、別の回答者がこう整理しています。先のエラーは、呼び出しに使われていた誤った認証情報の出どころを突き止めたことで解決したのだろう、と。

401 に当たったら、まず文言の伏字を読んでください。自分のキーと一致するかどうかで、調べる対象が正反対に変わります。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。