Microsoft Graph API /users の$selectで403/404/501が出る原因と対策(deviceEnrollmentLimit・Intune権限)

Microsoft Graph APIでユーザー情報を一括取得しようとして、/v1.0/users?$select=… に多くのプロパティを詰め込むと、403 Forbidden・404 Not Found・501 Not Implementedが混在して原因が見えづらくなります。本記事では、/users(一覧)と/users/{id}(単一)の仕様差、Intune系プロパティの権限設計、そして大量項目取得の実装パターンを整理します。

目次

現象:/users?$select=… で 403 / 404 / 501 が混在する

Azure AD(Microsoft Entra ID)連携済みのアプリから、次のように ユーザー一覧(/users) を取得しつつ、$select に「ほぼ全プロパティ」を指定すると、指定した項目の組み合わせによってエラーが変化することがあります。

GET https://graph.microsoft.com/v1.0/users?$select=id,displayName,mail,aboutMe,birthday,skills,deviceEnrollmentLimit,...

よく見られるパターンは次の3つです。

ステータス今回の典型的なトリガー実務上の見立て
404 Not FoundaboutMe / birthday / hireDate / interests / mySite / pastProjects / preferredName / responsibilities / schools / skills などを $select に含めるそのプロパティは「/users(一覧)」のレスポンスとしてはサポート外(エンドポイント上“存在しない”扱い)
403 ForbiddendeviceEnrollmentLimit を $select に含める(Intune系の UnknownError を伴うことが多い)Intune / デバイス管理領域の権限不足、またはトークン種別(委任/アプリケーション)の不一致
501 Not Implemented上記のような「一覧で返せない項目」を大量に混ぜて $select する一覧取得ではその操作が実装されていない(サポート外プロパティが混ざり内部処理パスが破綻)

結論から言うと、「/users(一覧)で取得できる項目」と「/users/{id}(単一ユーザー)でしか取得できない項目」を分けて設計することが最大の近道です。さらに、deviceEnrollmentLimit のように 別サービス領域(Intune)と結び付いたプロパティは、Graph のユーザー読み取り権限だけでは足りないケースが多く、権限設計も分けて考える必要があります。

/users(一覧)と /users/{id}(単一)の違いを押さえる

Microsoft Graph API の user エンティティはプロパティ数が多い一方で、エンドポイントによって「返せる(選べる)項目」が同一ではありません。特に、一覧系(コレクション)は最適化のため、複雑な属性や裏側で別システム参照が必要な属性を返さない(返せない)ことがあります。

呼び出し目的$select の考え方向いている場面
GET /v1.0/usersユーザー一覧の取得「list users で返せる」と明記されている一般属性に絞るアドレス帳、同期、一覧表示、検索インデックス作成
GET /v1.0/users/{id}単一ユーザーの詳細取得一覧では取れない詳細プロパティを必要に応じて $selectユーザー詳細画面、個別プロファイル参照、設定画面
GET /v1.0/meサインイン中ユーザーの取得委任権限の範囲で、ユーザー体験寄りの項目取得に強いフロントエンドでのマイページ、自己情報の表示

「全部ほしい」気持ちは分かりますが、Graph の設計思想としては “必要な時に必要なものだけ取る”が前提です。/users で無理に総取りしようとすると、今回のような 404/501 が出たり、レスポンスが肥大化してスロットリング(429)やタイムアウトの温床になりがちです。

404 Not Found の正体:一覧(/users)でサポートされないプロパティを $select している

aboutMe や birthday などの「プロフィール詳細」系プロパティは、user リソースのプロパティとしては存在しますが、/users(一覧)での返却対象としてはサポートされていない扱いになることがあります。その結果、一覧取得に対して $select してしまうと、エンドポイント上は “存在しないプロパティ” のように扱われ、404 が返るケースが出ます。

今回 404 になっていた代表例は次のとおりです。

  • aboutMe
  • birthday
  • hireDate
  • interests
  • mySite
  • pastProjects
  • preferredName
  • responsibilities
  • schools
  • skills
  • mailboxSettings(環境によっては一覧で取得不可になりやすい)

これらが必要な場合は、単一ユーザー取得(/users/{id})で明示的に $select するのが基本です。

GET https://graph.microsoft.com/v1.0/users/{user-id}?$select=aboutMe,birthday,skills,preferredName

ここでのポイントは、「一覧」→「個別」へ段階的に取得する設計にすることです。ユーザー全員分の詳細が本当に必要なのか、ビジネス要件として再確認し、必要なユーザーにだけ詳細 API を打つようにすると、パフォーマンスと安定性が一気に上がります。

