Add
Write messages, documents and notes into one end user's memory. The same id again is an edit, and a retry is safe.
POST /v1/memory/addAdd commits each item at once and answers 202 Accepted. The item lands a moment later and is then
searchable by its words. Reading it into dated facts happens later, in the background. Adding makes no model call.
An item's id is its identity. Send the same id again with other words and it is an edit of that item, not a
second one. An item keeps the time it was first given: to move it in time, forget it and add
it again.
Reuse an id only to fix the same statement, such as a typo or a mistake. When something changes, add a new item
with a new id: memory keeps both, each dated. An edit replaces the text and keeps the item's about,
metadata, expires and author unless you send them; send null to clear one.
Headers
AuthorizationstringBearer tm_…. The key must read and write.
X-Twin-AccountstringThe end user whose memory this is. The first add for an end user creates their memory. See End users for the length limit.
Idempotency-KeystringNames this request. Trimmed, and cut to its first 200 characters. An item sent without an id is named
<key>:<index>, its place in the batch, so a retry of the same request is the same items again. Items that
have an id ignore it.
X-Request-IdstringYour id for this request, echoed on the response.
Body
itemsItem[]1 to 500 items. The fields of an item are below.
waitbooleanDefault false. When true, the request waits up to 10 seconds for the items to land, and answers 200
with pending: 0 once they have. If some are still waiting after 10 seconds, it answers 202 with the
count.
Item
textstringThe words. At least one character. A document's whole text; the request body cap of 1 MB bounds it.
kindstringmessage, document or note. Default message. See Items.
idstringYour id for the item, up to 512 characters. The same id again is an edit: use it to fix the same
statement, and a new id for a change. Without one, an item that gives
an at is named by its words and time; one that gives neither is new every time.
atdateWhen it was said or written, as an ISO time that begins with a real day (2026-10-01T09:30:00Z), up to 40
characters. Default now. Once an id is held, its time is fixed.
authorstring | object | nullWho said it: a name, or {id, name, email, handle, self}. id is your stable id for the person. self: true marks the end user whose memory this is. On an edit, left out it is kept from the earlier copy; null clears it.
rolestringUp to 512 characters. user marks the end user. assistant, agent, bot, tool and system mark a
machine.
sessionstringFor messages. The messages of one session on one day are one thread. Without a session, messages are cut into conversations by time. Up to 512 characters.
titlestringA title, up to 512 characters. A document's passages are headed by it.
aboutstring[] | nullUp to 20 pieces of work the item names, as kind:name, such as project:relocation. A bare name is filed
as a topic. On an edit, left out it is kept from the earlier copy; null clears it.
expiresdate | nullWhen the item is forgotten. From then no read returns it or anything learned from it, and every copy and
every version is erased. Send the item again with the same id and a new expires to move it, or with
"expires": null to lift it. Left out, the expiry stays. See
Expiry.
Response
itemsobject[]One per item, in the order sent: id (yours, or the one given to it), event_id (ev_…), and landed.
queuedintegerHow many items were put on the queue for landing.
pendingintegerHow many have not landed yet. 0 means every item is searchable.
Errors
400bad requestX-Twin-Account is missing or not a usable id.401unauthorizeda bearer this service issued is required.403forbiddenthis key reads; writing takes a key that writes. The key is read-only.413too largebody over 1000000 bytes.422invalidA field is out of bounds, such as a kind that is not one of the three, more than 500 items, or a date that
is not a real day. Also item '<id>' has no text for text that is only whitespace.
503unavailableRetry-After seconds.curl https://api.twinmemory.dev/v1/memory/add \
-H "Authorization: Bearer $TWIN_MEMORY_API_KEY" \
-H "X-Twin-Account: user_42" \
-H "Idempotency-Key: batch-2026-10-03" \
-H "Content-Type: application/json" \
-d '{
"items": [
{"id": "msg-001", "session": "onboarding",
"role": "user", "at": "2026-10-01T09:30:00Z",
"author": {"id": "u_42", "name": "Alice Chen"},
"text": "I just moved to Lisbon from Berlin for the new job at Acme."},
{"id": "pref-diet", "kind": "note",
"at": "2026-10-02T12:00:00Z",
"text": "Alice is vegetarian and allergic to peanuts.",
"metadata": {"source": "crm"}}
]
}'{
"items": [
{ "id": "msg-001", "event_id": "ev_620270db0bff9328", "landed": false },
{ "id": "pref-diet", "event_id": "ev_50d009638ecf9772", "landed": false }
],
"queued": 2,
"pending": 2
}{
"items": [
{ "id": "msg-001", "event_id": "ev_620270db0bff9328", "landed": true },
{ "id": "pref-diet", "event_id": "ev_50d009638ecf9772", "landed": true }
],
"queued": 2,
"pending": 0
}