# 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.

**Short answer: `previous_response_id` saves you sending the history, not paying for it.** OpenAI's [conversation state guide](https://developers.openai.com/api/docs/guides/conversation-state) 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](https://developers.openai.com/api/docs/guides/conversation-state) and the [API reference](https://developers.openai.com/api/reference/resources/responses/methods/create):

- **`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](https://developers.openai.com/api/docs/guides/tools-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`".

```ts
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:

| Turn | Input tokens on that call | Cumulative input billed | Cumulative, best-case caching |
|---|---|---|---|
| 10 | about 10,000 | about 55,000 | about 17,000 |
| 50 | about 50,000 | about 1.3 million | about 185,000 |
| 100 | about 100,000 | about 5 million | about 620,000 |
| 200 | about 200,000 | about 20 million | about 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](https://developers.openai.com/api/docs/guides/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](https://developers.openai.com/api/docs/guides/prompt-caching) 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](https://developers.openai.com/api/reference/resources/responses/methods/create)). 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](https://developers.openai.com/api/docs/guides/your-data)).
- **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_id` | `conversation` |
|---|---|---|
| What you keep | The last response id | One conversation id |
| Retention | 30 days by default | Until deleted |
| Billed input | All earlier input in the chain | Every item in the conversation |
| Edit the history | No | Yes: add or delete items |
| ZDR-eligible | No storage under ZDR; replay items instead | No: `/v1/conversations` is not ZDR-eligible |

Retention and ZDR eligibility are from OpenAI's [data controls table](https://developers.openai.com/api/docs/guides/your-data).

## The Conversations API in 60 seconds

Endpoints, from the [Conversations reference](https://developers.openai.com/api/reference/resources/conversations):

| Method and path | What it does |
|---|---|
| `POST /v1/conversations` | Create 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}/items` | Add up to 20 items at a time |
| `GET /v1/conversations/{conversation_id}/items` | List 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)
```

```ts
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.' }],
});
```

```bash
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](https://developers.openai.com/api/docs/pricing) 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](https://openai.github.io/openai-agents-python/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](https://threadlessai.com/guides/openai-compaction-vs-memory), 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.

```ts
// 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.

## Related

- [OpenAI Responses API memory](https://threadlessai.com/guides/responses-api-memory)
- [How to stop resending the whole chat history](https://threadlessai.com/guides/stop-resending-chat-history)
- [Fix OpenAI context_length_exceeded errors](https://threadlessai.com/guides/openai-context-length-exceeded)
- [Does the OpenAI API have memory?](https://threadlessai.com/guides/openai-api-memory)
