---
title: 메시지 스레드
url: https://platform.claude.com/docs/ko/build-with-claude/message-threads
description: 각 요청에서 새 메시지만 전송하여, 특히 긴 대화에서 장기 실행 에이전트의 첫 토큰까지의 시간을 단축합니다.
featureMetadata:
  status: beta
  betaHeader: message-threads-2026-08-12
  zdr:
    eligibility: not-eligible
    note: 스레드의 내용(대화, 시스템 프롬프트, 도구)은 서버에서 관리하는 키로 암호화되어 메모리에 보관되며, 영구 저장소에는 기록되지 않고, 스레드를 마지막으로 사용한 후 1시간 이내에 삭제됩니다. 이 기능을 사용해도 다른 ZDR 적격 기능의 사용에는 영향을 주지 않습니다.
  supportedModels:
    - claude-fable-5-1
    - claude-mythos-5-1
    - claude-fable-5
    - claude-mythos-5
    - claude-mythos-preview
    - claude-opus-5-5
    - claude-opus-5
    - claude-sonnet-5-5
    - claude-sonnet-5
    - claude-haiku-5-5
    - claude-haiku-4-5-20251001
    - claude-opus-4-8
    - claude-opus-4-7
    - claude-opus-4-6
    - claude-sonnet-4-6
    - claude-opus-4-5-20251101
    - claude-sonnet-4-5-20250929
  supportedPlatforms:
    Claude API: beta
    Claude Platform on AWS: beta
    Claude Models on Google Cloud (Anthropic managed): beta
    Microsoft Foundry: beta
---

“Message thread”(메시지 스레드)를 사용하면 요청이 더 작아지고, 대화가 길어져도 “time to first token”(첫 토큰까지의 시간)이 더 낮고 안정적으로 유지됩니다. 스레드는 긴 에이전트 루프에서 가장 큰 도움이 됩니다. 스레드가 없으면 매 턴마다 전체 대화를 다시 업로드하고, API는 그 전체를 다시 검증하고 토큰화합니다. 스레드를 사용하면 이 작업의 대부분이 새 턴에만 적용됩니다.

