Threads persist conversation state across requests. You never create one explicitly — pass a thread_id on a chat request and Truememory creates or resumes it. This page covers managing them afterwards: list, rename, delete, and history. All endpoints require a Bearer token from Truememory Auth.
Creating Threads
Pass a thread_id in your /v1/chat/completions request to maintain conversation state. Truememory uses it to persist message history so the LLM has context from previous turns.
- If omitted, Truememory auto-generates a UUID for the thread. Each request starts a new conversation.
- If provided, Truememory resumes the conversation from where it left off. Use any string (e.g. a UUID you generate client-side).
Retrying a Failed Turn
A chat request that fails partway through can still have stored its messages on the thread. Sending the same request again would store them twice. To avoid that, anchor each request on the last message you received.
message_id— the ID of the last message the turn stored. A non-streaming response returns it as a top-level field. A streaming response adds it as a top-level key on the chunk that carries thefinish_reason(stoportool_calls). It is not sent whenfinish_reasoniserror, and it is only returned for requests with athread_id.parent_message_id— send the lastmessage_idyou received.messagesholds the messages after it. It may also start with replayed history that ends at the parent; that history is dropped exactly as it is withoutparent_message_id.
{
"model": "anthropic/claude-sonnet-4-5",
"thread_id": "my-thread",
"parent_message_id": "<message_id from the previous response>",
"messages": [{ "role": "user", "content": "What about tomorrow?" }]
}If the request fails, send it again unchanged, with the same parent_message_id and messages. You never need to trim it. Truememory drops the replayed history the same way the first attempt did, compares the rest with what is already stored after the parent, and only appends what is missing. When the whole request is already stored and the turn did not finish, the model runs again on the stored thread. A server tool that already returned a result is not run again.
A turn that finished is never run again. That includes a turn that ended in tool calls for your client tools: you already received those calls. You get a 409 THREAD_PARENT_STALE instead. Read the thread history and use the last message’s run_id as the new parent_message_id.
In a client tool loop, use the message_id from the tool_calls finish as the parent of the request that sends the tool result.
parent_message_id is ignored when thread_id is not set. Without it, requests behave as before.
| Code | Status | When |
|---|---|---|
THREAD_PARENT_NOT_FOUND |
409 | parent_message_id is not a message on this thread |
THREAD_PARENT_STALE |
409 | The turn after the parent already completed, including one that ended in client tool calls (read it from history), or the thread holds different or later messages after the parent |
SERVICE_UNAVAILABLE |
503 | The thread could not be read to check the parent. Send the same request again later |
Per-App Thread Isolation
Threads can be scoped to a specific application using the X-App-Id header. This allows multiple apps to share the same Truememory backend while keeping their conversation threads separate.
X-App-Idprovided — the thread is scoped to that app. Only requests with the sameX-App-Idcan see, rename, or delete it.X-App-Idomitted — the thread is unscoped (shared). It is visible when listing threads withoutX-App-Id, but hidden from app-scoped lists.
# Create a thread scoped to "vibrantly"
curl -X POST https://your-tie-host/v1/chat/completions \
-H "Authorization: Bearer $TOKEN" \
-H "X-App-Id: vibrantly" \
-H "X-Agent-Id: chatbot" \
-d '{"model": "anthropic/claude-sonnet-4-5", "messages": [{"role": "user", "content": "Hello"}], "thread_id": "my-thread"}'Thread Metadata
Each thread can carry an arbitrary JSON metadata object. Use it to store application-specific context that doesn’t belong in the conversation itself — for example, where the thread originated, which page it’s associated with, or whether it’s pinned in a UI.
{
"source": "whatsapp",
"pageType": "whatsapp-dm",
"pageId": "550e8400-e29b-41d4-a716-446655440000",
"isPinned": false
}Metadata is set and updated via the Update Thread endpoint. It is returned in every thread summary (list and update responses). Truememory stores metadata as-is and does not interpret it — your application defines the schema.
List Threads
Returns threads for the authenticated user. Supports cursor-based pagination, sorting, and date filtering.
GET https://your-tie-host/threads
Authorization: Bearer $TOKENHeaders:
| Header | Type | Default | Description |
|---|---|---|---|
X-App-Id |
string | null | Filter threads by app. When set, only threads matching that exact app ID are returned (unscoped threads are excluded). When omitted, all threads are returned (both scoped and unscoped). |
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
agent_id |
string | null | Filter threads by agent |
cursor |
string | null | Pagination token from a previous response (treat as opaque; do not parse or construct it) |
limit |
integer | 20 | Results per page (1-100) |
order_by |
string | created_at |
Sort field: created_at or updated_at |
asc |
boolean | false | Sort direction: true for ascending (oldest first), false for descending (newest first) |
created_after |
ISO 8601 datetime | null | Only return threads created on or after this timestamp |
created_before |
ISO 8601 datetime | null | Only return threads created on or before this timestamp |
Response:
{
"threads": [
{
"thread_id": "847c6285-8fc9-4560-a83f-4e6285809254",
"agent_id": "chatbot",
"app_id": "vibrantly",
"title": "What is the weather in Tokyo?",
"metadata": {
"source": "whatsapp",
"isPinned": false
},
"created_at": "2026-03-25T10:00:00+00:00",
"updated_at": "2026-03-25T10:05:00+00:00"
}
],
"next_cursor": "MjAyNi0wMy0yNVQxMDowMDowMCswMDowMHw4NDdjNjI4NS04ZmM5LTQ1NjAtYTgzZi00ZTYyODU4MDkyNTQ="
}Pass next_cursor as the cursor query parameter to fetch the next page. When next_cursor is null, there are no more results.
The app_id field is null for unscoped threads. The metadata field is null when no metadata has been set.
Get Thread History
Retrieve the full message history for a thread:
POST https://your-tie-host/history
Authorization: Bearer $TOKEN
Content-Type: application/json
{ "thread_id": "<your-thread-id>" }Response:
{
"messages": [
{
"type": "human",
"content": "What is the weather in Tokyo?",
"metadata": {
"session_id": "8b94776f-31ae-49ac-9f06-3be137d69186",
"platform": "ios",
"app_version": "2.4.1"
}
},
{
"type": "ai",
"content": "The current weather in Tokyo is...",
"metadata": null
}
]
}The metadata field on user messages contains the key-value pairs sent with the original request. AI messages have metadata: null since metadata is attached at request time, not per-response.
Update Thread
Update a thread’s title and/or metadata. The title is automatically set from the first user message, but you can override it. Metadata can be set at any time.
At least one of title or metadata must be provided in the request body.
PATCH https://your-tie-host/threads/<thread-id>
Authorization: Bearer $TOKEN
Content-Type: application/json
# Required only if the thread was created with X-App-Id
X-App-Id: vibrantly
{ "title": "My weather conversation", "metadata": { "isPinned": true } }Request body:
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | No | New title for the thread (1-500 characters, cannot be blank) |
metadata |
object | null | No | Arbitrary metadata dict. Replaces existing metadata entirely. Pass null to clear. |
You can send either field alone or both together:
# Rename only
{ "title": "New title" }
# Set metadata only
{ "metadata": { "source": "telegram", "isPinned": false } }
# Clear metadata
{ "metadata": null }Response:
{
"thread_id": "847c6285-8fc9-4560-a83f-4e6285809254",
"agent_id": "chatbot",
"app_id": "vibrantly",
"title": "My weather conversation",
"metadata": { "isPinned": true },
"created_at": "2026-03-25T10:00:00+00:00",
"updated_at": "2026-03-25T10:10:00+00:00"
}Delete Thread
Permanently delete a thread and all its data (title, metadata, timestamps, agent/app association):
DELETE https://your-tie-host/threads/<thread-id>
Authorization: Bearer $TOKEN
# Required only if the thread was created with X-App-Id
X-App-Id: vibrantlyResponse:
{ "status": "deleted", "thread_id": "847c6285-8fc9-4560-a83f-4e6285809254" }Error Codes
| Code | Status | When |
|---|---|---|
AUTH_TOKEN_MISSING |
401 | No Bearer token provided |
THREAD_NOT_FOUND |
404 | Thread does not exist or is not owned by the user |
VALIDATION_ERROR |
422 | Invalid cursor, empty title, title exceeds 500 characters, or empty update body |
THREADS_FETCH_FAILED |
500 | Unexpected server error while listing threads |
THREAD_UPDATE_FAILED |
500 | Unexpected server error while updating a thread |
THREAD_DELETE_FAILED |
500 | Unexpected server error while deleting a thread |
SERVICE_UNAVAILABLE |
501 | Thread management requires the PostgreSQL memory backend |