Mistral's Moderation API
On this page
Updated November 3, 2026. The response example previously included non-existent keys (
hate,violence,unpredictable); it now reflects the eleven categories frommistral-moderation-2603. The final pipeline previously used an invented guardrail format (enabled) and detected blocks by searching for “403” in the error message: it now follows the official format and checkse.status_code. The model presentation adheres to its official datasheet, and the lesson introduces Shieldstral 1.0, the moderation model in public preview since August 2026. Code verified againstmistralaiSDK types version 3.7.0. Sources: Moderation & Guardrailing · Mistral Moderation 2 · Shieldstral 1.0.
Classify content after generation
Guardrails filter input prompts. But what about content generated by the model? An innocent prompt can produce a problematic response: a middle schooler’s question about World War II might yield a description you don’t want to display without caveats. That’s the role of the Moderation API: to analyze content after the fact and assign risk scores by category.
The Moderation API is your second line of defense—it complements guardrails to create a complete security architecture, with one major difference: it doesn’t block anything on its own; it informs you.
The Mistral Moderation 2 model
The dedicated moderation model is called mistral-moderation-2603 (Mistral Moderation 2, March 2026 version). It is a specialized classifier, distinct from generation models: it doesn’t write text, it assigns scores. Its official datasheet highlights a 128,000-token context window, jailbreak detection, and strong performance on complex multilingual content, including long conversations. It replaces mistral-moderation-2411, which was deprecated on March 31, 2026.
It scores each text across 11 categories, with a score from 0.0 to 1.0 per category. It accepts two input modes, plain text or conversational, and choosing between them affects result quality more than you might expect.
A second moderation model has existed since August 4, 2026, in public preview: Shieldstral 1.0. It works differently: compact (3.8 billion parameters) and multimodal, it classifies text and images based on policy questions formulated in natural language, answering yes or no. Its weights are published under the Apache 2.0 license, allowing self-hosting. As long as it remains in preview, mistral-moderation-2603 remains the production model, and it is the one used for guardrails.
The /v1/moderations endpoint
Plain text mode is the simplest: you send one or more texts to classify, without additional context.
from mistralai.client import Mistral
import os
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
# Moderate simple text
response = client.classifiers.moderate(
model="mistral-moderation-2603",
inputs=["Text to analyze for moderation"]
)
# Access results
for result in response.results:
print(f"Categories: {result.categories}")
print(f"Scores: {result.category_scores}")
To moderate complete exchanges, prompt and response together, conversational mode gives the classifier what it was missing: the thread of the discussion.
# Moderate a complete conversation
response = client.classifiers.moderate_chat(
model="mistral-moderation-2603",
inputs=[
[
{"role": "user", "content": "How do I treat a headache?"},
{"role": "assistant", "content": "Take 1g of paracetamol..."}
]
]
)
# The classifier evaluates the full context
for result in response.results:
print(f"Health score: {(result.category_scores or {}).get('health', 0)}")
Conversational mode, which calls the /v1/chat/moderations endpoint, is what Mistral recommends for an exchange: the model is trained to classify the last turn of the conversation in light of the previous ones. To moderate your assistant’s response, place it last. The list of lists in the example allows sending multiple conversations at once; a simple list of messages suffices for a single one. The same text can thus receive a different score depending on whether it answers a legitimate or malicious question—a dosage cited in response to an information request does not have the same status as the same dosage slipped into an exchange about risky self-medication.
Response structure
For each analyzed text, the response contains two parallel blocks: categories, booleans calculated with thresholds set by Mistral based on its internal test set, and category_scores, the raw scores from 0.0 to 1.0. The example below is illustrative, with rounded values; some documentation examples still show the nine categories of the old model, where dangerous_and_criminal_content combined dangerous and criminal. You will use the latter because they let you set your own boundaries.
{
"id": "mod-abc123",
"model": "mistral-moderation-2603",
"results": [
{
"categories": {
"sexual": false,
"hate_and_discrimination": false,
"violence_and_threats": false,
"dangerous": false,
"criminal": false,
"selfharm": false,
"health": true,
"financial": false,
"law": false,
"pii": false,
"jailbreaking": false
},
"category_scores": {
"sexual": 0.01,
"hate_and_discrimination": 0.02,
"violence_and_threats": 0.03,
"dangerous": 0.02,
"criminal": 0.01,
"selfharm": 0.01,
"health": 0.72,
"financial": 0.05,
"law": 0.08,
"pii": 0.02,
"jailbreaking": 0.01
}
}
]
}
In this example, only health is marked true, with 0.72. A consumer-facing assistant would see this as a block signal; a tool intended for pharmacists would let it pass without hesitation. The same score, two opposite decisions, and that is precisely why you work with scores rather than booleans.
Moderate multiple texts in batch
The API accepts multiple texts in a single request, which changes everything for batch processing: retroactively analyzing a conversation history or a comment database becomes a reasonable operation in terms of call count.
# Moderate multiple texts in a single request
texts = [
"First text to analyze",
"Second text to analyze",
"Third text to analyze"
]
response = client.classifiers.moderate(
model="mistral-moderation-2603",
inputs=texts
)
# Each text has its own result
for i, result in enumerate(response.results):
scores = result.category_scores or {}
max_cat = max(scores, key=lambda c: scores[c])
max_score = scores[max_cat]
print(f"Text {i+1}: max score = {max_score:.2f} ({max_cat})")
Guardrails vs Moderation API: when to use what
| Aspect | Custom Guardrails | Moderation API |
|---|---|---|
| Timing | BEFORE generation | AFTER generation |
| Action | Blocks automatically (403) | Returns scores (you decide) |
| Control | Your thresholds per category, applied by Mistral | Raw scores, free logic |
| Call | Parameter of the generation request | Separate call to /v1/moderations |
| Use case | Filter dangerous prompts | Classify responses |
Example: complete pipeline
The pipeline below shows the sequencing of the two mechanisms. Generation is protected by a guardrail focused on jailbreaking; the response is then classified along with the question that produced it, and a custom decision intervenes as soon as a score exceeds 0.5. Note that the two steps return distinct statuses—blocked_input and blocked_output—so your logs clearly tell where the request stopped.
from mistralai.client import errors
from mistralai.client.models import GuardrailConfigTypedDict
GUARDRAIL_JAILBREAK: list[GuardrailConfigTypedDict] = [
{
"block_on_error": True,
"moderation_llm_v2": {
"custom_category_thresholds": {"jailbreaking": 0.1},
"action": "block",
},
}
]
def secure_pipeline(user_message: str) -> dict:
"""Pipeline with guardrails (input) and moderation (output)."""
# Step 1: generate with guardrails
try:
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": user_message}],
guardrails=GUARDRAIL_JAILBREAK,
)
except errors.SDKError as e:
if e.status_code == 403:
return {"status": "blocked_input", "content": None}
raise
generated = response.choices[0].message.content
if not isinstance(generated, str):
return {"status": "error", "content": None}
# Step 2: moderate the output, in the context of the question
moderation = client.classifiers.moderate_chat(
model="mistral-moderation-2603",
inputs=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": generated},
],
)
scores = moderation.results[0].category_scores or {}
if max(scores.values(), default=0.0) > 0.5:
return {"status": "blocked_output", "scores": scores}
return {"status": "ok", "content": generated, "scores": scores}
Key takeaways
- The Moderation API uses the
mistral-moderation-2603model to classify content - Two modes available: plain text and conversational
- Scores range from 0.0 to 1.0—you define your own decision thresholds
- Conversational mode is more accurate because it accounts for context
- Combine guardrails (input) + moderation (output) for defense in depth