← Blog

Claude API Conversation State Management Guide

2026-10-21 · 5 min read · SubToAPI Team

Why conversation state management matters

The Claude API is stateless. Every request to /v1/messages is self-contained — Claude has no memory of previous calls unless you send the full conversation back to it. If you've built a chat feature and noticed Claude "forgetting" earlier context, the cause is almost always incomplete state management on your side, not a model limitation.

This guide covers the practical mechanics of managing conversation state: how to structure the message array, how to handle context window limits, when to truncate or summarize, and how to track state across sessions and tool calls. It assumes you already know how to send a basic request — if not, start with the quickstart.

How Claude tracks conversation turns

Claude's API has no server-side session object. State lives entirely in the messages array you send on every call. Each call must include the full history you want Claude to be aware of:

{
  "model": "claude-opus-4",
  "max_tokens": 1024,
  "messages": [
    { "role": "user", "content": "I'm building a weather app in Python." },
    { "role": "assistant", "content": "Great, what data source are you using?" },
    { "role": "user", "content": "OpenWeatherMap. How do I parse the JSON response?" }
  ]
}

Roles must alternate between user and assistant, starting with user. There's no system role inside the array — system prompts are passed as a separate top-level system field and are not part of the turn-taking structure.

If you're calling through SubToAPI, the request shape is identical — you're just pointing at https://api.subtoapi.app/v1/messages with your sub_live_... key instead of managing provider credentials directly. See the messages docs for the full parameter list.

The core state management pattern

Most production apps follow this loop:

  1. Store the conversation (in a database, not in memory) keyed by a conversation ID.
  2. On each new user message, fetch the stored history.
  3. Append the new user message to the array.
  4. Send the full array to Claude.
  5. Append Claude's response to the stored history.
  6. Persist the updated array before returning the response to the client.
async function sendTurn(conversationId, userMessage) {
  const history = await db.getMessages(conversationId); // array of {role, content}
  history.push({ role: "user", content: userMessage });

  const response = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-opus-4",
      max_tokens: 1024,
      messages: history
    })
  });

  const data = await response.json();
  history.push({ role: "assistant", content: data.content });
  await db.saveMessages(conversationId, history);
  return data;
}

The important detail: persistence happens outside the API call. Claude doesn't store anything for you between requests, so your database is the source of truth for state, not the model provider.

Managing the context window

Every model has a maximum context window measured in tokens, covering the system prompt, every message in the array, and the response. As conversations grow, you'll eventually hit that ceiling. There are three common strategies:

Truncation — drop the oldest messages once you approach the limit. Simple, but loses early context entirely. Fine for support chats or short-lived sessions.

Sliding window with summary — periodically compress older turns into a short summary message and replace the raw history with [summary] + [recent N turns]. This preserves continuity while keeping token usage bounded.

function buildWindow(fullHistory, summary, recentCount = 10) {
  const recent = fullHistory.slice(-recentCount);
  if (!summary) return recent;
  return [
    { role: "user", content: `Conversation summary so far: ${summary}` },
    { role: "assistant", content: "Understood, continuing from that context." },
    ...recent
  ];
}

Full retention with usage tracking — keep everything, but monitor token counts via the usage metadata returned with each response so you know exactly when to intervene. This is the safest option for apps where losing context is costly, like legal or coding assistants.

Whichever strategy you use, track token usage per conversation, not just per request. A conversation that's fine at turn 5 can silently blow past the limit by turn 40 if nobody is watching cumulative size.

Handling state with tool use

Tool use adds another layer of state. When Claude requests a tool call, the response includes a tool_use content block, and your next request must include a tool_result block in the same conversation to continue the exchange. These intermediate turns are part of the same stateless array — you're responsible for appending both the tool call and its result before sending the next message. Skipping a step or sending results out of order breaks the thread, since Claude has no independent record of what it asked for. Details on the exact block structure are in the tools documentation.

State across streaming responses

If you're using streaming, the final assembled message still needs to be reconstructed and saved as a single assistant turn once the stream ends — don't persist partial chunks as separate messages. Accumulate the deltas client-side or server-side, then write one complete entry to your history store. The streaming guide covers the event format in detail.

A practical checklist

If you're routing requests through SubToAPI, usage metadata is returned with every response, which makes it straightforward to log token counts per conversation and trigger truncation or summarization logic automatically. Check pricing if you're evaluating plans for a team handling multiple concurrent conversation threads, or sign up to test the message flow directly.

Questions

Does the Claude API remember previous conversations automatically? No. The API is stateless — you must send the full message history (or a managed subset of it) with every request for Claude to have any awareness of prior turns.

What happens if I exceed the context window? Requests that exceed the model's token limit will fail with an error. You need to truncate, summarize, or otherwise reduce the message array before hitting that ceiling, ideally by tracking token usage proactively.

Should I store raw message arrays in my database, or a custom format? Storing the raw role/content array format is usually best — it maps directly to what the API expects, avoiding translation bugs when you reconstruct history for the next request.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →