openmemory.sh
+ Docs menu

Concepts

Concepts

The primitives in an OpenMemory space - memories, journal, records, actions, KV, events, context, batch, and forget - and how they fit together.

Everything lives in a space. A space has one API key, a timezone, a dream mode, and these stores:

PrimitiveWhat it holdsTypical use
MemoriesShort facts with full-text recallPreferences, people, decisions
JournalOne entry per day (and per week)What happened, decisions, open loops
RecordsTyped JSON in named collectionsCommitments, goals, routines, contacts
ActionsAn approval ledgerAnything with side effects that needs a yes
KVNamespaced JSON valuesSettings, sync cursors, locks
EventsA capped append-only logRun history, audit breadcrumbs

Two calls tie them together: GET /v1/context reads what a run needs in one go, and POST /v1/batch writes several things atomically. Dreaming tidies memory overnight, and forget deletes things for good.

Spaces

Create one with POST /v1/spaces (no auth, rate limited per IP address). The response contains space_id and api_key (om_...). The key is shown once. Only its SHA-256 hash is stored.

Send the key on every other call as Authorization: Bearer om_... or X-API-Key: om_....

  • GET /v1/space shows settings, usage counts, limits, and delete_after (when the space will be deleted if it stays unused).
  • PATCH /v1/space changes timezone (IANA name) or dream_mode (client, server, or off).
  • GET /v1/export returns everything as one JSON document.
  • DELETE /v1/space deletes the space and everything in it, permanently.

Spaces are isolated: one key can never read or change another space. Spaces unused for 90 days are deleted.

Memories

A memory is one fact, in one or two sentences.

{
  "subject": "Sam",
  "text": "Prefers code reviews on Thursdays",
  "tier": "core",
  "tags": ["work"],
  "source_ref": "slack:C123/1712",
  "pinned": false,
  "origin": "user",
  "expires_at": null
}
FieldMeaning
subjectWho or what it is about (up to 200 characters). Optional.
textThe fact (1 to 4,000 characters). Required.
tiercore facts are always in context. long (default) facts are recalled on demand. scratch facts are short-lived.
tagsUp to 20 tags of up to 50 characters each.
source_refWhere it came from (message id, URL). Do not paste message bodies here.
pinnedPinned facts are always in context and never changed by dreaming.
originuser when the person stated it, agent (default) when the agent inferred it. Dreaming writes dream.
expires_atOptional ISO 8601 time after which the fact is hidden and later removed.

Writing the exact same subject and text again returns the existing fact (200, created: false) instead of storing a duplicate.

Recall (GET /v1/memories, MCP recall) searches with full-text matching that ignores case and accents and works in any script, including Greek (final sigma and tonos are folded). Any word may match, words match as prefixes, and pinned and core facts get a small boost. Without q it lists facts: pinned first, then core, long, scratch, newest first. You can filter by subject (exact, case-insensitive), tier, or tag.

Each recall increments recall_count on the facts it returns (pass touch=false over REST to skip that). Dreaming uses recall counts to decide which facts to promote to core.

Change a fact with PATCH /v1/memories/{id}. Delete one with DELETE /v1/memories/{id}, or many with forget.

Journal

One entry per date and kind. kind is day (default) or week. Dreaming writes week digests.

curl -s -X PUT https://api.openmemory.sh/v1/journal/2026-11-01 \
  -H "Authorization: Bearer $OPENMEMORY_KEY" -H 'content-type: application/json' \
  -d '{"summary":"Shipped the export endpoint","decisions":["Use JSON, not CSV"],"open_loops":["Ask Sam about retention"],"merge":true}'

PUT replaces the entry unless merge is true. With merge, the summary is appended on a new line and the lists are combined without duplicates. Lists hold up to 50 items of up to 500 characters each, and the summary up to 4,000 characters. GET /v1/journal lists entries newest first, filtered by from, to, and kind.

Records

Records are typed JSON objects in named collections (commitments, goals, routines, anything you like). Collection names use letters, digits, _, ., and -, up to 64 characters. Names starting with dream. are written by dreaming.

{ "key": "deck-for-sam", "status": "open", "due_at": "2026-11-05T09:00:00Z", "data": { "what": "Send Sam the deck" } }
  • Upsert by key. POST /v1/collections/{collection}/records with a key updates the record that has that key (and bumps its version). Without a key it always creates.
  • Compare-and-set. PATCH takes expected_version. If the record has moved on, you get 409 with current_version in the error details.
  • Shallow merge. PATCH merges data into the existing object. Set a field to null to remove it.
  • Query by status (comma-separated), key, or due_before. Results are ordered by due date (undated last). Page with next_cursor.

Records whose status is done, dropped, closed, cancelled, archived, or rejected count as closed. Every other status counts as open for context.

Actions

The approval ledger. An agent proposes something with side effects, the owner approves it, and execution is recorded with a receipt.