/users で取りやすい項目と、分離すべき項目の目安

実装で迷ったら、まずは次のように整理すると判断しやすくなります。

分類例/users(一覧)推奨する取得方法
ディレクトリ基本属性id, userPrincipalName, displayName, mail, givenName, surname, department, jobTitle, accountEnabled など取得しやすいGET /users?$select=...(必要最低限に絞る)
プロフィール詳細aboutMe, birthday, hireDate, interests, pastProjects, responsibilities, schools, skills など取得不可/不安定になりやすいGET /users/{id}?$select=...(個別取得)
メールボックス/設定系mailboxSettings, automaticRepliesSetting など一覧では避けるGET /users/{id}/mailboxSettings や単一取得で必要時に
関係(ナビゲーション)manager, memberOf, directReports など一覧では直接返さないGET /users/{id}/manager など専用エンドポイント
別サービス領域に紐づく属性deviceEnrollmentLimit(Intune)など権限不足で 403 の原因になりやすい権限を分離し、別リクエストで取得(または Intune 側 API を活用)

「/users で返らないものは、Graph が持っていない」のではなく、一覧レスポンスとしてサポートしていない(または最適化上返さない)というニュアンスです。ここを誤解すると、権限をいくら追加しても 404 が消えない、という状態に陥ります。

501 Not Implemented の正体:一覧(/users)に “一覧で返せない項目” を詰め込みすぎている

501 Not Implemented は「その操作は API として実装されていない(サポート外)」を意味します。今回の文脈では、/users の一覧取得で実装されていないプロパティ取得を強制していることが原因になりやすいです。

特に、次のような状態になると 501 が出やすくなります。

  • /users に対して、プロフィール詳細やメールボックス設定、Intune 系など “別系統のデータ参照” が必要な項目を大量に混在させる
  • $select の項目数が増えすぎて、内部的に最適化ルートが外れたり、バックエンド連携の制約に当たる
  • 一部はサポート外(本来は 404/400 相当)でも、まとめて評価され 501 として返ってくる

「403 と 404 が混ざった結果 501 になっているのでは?」という疑問は自然ですが、実務上は “一覧で返せないものが混ざった時点で、一覧取得のやり方が間違っている”と捉えるのが正解です。501 を回避する最短ルートは、/users の $select を「一覧で返せる一般属性」だけに絞ることです。

501 を回避するための $select 設計例(一覧用)

まずは一覧で必要な“骨格”だけを取るのが安定します。例えば、組織のユーザー台帳・検索・同期でよく使うセットは次のようになります。

GET https://graph.microsoft.com/v1.0/users?$select=
id,userPrincipalName,displayName,mail,givenName,surname,
department,jobTitle,officeLocation,mobilePhone,businessPhones,
accountEnabled,employeeId,companyName,usageLocation,preferredLanguage

ここに aboutMe や skills を混ぜないのがポイントです。詳細が必要な場面でだけ、後述の個別取得やバッチで補完します。

deviceEnrollmentLimit の 403 Forbidden:Intune 領域の権限とトークン種別を疑う

deviceEnrollmentLimit はユーザー属性のように見えますが、実務的には Intune のデバイス登録制限(デバイス管理ポリシー)と関係が深いプロパティです。そのため、User.Read.All など “ディレクトリ読み取り系” の権限だけでは取得できず、Intune / deviceManagement 系の権限が必要になることがあります。

また、ここが重要なのですが、Graph の権限は 委任権限(Delegated) と アプリケーション権限(Application)で別物です。バックエンドでクライアント資格情報フロー(client credentials)を使っているのに、委任権限だけ付与して「付けたつもり」になっていると、トークンに必要な権限が載らず 403 が出ます。

委任権限とアプリケーション権限の違い(トラブルの温床)

観点委任権限(Delegated)アプリケーション権限(Application)
主な利用シーンユーザーがサインインして操作する(UI/フロント)デーモン/サーバー間、バッチ処理、バックエンド同期
トークンの特徴ユーザーの権限 + アプリの許可の合成アプリ単体の許可(ユーザー不在)
トークンで見るべきクレームscp(スコープ)roles(アプリロール)
よくある事故“管理者同意が必要な権限” を付け忘れる委任権限だけ付けて、アプリケーション権限が空のまま呼ぶ

deviceEnrollmentLimit をアプリケーション トークンで取得する場合は、一般に次のような Graph 権限(アプリケーション権限)が必要になります。

  • DeviceManagementConfiguration.Read.All または DeviceManagementConfiguration.ReadWrite.All

