Conversation context
Chat completions are stateless by default, so a multi-turn conversation normally means resending
the entire history on every call. Send context_id and we keep the
thread for you, so each call carries only the new message.
Quickstart
Two calls. The first opens a thread, the second continues it.
curl https://dreamprompting.com/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"context_id": "new",
"messages": [{"role": "user", "content": "My project is called Kuza."}]
}'
{
"object": "chat.completion",
"model": "openai/gpt-oss-120b",
"provider": "groq",
"context_id": "ctx-Ngz5JE-UdyE_YB6WfVz7hQ",
"choices": [{"index": 0, "message": {"role": "assistant", "content": "Noted."}}],
"usage": {"total_tokens": 27}
}
curl https://dreamprompting.com/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"context_id": "ctx-Ngz5JE-UdyE_YB6WfVz7hQ",
"messages": [{"role": "user", "content": "What is my project called?"}]
}'
# -> "Kuza" the earlier turns were replayed for you
What happens on each call
- You send
context_idplus the new message or messages. - We load the earlier turns of that thread, oldest first, and put them in front of your messages.
- The combined conversation is trimmed to fit the token budget.
- The result is sent to a provider, with the usual automatic failover.
- Your message and the reply are stored against the thread, so the next call can replay them.
Only successful turns are stored for replay. If a call fails, it leaves nothing behind, so retrying does not make it look like you asked twice.
Reference
Value of context_id | Effect |
|---|---|
| "new" | Starts a thread. A fresh id comes back on the response. |
| true | Same as "new", for clients that prefer a boolean. |
| "ctx-..." | Continues that thread. Earlier turns are replayed. |
| omitted | Stateless, exactly as before. Nothing is stored as a thread. |
| anything else | 400, so a typo fails loudly instead of silently losing the conversation. |
| Where the id appears | How to read it |
|---|---|
| Response body | context_id |
| Response header | X-Context-Id |
Ids look like ctx- followed by a random string. Treat one as a secret:
it names a conversation. Store it wherever you keep the rest of your session state.
Inspect a thread
GET /api/v1/contexts/{context_id}
Shows exactly what we would replay, and how big it is. Useful when a thread stops behaving the way you expect.
curl https://dreamprompting.com/api/v1/contexts/ctx-Ngz5JE-UdyE_YB6WfVz7hQ \
-H "Authorization: Bearer YOUR_API_KEY"
{
"context_id": "ctx-Ngz5JE-UdyE_YB6WfVz7hQ",
"messages": [
{"role": "user", "content": "My project is called Kuza."},
{"role": "assistant", "content": "Noted."}
],
"turns": 2,
"estimated_tokens": 12,
"token_budget": 12000,
"trimmed_turns": 0
}
trimmed_turns counts older turns the budget is currently leaving out.
An unknown id returns an empty thread rather than an error, because a thread only exists once its
first turn has been answered.
Streaming
Context works with "stream": true. The body carries standard
OpenAI chunks with no room for extra fields, so read the id from the
X-Context-Id response header.
import requests
r = requests.post(
"https://dreamprompting.com/api/v1/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"model": "auto", "context_id": "new", "stream": True,
"messages": [{"role": "user", "content": "Hello"}]},
stream=True,
)
context_id = r.headers["X-Context-Id"] # keep this for the next turn
for line in r.iter_lines():
if line:
print(line.decode())
Trimming, and why threads do not grow forever
A conversation that keeps growing eventually exceeds what any provider will accept, at which point every call fails. To avoid that, a replayed thread is trimmed to about 12,000 tokens:
- System messages always survive. They carry the instructions that define behaviour, so losing them changes the assistant mid-conversation.
- Newest turns win. Older turns are dropped first, because recent context usually matters more.
- Nothing is deleted. Trimming decides what gets replayed; the full thread stays in your usage history.
If your thread needs to remember something permanently, put it in a
system message rather than relying on an early user turn, since a
system message is never trimmed away.
This does not reduce your token usage
Worth being clear about, because it surprises people. Language models are stateless: they have no
memory between calls. "Remembering" a conversation always means sending the history again. When you use
context_id, we are the ones resending it, so the provider still
receives the full thread and it still counts against your
token budget.
What it does save:
- You stop maintaining conversation state, so the client gets simpler.
- You stop uploading the same history repeatedly, which matters on slow or metered connections.
- Trimming keeps the thread inside provider limits, so a long conversation does not start failing.
If your goal is lower token spend, the levers are a shorter budget, a compact system message, and starting a fresh thread when the subject changes. Long conversations are expensive by nature: input tokens accumulate every turn, and in practice they tend to dwarf the replies.
Privacy and scoping
- A thread belongs to the account that created it. Another account cannot read it, even with the id.
- Any active key on your own account can use your threads, so rotating a key does not lose them.
- Threads are stored on our side, in the same request log that powers your usage page. Do not put secrets in a conversation you would not want stored.
- Ids are unguessable, but treat one as a credential for that conversation and keep it server side.
When to use it, and when not to
Good fit
- Chat interfaces and assistants that hold a conversation
- Roleplay or tutoring sessions that must stay in character
- Multi-step work where later steps depend on earlier answers
- Clients where storing history yourself is awkward, like a shell script or an automation node
Skip it
- One-off calls such as classification, extraction or translation, where history only adds cost
- Cases where you already keep the transcript and want exact control over what is sent
- Fan-out work in parallel: give each line of work its own thread, or none
- Anything needing an audit trail of the precise payload, since we prepend on your behalf
One thread is one conversation. If your app serves several people, create a thread per person and store the id next to that person's session. Do not share one thread across users, or their conversations will run together.
Errors
| Status | Meaning | Fix |
|---|---|---|
| 400 | Malformed context_id | Use "new" or an id from a previous response. |
| 401 | Missing or invalid key | Send Authorization: Bearer. Get a key. |
| 413 | This turn alone is too large | Shorten the message. Trimming applies to replayed history, not to what you send. |
| 429 | Rate limit or daily budget | Back off, or ask us to raise the limit. |
| 502 | Every provider declined | If the message mentions size, shorten the thread or lower the budget. |
Ready to build?