twin memory

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/addAuth: write key

Add 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

Authorizationstringrequired

Bearer tm_…. The key must read and write.

X-Twin-Accountstringrequired

The end user whose memory this is. The first add for an end user creates their memory. See End users for the length limit.

Idempotency-Keystring

Names 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-Idstring

Your id for this request, echoed on the response.

Body

itemsItem[]required

1 to 500 items. The fields of an item are below.

waitboolean

Default 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

textstringrequired

The words. At least one character. A document's whole text; the request body cap of 1 MB bounds it.

kindstring

message, document or note. Default message. See Items.

idstring

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

atdate

When 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 | null

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

rolestring

Up to 512 characters. user marks the end user. assistant, agent, bot, tool and system mark a machine.

sessionstring

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

titlestring

A title, up to 512 characters. A document's passages are headed by it.

aboutstring[] | null

Up 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 | null

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

metadataobject | null

Any JSON object. Kept with the item and shown by read of its ledger entry and by versions. Nothing filters on it. On an edit, left out it is kept from the earlier copy; null clears it.

Response

itemsobject[]

One per item, in the order sent: id (yours, or the one given to it), event_id (ev_…), and landed.

queuedinteger

How many items were put on the queue for landing.

pendinginteger

How many have not landed yet. 0 means every item is searchable.

Errors

400bad request
X-Twin-Account is missing or not a usable id.
401unauthorized
a bearer this service issued is required.
403forbidden
this key reads; writing takes a key that writes. The key is read-only.
413too large
body over 1000000 bytes.
422invalid

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

503unavailable
The service is at capacity, or this end user is being rebuilt. Retry after Retry-After seconds.
Request
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"}}
    ]
  }'
202 Accepted
{
  "items": [
    { "id": "msg-001", "event_id": "ev_620270db0bff9328", "landed": false },
    { "id": "pref-diet", "event_id": "ev_50d009638ecf9772", "landed": false }
  ],
  "queued": 2,
  "pending": 2
}
200 OK, with wait: true
{
  "items": [
    { "id": "msg-001", "event_id": "ev_620270db0bff9328", "landed": true },
    { "id": "pref-diet", "event_id": "ev_50d009638ecf9772", "landed": true }
  ],
  "queued": 2,
  "pending": 0
}