“Message thread”(메시지 스레드)는 대화를 짧은 시간 동안 서버에 유지합니다. 전체 대화를 시스템 프롬프트 및 도구와 함께 한 번 보냅니다. 이후의 각 턴에서는 새 메시지와 이전 응답의 `id`만 보냅니다. API는 모델을 호출하기 전에 대화를 다시 구성합니다. [Messages API](https://platform.claude.com/docs/ko/api/messages) 요청에 매개변수 하나, `thread`를 추가하면 됩니다. 응답은 일반적인 메시지입니다.

<Note>
  메시지 스레드는 Claude Managed Agents의 [세션 스레드](https://platform.claude.com/docs/ko/managed-agents/session-threads)와 다릅니다.
</Note>

## 메시지 스레드의 작동 방식

* **생성.** 지금처럼 전체 대화, `system`, `tools`를 포함하여 첫 번째 요청을 보내고, `"thread": {"type": "create"}`를 추가하세요. API는 Claude의 응답을 포함한 대화를 응답 `id` 아래에 저장합니다.
* **계속.** 이전 응답 이후의 메시지만 보내고, `"thread": {"type": "continue", "previous_message_id": "<previous id>"}`를 추가하세요. 요청에서 `system`과 `tools`는 제외하세요. 스레드는 생성 요청의 값을 유지합니다. 다음 턴에는 각각의 새 응답 `id`를 사용하세요.
* **재전송.** 스레드는 최선의 노력(best-effort) 방식으로 제공됩니다. 스레드는 마지막 요청 후 5분이 지나면 만료되며, API는 마지막 사용 후 1시간 이내에 스레드를 삭제합니다. API가 저장하는 각 응답마다 5분이 다시 시작됩니다. API가 더 이상 스레드를 계속할 수 없으면 계속 요청은 404 오류를 반환합니다. 새 생성 요청으로 전체 대화를 다시 보내고, 해당 응답의 `id`에서 계속하세요. SDK 스레드 헬퍼가 이 재전송을 대신 수행합니다([SDK 헬퍼의 동작 방식](https://platform.claude.com/docs/ko/build-with-claude/message-threads#sdk-helper-handles) 참조). 폴백 모델의 응답 이후에는 [스레드에서 폴백 사용하기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#fallbacks)를 참조하세요.

애플리케이션이 대화의 기준 원본(source of truth)으로 유지됩니다. 모든 메시지의 사본을 직접 보관하고, 처음부터 [재전송 경로](https://platform.claude.com/docs/ko/build-with-claude/message-threads#recover)를 구축하세요. 생성 요청이든 계속 요청이든, 어떤 응답도 API가 해당 턴을 저장했는지 알려주지 않습니다. 이는 다음 계속 요청에서 알게 됩니다. SDK 스레드 헬퍼는 헬퍼 객체가 존재하는 동안 이 사본을 메모리에 보관하며, 다른 곳에는 저장하지 않습니다. 대화가 프로세스보다 오래 유지되어야 한다면 직접 사본을 보관하세요.

## SDK로 스레드 사용하기

SDK에는 스레드 요청을 대신 구성해 주는 스레드 헬퍼가 있습니다. Messages 요청과 동일한 필드로 헬퍼를 생성한 다음, 한 번에 한 턴씩 보내세요. 헬퍼는 전체 대화를 메모리에 유지합니다.

이 예제에는 두 개의 턴이 있습니다. 헬퍼는 첫 번째 턴에서 스레드를 생성하고 두 번째 턴에서 이를 계속하며, 베타 헤더도 자동으로 추가합니다. 최상위 `cache_control`은 선택 사항입니다. 이는 각 턴에서 프롬프트 캐싱을 요청합니다([스레드에서의 프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/message-threads#prompt-caching) 참조). 이것이 없어도 이후의 각 턴은 새 메시지만 보내지만, 캐시 중단점을 직접 추가하지 않는 한 아무것도 캐시되지 않습니다.

<CodeGroup exclude="shell">
  ```python Python
  client = anthropic.Anthropic()

  # 헬퍼를 생성합니다. 이 호출은 요청을 보내지 않습니다.
  thread = client.beta.messages.thread(
      model="claude-opus-5-5",
      max_tokens=1024,
      cache_control={"type": "ephemeral"},
      system="You are a support agent for Acme. Keep replies short.",
  )

  # 턴 1: 헬퍼가 스레드를 생성하고 전체 대화를 전송합니다
  thread.send({"role": "user", "content": "Hi! I have a question about an order."})

  # 턴 2: 헬퍼가 스레드를 이어가며 새 메시지만 전송합니다
  second = thread.send(
      {"role": "user", "content": "How long does standard shipping usually take?"}
  )

  for block in second.content:
      if block.type == "text":
          print(block.text)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  // 헬퍼를 생성합니다. 이 호출은 요청을 보내지 않습니다.
  const thread = client.beta.messages.thread({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    cache_control: { type: "ephemeral" },
    system: "You are a support agent for Acme. Keep replies short."
  });

  // 턴 1: 헬퍼가 스레드를 생성하고 전체 대화를 전송합니다
  await thread.send({ role: "user", content: "Hi! I have a question about an order." });

  // 턴 2: 헬퍼가 스레드를 이어 가며 새 메시지만 전송합니다
  const second = await thread.send({
    role: "user",
    content: "How long does standard shipping usually take?"
  });

  for (const block of second.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```csharp C#
  using Anthropic.Helpers.Beta;

  AnthropicClient client = new();

  // 헬퍼를 생성합니다. 이 호출은 요청을 보내지 않습니다.
  var thread = client.Beta.Messages.Thread(
      new ThreadCreateParams
      {
          Model = Messages::Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          Messages = [],
          CacheControl = new(),
          System = "You are a support agent for Acme. Keep replies short.",
      }
  );

  // 턴 1: 헬퍼가 스레드를 생성하고 전체 대화를 전송합니다
  await thread.Send(
      new BetaUserMessageParam { Content = "Hi! I have a question about an order." }
  );

  // 턴 2: 헬퍼가 스레드를 이어 가며 새 메시지만 전송합니다
  var second = await thread.Send(
      new BetaUserMessageParam { Content = "How long does standard shipping usually take?" }
  );

  foreach (var block in second.Content)
  {
      if (block.TryPickText(out var textBlock))
      {
          Console.WriteLine(textBlock.Text);
      }
  }
  ```

  ```go Go
  client := anthropic.NewClient()
  ctx := context.Background()

  // 헬퍼를 생성합니다. 이 호출은 요청을 보내지 않습니다.
  thread := client.Beta.Messages.NewThread(anthropic.BetaMessageThreadCreateParams{
  	Model:        anthropic.ModelClaudeOpus5_5,
  	MaxTokens:    1024,
  	CacheControl: anthropic.NewBetaCacheControlEphemeralParam(),
  	System: []anthropic.BetaTextBlockParam{
  		{Text: "You are a support agent for Acme. Keep replies short."},
  	},
  })

  // 턴 1: 헬퍼가 스레드를 생성하고 전체 대화를 전송합니다
  _, err := thread.Send(ctx, anthropic.BetaMessageThreadSendParams{
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hi! I have a question about an order.")),
  	},
  })
  if err != nil {
  	panic(err)
  }

  // 턴 2: 헬퍼가 스레드를 이어가며 새 메시지만 전송합니다
  second, err := thread.Send(ctx, anthropic.BetaMessageThreadSendParams{
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("How long does standard shipping usually take?")),
  	},
  })
  if err != nil {
  	panic(err)
  }

  for _, block := range second.Content {
  	if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
  		fmt.Println(textBlock.Text)
  	}
  }
  ```

  ```java Java
  var client = AnthropicOkHttpClient.fromEnv();

  // 헬퍼를 생성합니다. 이 호출은 요청을 보내지 않습니다.
  var thread = client.beta().messages().thread(
      ThreadCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .cacheControl(BetaCacheControlEphemeral.builder().build())
          .system("You are a support agent for Acme. Keep replies short.")
          .build()
  );

  // 턴 1: 헬퍼가 스레드를 생성하고 전체 대화를 전송합니다
  thread.send("Hi! I have a question about an order.");

  // 턴 2: 헬퍼가 스레드를 이어가며 새 메시지만 전송합니다
  var second = thread.send("How long does standard shipping usually take?");

  second.content().stream()
      .flatMap(block -> block.text().stream())
      .forEach(textBlock -> IO.println(textBlock.text()));
  ```

  ```php PHP
  $client = new Client();

  // 헬퍼를 생성합니다. 이 호출은 요청을 보내지 않습니다.
  $thread = $client->beta->messages->thread(
      maxTokens: 1024,
      model: Model::CLAUDE_OPUS_5_5,
      cacheControl: new BetaCacheControlEphemeral,
      system: 'You are a support agent for Acme. Keep replies short.',
  );

  // 턴 1: 헬퍼가 스레드를 생성하고 전체 대화를 전송합니다
  $thread->send(['role' => 'user', 'content' => 'Hi! I have a question about an order.']);

  // 턴 2: 헬퍼가 스레드를 이어 가며 새 메시지만 전송합니다
  $second = $thread->send(['role' => 'user', 'content' => 'How long does standard shipping usually take?']);

  foreach ($second->content as $block) {
      if ($block instanceof BetaTextBlock) {
          echo $block->text, PHP_EOL;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  # 헬퍼를 생성합니다. 이 호출은 요청을 보내지 않습니다.
  thread = client.beta.messages.thread(
    model: :"claude-opus-5-5",
    max_tokens: 1024,
    cache_control: {type: "ephemeral"},
    system_: "You are a support agent for Acme. Keep replies short."
  )

  # 턴 1: 헬퍼가 스레드를 생성하고 전체 대화를 전송합니다
  thread.send_message({role: "user", content: "Hi! I have a question about an order."})

  # 턴 2: 헬퍼가 스레드를 이어 가며 새 메시지만 전송합니다
  second = thread.send_message({role: "user", content: "How long does standard shipping usually take?"})

  second.content.each do |block|
    puts block.text if block.type == :text
  end
  ```
</CodeGroup>

## `thread` 필드를 직접 보내기

예를 들어 cURL, CLI 또는 SDK의 일반적인 `create` 메서드를 사용하여 스레드 요청을 직접 구성하려면, 각 요청에 `thread` 필드를 추가하세요.

스레드의 모든 요청에 `message-threads-2026-08-12` 베타 헤더를 보내세요.

이 예제는 첫 번째 턴에서 스레드를 생성하고 두 번째 턴에서 이를 계속합니다. 최상위 `cache_control`은 각 턴에서 프롬프트 캐싱을 요청합니다. 이는 대화가 [최소 캐시 가능 길이](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)보다 길어지면 적용됩니다.

<CodeGroup>
  ```bash cURL
  # 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  curl -sS --fail-with-body https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: message-threads-2026-08-12" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "cache_control": {"type": "ephemeral"},
      "system": "You are a support agent for Acme. Keep replies short.",
      "messages": [{"role": "user", "content": "Hi! I have a question about an order."}],
      "thread": {"type": "create"}
    }'

  # 턴 2: 새 메시지만 보냅니다. message_id를 턴 1 응답의 id로 설정하세요.
  # system과 tools는 생략하세요. 스레드가 이를 유지합니다.
  curl -sS --fail-with-body https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: message-threads-2026-08-12" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "cache_control": {"type": "ephemeral"},
      "messages": [{"role": "user", "content": "How long does standard shipping usually take?"}],
      "thread": {"type": "continue", "previous_message_id": "'"$message_id"'"}
    }'
  ```

  ```bash CLI
  # 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  ant beta:messages create --beta message-threads-2026-08-12 <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  cache_control:
    type: ephemeral
  system: "You are a support agent for Acme. Keep replies short."
  messages:
    - role: user
      content: "Hi! I have a question about an order."
  thread:
    type: create
  YAML

  # 턴 2: 새 메시지만 보냅니다. message_id를 턴 1 응답의 id로 설정하세요.
  # system과 tools는 생략하세요. 스레드가 이를 유지합니다.
  ant beta:messages create --beta message-threads-2026-08-12 <<YAML
  model: claude-opus-5-5
  max_tokens: 1024
  cache_control:
    type: ephemeral
  messages:
    - role: user
      content: "How long does standard shipping usually take?"
  thread:
    type: continue
    previous_message_id: $message_id
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  # 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  first = client.beta.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      cache_control={"type": "ephemeral"},
      system="You are a support agent for Acme. Keep replies short.",
      messages=[{"role": "user", "content": "Hi! I have a question about an order."}],
      thread={"type": "create"},
      betas=["message-threads-2026-08-12"],
  )

  # 턴 2: 새 메시지와 턴 1 응답의 id만 보냅니다.
  # system과 tools는 생략하세요: 스레드가 이를 유지합니다.
  second = client.beta.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      cache_control={"type": "ephemeral"},
      messages=[
          {"role": "user", "content": "How long does standard shipping usually take?"}
      ],
      thread={"type": "continue", "previous_message_id": first.id},
      betas=["message-threads-2026-08-12"],
  )

  for block in second.content:
      if block.type == "text":
          print(block.text)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  // 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  const first = await client.beta.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    cache_control: { type: "ephemeral" },
    system: "You are a support agent for Acme. Keep replies short.",
    messages: [{ role: "user", content: "Hi! I have a question about an order." }],
    thread: { type: "create" },
    betas: ["message-threads-2026-08-12"]
  });

  // 턴 2: 새 메시지와 턴 1 응답의 id만 보냅니다.
  // system과 tools는 생략하세요. 스레드가 이를 유지합니다.
  const second = await client.beta.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    cache_control: { type: "ephemeral" },
    messages: [{ role: "user", content: "How long does standard shipping usually take?" }],
    thread: { type: "continue", previous_message_id: first.id },
    betas: ["message-threads-2026-08-12"]
  });

  for (const block of second.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```csharp C#
  AnthropicClient client = new();

  // 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  var first = await client.Beta.Messages.Create(
      new()
      {
          Model = Messages::Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          CacheControl = new(),
          System = "You are a support agent for Acme. Keep replies short.",
          Messages =
          [
              new BetaUserMessageParam { Content = "Hi! I have a question about an order." },
          ],
          Thread = new BetaThreadCreateParams(),
          Betas = [AnthropicBeta.MessageThreads2026_08_12],
      }
  );

  // 턴 2: 새 메시지와 턴 1 응답의 id만 보냅니다.
  // System과 Tools는 생략하세요. 스레드가 이를 유지합니다.
  var second = await client.Beta.Messages.Create(
      new()
      {
          Model = Messages::Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          CacheControl = new(),
          Messages =
          [
              new BetaUserMessageParam { Content = "How long does standard shipping usually take?" },
          ],
          Thread = new BetaThreadContinueParams(first.ID),
          Betas = [AnthropicBeta.MessageThreads2026_08_12],
      }
  );

  foreach (var block in second.Content)
  {
      if (block.TryPickText(out var textBlock))
      {
          Console.WriteLine(textBlock.Text);
      }
  }
  ```

  ```go Go
  client := anthropic.NewClient()
  ctx := context.Background()

  // 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  threadCreate := anthropic.NewBetaThreadCreateParams()
  first, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  	Model:        anthropic.ModelClaudeOpus5_5,
  	MaxTokens:    1024,
  	CacheControl: anthropic.NewBetaCacheControlEphemeralParam(),
  	System: []anthropic.BetaTextBlockParam{
  		{Text: "You are a support agent for Acme. Keep replies short."},
  	},
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Hi! I have a question about an order.")),
  	},
  	Thread: anthropic.BetaMessageThreadParamUnion{OfCreate: &threadCreate},
  	Betas:  []anthropic.AnthropicBeta{anthropic.AnthropicBetaMessageThreads2026_08_12},
  })
  if err != nil {
  	panic(err)
  }

  // 턴 2: 새 메시지와 턴 1 응답의 id만 보냅니다.
  // System과 Tools는 생략하세요. 스레드가 이를 유지합니다.
  second, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  	Model:        anthropic.ModelClaudeOpus5_5,
  	MaxTokens:    1024,
  	CacheControl: anthropic.NewBetaCacheControlEphemeralParam(),
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("How long does standard shipping usually take?")),
  	},
  	Thread: anthropic.BetaMessageThreadParamOfContinue(first.ID),
  	Betas:  []anthropic.AnthropicBeta{anthropic.AnthropicBetaMessageThreads2026_08_12},
  })
  if err != nil {
  	panic(err)
  }

  for _, block := range second.Content {
  	if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
  		fmt.Println(textBlock.Text)
  	}
  }
  ```

  ```java Java
  var client = AnthropicOkHttpClient.fromEnv();

  // 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  var first = client.beta().messages().create(
      MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .cacheControl(BetaCacheControlEphemeral.builder().build())
          .system("You are a support agent for Acme. Keep replies short.")
          .addUserMessage("Hi! I have a question about an order.")
          .thread(BetaThreadCreateParams.builder().build())
          .addBeta(AnthropicBeta.MESSAGE_THREADS_2026_08_12)
          .build()
  );

  // 턴 2: 새 메시지와 턴 1 응답의 id만 보냅니다.
  // system과 tools는 생략하세요. 스레드가 이를 유지합니다.
  var second = client.beta().messages().create(
      MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .cacheControl(BetaCacheControlEphemeral.builder().build())
          .addUserMessage("How long does standard shipping usually take?")
          .continueThread(first.id())
          .addBeta(AnthropicBeta.MESSAGE_THREADS_2026_08_12)
          .build()
  );

  second.content().stream()
      .flatMap(block -> block.text().stream())
      .forEach(textBlock -> IO.println(textBlock.text()));
  ```

  ```php PHP
  $client = new Client();

  // 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  $first = $client->beta->messages->create(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      cacheControl: new BetaCacheControlEphemeral,
      system: 'You are a support agent for Acme. Keep replies short.',
      messages: [
          ['role' => 'user', 'content' => 'Hi! I have a question about an order.'],
      ],
      thread: BetaThreadCreateParams::with(),
      betas: ['message-threads-2026-08-12'],
  );

  // 턴 2: 새 메시지와 턴 1 응답의 id만 보냅니다.
  // system과 tools는 생략하세요. 스레드가 이를 유지합니다.
  $second = $client->beta->messages->create(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      cacheControl: new BetaCacheControlEphemeral,
      messages: [
          ['role' => 'user', 'content' => 'How long does standard shipping usually take?'],
      ],
      thread: BetaThreadContinueParams::with(previousMessageID: $first->id),
      betas: ['message-threads-2026-08-12'],
  );

  foreach ($second->content as $block) {
      if ($block instanceof BetaTextBlock) {
          echo $block->text, PHP_EOL;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  # 턴 1: 전체 대화를 보내고 스레드를 생성합니다
  first = client.beta.messages.create(
    model: :"claude-opus-5-5",
    max_tokens: 1024,
    cache_control: {type: "ephemeral"},
    system_: "You are a support agent for Acme. Keep replies short.",
    messages: [{role: "user", content: "Hi! I have a question about an order."}],
    thread: {type: "create"},
    betas: ["message-threads-2026-08-12"]
  )

  # 턴 2: 새 메시지와 턴 1 응답의 id만 보냅니다.
  # system_ 및 tools는 생략하세요: 스레드가 이를 유지합니다.
  second = client.beta.messages.create(
    model: :"claude-opus-5-5",
    max_tokens: 1024,
    cache_control: {type: "ephemeral"},
    messages: [{role: "user", content: "How long does standard shipping usually take?"}],
    thread: {type: "continue", previous_message_id: first.id},
    betas: ["message-threads-2026-08-12"]
  )

  second.content.each do |block|
    puts block.text if block.type == :text
  end
  ```
</CodeGroup>

## 각 요청에서 보낼 내용

| 그룹    | 필드                                                                                                                                                                               | 규칙                                                                                                                          |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| 생성 전용 | `system`, `tools`                                                                                                                                                                | `cache_control` 중단점이 있다면 함께 포함하여 생성 요청에서만 보냅니다. 계속 요청에서 둘 중 하나라도 보내면 `null`이라도 400 오류가 반환됩니다.                               |
| 고정    | `model`, `tool_choice`, `thinking`, `mcp_servers`(각 서버의 인증 토큰 제외), `context_management`, `output_config`, `container`의 `skills`, 최상위 `cache_control`, 그리고 `anthropic-beta` 값의 집합 | 스레드의 모든 요청에서 생성 요청과 동일한 값으로 보냅니다. 다른 값을 보내면 `thread_fingerprint_mismatch` 코드와 함께 400 오류가 반환되며, 오류 메시지에 값이 다른 필드의 이름이 표시됩니다. |
| 자유    | `max_tokens`, `stop_sequences`, `stream`, `metadata`, `service_tier` 및 이 페이지에 언급되지 않은 기타 필드                                                                                      | 턴마다 변경할 수 있습니다.                                                                                                             |

API는 MCP 서버 URL, MCP 인증 토큰 또는 컨테이너 `id`를 저장하지 않습니다. 이를 사용하는 모든 요청에서 `mcp_servers`와 `container`를 보내세요. MCP 인증 토큰은 요청 간에 변경될 수 있습니다.

SDK 스레드 헬퍼는 계속 요청에서 `system`과 `tools`를 대신 제외하고, 고정 필드는 매 턴마다 다시 보냅니다.

[서버 측 폴백](https://platform.claude.com/docs/ko/build-with-claude/refusals-and-fallback#server-side-fallback)을 사용하는 경우, 스레드의 모든 요청에서 `fallbacks` 필드를 보내거나 어떤 요청에서도 보내지 마세요. [스레드에서 폴백 사용하기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#fallbacks)를 참조하세요.

생성 전용 필드나 고정 필드를 변경하려면 새 스레드를 시작하세요. 새 값과 함께 생성 요청으로 전체 대화를 보내면 됩니다.

Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5.5, Claude Sonnet 5.5, Claude Haiku 5.5에서는 `system` 또는 `tools`를 변경하면 대화에 이미 있는 `thinking` 및 `redacted_thinking` 블록이 무효가 되며, 생성 요청이 400 오류를 반환할 수 있습니다. 이를 피하려면 보내는 대화에서 모든 `thinking` 및 `redacted_thinking` 블록을 제외하세요. 그러면 모델은 해당 이전 추론 없이 응답합니다. [편집으로 간주되는 것](https://platform.claude.com/docs/ko/build-with-claude/preserved-thinking#what-counts-as-an-edit), [`prefix_mismatch_behavior`가 설정되지 않은 경우](https://platform.claude.com/docs/ko/build-with-claude/preserved-thinking#enforcement), [API가 유효하지 않은 블록을 처리하는 방식](https://platform.claude.com/docs/ko/build-with-claude/preserved-thinking#mismatch-behavior)을 참조하세요.

계속 요청에서는 새 메시지만 보내세요. API는 이를 이미 보유한 메시지와 대조하지 않습니다. 다시 보낸 메시지는 대화에 두 번 나타날 수 있으며, 요청이 오류를 반환할 수도 있습니다. 예를 들어 이전 응답으로 시작하는 계속 요청은 오류를 반환합니다.

새 메시지는 일반적으로 `user` 메시지로 시작합니다. 즉, 다음 사용자 턴이거나 이전 응답의 `tool_use`에 응답하는 `tool_result` 블록입니다. 응답이 [`stop_reason: "pause_turn"`](https://platform.claude.com/docs/ko/build-with-claude/handling-stop-reasons#pause-turn)으로 중지되면, 해당 응답의 `id`와 빈 `messages` 목록(`"messages": []`)을 사용한 계속 요청으로 재개하세요. 해당 요청이 404 오류를 반환하면, 일시 중지된 응답을 포함한 전체 대화를 새 생성 요청으로 재전송하세요. 턴이 일시 중지된 동안에는 새 `user` 메시지를 보내지 마세요. API가 이를 거부합니다. SDK 스레드 헬퍼를 사용하는 경우, 메시지 없이 `send()` (go, csharp: `Send()`; ruby: `send_message()`)를 호출하세요. 헬퍼가 이 요청을 보내고 404 오류 시 재전송합니다.

스레드는 추가 전용이며, 각 응답 `id`는 한 번만 계속할 수 있습니다. 항상 스레드의 최신 응답 `id`에서 계속하세요. 이전 메시지를 편집하거나 분기하려면 생성 요청으로 새 스레드를 시작하세요. 이전 턴을 요약으로 대체하려면 [대화 압축하기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#compaction)를 참조하세요.

## 스레드에서의 프롬프트 캐싱

스레드는 프롬프트를 자동으로 캐시하지 않습니다. [프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)은 스레드가 없을 때와 동일하게 작동합니다. 캐시 중단점의 위치는 사용자가 선택합니다.

각 턴을 캐시하려면 앞의 예제처럼 스레드의 모든 요청에서 최상위 `cache_control`을 보내세요. API는 이를 각 요청의 `messages` 중 캐시할 수 있는 마지막 블록에 적용합니다. 그러면 각 턴은 이전 대화를 캐시에서 읽고 새 턴을 캐시에 씁니다. 최상위 `cache_control`은 고정 필드이므로 모든 요청에서 동일한 값을 보내세요.

블록에 `cache_control` 중단점을 직접 배치할 수도 있습니다.

* 생성 요청에서 보낸 중단점은 `tools`, `system` 또는 마지막 블록 이전의 메시지 블록에 있는 경우 이후의 모든 턴에서 계속 작동합니다. 요청의 `messages`의 마지막 블록에 있는 중단점은 해당 요청에서 캐시를 기록합니다. 각 새 턴을 캐시하려면 모든 요청에 중단점을 보내세요. 중단점이 없는 요청도 대화의 일부를 캐시에서 읽을 수 있습니다.
* 계속 요청은 이전 메시지를 다시 전송할 수 없으므로, 중단점을 전송한 후에는 이동하거나 제거할 수 없습니다.
* `thread`가 포함된 요청은 최대 4개의 `cache_control` 중단점을 가질 수 있습니다. 최상위 `cache_control`도 하나로 계산됩니다. 생성 요청의 `system` 및 `tools`에 있는 중단점은 모든 계속 요청에서 다시 계산됩니다. 저장된 이전 메시지의 중단점은 계산되지 않습니다. 중단점이 하나라도 초과되면 `thread_unsupported_request` 코드와 함께 400 오류가 반환됩니다.
* 메시지 블록에 직접 중단점을 배치하는 경우, 새 생성 요청에서 대화를 재전송할 때 이전 메시지의 중단점을 제거하고 마지막 블록에 하나를 유지하여 요청이 4개 제한 내에 머물도록 하세요.

## 스트리밍

스레드는 [스트리밍](https://platform.claude.com/docs/ko/build-with-claude/streaming)과 함께 작동합니다. 응답 `id`는 `message_start` 이벤트에서 도착하지만, `message_stop` 이벤트를 받은 후에만 그 `id`에서 계속하세요. 그보다 먼저 보낸 계속 요청은 404 오류를 반환할 수 있습니다.

계속 요청의 스트림이 `message_stop` 전에 끊기면 동일한 계속 요청을 다시 보내세요. 해당 요청이 `thread_already_continued` 또는 `thread_continue_in_flight` 오류를 반환하면, 새 생성 요청으로 전체 대화를 재전송하세요.

서버가 아직 응답을 생성하는 중이거나 서버가 응답을 저장한 후에 연결이 끊겼을 수 있습니다. 그래서 API가 재시도를 거부할 수 있습니다. 새 생성 요청으로 전체 대화를 재전송하는 것은 항상 안전합니다.

SDK 스레드 헬퍼는 스트림이 중단된 후 계속 요청을 재시도하지 않습니다. [SDK 헬퍼의 동작 방식](https://platform.claude.com/docs/ko/build-with-claude/message-threads#sdk-helper-handles)을 참조하세요.

## 스레드를 계속할 수 없을 때 복구하기

API가 스레드를 계속할 수 없으면 계속 요청은 `error.details.error_code`가 `thread_not_found`로 설정된 404 오류를 반환합니다. 응답은 그 이유를 알려주지 않습니다. 원인에는 다음이 포함됩니다.

* 스레드가 만료되었습니다.
* API가 이전 턴을 제공했지만 저장하지 않았습니다. 예를 들어 API는 `max_tokens`로 인해 잘린 도구 호출로 끝나는 응답을 저장하지 않습니다.
* 폴백 모델이 이전 응답을 작성했습니다. [스레드에서 폴백 사용하기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#fallbacks)를 참조하세요.
* 이전 응답이 아직 생성 중입니다.
* 요청이 다른 조직 또는 워크스페이스에서 왔습니다.
* 요청이 스레드를 생성한 사용자 또는 서비스 계정과 다른 사용자 또는 서비스 계정에서 왔습니다. [워크스페이스 키(레거시)](https://platform.claude.com/docs/ko/manage-claude/authentication#key-types)로 생성된 스레드는 해당 키에 귀속되므로, 스레드 전체에 동일한 키를 사용하세요.
* 새 메시지가 저장된 대화가 수용할 수 없는 것을 추가합니다. 예를 들어 이미지가 없던 대화의 첫 번째 이미지는 이 오류를 반환할 수 있습니다.

복구하려면 자체 사본의 전체 대화를 `system` 및 `tools`와 함께 새 생성 요청으로 보내세요. SDK 스레드 헬퍼는 이 생성 요청을 자체적으로 한 번 보내며, 이때 이전 턴의 `cache_control` 중단점을 제외합니다. 사본의 이전 메시지에 `cache_control` 중단점이 있다면 먼저 제거하세요([스레드에서의 프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/message-threads#prompt-caching) 참조). 그런 다음 새 응답 `id`에서 이어가세요. 이전 응답을 폴백 모델이 작성했다면 [스레드에서 폴백 사용하기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#fallbacks)를 참조하세요. 소유권 관련 원인을 배제하려면 스레드의 모든 요청에 동일한 API 키와 워크스페이스를 사용하세요. Claude Platform on AWS에서는 스레드의 모든 요청을 해당 스레드를 생성한 IAM 역할 또는 IAM 사용자의 자격 증명으로 서명하세요.

존재하지 않는 모델에 대한 요청도 404 오류를 반환하므로, 재전송하기 전에 `error.details.error_code`를 확인하세요.

재전송 직후에도 계속 요청이 여전히 404 오류를 반환한다면, API가 해당 대화(예: 매우 큰 대화)를 저장하지 못하는 것일 수 있습니다. 이 경우 `thread` 없이 보내세요. SDK 스레드 헬퍼는 `unsupported_request_behavior` (typescript, java, php: `unsupportedRequestBehavior`; go, csharp: `UnsupportedRequestBehavior`)를 `continue_unthreaded` (ruby: `:continue_unthreaded`; java: `CONTINUE_UNTHREADED`; csharp: `ContinueUnthreaded`; go: `BetaMessageThreadUnsupportedRequestBehaviorContinueUnthreaded`)로 설정하여 생성하면 이 작업을 수행합니다. 그렇지 않으면 매 턴마다 재전송합니다.

### 거부 이후

응답에 `stop_reason: "refusal"`이 있으면 스레드를 이어가지 마세요. API가 이미 거부된 턴을 보관하고 있을 수 있으며, 계속 요청으로는 이를 제거할 수 없습니다. 거부의 원인이 된 턴을 생략하거나 표현을 바꾼 생성 요청으로 새 스레드를 시작하세요. [거부 및 폴백](https://platform.claude.com/docs/ko/build-with-claude/refusals-and-fallback)을 참조하세요. SDK 스레드 헬퍼는 이 작업을 자동으로 수행합니다. 거부된 턴을 유지하지 않으며, 다음 턴에서 새 스레드를 시작합니다.

### 스레드에서 폴백 사용하기

[서버 측 폴백](https://platform.claude.com/docs/ko/build-with-claude/refusals-and-fallback#server-side-fallback)은 Claude API에서 스레드와 함께 사용할 수 있습니다. 스레드를 생성할 때 결정하세요. 스레드의 모든 요청에서 `fallbacks`를 보내거나, 어떤 요청에서도 보내지 마세요. `fallbacks`의 값은 요청 간에 변경될 수 있습니다. `fallbacks`를 추가하거나 제거하는 계속 요청은 `thread_fingerprint_mismatch` 코드와 함께 400 오류를 반환합니다.

스레드는 하나의 모델을 유지하므로, API는 폴백 모델이 작성한 턴을 저장하지 않습니다. 응답은 평소처럼 받지만, 그 `id`에서 계속할 수는 없습니다. 각 응답 후에 다음 단계를 따르세요.

1. `stop_reason`이 `"refusal"`이면 [거부 이후](https://platform.claude.com/docs/ko/build-with-claude/message-threads#after-a-refusal)를 따르세요.

2. 그렇지 않으면 `usage.iterations`에서 `fallback_message` 항목을 찾으세요. 스트림에서는 마지막 `message_delta` 이벤트에서 읽으세요. 항목이 없으면 평소처럼 스레드를 계속 진행하세요.

3. 있으면 폴백 모델이 응답을 작성한 것입니다. 해당 응답과 다음 메시지를 포함한 전체 대화를 새 생성 요청으로 보내세요. 이 요청에 `server-side-fallback-2026-07-01` 베타 헤더를 유지하세요. 다음 두 가지 요청 중 하나를 선택하세요.

   * **동일한 요청을 다시 보내기.** `model`, `fallbacks` 및 기타 매개변수를 그대로 유지하세요. 요청한 모델이 응답하면 API가 새 스레드를 저장합니다. 폴백 모델이 응답하면 응답에 다시 `fallback_message` 항목이 포함되고 API는 이를 저장하지 않으므로, 다음 턴에서 이 단계를 반복하세요. [고정 라우팅](https://platform.claude.com/docs/ko/build-with-claude/refusals-and-fallback#sticky-routing)이 이러한 요청을 폴백 모델로 바로 보낼 수 있으므로, 매 턴마다 전체 대화를 보내게 될 수 있습니다. SDK 스레드 헬퍼는 이 방식으로 동작합니다.
   * **폴백 모델에서 새 스레드 시작하기.** `model`을 해당 `fallback_message` 항목의 `model`로 설정하고, `fallbacks`를 제외하고, 이 모델이 허용하는 매개변수를 사용하세요. API는 이 스레드를 즉시 저장하며, 스레드는 폴백 모델에 유지됩니다. 폴백 시도는 보낸 설정과 다른 설정으로 실행되었을 수 있으므로, API가 첫 번째 요청에서 복사한 값을 거부할 수 있습니다.

4. 해당 요청의 응답으로 1단계로 돌아가세요. 응답에 `fallback_message` 항목이 없으면, 해당 `id`로 새 스레드를 이어가세요.

[고정 라우팅](https://platform.claude.com/docs/ko/build-with-claude/refusals-and-fallback#sticky-routing)은 요청이 폴백된 후(생성 요청 또는 `thread`가 없는 요청) 시작됩니다. 약 1시간 동안, `fallbacks`를 포함하는 동일한 대화에 대한 이후 생성 요청을 요청한 모델을 시도하지 않고 폴백 모델로 바로 보낼 수 있습니다. API는 이러한 턴을 하나도 저장하지 않으며, 응답된 각 턴은 1시간을 다시 시작합니다. 계속 요청은 이 라우팅을 시작하지 않으며 사용하지도 않습니다. 조직 또는 워크스페이스가 ZDR(zero data retention) 또는 고객 관리형 암호화 키를 사용하는 경우 고정 라우팅은 적용되지 않습니다.

## 대화 압축하기

스레드는 추가 전용이므로 이전 턴을 요약으로 대체할 수 없습니다. 대화를 압축하려면 요약에서 새 스레드를 시작하세요. 코드에서 요약을 작성하고, 이를 `system` 및 `tools`와 함께 새 생성 요청에서 일반 메시지로 보내세요. [온디맨드 압축](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand)은 스레드와 함께 사용할 수 없습니다([스레드가 지원하지 않는 기능](https://platform.claude.com/docs/ko/build-with-claude/message-threads#unsupported) 참조).

## SDK 헬퍼의 동작 방식

헬퍼가 대신 처리하는 작업:

* 스레드를 사용하는 모든 요청에 `message-threads-2026-08-12` 베타 헤더를 추가합니다.
* 첫 번째 턴에서 스레드를 생성하고 그 이후에는 스레드를 이어가며, 새 메시지만 보내고 `system`과 `tools`는 생략합니다.
* 계속 요청이 `thread_not_found`, `thread_already_continued` 또는 `thread_continue_in_flight`를 반환하면 새 생성 요청으로 전체 대화를 한 번 재전송합니다.
* 메시지 없이 `send()` (go, csharp: `Send()`; ruby: `send_message()`)를 호출하면 `pause_turn` 응답을 재개합니다.
* 거부 후에는 새 스레드를 시작합니다. 평소처럼 `stop_reason: "refusal"` 응답을 받지만, 헬퍼는 해당 턴을 유지하지 않고 그 호출에 전달한 메시지를 되돌립니다. `thread.append(...)` (csharp, go: `thread.Append(...)`; php: `$thread->append(...)`)로 추가했거나 헬퍼를 생성할 때 전달한 메시지는 스테이징된 상태로 남습니다. 다음 턴에서는 이 메시지들을 거부된 턴 이전의 대화와 함께 새 생성 요청으로 보냅니다. 표현을 바꾼 메시지를 보내거나, 스테이징된 메시지가 거부의 원인이었다면 해당 메시지를 제외한 params로 새 스레드를 시작하세요.
* 폴백 모델의 응답 후에는 새 생성 요청을 보냅니다. 응답의 `usage.iterations`에 `fallback_message` 항목이 있으면, 헬퍼는 해당 응답을 유지하고 다음 턴에서 전체 대화를 새 생성 요청으로 보냅니다. 404 오류를 기다리지 않습니다. 해당 요청은 설정한 `model`과 `fallbacks`를 유지합니다.
* 이전 턴을 다시 보낼 때마다(예: 재전송 시 또는 포크의 첫 번째 턴에서) 해당 턴의 `cache_control` 중단점을 생략합니다. 가장 최근 메시지, `system`, `tools` 및 최상위 `cache_control`은 전달한 그대로 전송됩니다.

직접 처리해야 하는 작업:

* **폴백.** API가 폴백 모델로 계속 응답하는 동안, API는 해당 턴을 하나도 저장하지 않으므로 헬퍼는 매 턴마다 전체 대화를 새 생성 요청으로 보냅니다. 요청한 모델로부터 응답을 받으면 다시 계속 요청으로 돌아갑니다. 대신 대화를 폴백 모델로 옮기려면, [스레드에서 폴백 사용하기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#fallbacks)에서처럼 해당 `model`을 지정하고 `fallbacks`를 제외한 params로 새 스레드를 시작하세요.
* **중단된 스트림.** 응답 도중 스트림이 끝나면, 헬퍼는 완료된 블록과 도착한 텍스트를 유지하고 다음 턴에서 전체 대화를 새 생성 요청으로 보냅니다. 헬퍼는 자체적으로 재시도하지 않습니다. 대신 중단된 턴을 다시 보내려면 메시지 없이 `send()` (go, csharp: `Send()`; ruby: `send_message()`)를 호출하세요. 유지된 부분이 도구 호출로 끝나면 다음 메시지에서 이에 응답하세요. 스트림에 `stop_reason: "refusal"`이 표시된 후 스트림 읽기를 중단하면, 헬퍼는 해당 턴을 거부로 처리합니다.
* **기타 오류.** `thread_fingerprint_mismatch` 및 `thread_unsupported_request` 오류는 코드로 전달됩니다. `thread_unsupported_request` 오류 후 `thread` 없이 계속하려면 `unsupported_request_behavior` (typescript, java, php: `unsupportedRequestBehavior`; go, csharp: `UnsupportedRequestBehavior`)를 `continue_unthreaded` (ruby: `:continue_unthreaded`; java: `CONTINUE_UNTHREADED`; csharp: `ContinueUnthreaded`; go: `BetaMessageThreadUnsupportedRequestBehaviorContinueUnthreaded`)로 설정하여 헬퍼를 생성하세요. 이 설정을 사용하면, 재전송 후 첫 번째 계속 요청이 다시 `thread_not_found`를 반환할 때도 헬퍼가 `thread` 사용을 중단합니다. API가 해당 대화를 저장하지 못할 수 있기 때문입니다. 이 설정이 없으면 헬퍼는 매 턴마다 재전송합니다. 자체 스레드를 생성하는 도구 러너는 기본적으로 이 설정을 사용합니다.

SDK의 베타 도구 러너도 기본적으로 스레드를 사용하며, 러너를 생성할 때 이를 끌 수 있습니다.

### 기타 헬퍼 메서드

| 수행할 작업                | 사용                                                                                                                                                                                                                                                                                                                                   |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 턴 스트리밍                | `thread.stream(...)` (csharp: `thread.SendStreaming(...)`; go: `thread.SendStreaming(ctx, params)`; java: `thread.sendStreaming(...)`; php: `$thread->sendStream(...)`)                                                                                                                                                              |
| 스트리밍된 턴의 완료된 응답       | `stream.get_final_message()` (typescript: `stream.finalMessage()`; csharp: `thread.LastMessage`; go: `thread.LastMessage()`; java: `thread.lastMessage()`; php: `$thread->lastMessage()`; ruby: `stream.accumulated_message`)                                                                                                        |
| 전체 대화                 | `thread.messages` (csharp: `thread.Messages`; go: `thread.Messages()`; java: `thread.messages()`; php: `$thread->messages()`)                                                                                                                                                                                                        |
| 다음 턴이 이어서 진행할 응답 `id` | `thread.previous_message_id` (typescript: `thread.previousMessageId`; csharp: `thread.PreviousMessageID`; go: `thread.PreviousMessageID()`; java: `thread.previousMessageId()`; php: `$thread->previousMessageId()`)                                                                                                                 |
| 전송하지 않고 메시지 추가        | `thread.append(...)` (csharp, go: `thread.Append(...)`; php: `$thread->append(...)`)                                                                                                                                                                                                                                                 |
| 이후 턴의 필드 변경           | `thread.update(...)` (csharp: `thread.Update(p => p with { ... })`; go: `thread.Params.MaxTokens = ...`; java: `thread.update(b -> ...)`; php: `$thread->update([...])`)                                                                                                                                                             |
| 스레드 복사                | `thread.fork()` (csharp, go: `thread.Fork()`; php: `$thread->fork()`; ruby: `thread.fork`)                                                                                                                                                                                                                                           |
| 새 스레드를 시작하기 위한 필드와 대화 | `thread.params` (csharp, go: `thread.Params`; java: `thread.params()`; php: `$thread->params()`)                                                                                                                                                                                                                                     |
| 이 스레드의 도구 러너          | `thread.tool_runner()` (typescript, java: `thread.toolRunner()`; csharp: `thread.ToolRunner(tools)`; go: `thread.NewToolRunner(tools)`; php: `$thread->toolRunner()`; ruby: `thread.tool_runner`)                                                                                                                                    |
| 스레드 없는 도구 러너          | `tool_runner(..., thread=False)` (typescript: `toolRunner({ ..., thread: false })`; csharp: `ToolRunner(parameters, tools, thread: false)`; go: `BetaToolRunnerParams{DisableThread: true}`; java: `ToolRunnerCreateParams.builder().thread(false)`; php: `toolRunner(..., thread: false)`; ruby: `tool_runner(..., thread: false)`) |

**스트리밍.** 헬퍼는 스트림이 `message_stop`에 도달할 때 응답을 기록하므로, 스트림을 끝까지 읽으세요. 일찍 읽기를 중단하는 경우, 다음 턴에서 스레드를 사용할 수 있도록 스트림을 종료하세요.

* Python: `with` 블록을 벗어납니다.
* TypeScript: 루프를 벗어나거나 `abort()`를 호출합니다.
* Go: `Close`를 호출합니다.
* Java 및 Ruby: `close()`를 호출합니다.
* C#: 열거자를 dispose합니다. `await foreach`를 벗어나면 이렇게 됩니다.
* PHP: `close()`를 호출합니다.

C#에서는 스트림을 읽기 시작할 때 요청이 전송됩니다.

C#, Go, Java 및 PHP에서는 거부 후에도 `thread.LastMessage` (go: `thread.LastMessage()`; java: `thread.lastMessage()`; php: `$thread->lastMessage()`)가 여전히 이전 턴의 응답을 반환하며, 첫 번째 턴에서는 아무것도 반환하지 않습니다. 거부 내용은 스트림의 이벤트에서 읽으세요.

**필드 변경.** 업데이트는 아무것도 보내지 않으며 이후의 모든 턴에 적용됩니다. 스레드에 응답이 생긴 후에는 헬퍼가 `system` 또는 `tools`를 변경하는 턴을 거부하고, API는 다른 고정 필드의 변경을 거부합니다([각 요청에서 보낼 내용](https://platform.claude.com/docs/ko/build-with-claude/message-threads#what-to-send) 참조). 이러한 필드 중 하나를 변경하려면 params에서 새 스레드를 시작하거나, 스레드를 포크하고 첫 번째 턴 전에 포크를 업데이트하세요.

**포크.** 포크는 대화, 필드, 요청 옵션을 복사하며 아무것도 보내지 않습니다. 포크의 첫 번째 턴은 새 생성 요청으로 전체 대화를 보내며, 그 이후 두 스레드는 독립적입니다. 포크는 항상 이전 응답이 아닌 전체 대화에서 시작합니다.

**Params.** params에는 헬퍼의 필드와 전체 대화가 들어 있습니다. 새 스레드를 시작하려면 params를 헬퍼를 생성하는 메서드에 전달하세요. 예를 들어 다른 `system`을 사용하거나([각 요청에서 보낼 내용](https://platform.claude.com/docs/ko/build-with-claude/message-threads#what-to-send) 참조), 이후 턴을 삭제하도록 `messages`를 잘라낼 수 있습니다. params로 시작한 스레드는 `cache_control` 중단점을 포함하여 모든 메시지를 주어진 그대로 전송하므로, 제한인 4개를 넘지 않도록 하세요([스레드에서의 프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/message-threads#prompt-caching) 참조). Ruby를 제외하고 params에는 요청 옵션이 포함되지 않으므로 요청 옵션을 다시 전달하세요.

**구조화된 출력.** 헬퍼를 생성할 때 출력 형식을 지정하세요. 이후 턴에서는 이를 변경할 수 없습니다. 파싱된 결과를 얻는 방법은 SDK에 따라 다릅니다:

* Python 및 TypeScript: `thread.parse(...)`를 호출하세요.
* C#: `client.Beta.Messages.Thread<T>(...)`로 헬퍼를 생성하세요.
* Go: `Send`는 `Params.OutputConfig.Format.Schema`가 가리키는 구조체로 언마샬링합니다.
* Java: `thread(...)`와 `outputConfig(MyClass.class)`로 헬퍼를 생성하세요.
* PHP: `outputConfig`가 모델 클래스를 지정하면 `send()`가 텍스트 블록에 `parsed`를 설정합니다.
* Ruby: `thread(...)`에 `output_config: {format: MyModel}`을 전달한 다음, `reply.parsed_output`을 읽으세요.

## 오류

API는 모델이 실행되기 전에 이 표의 오류를 반환합니다. 요청은 토큰을 사용하지 않으며, 스레드와 그 만료 시간은 변경되지 않습니다. `error.type`은 400 오류의 경우 `invalid_request_error`이고 404 오류의 경우 `not_found_error`입니다.

| 상태  | `error_code`                  | 의미                                                                                     | 조치                                                     |
| --- | ----------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| 404 | `thread_not_found`            | API가 스레드를 계속할 수 없습니다.                                                                  | 새 생성 요청으로 전체 대화를 보내세요.                                 |
| 400 | `thread_fingerprint_mismatch` | 고정 필드가 스레드와 함께 저장된 값과 다릅니다. 오류 메시지에 해당 필드의 이름이 표시됩니다.                                  | 고정 필드를 동일하게 유지하거나, 변경하려면 새 스레드를 시작하세요.                 |
| 400 | `thread_already_continued`    | 각 응답 `id`는 한 번만 계속할 수 있습니다.                                                            | 스레드의 최신 응답 `id`에서 계속하세요. 클라이언트가 해당 응답을 받지 못했다면 재전송하세요. |
| 400 | `thread_continue_in_flight`   | 이 응답 `id`에서의 다른 계속 요청이 아직 실행 중입니다. 이는 보통 사용자 자신의 이전 시도입니다.                             | 완료될 때까지 기다린 후 그 응답 `id`에서 계속하거나, 재전송하세요.               |
| 400 | `thread_unsupported_request`  | 요청이 스레드가 지원하지 않는 기능이나 조직 설정을 사용합니다. `error.details.unsupported`가 있으면 해당 항목의 이름이 표시됩니다. | 요청에서 해당 기능을 제거하거나, `thread` 없이 전체 대화를 보내세요.            |

스레드 요청에 대한 일부 400 오류에는 `error_code`가 없습니다. 예를 들어 `previous_message_id`가 없는 계속 요청이나 `system` 또는 `tools`를 포함하는 계속 요청이 그렇습니다.

다음 예제는 오류 본문을 보여줍니다.

```json
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "thread: a maximum of 4 blocks with cache_control may be provided when `thread` is set. Found 5.",
    "details": {
      "error_code": "thread_unsupported_request",
      "unsupported": ["cache_control"]
    }
  },
  "request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"
}
```

## 스레드가 지원하지 않는 기능

스레드는 다음 기능을 지원하지 않습니다.

* [Message Batches](https://platform.claude.com/docs/ko/build-with-claude/batch-processing). `thread`를 포함하는 요청 항목은 배치 결과에서 `invalid_request_error`로 끝납니다.
* [토큰 카운팅](https://platform.claude.com/docs/ko/build-with-claude/token-counting). `thread`를 포함하는 토큰 카운팅 요청은 400 오류를 반환합니다. 대신 `system` 및 `tools`와 함께 전체 대화를 카운트하세요.
* [압축](https://platform.claude.com/docs/ko/build-with-claude/compaction). [온디맨드 압축](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand)은 스레드와 함께 사용할 수 없습니다. `thread`를 포함하는 요청은 `compaction` 매개변수를 설정하거나 `compact-2026-09-04` 베타 헤더와 함께 `compaction` 블록을 보내면 400 오류를 반환합니다. [대화 압축하기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#compaction)를 참조하세요.
* [컨텍스트 편집](https://platform.claude.com/docs/ko/build-with-claude/context-editing). 단, `keep: "all"`을 사용하는 `clear_thinking_20251015`는 예외입니다.
* [HIPAA 준비](https://platform.claude.com/docs/ko/manage-claude/api-and-data-retention#hipaa-readiness)가 활성화된 조직.
* [Inference hooks](https://platform.claude.com/docs/ko/manage-claude/inference-hooks)가 켜져 있는 조직.

API는 스레드가 지원하지 않는 대부분의 요청을 `thread_unsupported_request` 코드와 함께 400 오류로 거부합니다. `error.details.unsupported`가 있으면 여기에는 다음 값 중 하나가 들어 있습니다.

| 값                    | 의미                                                                                                                                                     |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `context_management` | 요청이 스레드가 지원하지 않는 컨텍스트 관리 또는 압축을 사용합니다.                                                                                                                 |
| `cache_control`      | 요청에 4개를 초과하는 `cache_control` 중단점이 있습니다. [스레드에서의 프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/message-threads#prompt-caching)을 참조하세요. |
| `fallbacks`          | 요청이 `fallbacks`를 설정했으며, 조직이 스레드와 함께 `fallbacks`를 사용할 수 없습니다.                                                                                           |
| `hipaa`              | 조직에 HIPAA 준비가 활성화되어 있습니다.                                                                                                                              |
| `inference_hooks`    | 조직에 Inference hooks가 켜져 있습니다.                                                                                                                          |

API는 이 목록에 값을 추가할 수 있습니다. 인식하지 못하는 값을 받으면 `thread` 없이 전체 대화를 보내세요. 이러한 조건 중 여러 개를 충족하는 요청은 그중 하나만 명시하는 오류를 받습니다.

## 제한 사항

* 스레드를 나열, 읽기 또는 삭제하는 API는 없습니다.
* 스레드는 Claude API, [Claude Platform on AWS](https://platform.claude.com/docs/ko/build-with-claude/claude-platform-on-aws), Claude Models on Google Cloud (Anthropic managed), 및 [Claude in Microsoft Foundry](https://platform.claude.com/docs/ko/build-with-claude/claude-in-microsoft-foundry)에서 사용할 수 있습니다.

## 가격 및 속도 제한

스레드는 가격이나 [속도 제한](https://platform.claude.com/docs/ko/api/rate-limits)을 변경하지 않습니다.

## 데이터 보존

메시지 스레드는 ZDR 적용 대상이 아닙니다. 이 기능을 사용해도 다른 ZDR 적용 대상 기능의 사용에는 영향을 주지 않습니다. API는 스레드를 서버 관리 키로 암호화하여 메모리에 보관하고, 영구 저장소에 절대 쓰지 않으며, 마지막 사용 후 1시간 이내에 삭제합니다.

스레드에는 API가 모델에 보내는 대화, 생성 요청의 `system` 및 `tools`, 그리고 이후 요청을 확인하는 데 사용하는 고정 필드의 지문(fingerprint)이 들어 있습니다. 스레드는 파일을 참조로 보관하므로, 스레드를 사용하는 동안 대화가 사용하는 모든 파일을 사용 가능한 상태로 유지하세요.

모든 기능의 ZDR 적용 여부는 [API 및 데이터 보존](https://platform.claude.com/docs/ko/manage-claude/api-and-data-retention)을 참조하세요.

## 참고 항목

* [프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)
* [스트리밍](https://platform.claude.com/docs/ko/build-with-claude/streaming)
* [오류](https://platform.claude.com/docs/ko/api/errors)

## 제공 여부

메시지 스레드은(는) 모든 모델에서 베타 상태입니다.

### API 지원

#### [Messages API](https://platform.claude.com/docs/ko/build-with-claude/working-with-messages)

* 제공 여부: 사용 가능

| 상태 | 클라우드 플랫폼                                                                                                                                                                                                                                                                                                                                                                               |
| -- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 베타 | Claude API, [Claude Platform on AWS](https://platform.claude.com/docs/ko/build-with-claude/claude-platform-on-aws), [Claude Models on Google Cloud (Anthropic managed)](https://platform.claude.com/docs/ko/build-with-claude/claude-models-on-google-cloud/anthropic-managed), [Microsoft Foundry](https://platform.claude.com/docs/ko/build-with-claude/claude-in-microsoft-foundry) |

### 필수 베타 헤더

[베타 헤더 개요](https://platform.claude.com/docs/ko/api/beta-headers)

베타 헤더를 사용하면 실험적 기능과 새로운 모델 기능이 표준 API에 포함되기 전에 액세스할 수 있습니다.

* `message-threads-2026-08-12`

### 데이터 처리

[데이터 처리 개요](https://platform.claude.com/docs/ko/manage-claude/api-and-data-retention#feature-eligibility)

| 항목    | 제로 데이터 보존                                                                                                                                                                                                                                              | HIPAA |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- |
| 상태    | 적격 아님                                                                                                                                                                                                                                                  | 적격 아님 |
| 세부 정보 | 스레드의 내용(대화, 시스템 프롬프트, 도구)은 서버에서 관리하는 키로 암호화되어 메모리에 보관되며, 영구 저장소에는 기록되지 않고, 스레드를 마지막으로 사용한 후 1시간 이내에 삭제됩니다. 이 기능을 사용해도 다른 ZDR 적격 기능의 사용에는 영향을 주지 않습니다. [자세히 알아보기](https://platform.claude.com/docs/ko/build-with-claude/message-threads#data-retention) | —     |

적용 엔드포인트:

* `/v1/messages` `thread` 사용 시
