- In short
- Structured refusal handling means reacting to the API's refusal signal - the stop_reason and its optional stop_details object - rather than parsing the model's text. The detail object can carry a policy category and readable explanation, but both can be null and some models or responses omit the object entirely, so handlers must tolerate its absence and fall back to generic handling. After a refusal, the triggering turn should be removed or rephrased, or the history cleared, before sending the next request, because re-sending on the same refused context typically produces further refusals.
Read the signal, not the sentence
When Claude refuses a request, an application has two ways to notice: it can parse the model's natural-language response and guess, or it can read the structured signal the API provides. The CCAR-P exam treats choosing the structured path as an apply-level skill, because string-matching a refusal is brittle - phrasings vary, and a response that merely discusses a sensitive topic can look like a refusal while an actual refusal can be worded in a way your matcher misses. The reliable approach reacts to the fields the API sets, not to the prose.
On the Messages API, a refusal triggered by a streaming safety classifier is reported as a distinct stop_reason value of "refusal", accompanied by an optional stop_details object. This structured signal has been available since Claude Opus 4.7, so on newer models it is the signal to build on. It is a machine-readable indicator that a refusal occurred, and it is far more dependable than inspecting the text the model produced.
- Structured refusal handling
- Detecting and responding to refusals through the API's stop_reason and its optional stop_details object rather than by parsing the model's text. The detail object may carry a policy category and explanation but both can be null and the object can be absent, so handlers must fall back gracefully; after a refusal, the conversation must be reset before the next request.
The detail object, and why nulls and absence are the norm
When present, the stop_details object can carry two useful pieces of information: a policy category and a readable explanation of the refusal. That lets an application route different classes of refusal differently instead of treating every refusal as one undifferentiated event. But the exam wants you to hold the caveats as firmly as the capability.
Both the category and the explanation can be null - specifically when the refusal does not map to a named policy category. So a handler that assumes a category is always populated will break on a legitimate refusal that simply had none. Beyond that, some models or responses do not return the detail object at all, so your handler must tolerate its complete absence and fall back to generic refusal handling. When a category is present, it comes from an enumerated set documented on platform.claude.com - at the time of writing it spans values such as cyber, bio, frontier_llm, and reasoning_extraction - and your application can route different refusal classes accordingly rather than treating every refusal as one undifferentiated event. Treat that set itself as something to re-verify against current documentation rather than hardcoding a fixed list into application logic, because the enumerated categories can change and a hardcoded list will silently drift out of date.
Reset the context before you continue
The second half of the skill is what you do after a refusal. The rule is: reset the conversation context before continuing. Concretely, remove or rephrase the turn that triggered the refusal, or clear the history entirely, before sending the next request. The reason is mechanical - if you send the next request on top of the same refused context, the model is still looking at the content that caused the refusal, so it typically refuses again. You are not going to argue it into a different answer by resending on the same context.
This is a common source of confusing production behaviour: an application catches a refusal, immediately retries the next user turn without touching the history, and gets a cascade of refusals that look like the model "being stubborn." The fix is not a retry loop but a context reset - clear or repair the offending turn so the next request is evaluated fresh.
What the CCAR-P exam trips candidates on
The first trap is re-sending the next request on top of the same refused context and expecting a different outcome. A scenario shows an application that catches a refusal and retries without changing the history, then puzzles over repeated refusals; the credited reading is that the context still contains the triggering content, so the refusal recurs, and the fix is to reset before continuing.
The second trap is hardcoding a fixed list of refusal categories into application logic. A scenario shows code that branches on an enumerated category set baked into the app; the credited reading is that the category set should be re-verified against current documentation, not frozen in code, and that the handler must also tolerate null categories and a missing detail object. The exam rewards handlers that degrade gracefully rather than assuming the richest possible response shape.
Worked example
An application detects refusals by checking whether the model's text contains 'I can't help with that,' and when it sees a refusal it immediately re-sends the user's next message on the same conversation history. In production it both misses some refusals and, when it does catch one, gets a run of further refusals. Diagnose both problems and give the correct handling.
Two separate mistakes are stacked here. The detection is brittle: string-matching a fixed phrase misses refusals worded differently and can false-positive on responses that merely mention the phrase. The fix is to read the structured signal - the stop_reason field and its optional stop_details object - instead of parsing text. That is reliable precisely because it does not depend on the exact words the model chose.
The continuation is also wrong. When it catches a refusal, the app re-sends the next turn on the same history, so the content that triggered the refusal is still in context and the model refuses again - the run of further refusals is the predictable result. The fix is to reset the context first: remove or rephrase the triggering turn, or clear the history, before sending the next request.
While fixing this, harden the detail handling too. If the app wants to route by refusal category, it must tolerate a null category and a missing stop_details object, falling back to generic handling, and it should re-verify the category set against current documentation rather than hardcoding it. The corrected handler reads the structured signal, degrades gracefully when detail is absent, and always resets before continuing.
Common misreadings to avoid
Misconception
After catching a refusal, re-sending the next request on the same conversation history is fine.
What's actually true
Misconception
Applications should hardcode the set of refusal categories and always expect a populated category and explanation.
What's actually true
How this shows up on the exam
Domain 5 items describe an application mishandling refusals - parsing text, retrying on the same context, or assuming a category is always present - and ask for the correct handling. The reliable method is to react to the structured stop_reason and stop_details signal, tolerate null fields and a missing object, and reset the conversation before continuing. Any answer built on string-matching or on resending unchanged context is the trap.
This skill operationalises the boundary from training-time alignment vs inference-time control, since a refusal is where the model's trained behaviour becomes an API signal your application must handle. It complements the three-point guarded request path - refusals are one way a request ends short of a normal response - and it is one of the API behaviours worth naming in the LLM system risk taxonomy review of how a system handles blocked traffic.
An app detects refusals by string-matching the model's text and, on a refusal, re-sends the next turn on the same history. It misses some refusals and triggers runs of further ones. What is the correct handling?
People also ask
How should an application detect a Claude refusal?
What are the stop_reason and stop_details fields?
Why reset the conversation after a refusal?
Watch and learn
Official Anthropic Academy lessons first, then hand-picked walkthroughs. Videos load only when you press play.
No videos curated for this concept yet
We are still curating the best official and community videos for this topic.
Official prep for this domain
Anthropic's own free prep module for this part of the syllabus, on the official prep course. Free with an Anthropic Academy sign-in.
References & primary sources
Master this concept with Archie
Practice it inside an adaptive study session. Archie, your Socratic AI tutor, tracks your mastery with Bayesian Knowledge Tracing and schedules the perfect next review.