Conversation History & State
Keep the transcripts and framework state your code reads back, apart from memories. Nothing written here is ever listed, searched, recalled or counted as a memory.
Two stores beside memories
Some integrations need exact data back: a chat history a framework replays, or key/value state it reloads between runs. Those live here, in conversation history and framework state, addressed by tenant_id and user_id in the body like the other /v1 operations. Memories stay the durable facts: history appends and state puts can send text to fact extraction, and the facts found become memories of the same user.
| Operation | Method and path | Billing |
|---|---|---|
| Append turns to a session | POST /v1/history/append | One add per user turn sent to extraction, or one add when it stores new turns and extracts none. A retry that stores nothing new is free. |
| Read a session’s turns | POST /v1/history/list | One retrieval |
| Delete a session or turns | POST /v1/history/delete | Free |
| Store a key’s value | POST /v1/state/put | One add for a new value. Putting the value a key already holds is free. |
| Read one value | POST /v1/state/get | One retrieval |
| List values under a namespace | POST /v1/state/list | One retrieval |
| Rank values against a query | POST /v1/state/search | One retrieval |
| Delete keys or a namespace | POST /v1/state/delete | Free |
| List namespaces | POST /v1/state/namespaces | One retrieval |
- Every body carries
tenant_idanduser_id. source(defaultapi, up to 64 characters, lowercased) scopes the rows. Pass your integration’s name.- Rows come back with
h_<n>ids. The delete routes andDELETE /memory/forgetalso acceptm_<n>or the bare number.
Append History
Append 1 to 100 turns to one session, in order. Turns are stored exactly as given and read back with List History. With extract: true (the default) the user turns are also sent to fact extraction.
| Field | Type | Contract |
|---|---|---|
session_id | string | Required, 1–2000 characters. The conversation the turns belong to. |
turns | object[] | Required, 1–100 turns. Each has role (user, assistant, system, tool or any label up to 64 characters; human is stored as user, ai as assistant) and content (string, may be empty), and optionally seq (integer ≥ 0, orders the transcript), occurred_at, payload (JSON your framework needs to rebuild the message) and idempotency_key (up to 200 characters). |
source | string | Optional. Scopes the rows. Defaults to api. |
extract | boolean | Optional. Also extract durable facts from the user turns. Defaults to true. |
metadata | object | Optional. Scalar values are copied onto the extracted facts. |
- A turn is recognised as a retry when it carries the same
idempotency_key, or the sameseq, role and content. The stored turn comes back withcreated: falseinstead of being added twice. - A turn with neither an
idempotency_keynor aseqis appended every time.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",project_id=os.environ["MEMORYSYNC_PROJECT_ID"],end_user_id="usr_7f3a9c2e",)result = client.append_history(tenant_id="acme",user_id="usr_7f3a9c2e",session_id="thread-42",source="my-app",turns=[{"role": "user", "content": "I always fly with the window seat.", "seq": 0},{"role": "assistant", "content": "Noted, window seat it is.", "seq": 1},],)
{"items":[{"id":"h_1000000000123","session_id":"thread-42","role":"user","content":"I always fly with the window seat.","seq":0,"occurred_at":null,"payload":null,"source":"my-app","created_at":"2026-09-27T10:00:00","created":true},{"id":"h_1000000000124","session_id":"thread-42","role":"assistant","content":"Noted, window seat it is.","seq":1,"occurred_at":null,"payload":null,"source":"my-app","created_at":"2026-09-27T10:00:00","created":true}],"request_ids":["9f3a51c0e2d84b7a"],"status":"ok"}
List History
A session’s turns, oldest first. When any turn in the session has a seq, the turns are ordered by it.
| Field | Type | Contract |
|---|---|---|
session_id | string | Required. The session to read. |
source | string | Optional. Omit to read turns from every source. |
limit | integer | Optional. 0 (the default) returns every turn, otherwise the latest N, at most 5000. |
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",project_id=os.environ["MEMORYSYNC_PROJECT_ID"],end_user_id="usr_7f3a9c2e",)history = client.list_history(tenant_id="acme",user_id="usr_7f3a9c2e",session_id="thread-42",source="my-app",)for turn in history["items"]:print(turn["role"], turn["content"])
{"items":[{"id":"h_1000000000123","session_id":"thread-42","role":"user","content":"I always fly with the window seat.","seq":0,"occurred_at":null,"payload":null,"source":"my-app","created_at":"2026-09-27T10:00:00"}],"total":1}
Delete History
Delete a whole session or specific turns. Not billed.
| Field | Type | Contract |
|---|---|---|
session_id | string | Delete this session. Exactly one of session_id or ids. |
ids | array | Delete these turns (h_<n> strings or numbers, up to 1000). Exactly one of session_id or ids. |
source | string | Optional. Limit the delete to one source. |
delete_derived_facts | boolean | Optional. Also delete the memories extracted from the deleted turns. Defaults to false. |
{"deleted_ids":["h_1000000000123","h_1000000000124"],"deleted_fact_ids":["m_311"]}
Put State
Store the value of a key in a namespace. One key holds one live value: a put replaces the previous one. Putting the identical value again returns created: false, so a retry is safe.
| Field | Type | Contract |
|---|---|---|
namespace | string[] | Required, 1–20 segments, for example ["memories", "user-1"]. |
key | string | Required, 1–1000 characters. |
value | object | Required JSON object. |
text | string | Optional searchable text. Defaults to the value’s JSON. |
source | string | Optional. Scopes the rows. Defaults to api. |
index | boolean | Optional. Embed text so Search State can rank it by meaning. Defaults to true. |
extract | boolean | Optional. Also extract durable facts from text, like Add Memory. Defaults to false. |
replace_derived_facts | boolean | Optional. When the put replaces a value, also delete the facts extracted from the previous one. Defaults to false. |
metadata | object | Optional. Scalar values are copied onto the extracted facts. |
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",project_id=os.environ["MEMORYSYNC_PROJECT_ID"],end_user_id="usr_7f3a9c2e",)result = client.put_state(tenant_id="acme",user_id="usr_7f3a9c2e",namespace=["preferences"],key="seat",value={"seat": "window"},text="Prefers the window seat.",source="my-app",)
{"item":{"id":"h_1000000000130","namespace":["preferences"],"key":"seat","value":{"seat":"window"},"text":"Prefers the window seat.","source":"my-app","created_at":"2026-09-27T10:00:00","updated_at":"2026-09-27T10:00:00"},"created":true,"request_id":null,"deleted_fact_ids":[],"status":"ok"}
Get and List State
Get returns the current value of one key (namespace, key, source) as {"item": …}, with item: null when the key has none. List returns the current value of every key whose namespace equals namespace_prefix or starts with it, segment by segment, newest first, as {"items": […], "total": n}; limit is 1–5000 (default 100) and offset pages. Both read the source the values were put with (default api).
Search State
Rank the current values under namespace_prefix against query. Each item carries a score: cosine similarity when the value was embedded (index: true), token overlap otherwise. limit is 1–100 (default 10). Filtering on value fields is left to the caller.
import osfrom memorysync import MemorySyncClientclient = MemorySyncClient(api_key=os.environ["MEMORYSYNC_API_KEY"],base_url="https://api.memorysync.io",project_id=os.environ["MEMORYSYNC_PROJECT_ID"],end_user_id="usr_7f3a9c2e",)hits = client.search_state(tenant_id="acme",user_id="usr_7f3a9c2e",query="seating",namespace_prefix=["preferences"],source="my-app",)
Delete State and List Namespaces
Delete takes a namespace and keys (up to 1000; omit keys to delete the whole namespace, an empty list deletes nothing) plus delete_derived_facts to also delete the memories extracted from the deleted values, and answers {"deleted_ids": […], "deleted_fact_ids": […]}. It is not billed. Namespaces lists the namespaces holding at least one value for this user, as {"namespaces": [["a", "b"], …]}; prefix keeps those starting with the given segments and max_depth cuts each to that many segments.
Quota behaviour
Errors and next action
A 401 or 403 means the key is missing, invalid, or lacks the scope for the operation. A 422 names the invalid field in detail — for example more than 100 turns, both session_id and ids on a delete, or a namespace with more than 20 segments. For 429 or 5xx, retry with the same payload: idempotency keys, seq and identical puts make the retry safe.