Azure Healthcare APIs の DICOM サービスに対して URL を叩くと 404 Not Found。よくある指摘(URL 形式、API バージョン、アプリ登録、トークン)を見直しても直らない——そんなときの決定版トラブルシューティングです。結論から言うと、原因の大半は「API バージョンの指定場所」と「パーティションの扱い」。この記事では /v2 を含む正しいエンドポイント設計、認証・権限、ネットワーク、アップロード検証まで、現場で詰まりやすいポイントを体系的に整理します。
Azure DICOMWeb が常に 404 を返す主因と全体像
Azure Healthcare APIs の DICOM サービスは、管理プレーン(ARM の api-version)とは異なり、データプレーンの DICOMWeb は「パスに API バージョンを含める」構造です。すなわち ?api-version=... のクエリ文字列は使いません。正しくは次のように パスで指定します。
https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/studies
これを外すと、サービス ルート(/)か存在しないパスにアクセスすることになり、常時 404 Not Found が返ります。さらに、マルチパーティション環境や将来の拡張を見越した DICOM サービスでは、パーティション名を URL に含める必要が生じることがあります。
| 現象 | 主な原因 | 対処の要点 |
|---|---|---|
| 常時 404 Not Found | パスに /v2 等のバージョンがない/大文字パスや末尾スラッシュ誤り/ワークスペース・サービス名の誤記 | https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/studies のように小文字・正確なパスで再試行 |
| 一部 API が 400 Bad Request | パーティション未指定、または指定名の不一致 | /v2/partitions で一覧確認し、.../v2/partitions/<PartitionName>/studies のように明示 |
| 401/403 | トークン Audience の誤り、ロール未付与 | Audience を https://dicom.healthcareapis.azure.com/ に、ロールを DICOM Data Owner/Reader で再設定 |
| 415/422 | STOW-RS の Content-Type 不備/DICOM タグ不正 | multipart/related; type=application/dicom でアップロードし、事前に DICOM を検証 |
正しいエンドポイント設計(URL の作り方)
Azure の DICOMWeb は QIDO-RS(検索)、WADO-RS(取得)、STOW-RS(格納)を提供します。エンドポイントは以下の形に正規化します。
| 要素 | 値/例 | 注意点 |
|---|---|---|
| ホスト | <workspace>-<service>.dicom.azurehealthcareapis.com | ワークスペース名・サービス名は Azure Portal/CLI の値を厳密一致で。 末尾にスラッシュを付けない。 |
| バージョン | /v2(推奨) | クエリの ?api-version= は使わない。2025 年時点の代表値: v1.0-prerelease / v1 / v2。 |
| パーティション(任意/推奨) | /partitions/<PartitionName>例: /partitions/Microsoft.Default | マルチパーティション有効時や 400 が出る場合は必須。/v2/partitions で確認可能。 |
| リソース | /studies / /series / /instances | 小文字を徹底(/Studies は非推奨)。 |
組み合わせ例:
https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/studies
https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/partitions/Microsoft.Default/studies
まずは 3 ステップで「生存確認」
- アクセストークンを取得
# Bash(Azure CLI) TOKEN=$(az account get-access-token \ --resource https://dicom.healthcareapis.azure.com/ \ --query accessToken -o tsv) echo ${TOKEN:0:20}...あるいは PowerShell:$token = (az account get-access-token --resource https://dicom.healthcareapis.azure.com/ | ConvertFrom-Json).accessToken $token.Substring(0,20) + "..." - バージョン付きのベース呼び出し
# QIDO-RS: studies を 1 件だけ確認 curl -sS -H "Authorization: Bearer $TOKEN" \ "https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/studies?limit=1" \ -H "Accept: application/dicom+json" - パーティション一覧を取得
curl -sS -H "Authorization: Bearer $TOKEN" \ "https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/partitions" # 例: ["Microsoft.Default"]一覧に表示された名前を使って、以下のようにパーティション付きで再試行します。curl -sS -H "Authorization: Bearer $TOKEN" \ "https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/partitions/Microsoft.Default/studies?limit=1" \ -H "Accept: application/dicom+json"
認証と権限の再点検(401/403 を確実になくす)
データプレーンの Audience は https://dicom.healthcareapis.azure.com/ です。クライアント資格情報フロー等では scope に https://dicom.healthcareapis.azure.com/.default を指定します。ユーザー/アプリには、DICOM サービスのスコープで次の RBAC を割り当てます。
- DICOM Data Owner(読み書き・管理)
- DICOM Data Reader(読み取り)
確認チェックリスト:
- トークンの
aud(Audience)がhttps://dicom.healthcareapis.azure.com/になっている。 - サブスクリプション/リソースレベルで適切なロールが付与されている。
- 異なるテナントのアプリを使う場合はクロステナント許可・同意が完了している。
MSAL を使った取得例(Node.js):
// Node.js (msal-node)
const { ConfidentialClientApplication } = require("@azure/msal-node");
const app = new ConfidentialClientApplication({
auth: { clientId: process.env.CLIENT_ID, authority: `https://login.microsoftonline.com/${process.env.TENANT_ID}`, clientSecret: process.env.CLIENT_SECRET }
});
const token = await app.acquireTokenByClientCredential({
scopes: ["https://dicom.healthcareapis.azure.com/.default"]
});
console.log(token.accessToken.substring(0,20) + "...");
ステータスコード別・原因と対処の対照表
| HTTP | よくある根本原因 | 即効性のある対処 |
|---|---|---|
| 404 Not Found | /v2 なし/パス表記ミス(/Studies 等)/ホスト名の組み立て誤り | URL を 小文字+バージョンに統一。ホスト名にリージョンは含めない。 |
| 400 Bad Request | パーティション名未指定、クエリパラメータの誤り、ヘッダー不足 | /v2/partitions で実在名を確認し、フルパスでアクセス。Accept を application/dicom+json に。 |
| 401 Unauthorized | Bearer トークン未設定・期限切れ、Audience 不一致 | トークンを再取得し、Authorization: Bearer を必ず付与。Audience を再確認。 |
| 403 Forbidden | RBAC ロール不足、VNet/Firewall で禁止 | DICOM Data Owner/Reader を割り当て。必要に応じてクライアントの送信元を許可。 |
| 415/422 | STOW-RS の Content-Type/DICOM タグ不正 | multipart/related; type=application/dicom を使用。アップロード前に DICOM を検証。 |
| 429 Too Many Requests | 過剰な並列/リトライ戦略無し | 指数バックオフと上限調整。Retry-After を尊重。 |
具体例で理解する「正しい呼び出し」
QIDO-RS(検索)
# 直近の Study を 10 件
curl -sS -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/dicom+json" \
"https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/partitions/Microsoft.Default/studies?limit=10"
WADO-RS(取得)
# Instance 本体(オリジナル DICOM ファイル)を取得
curl -L -H "Authorization: Bearer $TOKEN" \
"https://<host>/v2/partitions/Microsoft.Default/studies/<StudyInstanceUID>/series/<SeriesInstanceUID>/instances/<SOPInstanceUID>"
STOW-RS(格納/アップロード)
# 1 ファイルを STOW-RS でアップロード(multipart/related)
BOUNDARY="dicomboundary$(date +%s)"
curl -i -X POST "https://<host>/v2/partitions/Microsoft.Default/studies" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/dicom+json" \
-H "Content-Type: multipart/related; type=application/dicom; boundary=$BOUNDARY" \
--data-binary @- <<EOF
--$BOUNDARY
Content-Type: application/dicom
Content-Location: file1.dcm
$(cat ./file1.dcm)
--$BOUNDARY--
EOF
注:単一ファイルなら Content-Type: application/dicom で /instances に POST する実装もありますが、複数格納や互換性の観点で multipart/related を推奨します。
「パーティション」を理解する(400 の常連原因)
Azure DICOM サービスには既定パーティション Microsoft.Default が存在します。API によっては明示を要求するため、まず一覧で実在確認→フルパス指定が安全です。
# 一覧
GET /v2/partitions
# 既定パーティションで検索
GET /v2/partitions/Microsoft.Default/studies
環境を分けたい場合はパーティションを作成して運用する設計が有効です。権限やデータライフサイクルをパーティション単位で分離できます。
ネットワークと DNS の落とし穴(Private Endpoint/Firewall)
ネットワーク周りの設定不整合でも 404/403 に見える事象があります。以下を順に確認します。
| 観点 | 確認ポイント | 期待値 |
|---|---|---|
| PublicNetworkAccess | 必要に応じて「Enabled」。Private Only 運用なら無効+Private Endpoint 完備。 | 要件に一致 |
| Private Endpoint | プライベート DNS ゾーン(例:privatelink.dicom.azurehealthcareapis.com)と VNet のリンク | 名前解決が Private IP を指す |
| 名前解決 | nslookup <host> で Private/Global のどちらに解決されているか | 設計に合致(Private Only なら Private IP) |
| 送信元制御 | Firewall/Service Endpoints/NVA 経由で遮断されていないか | 許可 |
Private Endpoint へ切替中にキャッシュされた DNS が残っていると「時々 404」などの不可解な事象を引き起こすことがあります。クライアントの DNS キャッシュをクリアするか、明示的な DNS 設定を適用して安定化させてください。
ポータル/CLI での健全性チェック
- Provisioning State が Succeeded。
- Workspace と DICOM Service が同じサブスクリプション/リージョンで正常。
- 診断設定(Log Analytics 送信)を有効化し、データプレーン呼び出しのログが届いている。
参考 CLI(拡張コマンドの名称は環境により差異があります):
# 拡張の追加(必要に応じて)
az extension add --name healthcareapis
# DICOM サービスの状態確認(例)
az healthcareapis workspace dicom-service show
--resource-group
--workspace-name
--name
--output table
ブラウザ/JavaScript からの呼び出し(CORS)
フロントエンドから直接 DICOM サービスへアクセスする場合、CORS のプリフライト(OPTIONS)が通るように設定が必要です。検証段階では許可オリジンを限定しつつ、Authorization ヘッダーや Content-Type を許可リストに入れます。
// fetch 例(トークンは別途取得)
const res = await fetch("https://<host>/v2/studies?limit=5", {
headers: { "Authorization": `Bearer ${token}`, "Accept": "application/dicom+json" }
});
const data = await res.json();
console.log(data);
CORS が未設定のままブラウザから叩くと、ネットワークエラーに見えたり 404/401 の判定が困難になります。まずはサーバー側の CORS を明確に設定して、再現性のある環境を作りましょう。
アップロード前の DICOM 検証(415/422 の未然防止)
STOW-RS は受信するファイルの DICOM コンフォーマンスに厳格です。以下の観点で事前チェックを行うとトラブルを大幅に削減できます。
- 必須タグ(Study/Series/Instance UID、SOP Class、Patient/Study 情報など)が存在する。
- Transfer Syntax の宣言と実体が一致している。
- マルチフレームの Frame 記述が正しい。
- 匿名化ポリシーに沿って PHI が除去されている(必要な場合)。
テスト時は、1 件成功(201/202)を得てからバッチ投入に進むのが定石です。
運用に効く「URL 構築テンプレート」
# 環境変数ベースの汎用テンプレート(Bash)
HOST="https://$WORKSPACE-$SERVICE.dicom.azurehealthcareapis.com"
BASE="$HOST/v2"
PART="$BASE/partitions/$PARTITION" # 例: Microsoft.Default
# 検索
curl -H "Authorization: Bearer $TOKEN" -H "Accept: application/dicom+json" "$PART/studies?limit=50"
# 取得
curl -H "Authorization: Bearer $TOKEN" "$PART/studies/$STUDY/series/$SERIES/instances/$INSTANCE"
# 格納(multipart/related)
# ...(前掲の STOW-RS 例を流用)
CI/CD の変数化や IaC(Bicep/Terraform)の出力に組み合わせると、ヒューマンエラーによる 404 を根絶できます。
「よくある勘違い」Q&A
Q. ?api-version=2022-06-01 を付ければ動く? A. それは管理プレーン(ARM API)で使う形式です。DICOMWeb のデータプレーンでは無効です。/v2 のようにパスで指定してください。 Q. ホスト名にリージョンは入る? A. いいえ。<workspace>-<service>.dicom.azurehealthcareapis.com の固定形です。 Q. /Studies のような大文字パスは? A. 実装依存の挙動を避けるため小文字で統一しましょう。混在は 404 の温床です。 Q. 既定パーティションを省略できる? A. 環境や API により省略不可の場合があるため、Microsoft.Default を明示したフルパスを推奨します。
デプロイ直後から安定稼働まで:実践フロー
- Portal/CLI で Workspace と DICOM サービスを作成。Provisioning State = Succeeded を確認。
- 必要に応じて Private Endpoint と Private DNS を構成。
nslookupで解決先を検証。 - アプリ登録(Microsoft Entra ID)。
/.defaultスコープでトークン取得できることを確認。 - サービスに DICOM Data Owner/Reader を割り当て。
- CLI で
az account get-access-token→curlによる QIDO-RS の疎通を確認。 /v2/partitionsで既定パーティションを把握。必要なら専用パーティションを用意。- 小さな DICOM で STOW-RS を試験。201/202 を確認後、バッチ投入。
- 診断ログとメトリクスを可視化し、失敗時のリトライ/バックオフを実装。
チェックリスト(貼って使える運用表)
| 項目 | チェック | メモ |
|---|---|---|
URL に /v2 を含む | ☐ | |
小文字パス(/studies)で統一 | ☐ | |
パーティションを明示(例:Microsoft.Default) | ☐ | |
Audience:https://dicom.healthcareapis.azure.com/ | ☐ | |
| RBAC:DICOM Data Owner/Reader を割り当て | ☐ | |
| Public/Private のネットワーク設計に整合 | ☐ | DNS 解決先を確認 |
| CORS(フロント直叩き時)を設定 | ☐ | |
| STOW-RS の Content-Type を正しく指定 | ☐ | multipart/related 推奨 |
| 診断ログとメトリクスを有効化 | ☐ |
ケーススタディ:404 から復旧した実例
ある環境では、次の URL を呼び出して常時 404 に陥っていました。
# NG 例(管理 API の癖が混入)
https://<workspace>-<service>.dicom.azurehealthcareapis.com/Studies?api-version=2022-06-01
ここから、以下の最小修正で即時復旧しました。
/Studies→/studies(小文字化)?api-version=...を削除し、/v2をパスに追加- 必要に応じて
/partitions/Microsoft.Defaultを差し込み
# OK 例
https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/partitions/Microsoft.Default/studies
この 3 点をテンプレート化して以後のプロジェクトで再発が止まりました。ポイントは「管理プレーンの流儀をデータプレーンに持ち込まない」ことです。
トラブルシューティングの決定木(文字版)
Step 1: /v2 を付けて小文字パスで再試行 → 404 が消えれば解決。
Step 2: 400/401/403 が出るなら、そのコードの対処表に沿って修正(パーティション/トークン/RBAC)。
Step 3: それでも不可なら、nslookup・curl -v・診断ログでネットワーク/CORS を切り分け。
Step 4: STOW-RS の 415/422 は DICOM 検証ツールでファイル側を是正。
セキュリティと運用ベストプラクティス
- アプリ登録は必要最小限の権限で。自動化ジョブにはマネージド ID を活用。
- 本番は Private Endpoint/プライベート DNS で閉域化。テスト時のみパブリック許可。
- ログには PHI を載せない。必要に応じて疑似データで検証。
- スロットリングを想定した指数バックオフと冪等な再送設計。
まとめ(要点の再確認)
- 原因の本丸はAPI バージョンをクエリで渡していたこと。DICOMWeb は パス版の
/v2を使う。 - URL は
https://<workspace>-<service>.dicom.azurehealthcareapis.com/v2/...が正解。 - 400 が出るときは パーティション名を URL に追加(例:
/partitions/Microsoft.Default)。 - 401/403 は Audience と RBAC を再確認。トークンは
https://dicom.healthcareapis.azure.com/.default。 - STOW-RS は multipart/related と DICOM 検証で安定化。
- ネットワーク(PNA、Private Endpoint、DNS)と CORS の整合性を最後に点検。
付録:よく使うヘッダーとクエリパラメータ
| 項目 | 値 | 用途 |
|---|---|---|
| Authorization | Bearer <access_token> | 全 API で必須 |
| Accept | application/dicom+json | QIDO/WADO の JSON 応答 |
| Content-Type | multipart/related; type=application/dicom | STOW-RS(複数ファイル) |
| limit / offset | 例:?limit=100&offset=0 | QIDO のページング |
付録:PowerShell のワンライナー
$env:HOST = "https://$env:WORKSPACE-$env:SERVICE.dicom.azurehealthcareapis.com"
$token = (az account get-access-token --resource https://dicom.healthcareapis.azure.com/ | ConvertFrom-Json).accessToken
Invoke-RestMethod -Headers @{Authorization="Bearer $token";Accept="application/dicom+json"} `
-Uri "$($env:HOST)/v2/partitions/Microsoft.Default/studies?limit=5"
最後に
「URL のパスにバージョン、必要ならパーティションを明示」——この 2 点をチームの標準に落とし込むだけで、DICOMWeb の 404 はほぼ解消します。記事内のテンプレートやチェックリストを使い、設計・実装・運用の三層でミスを取り除いてください。迷ったらまず /v2/partitions と Accept: application/dicom+json から。確実に前進できます。

コメント