Azure OpenAI DeploymentNotFound — deployment truly missing or unreachable
The naming-confusion version of this error is covered elsewhere. This page is for when the deployment name is right, the SDK client is right, and you still get 404 — because the deployment does not exist where you think it does.
Quick fix (TL;DR)
Real error messages you'll see
These are the exact strings returned by the Azure OpenAI service and its SDKs when this error occurs. Copy-paste-searching any of them should land on this page.
openai.NotFoundError: Error code: 404 - {'error': {'code': 'DeploymentNotFound', 'message': 'The API deployment for this resource does not exist. If you created the deployment within the last 5 minutes, please wait a moment and try again.'}}# In portal → your resource → Deployments
# gpt4o-prod | gpt-4o | 2024-08-06 | Active | GlobalStandard
#
# But API call:
curl "https://myresource.openai.azure.com/openai/deployments/gpt4o-prod/chat/completions?api-version=2024-10-21" \
-H "api-key: $KEY" -d '{"messages":[{"role":"user","content":"hi"}]}'
# {"error":{"code":"DeploymentNotFound",...}}
Reference
Nine reasons a "correct" deployment name still 404s
| # | Cause | How to confirm |
|---|---|---|
| 1 | Deployment was deleted | Portal → Deployments; if absent, deleted |
| 2 | Endpoint URL points to a different resource | Parse hostname; verify resource |
| 3 | Wrong subscription context in auth | Compare token oid with subscription's tenant |
| 4 | Terraform apply recreated with new name | Check Terraform state history |
| 5 | Deployment renamed in a fix, code not updated | Check ARM audit log |
| 6 | Case mismatch — Azure is case-sensitive here | gpt4o-prod ≠ gpt4o-Prod |
| 7 | Deployment moved to different resource | Check ARM audit log for move ops |
| 8 | Resource itself deleted (accidental cleanup) | az resource show returns 404 |
| 9 | Cross-tenant confusion | Token tenant ≠ resource tenant |
Root causes, ranked by frequency
Based on developer reports across Azure OpenAI SDK forums, GitHub issues, and Microsoft Q&A during 2025–2026.
- 25%Deployment deleted. Accidental portal deletion, or Terraform destroy on the wrong workspace. Zero recovery — you must recreate.
- 18%Wrong endpoint in config. Not the naming-vs-model-id case — the endpoint hostname itself points to a different resource than where the deployment lives.
- 14%Terraform recreated with new name. Immutable resource replaced; new deployment has a slightly different name that code did not adopt.
- 12%Deployment was renamed. An engineer renamed for consistency; the code did not catch up. ARM allows rename; SDK code depends on the exact string.
- 10%Case mismatch. Deployment name is case-sensitive in the URL path.
- 8%Wrong subscription context. Managed identity has role on Subscription A; endpoint is on Subscription B. Auth may or may not succeed depending on role scope; DeploymentNotFound is the frequent shape.
- 6%Resource itself deleted. An automated cleanup script targeting "dev-*" caught a resource that got renamed to match.
- 4%Cross-tenant setup misconfigured. Multi-tenant SaaS; user's token from one tenant, deployment in another. Fails 404.
Fixes — copy-paste solutions
Diagnostic script — walks all nine causes in order
Reads your config, resolves the resource, lists deployments, checks case-sensitivity, and prints a specific "this is the cause" verdict at the end. Run this before opening a support ticket.
"""Diagnose a persistent DeploymentNotFound. Prints a verdict.""" import os import sys from urllib.parse import urlparse from azure.identity import DefaultAzureCredential from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient from azure.core.exceptions import ResourceNotFoundError endpoint = os.environ["AZURE_OPENAI_ENDPOINT"] deployment_name = os.environ["AZURE_OPENAI_DEPLOYMENT"] subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"] # Parse resource name from endpoint URL host = urlparse(endpoint).hostname if not host or not host.endswith(".openai.azure.com"): print(f"VERDICT: Endpoint hostname is malformed: {host!r}") sys.exit(1) resource_name = host.split(".")[0] print(f"Endpoint: {endpoint}") print(f"Resource name (parsed): {resource_name}") print(f"Deployment (from env): {deployment_name}") print(f"Subscription: {subscription_id}") credential = DefaultAzureCredential() client = CognitiveServicesManagementClient(credential, subscription_id) # Step 1: Does the resource exist in this subscription? found_resource = None for account in client.accounts.list(): if account.name == resource_name and account.kind == "OpenAI": found_resource = account break if not found_resource: print(f"\nVERDICT: No AOAI resource named '{resource_name}' in subscription {subscription_id}.") print("Likely causes: (a) resource deleted, (b) wrong subscription context.") print("Available AOAI resources in this subscription:") for a in client.accounts.list(): if a.kind == "OpenAI": print(f" {a.name} in {a.location}") sys.exit(1) rg = found_resource.id.split("/")[4] print(f"Resource found: {found_resource.name} in {found_resource.location} (rg: {rg})") # Step 2: List deployments — exact match, case-insensitive match, or nothing? deployments = list(client.deployments.list(rg, found_resource.name)) print(f"\nDeployments on this resource ({len(deployments)}):") for d in deployments: print(f" {d.name} -> {d.properties.model.name} v{d.properties.model.version}") exact = [d for d in deployments if d.name == deployment_name] case_insensitive = [d for d in deployments if d.name.lower() == deployment_name.lower()] if exact: print(f"\nVERDICT: Deployment '{deployment_name}' exists and endpoint matches.") print("This means the 404 is likely propagation (deployment created <5min ago) —") print("or an SDK problem. Test with curl to confirm.") sys.exit(0) if case_insensitive: match = case_insensitive[0].name print(f"\nVERDICT: Case mismatch. Config says '{deployment_name}', actual is '{match}'.") print(f"Fix: set AZURE_OPENAI_DEPLOYMENT={match}") sys.exit(1) # Step 3: No match at all print(f"\nVERDICT: Deployment '{deployment_name}' does not exist on resource '{resource_name}'.") print("Likely causes:") print(" - Deployment was deleted (check ARM audit log)") print(" - Deployment was renamed (check the deployment list above for likely candidates)") print(" - Wrong endpoint URL (are you sure this is the right resource?)") print(" - Terraform recreated with a new name — check Terraform state")
Check the ARM audit log for delete/rename operations
The Azure Activity Log records every ARM operation on the resource, including deployment deletes and renames. Query for events in the last 14 days to find who did what and when.
# List recent operations on the AOAI resource RG=my-rg AOAI=my-aoai-resource RESOURCE_ID=$(az cognitiveservices account show \ --name $AOAI -g $RG --query id -o tsv) # All deployment-scope events in last 14 days az monitor activity-log list \ --resource-id $RESOURCE_ID \ --start-time "$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)" \ --query "[?contains(operationName.value, 'deployments')].{time:eventTimestamp, op:operationName.value, status:status.value, caller:caller}" \ -o table # Deployment-specific delete events az monitor activity-log list \ --resource-id $RESOURCE_ID \ --start-time "$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)" \ --query "[?operationName.value=='Microsoft.CognitiveServices/accounts/deployments/delete/action'].{time:eventTimestamp, caller:caller, status:status.value}" \ -o table # When you find the culprit event, drill in EVENT_ID=<correlationId-from-above> az monitor activity-log list \ --correlation-id $EVENT_ID \ --query "[].{op:operationName.value, caller:caller, ip:callerIpAddress, ua:properties.userAgent}"
caller is a service principal, cross-reference with your CI/CD audit — Terraform destroys are the most common source of accidental delete.Add a resource-lock to prevent accidental deletion
Add a CanNotDelete lock at the resource-group or resource level. This blocks delete operations from anyone (including Owner-role users) unless the lock is removed first. Adds a two-step to intentional deletes; blocks accidental ones.
# Lock the entire AOAI resource against delete az lock create \ --name aoai-no-delete \ --resource-group my-rg \ --resource my-aoai-resource \ --resource-type Microsoft.CognitiveServices/accounts \ --lock-type CanNotDelete \ --notes "Blocks accidental delete. Remove only for planned decommission." # For extra safety, lock the entire resource group az lock create \ --name aoai-rg-no-delete \ --resource-group my-rg \ --lock-type CanNotDelete # Verify az lock list --resource-group my-rg -o table # Verify Terraform respects the lock (if using Terraform) # You may need "prevent_destroy = true" in the AOAI resource block
Prevention checklist
Ship these seven safeguards once and this error stops appearing in your logs.
- Add CanNotDelete locks to production AOAI resources and their resource groups.
- Set up Azure Monitor alerts on
Microsoft.CognitiveServices/accounts/deployments/deleteoperations. - Never grant Cognitive Services Contributor at the subscription level — scope to the specific resource.
- Add a startup health check that verifies the configured deployment exists before serving traffic.
- Version deployment names (
gpt4o-prod-v1,gpt4o-prod-v2) — renames leave a paper trail. - Use IaC with drift detection so accidental portal changes surface within hours.
- Document deployment name conventions and stick to them — case-sensitivity + typos are almost all preventable.
Frequently asked questions
az cognitiveservices account recover. Deployments inside them are NOT recovered as part of this operation and must be recreated after account recovery.Related errors & hubs
Get the weekly AI-error digest
New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.