Azure OpenAI content_filter — filtered by policy, no completion returned
Content filter is Azure OpenAI's biggest behavioural difference from openai.com and the source of the most surprising production failures. This page maps every category, severity, and mitigation.
Quick fix (TL;DR)
finish_reason: "content_filter" and no message.content. Fix by (a) reading content_filter_results to identify the triggering category, (b) creating a custom content filter policy in Azure AI Foundry with higher thresholds for legitimate use cases, and (c) handling the filtered state gracefully instead of treating it as a hard failure.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.
ChatCompletion(
id="chatcmpl-...",
choices=[
Choice(
finish_reason="content_filter",
message=ChatCompletionMessage(content=None, role="assistant"),
content_filter_results={
"hate": {"filtered": False, "severity": "safe"},
"self_harm": {"filtered": False, "severity": "safe"},
"sexual": {"filtered": True, "severity": "medium"},
"violence": {"filtered": False, "severity": "safe"}
}
)
]
)openai.BadRequestError: Error code: 400 - {'error': {'code': 'content_filter', 'message': "The response was filtered due to the prompt triggering Azure OpenAI's content management policy. Please modify your prompt and retry.", 'innererror': {'code': 'ResponsibleAIPolicyViolation', 'content_filter_result': {'hate': {'filtered': False, 'severity': 'safe'}, 'jailbreak': {'filtered': True, 'detected': True}, ...}}}
Reference
The four content filter categories
| Category | What it detects | Default action | Common false positives |
|---|---|---|---|
hate | Discriminatory language about protected groups | Block at medium+ | Historical analysis, quoted material |
sexual | Sexually explicit content | Block at medium+ | Medical, health-education, LGBTQ+ contexts |
violence | Descriptions of physical harm | Block at medium+ | News summaries, security research, fiction |
self_harm | Self-injury content | Block at medium+ | Mental health support, safety research |
jailbreak (prompt shield) | Attempts to bypass system prompt | Block on detection | Legitimate role-play, adversarial testing |
protected_material_text | Known copyrighted text | Block on detection | Public domain, fair use, quoted excerpts |
protected_material_code | Verbatim copies of public code | Annotate, do not block | Standard boilerplate |
Severity levels and default behaviour
| Severity | Meaning | Default action | Configurable in custom policy |
|---|---|---|---|
safe | No concerning content | Pass | Cannot be blocked |
low | Mild references | Pass | Can be blocked |
medium | Moderate concern | Block | Can be allowed |
high | Explicit / severe | Block | Cannot be allowed (except with approval) |
Root causes, ranked by frequency
Based on developer reports across Azure OpenAI SDK forums, GitHub issues, and Microsoft Q&A during 2025–2026.
- 28%Default policy is too strict for your use case. The default blocks at severity
mediumacross all four categories. Medical, legal, security-research, and fiction workloads routinely hitmediumfalse positives. - 20%Jailbreak detection triggering on legitimate role-play. Prompts like "You are a hacker helping me test my system" flag as jailbreak even when the intent is benign security research.
- 16%User input containing quoted harmful content. Even when the user is quoting a news article for the model to summarise, the quoted content itself trips the filter.
- 12%Streaming responses filtered mid-stream. Output that starts benign but drifts into a filtered category has its remaining tokens dropped; the client sees a truncated response.
- 10%Language other than English. Non-English content is filtered less accurately; expect a higher false-positive rate on translated harmful content and false negatives on the reverse.
- 8%Prompt shield / indirect prompt injection. Documents pasted into the prompt (RAG chunks) can carry jailbreak-style text and flag the whole request.
- 4%Protected material — code matches on common boilerplate like Apache LICENSE files, causing annotation on outputs but not blocking.
- 2%Region-specific filter differences. Filter models roll out in stages; a request that passes in East US may fail in Sweden Central until parity ships.
Fixes — copy-paste solutions
Read content_filter_results to detect and route filtered responses
The filtered response contains a content_filter_results block listing every category and its severity. Extract it to understand what tripped the filter, log for analysis, and return a specific message to the user instead of a generic error.
from openai import AzureOpenAI, BadRequestError from typing import Any client = AzureOpenAI( api_key="...", azure_endpoint="...", api_version="2024-10-21", ) def call_with_filter_handling(messages: list) -> dict: try: response = client.chat.completions.create( model="gpt4o-prod", messages=messages, max_tokens=500, ) except BadRequestError as e: # Prompt-side filter — request never ran body = e.body or {} inner = body.get("innererror", {}) cf = inner.get("content_filter_result", {}) triggered = [k for k, v in cf.items() if v.get("filtered") or v.get("detected")] return { "status": "prompt_filtered", "triggered_categories": triggered, "user_message": ( "Your request could not be processed because it contains content " f"related to: {', '.join(triggered)}. Please rephrase and try again." ), } choice = response.choices[0] if choice.finish_reason == "content_filter": # Response-side filter — model generated something we cannot show cf = getattr(choice, "content_filter_results", {}) or {} triggered = [k for k, v in cf.items() if v.get("filtered")] severity = {k: v.get("severity") for k, v in cf.items() if v.get("filtered")} return { "status": "response_filtered", "triggered_categories": triggered, "severity": severity, "user_message": "The generated response was blocked. Try rephrasing your question.", } return {"status": "ok", "content": choice.message.content}
Create a custom content filter policy for higher-severity categories
In Azure AI Foundry (portal), create a custom content filter with higher severity thresholds for categories that produce false positives in your domain. Attach the policy to your deployment. Requires approval for allowing high severity in any category.
# 1) Create the custom filter in Azure AI Foundry: # Portal -> Azure AI Foundry -> Your project -> Content Filters -> New # # For a medical Q&A app, typical config: # hate: block at medium # sexual: block at high (allow medium for reproductive-health topics) # violence: block at high (allow medium for injury descriptions) # self_harm: block at medium # jailbreak: block on detection # protected_material_text: annotate only (do not block) # # 2) Attach the filter to your deployment via CLI az cognitiveservices account deployment update \ --name my-aoai-resource \ --resource-group my-rg \ --deployment-name gpt4o-medical \ --set properties.raiPolicyName="medical-qa-filter-v1" # 3) Verify the attachment az cognitiveservices account deployment show \ --name my-aoai-resource -g my-rg \ --deployment-name gpt4o-medical \ --query "properties.raiPolicyName"
high severity in any category. Submit the Modified Content Filters application form to Microsoft — reviews take 5-10 business days and require a documented use case.Handle streaming filtered responses cleanly
When streaming, content_filter can arrive mid-stream. The client must watch for finish_reason: "content_filter" in the final chunk, discard or annotate the accumulated partial response, and signal the UI cleanly.
from openai import AzureOpenAI client = AzureOpenAI(api_key="...", azure_endpoint="...", api_version="2024-10-21") def stream_with_filter_awareness(messages: list): """Yields (event_type, payload) tuples: ('token', str) or ('filter', dict) or ('done', None).""" stream = client.chat.completions.create( model="gpt4o-prod", messages=messages, stream=True, max_tokens=500, ) partial = [] for chunk in stream: if not chunk.choices: continue choice = chunk.choices[0] # Filter fired mid-stream if choice.finish_reason == "content_filter": cf = getattr(choice, "content_filter_results", {}) or {} triggered = [k for k, v in cf.items() if v.get("filtered")] yield ("filter", { "triggered": triggered, "partial_length": len("".join(partial)), "message": "Response filtered mid-generation — content discarded.", }) return if choice.delta and choice.delta.content: partial.append(choice.delta.content) yield ("token", choice.delta.content) yield ("done", None) # UI-side usage for event, payload in stream_with_filter_awareness([{"role": "user", "content": "..."}]): if event == "token": print(payload, end="", flush=True) elif event == "filter": print(f"\n\n[Filtered: {payload['triggered']}]") elif event == "done": print("\n[Complete]")
Prevention checklist
Ship these seven safeguards once and this error stops appearing in your logs.
- Always read
content_filter_resultsinstead of treatingcontent_filteras an opaque failure. - Log triggered category + severity to your observability stack — patterns reveal prompt engineering issues.
- For domain-specific use cases (medical, legal, security research), file for a custom content filter policy early — reviews take 5-10 business days.
- Sanitise user-provided text before including it in prompts: strip HTML, escape harmful-looking quotes, and consider translating to third-person to reduce sexual/violence false positives.
- For RAG applications, run the retrieved chunks through the content filter first via the Azure Content Safety API — cheaper than a full completion failure.
- Set finish_reason handling in your streaming client to distinguish
stop,length,content_filter, andtool_callsas separate UI states. - Run your prompt library through a filter probe weekly — Azure updates filter models and behaviour drifts.
Frequently asked questions
content_filter_result. The response filter runs on generated output; the request completes with finish_reason=content_filter and no content. Both apply to the same four categories but detect different problems.Related errors & hubs
Get the weekly AI-error digest
New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.