# Claude Message Threads API 가이드

> Claude API의 message threads 사용법. 베타 헤더, create와 continue 요청, 재전송할 필드, 만료와 404 대응을 정리합니다.

- Canonical HTML: https://omniakey.com/ko/blog/claude-message-threads-api-guide
- Markdown: https://omniakey.com/ko/blog/claude-message-threads-api-guide.md
- Published: 2026-11-04 (updated 2026-11-04)
- Author: Kiki, COO at OmniaKey
- Language: ko
- Category: API setup
- Reading time: 9 min
- All posts: https://omniakey.com/blog/llms.txt

---

Claude message threads API는 긴 대화를 한 번만 보내고, 이후 턴에는 새 메시지만 보냅니다(Anthropic의 Message threads 페이지, 2026년 11월 4일 확인). 첫 Messages 요청에 `"thread": {"type": "create"}`와 `message-threads-2026-08-12` 베타 헤더를 넣으면 API가 대화를 메모리에 보관합니다. 다음 요청에서 보내는 양이 줄어듭니다.

모든 모델에서 베타입니다. 모델 ID 17개를 쓸 수 있고, 아래 코드에서 호출하는 [Claude Opus 5.5](https://omniakey.com/ko/claude-opus-5-5)도 포함됩니다. 베타 헤더는 스레드의 모든 요청에 붙입니다.

## 스레드가 줄여 주는 것

스레드가 없으면 매 턴 대화 전체를 실어 보냅니다. 시스템 프롬프트, 도구 정의, 이전 메시지, 도구 결과가 모두 들어갑니다. 스레드는 이 대화를 Claude의 답변까지 포함해 응답의 `id` 아래에 저장하고, continue 요청은 그 이후에 생긴 것만 보냅니다(Message threads 페이지, 2026년 11월 4일 확인). 매 턴 보내는 양이 줄어듭니다.

프롬프트 캐싱은 다른 방향에서 같은 대화를 돕습니다. 캐시 적중이 되면 반복되는 입력의 단가가 내려갑니다. Opus 5.5에서는 100만 토큰당 0.20달러이고 새 입력은 4달러입니다(Anthropic 가격 페이지, 2026년 11월 4일 확인). 그래도 요청은 그 입력을 계속 실어 보냅니다. 스레드는 보내는 양을 줄이고, 캐싱은 반복되는 부분의 가격을 낮춥니다. 긴 세션에는 둘 다 필요합니다. 컨텍스트 쪽은 [Claude Code 토큰 사용량 줄이기](https://omniakey.com/ko/blog/reduce-claude-code-token-usage)에서 다룹니다.

스레드는 가격도 속도 제한도 바꾸지 않습니다(Message threads 페이지, 2026년 11월 4일 확인). 현재 가격은 [가격 페이지](https://omniakey.com/ko/pricing)에 있습니다.

캐싱은 직접 붙입니다. 요청마다 최상위에 `cache_control`을 보내면 API가 그 요청 `messages`의 마지막 캐시 가능 블록에 적용합니다. 중단점 4개라는 제한은 플랫폼 전체의 프롬프트 캐싱 규칙이고, 자동 캐싱이 그중 하나를 씁니다(Anthropic 프롬프트 캐싱 페이지, 2026년 11월 4일 확인). 배분은 이렇게 합니다.

1. 최상위 `cache_control`, 스레드의 모든 요청에 하나씩.
2. `system`에 하나, create 요청에서 보냅니다.
3. `tools`에 하나, 역시 create에서 보냅니다.
4. 가장 새 메시지에 하나.

이전에 저장된 메시지의 중단점은 이 4개에 들어가지 않습니다. continue 요청이 새 메시지만 보내기 때문입니다. 한 번 보낸 중단점은 그 자리에 남습니다. 대화를 다시 보낼 때는 이전 메시지의 중단점을 빼고 마지막 블록에 하나만 남깁니다(Message threads 페이지, 2026년 11월 4일 확인).

## 첫 턴을 만들고 이어 보내기

세 단계입니다.

1. Anthropic 키를 `ANTHROPIC_API_KEY`에 넣습니다.
2. `system`과 `tools`를 쓴다면 함께 넣어 대화 전체를 보내고, `"thread": {"type": "create"}`를 더합니다.
3. 지난 응답 이후의 메시지만 `"thread": {"type": "continue", "previous_message_id": "<previous id>"}`와 함께 보내고, `system`과 `tools`는 빼둡니다.

모든 요청에 베타 헤더와 `anthropic-version: 2026-10-21`을 붙입니다. 지금의 버전입니다(Anthropic API 버전 페이지, 2026년 11월 4일 확인).

```bash
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2026-10-21" \
  -H "anthropic-beta: message-threads-2026-08-12" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1024,
    "system": "You are a careful backend reviewer.",
    "messages": [
      {"role": "user", "content": "Read this design and list the risks."}
    ],
    "thread": {"type": "create"}
  }'
```

그 응답의 `id`를 저장합니다. 다음 요청은 새 메시지만 보냅니다.

```bash
PREVIOUS_ID="<id from the previous response>"

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2026-10-21" \
  -H "anthropic-beta: message-threads-2026-08-12" \
  -H "content-type: application/json" \
  -d @- <<JSON
{
  "model": "claude-opus-5-5",
  "max_tokens": 1024,
  "messages": [
    {"role": "user", "content": "Turn those risks into a checklist."}
  ],
  "thread": {"type": "continue", "previous_message_id": "$PREVIOUS_ID"}
}
JSON
```

매 응답에서 가장 새 `id`를 남깁니다. 스레드는 뒤에 붙이기만 하고, 각 응답의 `id`는 한 번만 이어갈 수 있으며, 다음 요청은 최신 것을 씁니다(Message threads 페이지, 2026년 11월 4일 확인).

API 키를 다른 방식으로 구하고 싶다면 OmniaKey가 같은 모델 ID로 Claude API를 제공하므로 요청 body는 그대로 씁니다. 스레드는 Anthropic 쪽에서 베타이므로, 프로덕션에서 의존하기 전에 쓰는 중계 서비스가 `thread` 필드를 그대로 넘기는지 확인합니다.

Python SDK는 id를 대신 들고 있습니다. `client.beta.messages.thread(...)`로 헬퍼를 만들면 호출만 준비하고, 각 턴은 `thread.send()`로 보냅니다. 헬퍼는 베타 헤더를 붙이고, 첫 턴에서 create하고, 이후에는 새 메시지만 실어 이어갑니다. continue가 `thread_not_found`, `thread_already_continued`, `thread_continue_in_flight`를 돌려주면 새 create 요청으로 대화 전체를 한 번 다시 보냅니다(Message threads 페이지, 2026년 11월 4일 확인).

```python
import anthropic

client = anthropic.Anthropic()  # ANTHROPIC_API_KEY를 읽습니다

# 이 헬퍼는 아직 아무것도 보내지 않습니다. 매 턴 재사용할 필드를 들고 있습니다.
thread = client.beta.messages.thread(
    model="claude-opus-5-5",
    max_tokens=1024,
    system="You are a careful backend reviewer.",
)

first = thread.send({"role": "user", "content": "Read this design and list the risks."})
print(first.id, first.stop_reason)

second = thread.send({"role": "user", "content": "Turn those risks into a checklist."})
print(second.id)  # 다음에는 이 id에서 이어갑니다
```

이 요청들을 Anthropic API에 실제로 실행하지는 않았습니다. 요청 형태는 Anthropic의 Message threads 페이지에서 가져왔습니다. 스트리밍은 `thread.stream(...)`과 `stream.get_final_message()`로 같은 흐름을 만듭니다. id를 직접 관리하고 싶다면 `thread.previous_message_id`가 값을 돌려줍니다.

## 다시 보낼 필드와 보내면 안 되는 필드

고정 필드는 매 요청에 같은 값을 보냅니다. `system`과 `tools`는 create 요청에서만 보내고, 자유 필드는 턴마다 바뀌어도 됩니다.

|구분|필드|규칙|
|-|-|-|
|create 전용|`system`, `tools`|create 요청에만. continue 요청에 둘 중 하나를 보내면 `null`이어도 400입니다.|
|고정|`model`, `tool_choice`, `thinking`, 각 서버의 인증 토큰을 뺀 `mcp_servers`, `context_management`, `output_config`, `container` 안의 `skills`, 최상위 `cache_control`, `anthropic-beta` 값의 집합|매번 같은 값. 다른 값이면 400이 나오고 어느 필드가 다른지 알려 줍니다.|
|자유|`max_tokens`, `stop_sequences`, `stream`, `metadata`, `service_tier`, 이름 없는 필드|언제든 바꿔도 됩니다.|

고정 필드를 바꾼 400에는 `thread_fingerprint_mismatch`가 붙고, 메시지에 다른 필드가 나열됩니다. 값을 바꾸려면 대화 전체와 새 값으로 새 스레드를 시작합니다.

`previous_message_id`를 빼면 continue가 400을 돌려주고, body에는 message가 있지만 `error_code`는 없습니다. `system`이나 `tools`를 넣어도 같습니다. continue는 지난 응답 이후의 메시지만 실어야 합니다.

`mcp_servers`는 고정이지만 서버 URL과 인증 토큰은 요청 안에만 있습니다. MCP를 쓰는 요청마다 이 목록을 보내야 하고, 토큰은 요청 사이에 바뀔 수 있습니다. 컨테이너 `id`도 요청 안에만 있으므로 `container`는 매번 보냅니다(Message threads 페이지, 2026년 11월 4일 확인).

서버 쪽 폴백은 create 시점에 정해집니다. `fallbacks`는 스레드의 모든 요청에 보내거나 아예 보내지 않습니다. 값은 턴마다 달라도 되지만, 필드를 더하거나 빼면 `thread_fingerprint_mismatch`가 돌아옵니다.

## 만료, 재전송, 404

스레드는 마지막 요청 후 5분이 지나면 만료되고, 마지막 사용 후 1시간 안에 API가 삭제합니다. 응답을 저장할 때마다 5분이 다시 시작됩니다. 스레드는 제로 데이터 보존 대상이 아닙니다. 대화, 시스템 프롬프트, 도구는 암호화된 채 메모리에 있고 영구 저장소에 기록되지 않습니다(Message threads 페이지, 2026년 11월 4일 확인).

응답만 봐서는 그 턴이 저장됐는지 알 수 없습니다. create든 continue든 같습니다. 다음 continue에서 알게 되므로, 애플리케이션이 모든 메시지의 사본을 들고 기준이 됩니다(Message threads 페이지, 2026년 11월 4일 확인).

continue가 404 `thread_not_found`를 돌려주면 대화 전체를 새 create 요청으로 보내고 그 응답의 `id`에서 이어갑니다. 다음 경우에 API는 모두 같은 404를 줍니다.

- 스레드가 만료됐습니다.
- 이전 턴이 처리된 뒤 버려졌습니다. `max_tokens`로 잘린 도구 호출이 그렇습니다.
- 폴백 모델이 이전 응답을 썼습니다.
- 이전 응답이 아직 생성 중입니다.
- 다른 조직, 워크스페이스, 사용자, 워크스페이스 키에서 온 요청입니다.
- 새 메시지에 저장된 대화에 없는 내용이 들어 있습니다. 이미지가 없던 대화의 첫 이미지 같은 경우입니다.

대화 전체를 다시 보내면 어느 경우든 해결됩니다.

스트리밍에는 규칙이 하나 더 있습니다. `message_stop` 뒤에 응답의 `id`에서 이어갑니다. 그보다 먼저 보낸 continue는 404가 될 수 있습니다. continue 스트림이 `message_stop` 전에 끊기면 같은 continue 요청을 다시 보냅니다. 거기서 `thread_already_continued`나 `thread_continue_in_flight`가 나오면 다시 보냅니다. 끊긴 스트림의 재시도는 SDK 헬퍼가 대신하지 않습니다. 직접 다시 보냅니다.

다른 세 경우는 처리가 따로입니다.

- `pause_turn`: 그 응답의 `id`에서 `"messages": []`로 이어갑니다. 404가 나면 일시 중지된 응답을 넣어 다시 보냅니다.
- `stop_reason: "refusal"`: 원인이 된 턴을 빼고 새 스레드를 시작합니다. API가 거절된 턴을 이미 갖고 있을 수 있습니다.
- `usage.iterations`나 스트림의 마지막 `message_delta`에 `fallback_message` 항목이 있으면 폴백 답변입니다. 다시 보냅니다.

## 오류

스레드 오류 5개는 모두 모델이 돌기 전에 돌아옵니다. 요청은 토큰을 쓰지 않고 스레드의 만료 시각도 그대로입니다. 400의 `error.type`은 `invalid_request_error`, 404는 `not_found_error`입니다(Message threads 페이지, 2026년 11월 4일 확인).

- **404, `thread_not_found`**: API가 이 스레드를 이어갈 수 없습니다. 대화 전체를 새 create 요청으로 보냅니다.
- **400, `thread_fingerprint_mismatch`**: 고정 필드가 스레드에 저장된 값과 달라졌습니다. 같은 값으로 맞추거나 새 스레드를 시작합니다.
- **400, `thread_already_continued`**: 그 응답 id는 이미 한 번 사용됐습니다. 최신 id에서 이어가고, 받지 못했다면 다시 보냅니다.
- **400, `thread_continue_in_flight`**: 같은 id의 continue가 아직 실행 중입니다. 대개 직접 보낸 앞선 시도입니다. 끝나기를 기다리거나 다시 보냅니다.
- **400, `thread_unsupported_request`**: 스레드가 지원하지 않는 기능이나 조직 설정을 씁니다. `error.details.unsupported`에 `context_management`, `cache_control`, `fallbacks`, `hipaa`, `inference_hooks` 중 하나가 들어갑니다. 목록은 늘어날 수 있으니 모르는 값이면 `thread` 없이 대화 전체를 보냅니다.

Message Batches는 `thread` 필드를 받지 않습니다. 넣은 배치 항목은 `invalid_request_error`로 실패합니다. 토큰 계산과 압축은 400을 돌려주고, `clear_thinking_20251015`에 `keep: "all"`을 붙인 경우가 아닌 컨텍스트 편집도 400입니다. HIPAA 준비 조직과 Inference hooks를 켠 조직도 제외됩니다. 스레드 목록, 읽기, 삭제는 API에 없습니다.

## 자주 묻는 질문

### Claude message threads API는 무엇인가요?

대화를 Anthropic 쪽에 저장해 이후 턴에서 새 메시지만 보내게 해 주는 Messages API의 베타 기능입니다. 베타 헤더는 `message-threads-2026-08-12`이고, 각 요청에 `thread` 객체를 더해 `type`을 `create`나 `continue`로 둡니다.

### 프롬프트 캐싱과 함께 쓸 수 있나요?

됩니다. 스레드가 프롬프트를 대신 캐시하지는 않습니다. 요청마다 최상위에 `cache_control`을 보내고 중단점을 4개 이내로 유지합니다. 저장된 메시지의 중단점은 이 수에 들지 않고, 최상위 것과 `system`, `tools`의 것은 들어갑니다.

### 스레드 중간에 모델을 바꿀 수 있나요?

모델을 바꾸려면 새 스레드를 시작합니다. `model`은 고정 필드라서 다른 값이면 400 `thread_fingerprint_mismatch`가 납니다. 대화 전체와 새 모델로 새 스레드를 시작하세요.

### continue가 error\_code 없이 400을 돌려주는 이유는 무엇인가요?

continue에 `previous_message_id`가 없거나 `system`, `tools`가 들어 있습니다. `null`이어도 마찬가지입니다. 이런 400은 message만 있고 `error_code`가 없으니 본문을 읽습니다.

### 스레드는 얼마나 유지되고, 만료되면 어떻게 되나요?

마지막 요청 후 5분이 지나면 만료되고 마지막 사용 후 1시간 안에 삭제됩니다. 그 뒤 continue는 404 `thread_not_found`를 돌려줍니다. 대화 전체를 새 create 요청으로 보내고 그 `id`에서 이어갑니다.

## 확인한 출처

- [Anthropic: message threads](https://platform.claude.com/docs/en/build-with-claude/message-threads)
- [Anthropic: 프롬프트 캐싱](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
- [Anthropic: Claude API 가격](https://platform.claude.com/docs/en/about-claude/pricing)
- [Anthropic: API 버전](https://platform.claude.com/docs/en/api/versioning)
