Microsoft Entra ID の「API‑driven inbound provisioning」は、CSVなどの人事データをそのまま取り込みたいときに非常に便利ですが、/bulkUpload のバルク上限やレート制限を理解していないと、HTTP 429 や中途半端なプロビジョニングに悩まされます。本記事では「1日1万ユーザーをCSVから取り込む」シナリオを題材に、バッチサイズ・スロットリング・csv2SCIM.ps1 の挙動を、設計指針レベルまで詳しく整理します。
API‑driven inbound Provisioning と /bulkUpload の基本整理
まずは前提となる API の性質をざっくり整理します。Microsoft Entra の API‑driven inbound provisioning は、Graph の /bulkUpload エンドポイントに SCIM 形式の BulkRequest を投げることで、Entra のプロビジョニングサービスにデータを渡す仕組みです。
- リクエスト形式:SCIM BulkRequest(
schemas+Operations配列) - Operations 配列:各要素が「1ユーザーの Create/Update 相当データ」。最大 50 要素まで。
- HTTP メソッド:
POSTのみ。GET/PUT/PATCH はサポートされません。 - レスポンス:成功時は
202 Accepted(同期処理ではない)。 - 実際の作成・更新:バックエンドのプロビジョニングジョブが、スコープ条件と属性マッピングを評価して、必要に応じて Create/Update/Enable/Disable を実行。
ここで重要なのは、API クライアントは「ただデータを投げるだけ」という点です。ユーザー作成/更新の判断は Entra 側が行い、結果は プロビジョニングログ(Portal 画面または Graph の /auditLogs/provisioning API)で確認します。
/bulkUpload の上限値(バルク件数・レート・日次クォータ)
2025年時点の公式ドキュメントとトラブルシュート記事をベースにすると、/bulkUpload に関する主な上限は次の通りです。
| 項目 | 上限値 | 補足 |
|---|---|---|
| 1リクエストあたりのユーザー操作数 | 最大 50 件 | BulkRequest.Operations 配列の要素数上限。超えると 400 Bad Request 等。 |
| レート制限(短時間) | 任意の 5 秒間で 40 回 | 5 秒のスライディングウィンドウで 40 回を超えると HTTP 429。 |
| レート制限(24時間/テナント) | Entra ID P1/P2: 2,000 回/日 Entra ID Governance: 6,000 回/日 | 超過すると 429。50 件/呼び出しを前提に最大 10万〜30万件/日。 |
| Graph 全体のスロットリング | 別途サービス固有・グローバルな制限あり | 通常の 1万件/日レベルでは問題になりにくいが、大規模環境では考慮が必要。 |
なお、古い説明では「40 requests per second」と書かれていることがありますが、最新の概念ドキュメントとトラブルシュートガイドでは「任意の5秒間に40回」と明記されています。 設計する側は、5秒で40回を越えないように送信間隔を制御するのが安全です。
1日1万ユーザーを CSV から取り込むときの考え方
1回の /bulkUpload で1万件送れる?
答えははっきり No です。前述の通り、/bulkUpload の 1リクエスト上限は 50 操作 だからです。 したがって、1日 1 万ユーザーを取り込む場合は、少なくとも次の分割が必要になります。
- 1リクエスト = 50ユーザー
- 10,000 ÷ 50 = 200 リクエスト
10,000ユーザーを 1 回で投げることはできないので、必ず 200 回以上の POST を行う前提で設計します。
「ページング」ではなく「バッチ送信」で考える
ここで混同しがちなのが「ページング」と「バッチング」です。 / bulkUpload は サーバー側のデータをページング取得する API ではなく、クライアントがまとめてデータを投入するための「バルク入力窓口」です。
| 観点 | ページング | /bulkUpload の実態 |
|---|---|---|
| 主な用途 | サーバー側の大量データを分割して取得 | クライアント側データをまとめて投入 |
| API 役割 | GET + nextLink など | POST のみ。サーバーは「受け取るだけ」 |
| 制御する単位 | 取得ページサイズ | 1リクエストあたり最大50件のバッチ |
| 進捗確認 | レスポンスの継続取得 | プロビジョニングログを後追いで参照 |
つまり、「10,000件をページングで投げる」という発想ではなく、「50件ごとのバッチを 200 回 POST する」という設計に切り替える必要があります。
レート制限から見た「理論最速時間」
短時間レート制限は5秒で 40 リクエストです。これを逆算すると、1秒あたりの実質上限は 8 リクエストです。
- 8 リクエスト/秒 × 50ユーザー = 400ユーザー/秒(理論値)
- 10,000ユーザー ÷ 400ユーザー/秒 = 25秒
つまり、ネットワーク遅延やサーバー処理時間をすべて無視した「机上の計算」では、約25秒で 1万件を投入しきれるポテンシャルがあります。 一方で、実環境では Graph 側の応答時間やクライアント処理もあるため、
- 5秒あたり 30~35 リクエスト程度を目安に抑える
- 429 を受け取ったら指数バックオフでリトライする
といった「余裕をもったペーシング」が現実的です。
日次クォータとの関係:1万件/日は余裕あり
日次の上限はエディションによって異なります。
- Entra ID P1 / P2: 2,000 回/日
- Entra ID Governance: 6,000 回/日
今回のシナリオでは 200 リクエスト/日なので、
- 2,000 回/日の枠の 10% しか使わない
- バースト的に増えてもまだかなり余裕がある
と考えてよく、日次クォータに関してはほぼ心配不要です(他のジョブと共有している場合は全体として 2,000 回に収まるようにだけ注意)。
csv2SCIM.ps1(CSV2SCIM)の挙動
CSV2SCIM が自動でやってくれること
Microsoft が公開している PowerShell サンプル CSV2SCIM.ps1 は、API‑driven inbound provisioning を試すうえで非常に便利なスクリプトです。公式チュートリアルでは、主に次のような役割が説明されています。
- 任意の CSV ファイルを読み込む
- AttributeMapping.psd1 を使って CSV カラムと SCIM User/Enterprise User スキーマをマッピング
- SCIM BulkRequest JSON を生成(検証モードあり)
- オプションで、その JSON を
/bulkUploadに送信 - 「大きな CSV を扱うため、50件単位にチャンクして送信するロジック」を内蔵
この「50件チャンク」のおかげで、1万件の CSV を一気に渡しても、内部で 200 回の /bulkUpload に分割されるイメージになります(もちろん、細かい挙動はバージョンに依存しますが、少なくともサンプルコードの設計方針としてそう説明されています)。
CSV2SCIM がやってくれないこと(レート制限のスロットリング)
一方で、公式ドキュメントには次のような「注意書きに近いニュアンス」が含まれています。
- サンプルスクリプトはあくまで「リファレンス実装」。そのまま本番で使う場合は、各社要件に合わせてカスタマイズが前提。
- 「5秒で40リクエスト」「2,000/6,000回/日」の スロットリング上限をどう守るか は、クライアント側の実装責任。
- CSV2SCIM が「レート制限を意識して 5秒40回以内に自動調整する」といった記述はない。
つまり、CSV2SCIM は「50件単位のバッチング」は自動でこなしてくれるが、「40/5秒」や「日次クォータ」を自動で守ってくれるとは限らないと考えるべきです。
実際に 1万件以上を高頻度で回すような本番用途では、次のような追加実装を行うのが無難です。
- CSV2SCIM の「JSON生成モード」と「アップロードモード」を分離し、自前の送信ループでスロットリング制御を行う
- 429 / 503 を検知して 指数バックオフ + ジッター でリトライする
- 日次の呼び出し回数をカウンタリングし、2,000/6,000 の上限近くなったら自動的に処理を翌日に送る
実装者が押さえておきたい設計ポイント
権限設計:必要な Graph パーミッション
API‑driven inbound provisioning でバルク投入やログ参照を行うには、アプリケーションに次の Graph 権限を付与する必要があります。
| 用途 | Graph 権限 | 備考 |
|---|---|---|
| /bulkUpload へのアップロード | SynchronizationData-User.Upload または SynchronizationData-User.Upload.OwnedBy | 後者は ISV シナリオ向け。 |
| プロビジョニングログの参照 | ProvisioningLog.Read.All | 進捗・エラー確認用。 |
| アプリ登録操作(CSV2SCIM サンプルなど) | Application.Read.All / Application.ReadWrite.All 等 | サービスプリンシパルの検索・更新に利用。 |
「とりあえず広い権限を全部付ける」のではなく、最小限のアプリケーション権限で実装するのがセキュリティ面でもベストプラクティスです。
外部ID(externalId)を「唯一・不変」にする
API‑driven inbound provisioning では、デフォルトで SCIM の externalId と Entra 側の employeeId が「突き合わせキー」として使われます。 この値がブレると、同一人物が別ユーザーとして重複作成されたり、更新されないまま残ったりする原因になります。
- CSV 側の「社員番号」「学籍番号」など、人単位でユニークな値を externalId に割り当てる
- 雇用形態が変わっても externalId は変えない(社員→契約社員など)
- 再雇用などのシナリオでは、あえて同じ externalId を使うかどうかを HR プロセス側で定義しておく
特に大規模環境での「再送設計」(失敗レコードのリトライ)では、externalId が唯一・不変であることが、実装をシンプルに保つカギになります。
再送設計:失敗したユーザーだけを賢くリトライ
/bulkUpload は 202 Accepted を返したあと、裏側で非同期処理が走るため、その場では「どのユーザーが失敗したか」を知ることができません。 基本的な流れは次のようになります。
- 1回目のバッチ(50件)を /bulkUpload に POST
- 一定時間(数分)待つ
- プロビジョニングログ API で、直近サイクルのログを取得
- externalId 単位で「失敗レコード一覧」を抽出
- 失敗レコードだけで新たな BulkRequest を作り、次のサイクルで再送
この仕組みをうまく組むと、
- 大量バッチのうち数件が失敗しても、成功した分まで巻き戻さずに済む
- 人事システム側には「どの社員番号が何回目で成功したか」を履歴として残せる
といった、運用しやすい構成にできます。
スロットリング(40/5秒・日次上限)への具体的な対処
基本戦略:ペーシング + 429 リトライ
トラブルシュートガイドでは、429 が返る典型例として次の2つが挙げられています。
- 5秒間に 40 回を超える呼び出し
- 24時間で 6,000 回(または 2,000 回)の呼び出しを超過
これを踏まえると、実装側では次の2点をしっかり行う必要があります。
- 意図的に「送信速度を制限」する(ペーシング)
- 429/503 を受け取った場合は、即エラー終了ではなく「待ってから再試行」する
一般的な Web API と同様、429 応答には Retry-After ヘッダーが含まれることがあり、その値に従って待機するのが基本的なパターンです。
PowerShell での簡易スロットリング例
ここではイメージしやすいように、PowerShell での簡易的なペーシングコード例を示します(あくまで考え方の参考として)。
# 疑似コード:50件バッチのリスト $batches を 40/5秒 以内で送信
$maxRequestsPerWindow = 40
$windowSeconds = 5
$requestTimestamps = New-Object System.Collections.Generic.Queue[datetime]
foreach ($batch in $batches) {
$now = Get-Date
# 5秒より前のタイムスタンプを捨てる
while ($requestTimestamps.Count -gt 0 -and
($now - $requestTimestamps.Peek()).TotalSeconds -ge $windowSeconds) {
[void]$requestTimestamps.Dequeue()
}
# 直近5秒の呼び出しが 40 回を超えるようなら待機
if ($requestTimestamps.Count -ge $maxRequestsPerWindow) {
$sleepSeconds = $windowSeconds - `
(Get-Date - $requestTimestamps.Peek()).TotalSeconds + 0.5
Start-Sleep -Seconds [math]::Ceiling($sleepSeconds)
}
# /bulkUpload を呼び出す処理(CSV2SCIM の Upload 部分に相当)
Invoke-BulkUpload -Batch $batch
$requestTimestamps.Enqueue((Get-Date))
}
実運用では、さらに次のような工夫を加えると安定度が増します。
- 429/503 を検知した際は、指数バックオフ(例:2秒 → 4秒 → 8秒 → …) + ランダムジッター を入れる
- 各バッチに対し、最大リトライ回数(例:3回)を決める
- それでもダメなバッチは「要調査キュー」として別ファイルに出力し、後で手動対応
「あなた」と「API」と「CSV2SCIM」の責任分界
最後に、誰が何を担当するのかを整理しておくと、設計がクリアになります。
| レイヤー | 主な責任 | 具体例 |
|---|---|---|
| API(Microsoft Entra) | SCIM BulkRequest の受け取り スコープ・マッピングに基づく Create/Update/Disable 判定 スロットリング(40/5秒、日次上限)の強制 結果をプロビジョニングログに記録 | ドキュメントで定義された挙動を守る。クライアント側から見ると「ブラックボックス」。 |
| CSV2SCIM(サンプルスクリプト) | CSV → SCIM ユーザーへのマッピング BulkRequest JSON の生成とバリデーション 50件単位へのチャンク分割 オプションで /bulkUpload への送信 | 「とりあえず動かす」には便利だが、本番向けの完全なフレームワークではない。 |
| あなたの実装(カスタムクライアント) | 人事システムからの CSV 生成・取得 ジョブスケジューリング(例:毎日 2:00 に実行) レート制限を意識したペーシングとリトライ 外部ID設計・再送設計・ログ監視・通知 監査や障害対応用のロギング | PowerShell, Logic Apps, Azure Functions, 他の ETL ツールなど、好きな技術スタックで実装可能。 |
1万件/日シナリオの「おすすめ設計パターン」
ここまでの内容を踏まえて、「1日 1 万ユーザー」の典型パターンをまとめると、次のような構成が現実的です。
処理フロー(例:毎日深夜バッチ)
- 人事システムから当日分の CSV を出力(フル/差分は要件次第)
- CSV2SCIM.ps1 で JSON 生成のみ を実行(ローカル or ストレージに保存)
- カスタム PowerShell(または Logic App 等)で、JSON の Operations を 50件単位に分割(必要に応じて CSV2SCIM のチャンクロジックを再利用)
- 前述のペーシングロジックで、5秒あたり 30〜35 回程度 を上限に /bulkUpload へ送信
- 一定時間待機後、プロビジョニングログ API を呼び出してステータス確認
- 失敗レコードのみを次回バッチ用の「再送 CSV/JSON」として保存
- 失敗件数が一定閾値を超えたら、Slack/Teams/メールでアラート通知
この設計のメリット
- レート制限を意識した安全なスループット:5秒40回の枠を超えないよう制御しつつ、1万件/日程度であれば十分なバッファあり。
- トラブルシュートしやすい:バッチIDや externalId 単位でログと突き合わせが可能。
- 容量の伸びにも対応しやすい:将来 5万件/日、10万件/日になっても、「バッチ数」と「スケジュール」を調整すれば対応可能。
よくある誤解とアンチパターン
「とりあえず CSV2SCIM に全部任せる」は危険
CSV2SCIM は非常に優秀なサンプルですが、「そのまま本番のジョブエンジンとして使う」のはおすすめできません。
- スロットリングや再送戦略は「各社の要件に応じて追加実装して下さい」というスタンス
- ジョブスケジューラ・監視・アラート連携などは別途組む必要がある
- バージョンアップで挙動が変わる可能性もある(サンプルなので)
CSV2SCIM はあくまで 「CSV → SCIM → /bulkUpload までの流れを理解する教材」 と捉え、 本番運用では、そのロジックを取り込んだ 自社用のスクリプト/ワークフロー を構築する方が安全です。
「Users API で直接ユーザーを作ればよくない?」との違い
Graph の /users API を直接叩いてユーザーを作成する方法もありますが、/bulkUpload とは目的が異なります。
| 項目 | Users API | Inbound /bulkUpload |
|---|---|---|
| 対象 | Entra ユーザーオブジェクト | Entra のプロビジョニングサービス |
| 処理タイミング | 即時(同期) | 非同期(ジョブサイクルで処理) |
| 属性マッピング | クライアントがすべて判断してセット | Portal で設定したマッピング・スコープが適用される |
| HR 連携 | 複雑なロジックをクライアント側で実装する必要がある | 「生の HR データを流すだけ」でも、Entra 側で Joiner/Mover/Leaver 処理が可能 |
「人事システムからの ID ライフサイクル管理」を中心に考えるなら、API‑driven inbound provisioning + /bulkUpload を使う方が、設計も運用もシンプルになります。
まとめ:1万件/日を安全にさばくためのチェックリスト
最後に、本記事のポイントをチェックリストとして整理します。
- バッチサイズ:1リクエスト最大 50件。1万件なら 200 リクエストに分割する。
- レート制限:任意の 5秒に 40回まで。実装では 30〜35回/5秒程度に抑え、429 には指数バックオフで対応。
- 日次クォータ:P1/P2 なら 2,000 回/日、Governance なら 6,000 回/日。1万件/日(200 回)はかなり余裕あり。
- CSV2SCIM:50件チャンクは自動でやってくれるが、レート制限まで面倒を見てくれる前提にはしない。必要なら自前スロットリング実装と組み合わせる。
- externalId:人単位でユニークかつ不変な値(社員番号など)を割り当て、再送や差分更新のキーとして活用。
- ログと再送:プロビジョニングログ API で失敗レコードを抽出し、次回バッチにのみ再投入する設計にする。
- 権限:
SynchronizationData-User.Upload系とProvisioningLog.Read.Allを正しく付与し、最小権限で運用。
これらを押さえて設計すれば、「1日1万ユーザーを安全かつ確実に取り込む API‑driven inbound provisioning」 を実現できます。 CSV2SCIM をうまく活用しつつ、レート制限や再送戦略だけは自前でしっかり設計する――それが、これからの Entra ID プロビジョニング連携で失敗しないためのポイントです。

コメント