# The 7 Claude API stop_reason Values, and How to Handle Each

The Claude API stop_reason values tell you why a response stopped. All seven, the handler each one needs, and the three people forget in an agent loop.

From the book: [Claude Certified Architect Foundations: Crash Course](https://youcanbuildthings.com/books/claude-architect-crash-course.md)

Web page: https://youcanbuildthings.com/articles/claude-api-stop-reason-values

> **Summary:**
> 1. Every Claude Messages API response returns exactly one `stop_reason` telling you why it stopped.
> 2. There are seven values, and each maps to a different branch in your agent loop.
> 3. The three people forget are `pause_turn`, `refusal`, and `model_context_window_exceeded`.
> 4. You get the full handler table, the loop code, and the two false facts that break agents.

The Claude API stop_reason values tell you why a response stopped, and each of the seven maps to a different move in your agent loop. Read the wrong one, or forget that three of them exist, and your loop hangs, truncates silently, or treats a refusal as a finished answer. This is the spine of an agentic system, and on the Claude Certified Architect exam it is Domain 1, Agentic Architecture, worth 27% of your score, the heaviest slice on the test.

## What are the seven stop_reason values?

Every Messages API response includes a `stop_reason` field, and it takes one of seven values. Here they are with the move each one needs, pulled from the docs:

| `stop_reason` | Why it fired | What you do |
|-------------|--------------|-------------|
| `end_turn` | Claude finished naturally. | Use the response. |
| `max_tokens` | The response hit your `max_tokens` limit. | Raise the limit or continue the response. |
| `stop_sequence` | Claude emitted one of your `stop_sequences`. | Read `stop_sequence` to see which fired. |
| `tool_use` | Claude is calling a tool. | Run the tool, return the result. |
| `pause_turn` | A server-tool loop hit its iteration limit. | Send the assistant content back to continue. |
| `refusal` | Claude declined to respond. | Read `stop_details`, retry on a fallback model. |
| `model_context_window_exceeded` | The response filled the model's context window. | Treat as truncated; reduce the input. |

Source: [Claude API docs, handling stop reasons](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons).

![Radial map of the seven Claude API stop_reason values around a central hub reading read stop_reason not the sentence, each spoke pairing a value with its move, with pause_turn refusal and model_context_window_exceeded called out as the three everyone forgets](https://youcanbuildthings.com/images/articles/claude-api-stop-reason-values/stop-reason-values-map.png)

Read the hub of that map first: you decide why the turn ended by reading `stop_reason`, not by reading the sentence.

## Why read the field instead of the text?

You read the field because the control signal is programmatic, and the prose is not. An agent is a loop. You send a message with tools available, Claude responds with a `stop_reason`, and you branch on it. If the reason is `tool_use`, you run the tool and hand the result back. You repeat until a turn returns `end_turn`, and then you are done.

```python
messages = [{"role": "user", "content": "What's the weather in SF?"}]

while True:
    resp = client.messages.create(model=MODEL, max_tokens=1024,
                                   tools=tools, messages=messages)
    if resp.stop_reason == "tool_use":
        messages.append({"role": "assistant", "content": resp.content})
        results = [run_tool(b) for b in resp.content if b.type == "tool_use"]
        # the continuation message is NOTHING but tool_result blocks
        messages.append({"role": "user", "content": results})
        continue
    break   # end_turn, max_tokens, refusal, etc. — exit and inspect stop_reason
```

Any handler that decides an agent is done by scanning the text for "finished" or "complete" is broken. The model can say "all done" mid-tool-call, and it can finish a turn without saying anything of the sort. You read `stop_reason`, not the sentence. One more rule hides in that loop: the message you send back after a `tool_use` must contain nothing but the `tool_result` blocks. Slip a chatty note in beside them and you can end the turn early or error out on a server tool.

## The three everyone forgets

The three that break real agents are `pause_turn`, `refusal`, and `model_context_window_exceeded`, because their handlers are not intuitive and a careless list leaves them out. Branch on them explicitly:

```python
if resp.stop_reason == "refusal":
    # NOT: resend to the same model with a firmer prompt
    resp = client.messages.create(model=FALLBACK_MODEL, ...)   # different model
elif resp.stop_reason == "pause_turn":
    messages.append({"role": "assistant", "content": resp.content})
    continue   # server-tool loop paused; append and keep going
```

Look at what each branch refuses to do. On a `refusal` you switch models, because re-prompting the same one more sternly earns the same refusal. On a `pause_turn` you append and continue, because the partial turn is not the final answer. And `model_context_window_exceeded` is the one people collapse into `max_tokens`. They are different: `max_tokens` means you hit the cap you set, so raise a number; `model_context_window_exceeded` means you filled the model's window, so you cannot just raise a number, you have to reduce the input you send.

Two flat-out false statements show up as bait, and both are worth memorizing as false. "A `tool_result` block can carry an explanatory note" is wrong; results only. "On a refusal, retry the same model with a firmer prompt" is wrong; you switch models. Knowing a claim is false is as useful as knowing the right handler.


## Prove you have all seven

Recall feels solid until you write it down and find a hole. Grade yourself in code instead of trusting your memory. Fill in the set you can name, then let the script name what you dropped:

```python
# self-quiz: fill these in from memory, then run this to grade yourself
CANON = {
    "end_turn": "use the response",
    "max_tokens": "raise your limit or continue the response",
    "model_context_window_exceeded": "reduce the input, don't just raise a number",
    "stop_sequence": "read which sequence fired",
    "tool_use": "return tool_result blocks, nothing else",
    "pause_turn": "append the content and loop again",
    "refusal": "switch to a fallback model",
}
my_recall = {"end_turn", "max_tokens", "stop_sequence", "tool_use"}  # <- your set

missing = set(CANON) - my_recall
print("all seven present" if not missing
      else f"you forgot: {sorted(missing)}")
```

With the four-value set above, you get the exact three a fake list strips out:

```text
you forgot: ['model_context_window_exceeded', 'pause_turn', 'refusal']
```

Widen `my_recall` until it prints `all seven present`. That is the whole drill.

## What should you actually do?

- If you are deciding "is the agent done" → branch on `stop_reason == "end_turn"`, never on the model's text.
- If `stop_reason` is `tool_use` → run the tool and return a message of only `tool_result` blocks.
- If `stop_reason` is `refusal` → read `stop_details` and retry on a different fallback model.
- If `stop_reason` is `pause_turn` → append the assistant content and continue the loop.
- If `stop_reason` is `model_context_window_exceeded` → reduce the input; do not just raise `max_tokens`.

## The bottom line

- Seven values, one per response, each a branch. `end_turn`, `max_tokens`, `stop_sequence`, `tool_use`, `pause_turn`, `refusal`, `model_context_window_exceeded`.
- Read the field, not the prose. An agent that decides "done" by parsing text is a bug waiting for the model to phrase something inconveniently.
- The forgotten three are where agents break. `pause_turn` continues, `refusal` switches models, `model_context_window_exceeded` reduces input. None of those is "raise a number and retry."
- A `tool_result` carries results only. The moment you tuck a note in beside it, you have invented a failure the docs already warned you about.

## FAQ

### What are the stop_reason values in the Claude API?

There are seven: end_turn, max_tokens, stop_sequence, tool_use, pause_turn, refusal, and model_context_window_exceeded. Each Messages API response returns exactly one, telling you why generation stopped.

### How should an agent decide it is finished?

On stop_reason equal to end_turn, not by scanning the model's text for a word like 'finished' or 'complete'. The control signal is the stop_reason field, not the prose.

### What do you do on a refusal stop_reason?

Read stop_details, which is null for every other reason, and retry on a genuinely different fallback model. Re-sending the same request to the same model with a firmer prompt just earns the same refusal.

---

Get the book: Claude Certified Architect Foundations: Crash Course, $12.99 on Amazon: https://amzn.to/4gnoEbM
