previous_response_id cost and the OpenAI Conversations API

Is previous_response_id cheaper? No: every earlier input token is billed again. How chaining, caching and the Conversations API work, and what they cost.

/guides/previous-response-id-and-conversations-api.mdUpdated

Short answer: previous_response_id saves you sending the history, not paying for it. OpenAI's conversation state guide says that even when using previous_response_id, all previous input tokens for responses in the chain are billed as input tokens (checked 12 November 2026). The Conversations API works the same way: the conversation's items are prepended to every request.

The Responses API offers two ways to let OpenAI hold a conversation's state: chain each response to the last with previous_response_id, or keep the items in a durable conversation with the Conversations API. Both save you resending the history. Neither shrinks it. This guide covers what each costs, how caching changes the numbers, the mistakes that make it worse, and what a memory layer does instead.

How they work

From OpenAI's conversation state guide and the API reference:

  • previous_response_id chains a response to an earlier one, so you send only the new input. All earlier input tokens in the chain are billed as input tokens on each call.
  • Stored responses are kept for 30 days by default. Setting store to false turns storage off; without storage, OpenAI's guidance is to replay the full item sequence instead of chaining (programmatic tool calling).
  • The Conversations API stores a conversation's messages, tool calls and tool outputs under a durable id you can reuse across sessions, devices and jobs. Conversation items are not subject to the 30-day expiry.
  • The two do not mix. previous_response_id "cannot be used in conjunction with conversation".
TypeScript
import OpenAI from 'openai';

const openai = new OpenAI();
const instructions = 'You are a concise support agent.';

const first = await openai.responses.create({ model: 'gpt-5', instructions, input: 'Hi, my order is late.' });
const next = await openai.responses.create({
  model: 'gpt-5',
  instructions, // not carried over: send it every time
  previous_response_id: first.id,
  input: [{ role: 'user', content: 'It was order 4471.' }],
});

What does previous_response_id cost?

With chaining, the model still reads the whole conversation on every call. If each turn adds about 1,000 tokens:

TurnInput tokens on that callCumulative input billedCumulative, best-case caching
10about 10,000about 55,000about 17,000
50about 50,000about 1.3 millionabout 185,000
100about 100,000about 5 millionabout 620,000
200about 200,000about 20 millionabout 2.2 million

The last column is in uncached-input-token equivalents, using OpenAI's rates for GPT-5.6 and later: cache reads at 0.1× the input rate and cache writes at 1.25× for the default 30-minute lifetime (prompt caching). It assumes every earlier token is a cache hit and only the newest 1,000 are written, which is the best a chain can do. By turn 200 the call is also at the edge of a 200k window, whatever it costs.

With Threadless, token use stays bounded: the request is sized for a 200k-token context window and does not keep growing with the conversation, so the per-call cost does not either. Older messages stop being sent word for word, their facts stay in view, and the older messages that matter come back, with their dates.

Does prompt caching make chaining cheap?

Cheaper, not cheap. What OpenAI's prompt caching guide says today:

  • It is automatic. "Prompt caching is enabled by default for supported OpenAI models."
  • There is a minimum. The minimum cacheable prompt is 1,024 tokens for GPT-5.6 and later, and varies by request settings for earlier models.
  • The discount is large. Cached input is "discounted up to 95%", varying by model.
  • Only an exact prefix hits. "Cache reuse requires the entire rendered prefix to match." Change the instructions, the tools or an early message and everything after the change is read at the full rate.
  • Writes cost more on newer models. On GPT-5.6 and later, writes cost 1.25× the uncached rate for the 30-minute lifetime and 2.5× for 12 hours.
  • Entries expire. The default minimum lifetime is 30 minutes. A user who comes back after lunch pays full price for the whole history again.

To see what you actually paid for, read the cached count from usage:

Python
reply = client.responses.create(model="gpt-5", previous_response_id=last_id, input=text)
usage = reply.usage
print(usage.input_tokens, usage.input_tokens_details.cached_tokens)

Caching changes the price of what the model reads, not the amount. It does nothing for the window, and nothing for a model reading 150k tokens of old chat to answer one question.

Common mistakes

  • Sending the history and previous_response_id. The chain is already prepended, so the model sees every earlier turn twice and you pay for both copies.
  • Expecting instructions to persist. They do not: "the instructions from a previous response will not be carried over to the next response" (API reference). Send them on every call.
  • Chaining with store: false. Without storage there is no stored chain to continue from over HTTP; replay the items instead. Zero Data Retention forces store to false (Data controls).
  • Keeping response ids for months. Stored responses are kept for 30 days by default. A long-lived conversation belongs in the Conversations API or your own store.
  • Combining conversation and previous_response_id. The API refuses it. Pick one.

previous_response_id vs the conversation parameter

previous_response_idconversation
What you keepThe last response idOne conversation id
Retention30 days by defaultUntil deleted
Billed inputAll earlier input in the chainEvery item in the conversation
Edit the historyNoYes: add or delete items
ZDR-eligibleNo storage under ZDR; replay items insteadNo: /v1/conversations is not ZDR-eligible

