Claude Message Threads API 가이드
대화는 처음 한 번만 보내고, 이후에는 새 메시지만 보냅니다.
이 페이지 목차
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도 포함됩니다. 베타 헤더는 스레드의 모든 요청에 붙입니다.
스레드가 줄여 주는 것
스레드가 없으면 매 턴 대화 전체를 실어 보냅니다. 시스템 프롬프트, 도구 정의, 이전 메시지, 도구 결과가 모두 들어갑니다. 스레드는 이 대화를 Claude의 답변까지 포함해 응답의 id 아래에 저장하고, continue 요청은 그 이후에 생긴 것만 보냅니다(Message threads 페이지, 2026년 11월 4일 확인). 매 턴 보내는 양이 줄어듭니다.
프롬프트 캐싱은 다른 방향에서 같은 대화를 돕습니다. 캐시 적중이 되면 반복되는 입력의 단가가 내려갑니다. Opus 5.5에서는 100만 토큰당 0.20달러이고 새 입력은 4달러입니다(Anthropic 가격 페이지, 2026년 11월 4일 확인). 그래도 요청은 그 입력을 계속 실어 보냅니다. 스레드는 보내는 양을 줄이고, 캐싱은 반복되는 부분의 가격을 낮춥니다. 긴 세션에는 둘 다 필요합니다. 컨텍스트 쪽은 Claude Code 토큰 사용량 줄이기에서 다룹니다.
스레드는 가격도 속도 제한도 바꾸지 않습니다(Message threads 페이지, 2026년 11월 4일 확인). 현재 가격은 가격 페이지에 있습니다.
캐싱은 직접 붙입니다. 요청마다 최상위에 cache_control을 보내면 API가 그 요청 messages의 마지막 캐시 가능 블록에 적용합니다. 중단점 4개라는 제한은 플랫폼 전체의 프롬프트 캐싱 규칙이고, 자동 캐싱이 그중 하나를 씁니다(Anthropic 프롬프트 캐싱 페이지, 2026년 11월 4일 확인). 배분은 이렇게 합니다.
- 최상위
cache_control, 스레드의 모든 요청에 하나씩. system에 하나, create 요청에서 보냅니다.tools에 하나, 역시 create에서 보냅니다.- 가장 새 메시지에 하나.
이전에 저장된 메시지의 중단점은 이 4개에 들어가지 않습니다. continue 요청이 새 메시지만 보내기 때문입니다. 한 번 보낸 중단점은 그 자리에 남습니다. 대화를 다시 보낼 때는 이전 메시지의 중단점을 빼고 마지막 블록에 하나만 남깁니다(Message threads 페이지, 2026년 11월 4일 확인).
첫 턴을 만들고 이어 보내기
세 단계입니다.
- Anthropic 키를
ANTHROPIC_API_KEY에 넣습니다. system과tools를 쓴다면 함께 넣어 대화 전체를 보내고,"thread": {"type": "create"}를 더합니다.- 지난 응답 이후의 메시지만
"thread": {"type": "continue", "previous_message_id": "<previous id>"}와 함께 보내고,system과tools는 빼둡니다.
모든 요청에 베타 헤더와 anthropic-version: 2026-10-21을 붙입니다. 지금의 버전입니다(Anthropic API 버전 페이지, 2026년 11월 4일 확인).
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를 저장합니다. 다음 요청은 새 메시지만 보냅니다.
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일 확인).
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에서 이어갑니다.