JSON の代わりに HTML のページと「203 Non-Authoritative Information」
Azure DevOps Services は、資格情報を受け付けない REST の呼び出しに 401 ではなく、サインイン ページへの転送(HTTP 302)で答えます。PowerShell の Invoke-RestMethod、.NET の HttpClient、Python の requests、curl -L のように転送を自分で追うクライアントは、続けてサインイン ページを取りに行き、そのページは HTTP 203 Non-Authoritative Information と HTML で答えます。203 は 2xx なので、コードはそのまま進み、たいてい HTML を JSON として読むところで失敗します(「Unexpected character encountered while parsing value: <」「Expecting value: line 1 column 1」)。直すのは、ほとんどの場合トークンです。
実際に見た答え
dev.azure.com の公開組織に、作ったでたらめなトークンを送りました(2026年10月27日):
- サインインが要る呼び出し(プロジェクトの一覧):
302 Foundでhttps://…vssps.visualstudio.com/_signin?realm=dev.azure.com&reply_to=…へ。トークンを付けない時と同じ答えです。 - 転送を追うと:
203 Non-Authoritative Information、text/html、約16 KB=サインイン ページ。 - 同じ呼び出しにヘッダー
X-TFS-FedAuthRedirect: Suppressを付けると:401 Unauthorizedと JSON のTF400813: The user 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' is not authorized to access this resource.。a が並ぶ利用者は匿名=トークンは使われていません。
よくある原因
- トークンの期限切れ・取り消し・無効化。期限の前に Azure DevOps からメールが届きます。期限切れや取り消し済みのトークンは編集も再生成もできないので、新しく作ります。公開の GitHub リポジトリで見つかったトークンは、テナントが止めていなければ自動で取り消されます。組織の設定の監査に
Token.PatExpiredEvent・Token.PatRevokeEvent・Token.PatSystemRevokeEventが出ます(90日保存)。 - ヘッダー。個人用アクセストークンは Basic のヘッダーで送ります。コロンとトークンをつないだものを base64 にします(ユーザー名は空):
Authorization: Basic BASE64(":" + トークン)、またはcurl --user ":$AZURE_DEVOPS_PAT"。Microsoft がこのトークンについて書いているのは Basic だけで、Bearer のヘッダーは Microsoft Entra のトークン用です。 - 別の組織。トークンは1つの組織用に作ります。「All accessible organizations」のトークンは全組織用のトークンで、2026年12月1日に使えなくなります。
- スコープ。Microsoft は、トークンが使えなくなる理由にスコープ不足を挙げています。呼び出しに要るスコープを付けていない場合です(リポジトリとプルリクは Code (Read))。
- ご自身のサインイン。Microsoft Entra ID につないだ組織では、持ち主がしばらく Azure DevOps にサインインしていないと(90日、またはテナントのサインイン頻度)、トークンが使えなくなることがあります。Web でサインインし、最後まで済ませてからもう一度試してください。
Microsoft の 個人用アクセス トークンを使用する からまとめました。Azure DevOps Server では、IIS の基本認証が有効だとトークンでサインインできない、とも書かれています。無効のままにしてください。
401 とメッセージで答えさせる
X-TFS-FedAuthRedirect: Suppressを付けて送ります。Azure DevOps は転送の代わりに、上のとおり 401 と JSON のメッセージで答えます。Azure SDK for Python は2024年に、Azure Pipelines 用の資格情報でこのヘッダーを送るようにしました(「転送の答えを避けるため」・azure-sdk-for-python #37510)。Visual Studio も送ることがある、と Microsoft は書いています。このヘッダーを使うと、テナントのサインインのポリシーを満たしていないアカウントは、サインインし直すまで 401 になります(2022年のリリース ノート・英語)。- または、クライアントに転送を追わせず、
/_signinへの 302 を「サインインの失敗」として扱います。 - どちらでも、読む前に状態と
Content-Typeを確かめます。_apisからの 203 やtext/htmlは、答えではなくサインイン ページです。
公開プロジェクトでは悪いトークンが見えない
公開プロジェクトでは、Azure DevOps は受け付けないトークンを断りません。呼び出しは匿名として動き、誰でも読めるものを返します。でたらめなトークンでも、公開プロジェクトのリポジトリの一覧は 200 で返り、トークンなしと同じでした。期限切れのトークンを持つスクリプトは読み続け、書き込みなどサインインの要る最初の呼び出しで初めて失敗します。トークンが使われたかは https://dev.azure.com/{組織}/_apis/connectionData で分かります。受け付けられたトークンなら authenticatedUser はご自身、そうでなければ descriptor が System:PublicAccess;aaaaaaaa-… です。
IntelliJ IDEA・Rider などの JetBrains IDE では
Azure DevOps Pull Requests は、JetBrains IDE(2024.1 以降)と Android Studio のプルリク用プラグインです。転送は追いません。サインイン ページへの 302・203・JSON のはずの所の HTML は、サインインの問題として知らせます。トークンの時はよくある原因(期限切れ・Code スコープ不足・「All accessible organizations」用)を添え、設定 | ツール | Azure DevOps Pull Requests を開けるようにします。公開の組織では Azure DevOps が誰として答えたかを確かめ、トークンを設定しているのに匿名として答えが返った時は、匿名のまま読み進めずに、トークンが受け付けられなかったと知らせます。個人用アクセストークンを制限している組織では、Azure CLI のサインインを代わりに使えます。読むのは無料、投票・コメント・完了は Pro(30日間の無料試用つき)です。
Azure DevOps は Microsoft Corporation の商標、IntelliJ IDEA は JetBrains s.r.o. の商標です。このページは Microsoft・JetBrains のいずれとも提携しておらず、承認も受けていません。
ほかに:TF400813 · 全組織用の個人用アクセストークンの廃止 · TF401444 · English