DeepSeek's Documented Content Policy Restrictions
8 min read · updated August 11, 2026
“DeepSeek refused” describes at least three unrelated mechanisms with three different remedies. Which one you hit determines whether the fix is a prompt change, a contract question, or nothing at all.
Three different things, one complaint
- The terms of service and usage policy. A contract between you and DeepSeek governing what you may do with the hosted API. Breaching it is an account matter. It is not enforced by the model and does not necessarily produce any particular API response.
- The model’s trained behaviour. Alignment training makes certain continuations low-probability, so the model declines. This travels with the weights: download them, run them offline, and the refusals come with you.
- Serving-side filtering. Classification applied to input or output at the boundary, outside the model. This exists only where somebody deployed it, which means the hosted product and not weights on your own hardware.
These fail differently and that is how you tell them apart. A trained refusal is a normal completion — 200, a polite decline in content, finish_reason: "stop", and you are billed for it. A serving-side block is not a completion, and the OpenAI-compatible schema DeepSeek implements has a dedicated finish_reason value for it: content_filter. If you are not logging finish_reason you cannot distinguish these, and almost every confused report of “censorship” in a bug tracker is missing exactly that field.
Where the hosted rules live
DeepSeek publishes terms of use and a privacy policy from its platform site, and the developer documentation at api-docs.deepseek.com links the current versions. Those documents, at the version in force on the day you read them, are the authority. Any summary — including this page — is a description of where to look, not a substitute for looking.
What you will find is the shape common to every major provider’s acceptable-use terms: prohibitions on unlawful use, on generating content that harms minors, on producing deceptive material presented as genuine, on infringing others’ rights, and on attempting to circumvent the service’s safeguards. There is also, as there usually is, language reserving the provider’s discretion to suspend access.
Two clauses are worth locating specifically because they have engineering consequences rather than legal ones. First, whatever the terms say about data retention and whether inputs may be used to improve the service — that determines what you may send. Second, the governing law and jurisdiction, which for any provider affects the regulatory regime your processing sits under. Neither is a content rule, but both are decided in the same document and both are what a procurement review will ask about.
The signal you get back
Handle these as three distinct branches, because conflating them produces retry loops that spend money achieving nothing:
choice = resp.choices[0]
if choice.finish_reason == "content_filter":
# Blocked at the boundary. Retrying the identical request is pointless.
return blocked_response()
if choice.finish_reason == "stop" and looks_like_refusal(choice.message.content):
# Trained refusal. A different framing or a different model may work.
return refusal_response()
if choice.finish_reason == "insufficient_system_resource":
# Capacity, not policy. Retry with backoff.
return retry()There is no structured refusal field. A trained decline is ordinary text and detecting it requires inspecting content, which is inherently approximate — a model explaining why some other system would refuse looks identical to a refusal by any keyword test. Where you need certainty, ask for structured output and treat a well-formed answer as success and anything else as a decline, rather than pattern-matching prose.
The insufficient_system_resource branch is in that snippet on purpose. It is a capacity interruption unique to DeepSeek that arrives as a 200, and teams routinely misfile it as a refusal because both produce a short or empty answer. The full list of values is worth having in front of you when you write this branch.
Self-hosted weights are a different question
Run the open weights yourself and the hosted service’s terms do not apply to that deployment — you did not use the service. What applies instead is the model licence, and where that licence is one of the DeepSeek Model Licence variants rather than MIT, it carries its own schedule of prohibited uses that binds you and travels downstream to anyone you distribute to. Which licence attaches to which checkpoint is not uniform across the family and is worth checking rather than assuming.
What does come with the weights is the trained behaviour. A model that declines a category of request on the hosted API will generally decline it locally too, because that behaviour is in the parameters. What does not come with the weights is any boundary filter, so content_filter is not a value you will see from your own deployment unless you built one.
The distilled variants inherit their base model’s alignment training as well as its licence, so an R1-distill built on a Llama base refuses in the pattern of a Llama model rather than of DeepSeek-R1. That is a genuinely surprising result the first time you meet it, and it follows directly from what a distill is.
What this means for a product
- Log
finish_reasonon every request. Without it, refusals, filter blocks, truncation and capacity interruptions are one undifferentiated bucket labelled “bad response”. - Never retry a
content_filterunchanged. The same input produces the same block; you are paying for identical failures. - Read the terms for your own use case, not in general. The clauses that matter to a medical product, a consumer chat app and an internal code assistant are different clauses.
- Decide your own policy separately. A provider’s terms bound what you may do; they are not a substitute for the rules you owe your own users. Build your moderation on top rather than inheriting someone else’s by default.
- Keep a fallback that is not the same family. If a category of request matters to your product and one model declines it, the remedy is a route to a different model — which is a configuration decision to make before the incident, not during it.
The reasoning trace is content too
On the reasoning path there is a second body of generated text, and policy questions about it are routinely overlooked because it is treated as diagnostics rather than as output. It is not diagnostics. It is model-generated text derived from user input, and every rule you apply to content has to be considered for reasoning_content as well.
- It restates the input. A trace working through a problem quotes and paraphrases the prompt, so anything sensitive in the request is likely present in the trace. Redaction applied only to prompts and answers misses it.
- It explores rejected paths. Part of what makes a trace useful is that the model considers approaches it then abandons. Shown to a user without that framing, an abandoned line reads as a statement the system made. If you display traces, display them as working, not as conclusions.
- It is not covered by your answer-level checks. If you run moderation or validation over the final answer, the trace went nowhere near it. Decide whether that is acceptable rather than discovering it.
- It is the bulk of what you would store. Traces are an order of magnitude longer than answers, so a decision to log them in full is a decision about storage, retention and access at a scale answer-logging never reaches. The parsing page covers keeping the token count and dropping the text.
The workable default is to treat the trace as internal: retained briefly for debugging under the same rules as prompts, never surfaced as justification, and never persisted longer than the request it belongs to unless something specific requires it.