そして、管理者による同意(Admin consent)が必須です。同意が入っていないと、Azure ポータル上で権限が見えていても、実際のトークンには載りません。

403 を解消するための実務手順(最短コース)

  1. どのフローでトークンを取っているかを確認する(Authorization Code / On-Behalf-Of / Client Credentials など)。
  2. クライアント資格情報フローなら、アプリケーション権限として DeviceManagementConfiguration.Read.All(必要なら ReadWrite)を追加する。
  3. Azure ポータルの「API のアクセス許可」で 管理者による同意を実行する。
  4. 同意後に新しいトークンを取得し、トークン内の roles に deviceManagement 系の権限が入っていることを確認する。
  5. それでも 403 が出る場合、Intune が未有効化/未契約、MDM 権限の委任状態、条件付きアクセス、テナント制限など “環境側” の要因も疑う。

トークン確認は、開発者ツールや JWT デコーダ(例:jwt.ms)で scp/roles を見ると一発です。「アプリ登録に権限を足した」だけでは足りず、「同意が入った状態で取り直したトークン」で呼ぶ必要があります。

deviceEnrollmentLimit を取るなら、ユーザー一覧取得と分離するのが安全

deviceEnrollmentLimit を /users の一覧と一緒に取りに行くと、権限不足で一覧自体が 403 になる、という“巻き添え事故”が起こりがちです。現場では次のように 取得ルートを分離するのが安定します。

目的おすすめの取り方理由
ユーザー一覧を表示/同期したい/users は一般属性だけで取得(deviceEnrollmentLimit は除外)一覧が 403/501 で落ちるのを防ぐ
特定ユーザーの登録上限だけ見たい/users/{id} を別リクエストで取得(必要時にだけ)必要ユーザーだけに負荷と権限を適用できる
ポリシーや制限の全体像を把握したいIntune の deviceManagement 配下のエンドポイントを利用ユーザー属性ではなく“設定/ポリシー”として扱う方が自然

「一覧の1回で全て取れる設計」は、便利に見えても長期運用で壊れやすいです。Graph はバックエンドが複数サービスに跨るため、権限やライセンス、最適化の都合で“一覧に混ぜられない属性”が一定数存在すると割り切ると、設計がスムーズになります。

ステータスコード別の意味と、今回の当てはめ

エラー解析を早くするために、今回のケースを “ステータスコード→疑うべきこと” で整理します。

ステータス何が起きているか真っ先に確認すること打ち手
404そのエンドポイントでは、そのプロパティを返せない/存在しない扱い/users(一覧)に “単一でのみ取れる項目” を混ぜていないか/users の $select を絞る。必要なら /users/{id} で個別取得
403リソースはあるが、権限・同意・条件によりアクセス拒否委任/アプリケーションの取り違え、admin consent、トークン内の scp/roles必要な権限を「正しい種別」で付与し、同意してトークンを取り直す
501その操作は一覧としてサポートされていない/実装されていない$select に “一覧で返せない項目” を大量に含めていないか一覧は一般属性のみ。詳細は個別・バッチ・専用エンドポイントに分割

できるだけ多くのユーザー項目を取るベストプラクティス

“できるだけ多く” を “1回で全部” と同義にすると失敗します。ここでは、運用で破綻しにくい「多く取るための分割戦略」を具体例つきで紹介します。

一覧(/users)で取る項目は「検索・一覧表示・同期待ち受け」に必要なものだけ

/users は、いわばユーザー台帳のインデックスです。ここで扱うのは、検索・表示・紐付けに使うキー情報が中心になります。例えば次のように “よく使う属性” に絞ると、エラーが減り、レスポンスも安定します。

GET https://graph.microsoft.com/v1.0/users?$select=
id,userPrincipalName,displayName,mail,
department,jobTitle,officeLocation,
accountEnabled,employeeId,createdDateTime

さらに大規模テナントでは、取得件数が増えるほど障害時の影響が大きくなります。次の点も合わせて徹底すると運用が楽になります。

  • ページング(@odata.nextLink)を前提に実装する($top で固定しない)
  • レスポンスをキャッシュし、同じ一覧を短時間に何度も引かない
  • 可能なら delta クエリで変更分だけ追う(全件フルスキャンを減らす)

一覧で取れない項目は「必要なユーザーだけ」単一取得で補完する