Retention and ZDR eligibility are from OpenAI's data controls table.

The Conversations API in 60 seconds

Endpoints, from the Conversations reference:

Method and pathWhat it does
POST /v1/conversationsCreate a conversation, optionally with up to 20 initial items and metadata
GET /v1/conversations/{conversation_id}Retrieve it
POST /v1/conversations/{conversation_id}Update its metadata
DELETE /v1/conversations/{conversation_id}Delete it
POST /v1/conversations/{conversation_id}/itemsAdd up to 20 items at a time
GET /v1/conversations/{conversation_id}/itemsList items
GET /v1/conversations/{conversation_id}/items/{item_id}Retrieve one item
DELETE /v1/conversations/{conversation_id}/items/{item_id}Delete one item

Metadata is up to 16 key-value pairs, keys up to 64 characters and values up to 512.

Python
from openai import OpenAI

client = OpenAI()

conversation = client.conversations.create(metadata={"user_id": "u_42"})
reply = client.responses.create(
    model="gpt-5",
    conversation=conversation.id,
    input=[{"role": "user", "content": "My order 4471 is late."}],
)

items = client.conversations.items.list(conversation.id)
client.conversations.items.delete(items.data[0].id, conversation_id=conversation.id)
TypeScript
const conversation = await openai.conversations.create({ metadata: { user_id: 'u_42' } });
const reply = await openai.responses.create({
  model: 'gpt-5',
  conversation: conversation.id,
  input: [{ role: 'user', content: 'My order 4471 is late.' }],
});
await openai.conversations.items.create(conversation.id, {
  items: [{ type: 'message', role: 'user', content: 'Note: customer is on the Pro plan.' }],
});
Shell
curl https://api.openai.com/v1/conversations \
  -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"metadata":{"user_id":"u_42"}}'

OpenAI's pricing page lists no separate storage charge for conversations; what you pay for is the tokens their items add to each response.

With the Agents SDK

The OpenAI Agents SDK for Python wraps these in sessions (Sessions):

  • SQLiteSession keeps history in a local SQLite file or in memory.
  • OpenAIConversationsSession keeps it in the Conversations API.
  • OpenAIResponsesCompactionSession wraps another session and compacts its history with the Responses API.
  • Run-level options conversation_id, previous_response_id and auto_previous_response_id continue a conversation without a session. The SDK docs say a session cannot be combined with them: choose one.

All of these carry the history forward. Which one you pick changes where it is kept, not how much the model reads.

What they do not do

  • Decide what the model sees. Everything in the chain is sent, in order, until it does not fit.
  • Keep what was learned in a form you can read. The history is there; the knowledge is not drawn out of it.
  • Bring old details forward. A detail from turn 12 is as present, or as buried, on turn 300 as everything else.
  • Know about time. A conversation idle for a week resumes exactly as it was.

OpenAI's answer to the first problem is compaction, which replaces old context with an encrypted item.

What changes with a memory layer

With Threadless AI you stop chaining. Each call sends only what is new to decorate, and the request that comes back holds what the model should see: your instructions, the facts block, the history still in view, recall and, when needed, the note ask. previous_response_id and store are dropped from the request if you pass them, because we hold the history.

TypeScript
// Before: OpenAI holds the chain, and bills it every call.
const reply = await openai.responses.create({ model, input: text, previous_response_id: lastId });

// After: send what is new, submit what comes back.
const request = await memory.responses.decorate(memoryId, [{ role: 'user', content: text }], { model, tools });
const reply = await openai.responses.create(request);
memory.responses.report(memoryId, reply);

When chaining is enough

Short conversations that end well inside the window, where the simplest code wins. Use it, and move to memory when conversations start to run long.

Questions people ask

Does previous_response_id reduce tokens?

No. It reduces what you send, not what you are billed: every earlier input token in the chain is billed as input on each call. Prompt caching can discount the repeated prefix, but the model still reads all of it.

Does the usage object include the chained tokens?

Yes. usage.input_tokens counts the whole chain the model read, and usage.input_tokens_details.cached_tokens shows how much of it was a cache hit.

Is the Conversations API free?

OpenAI's pricing page lists no separate charge for storing conversations. You pay for the input tokens the conversation's items add to every response that uses it.

How long are conversations stored?

Until you delete them. Conversations and their items are not subject to the 30-day TTL that applies to stored responses.

Can I delete items from a conversation?

Yes: DELETE /v1/conversations/{conversation_id}/items/{item_id}, or conversations.items.delete in the SDKs. That is also the only way to shrink one by hand.

Can I use previous_response_id with Chat Completions?

No. previous_response_id and conversation are Responses API parameters; with Chat Completions you send the messages array yourself on every call.

Are instructions carried over with previous_response_id?

No. Send instructions on every request. The upside is that you can change them between turns.