curl -s -X POST https://api.openmemory.sh/v1/actions \
  -H "Authorization: Bearer $OPENMEMORY_KEY" -H 'content-type: application/json' \
  -d '{"class":"email_reply","summary":"Reply to Sam: Thursday works","payload":{"to":"sam@example.com","body":"Thursday works"},"idempotency_key":"reply-sam-1712","expires_at":"2026-11-03T18:00:00Z"}'
  • Each action gets a short id per space: A1, A2, ... Ids are case-insensitive (a7 works).
  • Idempotency. Reusing an idempotency_key with the same payload returns the existing action (200, created: false). Payloads are compared by content, so key order does not matter. The same key with a different payload is a 409.
  • Compare-and-set transitions. POST /v1/actions/{id}/transition with {"from": "approved", "to": "executing"} succeeds only if the action is still approved. Two workers cannot both execute A7.
  • Expiry. Pending or approved actions past expires_at become expired and can no longer be approved or executed.
  • Receipts. Send receipt (any JSON object) with to: "done" or to: "failed" to record what happened.

Allowed transitions:

FromTo
pendingapproved, executing, denied, expired, superseded, blocked
approvedexecuting, denied, expired, superseded
executingdone, failed
blockedpending, denied, superseded
failedpending
done, denied, expired, supersedednone (final)

GET /v1/actions lists open actions (pending, approved, executing) by default. Pass status=done,failed for others.

KV

Namespaced JSON values for settings, cursors, and small state. A namespace uses the same name rules as collections. Namespaces starting with _ are reserved. Values can be any JSON up to 64 KB.

PUT /v1/kv/{ns}/{key} with {"value": ..., "expected_version": n}:

  • omit expected_version to upsert,
  • 0 to create only (fails with 409 if the key exists, which makes a simple lock),
  • n to update only if the value is still at version n.

expires_at makes a value disappear after a time. GET /v1/kv/{ns} lists a namespace and GET /v1/kv/{ns}/{key} reads one value.

Events

An append-only log of small structured entries: {"kind": "run", "key": "daily-brief", "status": "ok", "detail": {...}}. It is capped per space and the oldest events are dropped first. List newest first with filters kind, since, and before_id (for paging). Keep message bodies and secrets out of detail.

Context

GET /v1/context (MCP get_context) is the one call to make at the start of a run. It returns:

FieldContents
now, timezone, todayThe current time and the space's local date.
profile.textPinned and core facts, one per line (- Subject: text [mem_id]), cut to budget_chars. truncated tells you whether some were left out.
profile.dream_summaryThe owner profile written by the last dream, if any.
dueOpen records due within due_within_days.
open_recordsOpen records in the collections you name in collections. Empty if you name none.
actionsPending, approved, and executing actions.
journalDay entries from the last journal_days days.
weeklyThe latest weekly digest.
kvThe namespaces you list in kv, as {ns: {key: value}}.
last_dreamId, status, finish time, and report of the most recent dream.

Parameters: budget_chars (200 to 20,000, default 4,000), journal_days (0 to 31, default 7), due_within_days (0 to 90, default 7), kv (comma-separated namespaces), and collections (comma-separated, limits due and fills open_records).

Batch

POST /v1/batch applies up to 50 writes in one transaction: all succeed or none do.

{
  "ops": [
    { "op": "memory.remember", "input": { "text": "Sam is out Nov 10-14", "origin": "user" } },
    { "op": "journal.put", "date": "2026-11-01", "input": { "summary": "Planned the review", "merge": true } },
    { "op": "record.update", "collection": "commitments", "id": "rec_...", "patch": { "status": "done", "expected_version": 3 } },
    { "op": "action.transition", "id": "A7", "input": { "from": "executing", "to": "done", "receipt": { "message_id": "m1" } } },
    { "op": "kv.set", "ns": "cursors", "key": "gmail", "input": { "value": { "history_id": 4411 } } },
    { "op": "event.append", "input": { "kind": "run", "status": "ok" } }
  ]
}

Ops: memory.remember, memory.update, memory.delete, journal.put, record.put, record.update, record.delete, action.propose, action.transition, kv.set, kv.delete, event.append.

The response is {"results": [...]} with one result per op, in order. A failed version or status check, or a missing row, aborts the whole batch with 409. The error message names the op index. Ops that read first (record.update, a journal merge, action.propose) see the state from before the batch.

Batch is REST-only. Over MCP, each write is its own tool call.

Forget

POST /v1/forget (MCP forget) hard-deletes. See Forget and privacy for the full rules.

{ "query": "Sam Lee", "scopes": ["memories", "records", "journal"], "dry_run": true }
  • ids deletes those memories (up to 200).
  • query deletes memories containing all of its words, plus records and journal entries containing the exact phrase (only in the scopes you list; the default scope is memories).
  • Journal text is redacted by default (the phrase becomes [forgotten]). Set journal_mode: "delete" to delete the entries instead.
  • dry_run: true returns what would be deleted without deleting anything.

Deleted items leave content-free tombstones (kind, id, and time only).

Errors

Errors have one shape: {"error": {"code", "message", "details"?}}.

StatusCodeWhen
400bad_requestInvalid input. details.issues lists the fields.
401unauthorizedMissing or unknown key.
404not_foundNo such item in this space.
409conflictA version, status, or idempotency check failed.
413bad_requestRequest body larger than 256 KB.
422quota_exceededThe space is full for that kind of item. See Limits.
429rate_limitedToo many requests. Retry in a minute.