aboutMe や skills のような詳細項目は、ユーザー詳細画面や、プロフィール充実度の表示など、“閲覧された時だけ必要”であることが多いです。その場合、一覧で無理に取らず、表示時に個別取得します。

GET https://graph.microsoft.com/v1.0/users/{id}?$select=
aboutMe,birthday,hireDate,interests,pastProjects,skills

全ユーザー分の詳細がどうしても必要(例:夜間バッチでプロファイルを収集して社内検索に載せる)なら、次の「バッチ API」と併用すると効率が上がります。

$batch を使って “個別取得の束ね” を現実的な回数に抑える

Graph の $batch を使うと、複数の GET を 1 回の HTTP リクエストにまとめられます。/users で id 一覧を取ったあと、必要なユーザーに対して /users/{id} を束ねるのが定番です。

{
  "requests": [
    {
      "id": "u1",
      "method": "GET",
      "url": "/users/00000000-0000-0000-0000-000000000000?$select=aboutMe,birthday,skills"
    },
    {
      "id": "u2",
      "method": "GET",
      "url": "/users/11111111-1111-1111-1111-111111111111?$select=aboutMe,birthday,skills"
    }
  ]
}

バッチ化するときは、次の実務ポイントを押さえるとトラブルが減ります。

  • 1 バッチに詰め込みすぎない(失敗時のリトライ範囲が大きくなる)
  • 429(スロットリング)と 503 を想定し、Retry-After を尊重してリトライする
  • バッチ内で 1 件だけ 404/403 が出ても、他の成功分は活かせるように設計する

“別領域” の情報は専用エンドポイントへ寄せる

Graph は “1つの API” に見えて、裏側は Azure AD / Exchange / SharePoint / Intune など複数サービスの統合です。次のような情報は、ユーザー一覧に混ぜるより、専用エンドポイントに寄せた方がトラブルが少なくなります。

欲しい情報混ぜがちなプロパティ/関係おすすめの取得ルート
上司情報managerGET /users/{id}/manager
所属グループmemberOfGET /users/{id}/memberOf
ユーザー写真photoGET /users/{id}/photo/$value
メールボックス設定mailboxSettingsGET /users/{id}/mailboxSettings
Intune/デバイス管理deviceEnrollmentLimit など/deviceManagement 配下の API + deviceManagement 系権限

「ユーザーを取る」ではなく「ユーザーに紐づく何を取るか」で API を切り分けると、権限も疎結合になり、監査・運用・セキュリティ面でもメリットが出ます。

トラブルシューティングのチェックリスト

403/404/501 が出たときに、現場で最短で潰すための確認ポイントをまとめます。

チェック項目見るべき場所ありがちな原因対処
$select に “一覧で返せないプロパティ” が混ざっていないかリクエスト URLaboutMe/skills などを /users に混ぜている/users の $select を絞り、詳細は /users/{id} に分離
呼び出しが v1.0 / beta どちらかエンドポイントbeta 前提の仕様を v1.0 で使っている本番は v1.0 を基本にし、必要なら beta を限定利用
トークンが委任かアプリケーションかJWT(scp/roles)フローと権限種別の不一致用途に合わせて権限を揃え、不要な混在を避ける
admin consent が実行されているかAzure ポータル(API のアクセス許可)追加した権限が “未同意” のまま管理者が同意し、トークンを取り直す
Intune 系の権限が “アプリケーション権限” に付いているかアプリ登録の権限一覧委任にはあるがアプリケーションには無いDeviceManagementConfiguration.Read.All などを Application に追加
条件付きアクセスやテナント制限でブロックされていないかEntra 管理センター/サインインログポリシーで Graph 呼び出しが制限対象アプリの除外や、適切な条件付きアクセス設計

まとめ:403/404/501 を回避して“必要なユーザー情報”を安全に集める

  • 404/501 は、/users(一覧)でサポートされないプロパティを $select している設計ミスが原因になりやすい。一覧は一般属性に絞る。
  • deviceEnrollmentLimit の 403 は、Intune 領域の権限不足や、委任/アプリケーション権限の取り違えが典型。DeviceManagementConfiguration 系の アプリケーション権限 + 管理者同意 を確認する。
  • “できるだけ多く取る” ためには、一覧(/users)→個別(/users/{id})→専用エンドポイントの三段構えで分割し、必要に応じて $batch や並列処理で効率化する。

この設計に切り替えるだけで、エラーの再現性が上がり、原因切り分けも容易になります。さらに、権限を用途別に分離できるため、セキュリティレビューや運用監査でも説明しやすい構成になります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次