memoception

Build on Memoception.

A REST API over the same engine as the MCP connector — every memory tool as an endpoint, plus format-aware custom pipelines. One key, one memory: what your code stores, your Claude and your dashboard see too. Try every call right here, in the browser.

Quickstart

Create an API key in the app → settings → Developers → create API key (shown once). Send it as Authorization: Bearer or x-api-key. Base URL https://www.engli.org/api/v1.

# store a memory
curl -s https://www.engli.org/api/v1/memory/store \
  -H "Authorization: Bearer $MEMOCEPTION_KEY" -H "content-type: application/json" \
  -d '{"project":"my-app","content":"We chose Postgres for the event store.","kind":"decision"}'

# recall it — structured, with scores
curl -s https://www.engli.org/api/v1/memory/recall \
  -H "x-api-key: $MEMOCEPTION_KEY" -H "content-type: application/json" \
  -d '{"project":"my-app","query":"which database did we pick for events?"}'

# → { "ok": true, "request_id": 184467440737, "memories": [ { "id": 12, "score": 0.93, … } ] }

The envelope & request ids

Every response is { ok, request_id, …data } or { ok: false, request_id, error }. Your request_id sequence starts at a random large number unique to your account and increments by exactly 1 per request — unguessable across accounts, strictly ordered within yours. Use it to correlate logs, deduplicate retries, and match callback deliveries (each carries the request_id that caused it).

Limits

20 requests/second per key, enforced account-wide — over-limit calls get 429 with a retry-after header (a throttled call never consumes a request_id).
Storage follows your plan — Pro: 1.5 TB per project, 9 TB per account (Enterprise removes the ceiling — book a call).
Nothing else is metered — recall, search, export, and every other call are unlimited.

Boilerplates

Ready-to-run examples — CRM/catalog/incident stores, a research agent, multi-project isolation, Unreal/Godot optimization, precise architecture plans, and the full API test suites. Explore the code, download any file, and copy a share link for a specific one.

Browse boilerplates →

Endpoint reference & playground

Every endpoint below has its parameters, an example response, and a live tester — set your key once at the top and send real requests without leaving the page. Requests go straight from your browser to https://www.engli.org/api/v1 (CORS is enabled); your key is stored in this browser only.

API keynot set — the tester needs it

Discovery

No auth — describe the API to your own tooling.

GET/v1/formats· List supported formatsno auth

The 17 custom-store formats and the standardized rules vocabulary, machine-readable. No auth required — call it to drive a format picker or validate rules client-side.

No parameters.

example response
{
  "ok": true,
  "formats": [
    {
      "id": "csv",
      "aliases": [],
      "splits": [
        "row"
      ],
      "default_split": "row",
      "summary": "Header + quoted rows — one memory per row."
    },
    {
      "id": "neo4j",
      "aliases": [
        "cypher"
      ],
      "splits": [
        "graph",
        "statement"
      ],
      "default_split": "graph",
      "summary": "Cypher nodes & relations → node memories + edge tags."
    },
    "… 15 more"
  ],
  "rules_standard": {
    "split": "…",
    "keep": "…",
    "weights": "…",
    "…": "…"
  },
  "docs": "https://www.memoception.com/developers"
}
▶ TRY ITGET https://www.engli.org/api/v1/formats

Memory

The ten core tools: store, recall, ingest, and manage.

GET/v1/projects· List your projects

Every project in your namespace with its memory count, byte size, and last-activity date. Byte size is what the storage caps are measured against.

No parameters.

example response
{
  "ok": true,
  "request_id": 184467440737,
  "projects": [
    {
      "project": "memory-science",
      "count": 412,
      "bytes": 8843120,
      "latest": "2026-07-23"
    }
  ]
}
▶ TRY ITGET https://www.engli.org/api/v1/projects
set your API key above to send
POST/v1/memory/store· Store a memory

Save one memory — a fact, decision, preference, note, or todo — into a project. Memories persist across sessions and are recalled semantically. Or pass a `file` instead of `content`: it is chunked into recallable memories AND the original is archived (when your account has an archive bucket configured).

content *stringthe memory text (required unless file is given)
projectstringproject partition (default "default")
titlestringshort label (defaults to the first line of content)
kind"note"|"fact"|"decision"|"preference"|"todo"memory type (default "note")
tagsstring[]up to 8 free-form tags
file{ filename, data, encoding? }ingest a file: data is utf8 text or base64 (encoding:"base64"). Chunked + original archived.
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "result": "Stored memory #128 in project \"memory-science\" (fact). It will be recalled semantically.",
  "memory_ids": [
    128
  ]
}
▶ TRY ITPOST https://www.engli.org/api/v1/memory/store
set your API key above to send
POST/v1/memory/recall· Recall memories

Semantic recall — paraphrases match via concept expansion, learned embeddings rank when the deployment provides them. Returns a structured, scored list. Omit project to search your whole namespace.

query *stringwhat to recall
projectstringlimit to one project (omit = all projects)
limitnumbermax results, 1–50 (default 6)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "memories": [
    {
      "id": 128,
      "project": "memory-science",
      "kind": "fact",
      "title": "Endel Tulving distinguished episodic…",
      "content": "Endel Tulving distinguished episodic from semantic memory in 1972.",
      "tags": [
        "episodic-memory",
        "history"
      ],
      "weight": 1,
      "score": 0.94,
      "created_at": "2026-07-23"
    }
  ]
}
▶ TRY ITPOST https://www.engli.org/api/v1/memory/recall
set your API key above to send
POST/v1/memory/ingest· Ingest a file

Chunk and store a whole file along the format's natural seams — markdown by headings, code by definitions, CSV by rows, JSON/JSONL by entries, prose by paragraphs. Every chunk becomes independently recallable.

filename *stringname with extension — drives format detection
content *stringthe file's text content
projectstringproject partition (default "default")
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "result": "Ingested tulving-1972.md into project \"memory-science\" as 2 markdown chunk(s) (ids 129–130). Every chunk is now recallable; forget them anytime with memory_forget {source: \"tulving-1972.md\"}."
}
▶ TRY ITPOST https://www.engli.org/api/v1/memory/ingest
set your API key above to send
POST/v1/memory/history· Index a whole history

Save a whole conversation or project history in one call. Pass distilled `entries` (preferred — one per decision/fact/milestone) and/or a raw `transcript` (chunked automatically). Incremental: near-duplicates of existing memories are skipped, so re-runs only add what's new.

project *stringproject to index into
entries{title?,content,kind?,tags?}[]distilled history items (preferred)
transcriptstringalternative: raw history text, chunked automatically
sourcestringprovenance label (default "conversation")
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "result": "Indexed 2 item(s) from reading-notes into \"memory-science\" + 3 distilled fact(s). Re-run any time; only new history lands."
}
▶ TRY ITPOST https://www.engli.org/api/v1/memory/history
set your API key above to send
POST/v1/memory/forget· Forget memories

Delete memories by ids, by whole project, or by source filename/URL. Provide at least one filter. Frees storage against your plan caps.

idsnumber[]specific memory ids
projectstringdelete an entire project partition
sourcestringdelete everything ingested from this filename/URL
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "result": "Forgot 2 memories."
}
▶ TRY ITPOST https://www.engli.org/api/v1/memory/forget
set your API key above to send
POST/v1/memory/consolidate· Consolidate a project

Merge near-duplicate memories into one stronger memory — its weight accumulates and boosts future recall. Run periodically so long-lived projects compress instead of silting up.

project *stringproject to consolidate
thresholdnumbersimilarity to merge at, 0.5–0.99 (default 0.9)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "result": "Consolidated project \"memory-science\": 3 cluster(s) merged, 7 duplicate(s) folded in. Survivors carry the group’s weight and rank higher at recall."
}
▶ TRY ITPOST https://www.engli.org/api/v1/memory/consolidate
set your API key above to send
POST/v1/memory/export· Export training data

Turn a project's memories into fine-tuning JSONL — prompt/completion pairs or ChatML messages. The `result` is the JSONL text, one row per memory.

project *stringproject to export
format"prompt-completion"|"chatml"default "prompt-completion"
limitnumbermax rows (default 500, cap 2000)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "result": "{\"messages\":[{\"role\":\"user\",\"content\":\"Recall from reading-notes: Fact: Suddendorf & Corballis mental time travel\"},{\"role\":\"assistant\",\"content\":\"Suddendorf & Corballis (2007) framed episodic memory as mental time travel.\"}]}\n…"
}
▶ TRY ITPOST https://www.engli.org/api/v1/memory/export
set your API key above to send
GET/v1/memory/1· Get one memory

Fetch a single memory by id, with its full content. (Path form: GET /v1/memory/:id — the 1 here is just an example id.)

projectstringoptional project scope
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "memory": {
    "id": 128,
    "project": "memory-science",
    "kind": "fact",
    "title": "Tulving 1972",
    "content": "Endel Tulving distinguished episodic from semantic memory.",
    "source": "",
    "tags": [
      "episodic-memory"
    ],
    "weight": 1,
    "created_at": "2026-07-23"
  }
}
▶ TRY ITGET https://www.engli.org/api/v1/memory/1
set your API key above to send
PATCH/v1/memory/1· Update one memory

Update a memory in place: change its weight (recall strength), tags, title, kind, or content. The hash signature is recomputed when text changes so recall stays correct. (Path form: PATCH /v1/memory/:id.)

weightnumberrecall strength 1–10
tagsstring[]replace the tag set
titlestringnew title
kindstringnew kind
contentstringnew content (re-signed)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "memory": {
    "id": 128,
    "project": "memory-science",
    "kind": "fact",
    "title": "Tulving 1972",
    "content": "…",
    "tags": [
      "episodic-memory",
      "landmark"
    ],
    "weight": 8,
    "created_at": "2026-07-23"
  }
}
▶ TRY ITPATCH https://www.engli.org/api/v1/memory/1
set your API key above to send
POST/v1/summarize· Summarize / optimize a project

Distill a whole project into a leaner, stronger version using the inference layer — the same optimize that powers the app. Defaults to Mistral when configured (cheap/free), else the account's model; pass an explicit model to override, deep:true for the multi-step (plan → distill → critic) pass, and an optional instruction to steer it. Requires the deployment's inference to be configured.

project *stringproject to distill
instructionstringsteer the summary (e.g. "focus on decisions")
deepbooleanmulti-step deep optimize (default false)
modelstringoverride the model
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "project": "memory-science",
  "optimized_project": "memory-science · optimized",
  "entries": 24,
  "source_entries": 210,
  "cost_usd": 0
}
▶ TRY ITPOST https://www.engli.org/api/v1/summarize
set your API key above to send
POST/v1/research· Research & store

Research a subject online and store the findings in one call. Discovers sources (web search when a subject is given), reaches each page through a clean-extraction reader with automatic fallback routing, chunks it into recallable memory, keeps each URL as a link memory, and writes a research-summary note. Pass explicit sources, a subject to search for, or both — best-effort per source, so one dead link never fails the call.

project *stringproject to store into
subjectstringsubject/query to research (drives discovery + tagging)
sourcesstring[]explicit URLs to reach and store
maxSourcesnumbercap on total sources, 1–20 (default 6)
discoverbooleanrun web-search discovery when a subject is set (default true)
tagsstring[]extra tags on every stored memory
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "subject": "evolution of episodic memory",
  "project": "memory-science",
  "reached": [
    {
      "url": "https://plato.stanford.edu/entries/memory/",
      "via": "direct",
      "chunks": 34
    },
    {
      "url": "https://en.wikipedia.org/wiki/Episodic_memory",
      "via": "reader",
      "chunks": 21
    }
  ],
  "failed": [],
  "memoriesCreated": 58,
  "summaryId": 191
}
▶ TRY ITPOST https://www.engli.org/api/v1/research
set your API key above to send
GET/v1/original· Get an original back

Retrieve a stored original. By content_id it returns the original record's text (or, for a file ingest, its chunks folded back together); by source or memory_id it returns that file/memory's content. When the original was archived to your bucket, it also returns a presigned download_url (15 min) for the raw bytes — pass filename + project to target the archived object directly. Alias: /file.

content_idstringreturn the original behind a content_id
sourcestringfold a file's chunks back into its full text
memory_idnumberreturn one memory's full content
filenamestringthe archived object's filename (for the presigned download_url)
projectstringproject partition
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "filename": "tulving-1972.md",
  "kind": "original",
  "content": "# Episodic and Semantic Memory\n\nTulving proposed…",
  "bytes": 4211,
  "archived_key": "user_12/memory-science/tulving-1972.md",
  "download_url": "https://s3.us-east-005.backblazeb2.com/…?X-Amz-Signature=…",
  "parts": null
}
▶ TRY ITGET https://www.engli.org/api/v1/original
set your API key above to send

Custom pipelines

Format-aware store & shaped recall.

GET/v1/content/cnt_9f3ac1b2e4d5f6a7· Get a content bundle

Everything behind one content_id in a single call: the original, the existing-methodology chunks, every format representation grouped by format, and any organized (SQL/graph) views. (Path form: GET /v1/content/:content_id.)

projectstringoptional project scope
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "content_id": "cnt_9f3ac1b2e4d5f6a7",
  "original": {
    "id": 130,
    "title": "landmark-studies",
    "content": "…",
    "bytes": 412
  },
  "chunks": [
    {
      "id": 131,
      "kind": "file-chunk",
      "title": "…",
      "chars": 210
    }
  ],
  "formats": {
    "csv": [
      {
        "id": 133,
        "kind": "record",
        "title": "…",
        "chars": 64
      }
    ]
  },
  "organized": [
    {
      "id": 140,
      "kind": "organized",
      "title": "sql · schema",
      "chars": 300
    }
  ],
  "total_memories": 6
}
▶ TRY ITGET https://www.engli.org/api/v1/content/cnt_9f3ac1b2e4d5f6a7
set your API key above to send
POST/v1/query· Query records / traverse graph

Query your structured memories two ways. mode:"sql" filters/projects/sorts the records (a practical SQL subset — pass structured where/select/order_by/limit, or a small `sql` string). mode:"graph" does a breadth-first traversal over the relationships (rel: edges) from a start node. Scope with content_id / format / name / memory_ids / tags.

mode"sql" | "graph"default sql
projectstringproject scope
content_idstringscope to one bundle
formatstringscope to one format
sqlstringsql-mode: a small SELECT … WHERE … ORDER BY … LIMIT string
where / select / order_by / limitstructuredsql-mode: structured query instead of a string
fromstringgraph-mode: start node
relstringgraph-mode: only follow this relationship
depthnumbergraph-mode: hops (1–6, default 2)
direction"out"|"in"|"both"graph-mode: edge direction (default out)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "mode": "sql",
  "columns": [
    "theorem",
    "year"
  ],
  "rows": [
    [
      "Godel First Incompleteness",
      1931
    ]
  ],
  "count": 1,
  "records_scanned": 6
}
▶ TRY ITPOST https://www.engli.org/api/v1/query
set your API key above to send
POST/v1/batch· Batch many operations

Run 1–100 operations in ONE request (one request_id) — the way to do bulk work under the 20 req/s cap. Each operation is { op, …args } where op is a write/read endpoint path (memory/store, memory/recall, memory/ingest, memory/history, memory/forget, memory/link, memory/consolidate, custom/store, custom/recall, organize). Results come back in order; a failed op is reported, the batch still completes.

operations *{ op, …args }[]1–100 operations
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "count": 3,
  "ok_count": 3,
  "failed": 0,
  "results": [
    {
      "op": "memory/store",
      "ok": true,
      "result": "Stored memory #201 …",
      "memory_ids": [
        201
      ]
    },
    {
      "op": "memory/store",
      "ok": true,
      "memory_ids": [
        202
      ]
    },
    {
      "op": "memory/recall",
      "ok": true,
      "memories": [
        {
          "id": 201,
          "score": 0.9,
          "title": "…"
        }
      ]
    }
  ]
}
▶ TRY ITPOST https://www.engli.org/api/v1/batch
set your API key above to send
POST/v1/custom/store· Custom store (format-aware)

Store the ORIGINAL content and, alongside it, a rule-transformed representation in each chosen `format` (one id or an ARRAY), plus our existing methodology — so everything is custom- and plain-recallable and every artifact carries an id. Returns: content_id (the original), memory_ids (existing-methodology chunks), and per-format custom_memory_ids (the new format representations). The original is always kept: the chunker runs (set "traditional": false to skip) and, with a `file`, the original is archived. Pass rules inline or reference a saved set with "rulesName".

format *string | string[]the format(s) to store the data as — one, or an array of the 17 (GET /formats)
content *stringthe raw data (required unless file is given)
file{ filename, data, encoding? }supply the original as a file: chunked, archived, and used as the pipeline input
projectstringproject partition (default "default")
namestringsource label — becomes custom:<format>:<name>
rulesobjectinline rules document (the rules standard)
rulesNamestringreference a saved rule set instead of inline rules
traditionalbooleanalso run the chunk pipeline on the original (default true)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "project": "memory-science",
  "content_id": "cnt_9f3ac1b2e4d5f6a7",
  "original": {
    "memory_id": 130,
    "archived": null,
    "filename": null
  },
  "memory_ids": [
    131,
    132
  ],
  "formats": [
    {
      "format": "csv",
      "ok": true,
      "custom_memory_ids": [
        133,
        134
      ],
      "stored": 2,
      "source": "custom:csv:landmark-studies",
      "dropped": 0
    },
    {
      "format": "neo4j",
      "ok": false,
      "custom_memory_ids": [],
      "stored": 0,
      "error": "no graph units found in the content — is it really neo4j?"
    }
  ],
  "stored": 2
}
▶ TRY ITPOST https://www.engli.org/api/v1/custom/store
set your API key above to send
POST/v1/custom/recall· Custom recall (shaped)

Filter custom-stored data (by format, source name, tags, kind, time range), optionally rank it semantically with a query, then return it in one of five shapes: memories, records, table, series, or graph.

shape"memories"|"records"|"table"|"series"|"graph"output shape (default "memories")
projectstringlimit to one project
querystringsemantic ranking (omit = newest first)
formatstringfilter to one format
namestringfilter to one source name
content_idstringreturn everything descended from one original (its content_id)
tagsstring[]require all of these tags
kindstringfilter by memory kind
sincestringISO date lower bound (created_at)
untilstringISO date upper bound
limitnumbermax results, 1–200 (default 20)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "shape": "table",
  "count": 2,
  "data": {
    "columns": [
      "_id",
      "year",
      "author",
      "finding"
    ],
    "rows": [
      [
        131,
        1885,
        "Ebbinghaus",
        "forgetting curve"
      ],
      [
        132,
        1953,
        "Milner",
        "patient H.M. and the hippocampus"
      ]
    ]
  }
}
▶ TRY ITPOST https://www.engli.org/api/v1/custom/recall
set your API key above to send
POST/v1/organize· Organize into SQL / graph

Take EXISTING memories (by content_id, memory_ids/ids, or a format/name/tags filter) and materialize their structured records as ORGANIZED data — a relational SQL schema (CREATE TABLE + INSERTs) or a Neo4j graph (nodes + relationships). The generated artifact is stored and parsed back through the pipeline, so it's queryable via custom/recall (shape:"table" for sql, shape:"graph" for neo4j), everything linked under a new content_id. `rules` control the shape: table/label, key, columns, and link {from,to,label} for relationships/foreign keys.

into *"sql" | "neo4j"relational (sql) or graph (neo4j) organization
projectstringproject partition
content_idstringorganize everything descended from one original
memory_idsnumber[]explicit memory/custom-memory ids to organize
formatstringfilter the records to organize by format
source_namestringfilter by source name
tagsstring[]filter by tags
namestringlabel for the organized artifact
rules{ table?, label?, key?, columns?, link? }how to organize — table/node name, primary key/node id, columns, and link {from,to,label}
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "into": "neo4j",
  "content_id": "cnt_1a2b3c4d5e6f7a8b",
  "organized_memory_id": 210,
  "custom_memory_ids": [
    211,
    212,
    213
  ],
  "stored": 3,
  "records_used": 2,
  "source": "custom:neo4j:influence-graph",
  "generated": "CREATE (n0:Thinker {name: 'Locke', influenced: 'Hume'})\nCREATE (n1:Thinker {name: 'Hume'})\nCREATE (n0)-[:INFLUENCED]->(n1)"
}
▶ TRY ITPOST https://www.engli.org/api/v1/organize
set your API key above to send
POST/v1/store-organize· Store & organize

One call: store the ORIGINAL content (or a `file`) as records in a parse `format` (existing methodology + custom memories, with a content_id), THEN organize those records into a relational SQL schema or Neo4j graph. Returns the full store ID model plus the organized artifact. `format` is how to PARSE the content; `into` is the organized target; `organizeRules` shape the SQL/graph.

format *stringthe format to PARSE the content into records (e.g. "csv", "json")
into *"sql" | "neo4j"organize the records into relational or graph form
contentstringthe raw data (required unless file is given)
file{ filename, data, encoding? }supply the original as a file (chunked + archived)
projectstringproject partition
namestringsource label
rulesobjectparse rules (the rules standard) for the record extraction
organizeRules{ table?, label?, key?, columns?, link? }how to organize the records into sql/graph
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "project": "math-graph",
  "content_id": "cnt_aa11bb22cc33dd44",
  "original": {
    "memory_id": 300,
    "archived": null,
    "filename": null
  },
  "memory_ids": [
    301,
    302
  ],
  "formats": [
    {
      "format": "csv",
      "ok": true,
      "custom_memory_ids": [
        303,
        304
      ],
      "stored": 2
    }
  ],
  "organized": {
    "into": "neo4j",
    "content_id": "cnt_ee55ff66aa77bb88",
    "organized_memory_id": 305,
    "custom_memory_ids": [
      306,
      307,
      308
    ],
    "records_used": 2,
    "source": "custom:neo4j:theorem-deps-neo4j",
    "generated": "CREATE (n0:Theorem {theorem: 'FTA', depends_on: 'euclid_lemma'})\n…"
  }
}
▶ TRY ITPOST https://www.engli.org/api/v1/store-organize
set your API key above to send

Rule sets

Save and reuse rules documents.

POST/v1/rules· Save a rule set

Store a reusable rules document keyed by (project, format, name). Reference it later from /custom/store with "rulesName". Rules are validated on save — every problem is named in the error.

project *stringproject the rules belong to
format *stringformat the rules target
name *stringreference name
rules *objectthe rules document
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "saved": {
    "project": "memory-science",
    "format": "csv",
    "name": "studies"
  }
}
▶ TRY ITPOST https://www.engli.org/api/v1/rules
set your API key above to send
GET/v1/rules· List / fetch rule sets

With no query, lists your saved rule sets (optionally filtered by project). With project + format + name, returns that one rule set's document.

No parameters.

example response
{
  "ok": true,
  "request_id": 184467440737,
  "rules": [
    {
      "project": "memory-science",
      "format": "csv",
      "name": "studies",
      "updated_at": "2026-07-23T10:12:00Z"
    }
  ]
}
▶ TRY ITGET https://www.engli.org/api/v1/rules
set your API key above to send

Callbacks

Signed, event-driven deliveries.

POST/v1/callback· Register a callback

Register one HTTPS endpoint that Memoception POSTs signed events to. Returns your signing secret ONCE and immediately sends a signed callback.test delivery. Subscribe to ["*"] (default) to receive event types added later.

url *stringhttps:// endpoint that receives events
eventsstring[]event filter (default ["*"] = all)
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "callback": {
    "url": "https://your-app.com/webhooks/memoception",
    "events": [
      "memory.stored",
      "custom_store.completed"
    ]
  },
  "secret": "whsec_… (shown once)",
  "note": "the signing secret is shown ONCE — verify deliveries via x-memoception-signature (HMAC-SHA256 of the raw body)"
}
▶ TRY ITPOST https://www.engli.org/api/v1/callback
set your API key above to send
GET/v1/callback· Inspect the callback

See your currently registered callback URL and event filter. The secret is returned truncated — the full value is only shown once at registration.

No parameters.

example response
{
  "ok": true,
  "request_id": 184467440737,
  "callback": {
    "url": "https://your-app.com/webhooks/memoception",
    "events": [
      "memory.stored",
      "custom_store.completed"
    ],
    "secret": "whsec_9f3ac1…"
  }
}
▶ TRY ITGET https://www.engli.org/api/v1/callback
set your API key above to send
DELETE/v1/callback· Remove the callback

Stop all event deliveries and forget the registration.

No parameters.

example response
{
  "ok": true,
  "request_id": 184467440737,
  "callback": null
}
▶ TRY ITDELETE https://www.engli.org/api/v1/callback
set your API key above to send

History & usage

Every call's request_id, output, and metrics.

GET/v1/history· Call history & usage

Your API call history — each request_id with its method, path, status, timestamp, and stored output. Default returns up to 100 recent records WITH outputs; full=1 returns up to 1000 ids + metadata only (no outputs). Page backward with before=<request_id> (use the nextBefore cursor). Add summary=1 for usage metrics (total, ok, errors, last 24h, busiest paths). Powers the in-app API Console.

limitnumberrecords to return (default 50, max 100; max 1000 with full=1)
beforenumberreturn records with request_id < this (paging cursor)
fullbooleanids + metadata only, no outputs (up to 1000)
pathstringfilter to paths containing this substring
statusnumberfilter to an exact HTTP status
summarybooleaninclude the usage-metrics summary block
* required
example response
{
  "ok": true,
  "request_id": 184467440737,
  "history": [
    {
      "request_id": 184467440736,
      "method": "POST",
      "path": "memory/store",
      "status": 200,
      "output": "{\"ok\":true,\"request_id\":…,\"result\":\"Stored memory #128 …\"}",
      "created_at": "2026-07-23T10:20:31Z"
    },
    {
      "request_id": 184467440735,
      "method": "POST",
      "path": "memory/recall",
      "status": 200,
      "output": "{\"ok\":true,…,\"memories\":[…]}",
      "created_at": "2026-07-23T10:20:12Z"
    }
  ],
  "count": 2,
  "nextBefore": 184467440735,
  "summary": {
    "total": 1284,
    "ok": 1270,
    "errors": 14,
    "last24h": 96,
    "firstRequestId": 184467440737,
    "lastRequestId": 184467440737,
    "byPath": [
      {
        "path": "memory/recall",
        "count": 812
      },
      {
        "path": "memory/store",
        "count": 402
      }
    ]
  }
}
▶ TRY ITGET https://www.engli.org/api/v1/history
set your API key above to send

The rules standard

One standardized rules document works across every format — each format declares which split units it supports, and the rest of the standard behaves identically everywhere. Invalid rules are rejected with every problem named.

{
  "split": "row",                                 // unit of memory — one of the format's splits
  "keep": ["id", "user.name", "status"],          // whitelist of dot-paths (omit = keep everything)
  "drop": ["internal_notes"],                     // paths removed before storing
  "redact": ["user.email"],                       // paths masked to ••• (kept, but unreadable)
  "map": {                                        // correspondences: record path → memory field
    "title": "user.name",
    "kind": "record_type",
    "time": "created_at",
    "tags": ["status", "region"]
  },
  "weights": {                                    // importance (memory strength 1–10)
    "status=critical": 5,                         //   exact-match form: path=value
    "assignee": 1                                 //   presence form: path is set & truthy
  },
  "tagRules": [                                   // conditional tagging
    { "path": "status", "equals": "critical", "tag": "urgent" },
    { "path": "title",  "contains": "auth",   "tag": "security" }
  ],
  "link": { "from": "src", "to": "dst", "label": "flows" },   // emit graph edges
  "time": { "path": "ts", "bucket": "day" },      // temporal bucketing (series formats)
  "titleTemplate": "{user.name} — {status}",      // used when map.title is absent
  "limit": 500                                    // max units stored (cap 2000)
}

Supported formats

POST /v1/custom/store runs your data through two pipelines at once — the traditional chunker (so plain recall finds it) and a format-specific pipeline that understands the structure. GET /v1/formatsreturns this list machine-readably.

json · geojsonrecord · key · documentdocuments & arrays — one memory per element, per top-level key, or per document; GeoJSON splits on features
jsonl · ndjsonrecordone JSON object per line; non-JSON lines are skipped
toonrecord · documentToken-Oriented Object Notation — tabular arrays become one memory per row
yaml · ymlkey · record · documentnested maps & lists via indentation (documented subset: no anchors/multiline strings)
tomlsection · documentone memory per [table] / [[array-of-tables]] section
csv / tsvrowheader + quoted rows; cells are typed (numbers, booleans)
xmlelement · documentthe most-repeated element becomes the record shape — attributes & simple children become fields
markdown · mdblock · documentone memory per heading-delimited section
sql · postgres · mysql · sqliterow · statementquote-aware statement split; INSERT … VALUES expands into one memory per row
mongodb · mongodocumentextended JSON ($oid/$date/$number* unwrapped), JSON/JSONL, or db.coll.insertOne/Many(...) shell dumps
timescaledb · timescalerow · buckettime-series rows (JSON/JSONL/CSV, time column auto-detected); bucket aggregates numeric fields per hour/day/month
neo4j · cyphergraph · statement(n:Label {props}) nodes and -[:REL]-> relations become node memories + edge tags the graph shape reads back
leandeclarationone memory per theorem / lemma / def / structure / … declaration
tslnblocktyped line notation — blocks of `key: value` / `name :: Type = value` lines
graphql · gqldefinitiontype / input / enum / query / mutation / fragment definitions with their field lists
logfmtlinekey=value log lines

Parquet, Avro, Protobuf, EDN, and RDF/Turtle are on the roadmap — request a format.

Recall shapes

POST /v1/custom/recall filters (format / name / tags / kind / time range), optionally ranks semantically with query, then returns one of five shapes:

memoriesthe default — full memories with tags, weight, and score
recordseach memory's structured payload parsed back into an object
table{ columns, rows } — the union of record keys as a spreadsheet
series{ points } — time-ordered, numeric fields only; built from time buckets
graph{ nodes, edges } — edges recovered from rel:… tags (Neo4j pipelines, link rules)

Callbacks

Register one HTTPS endpoint per account and Memoception POSTs it signed events — the backbone for event-driven flows: sync pipelines, cache invalidation, downstream tool calls, audit trails.

callback.testsent immediately on registration so you can verify your handler
memory.storedafter /memory/store, /memory/ingest, or /memory/history writes
memory.forgottenafter /memory/forget deletes
memory.updatedafter PATCH /memory/:id changes a memory
custom_store.completedafter a custom pipeline finishes — includes content_id, format ids, stored count
summarize.completedafter /summarize distills a project
organize.completedafter /organize or /store-organize builds a relational/graph view
research.completedafter /research finishes — includes subject, reached/failed counts, memories created

Every delivery carries x-memoception-event and x-memoception-signature — the HMAC-SHA256 of the raw body with your secret. Verify before trusting; deliveries are at-least-once with a 3-second timeout, so make handlers idempotent (dedupe on request_id + event). Subscribe to ["*"] to receive event types added later.

// node — express receiver
import { createHmac, timingSafeEqual } from "crypto";
app.post("/webhooks/memoception", express.raw({ type: "*/*" }), (req, res) => {
  const sig = createHmac("sha256", process.env.MEMO_WHSEC).update(req.body).digest("hex");
  const given = req.headers["x-memoception-signature"] ?? "";
  if (sig.length !== given.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(given))) return res.sendStatus(401);
  const { event, data, request_id } = JSON.parse(req.body);   // route on event, correlate on request_id
  res.sendStatus(200);
});

Errors

400  { ok: false, request_id, error }   // named, actionable — e.g. every invalid rule listed
401  missing / unknown key                429  rate limit exceeded (retry-after header)
404  unknown endpoint or resource         5xx  transient — retry with backoff

Python

A tiny wrapper over requests is all you need — every call is a JSON POST (or GET) to the base URL.

import os, requests

class Memoception:
    def __init__(self, key=None, base="https://www.engli.org/api/v1"):
        self.base, self.h = base, {"x-api-key": key or os.environ["MEMOCEPTION_KEY"]}

    def _post(self, path, **body):
        r = requests.post(self.base + path, headers=self.h, json=body, timeout=60).json()
        if not r.get("ok"):
            raise RuntimeError(f"{path}: {r.get('error')}  (request_id={r.get('request_id')})")
        return r

    def _get(self, path, **params):
        return requests.get(self.base + path, headers=self.h, params=params, timeout=60).json()

    # --- core memory ---
    def store(self, content, project="default", **kw):  return self._post("/memory/store", content=content, project=project, **kw)
    def recall(self, query, project=None, limit=6):     return self._post("/memory/recall", query=query, project=project, limit=limit)["memories"]
    def ingest(self, filename, content, project="default"): return self._post("/memory/ingest", filename=filename, content=content, project=project)
    def add_link(self, url, project="default"):         return self._post("/memory/link", url=url, project=project)
    def index_history(self, project, entries):          return self._post("/memory/history", project=project, entries=entries)
    def forget(self, **filt):                           return self._post("/memory/forget", **filt)
    def consolidate(self, project, threshold=0.9):      return self._post("/memory/consolidate", project=project, threshold=threshold)
    def projects(self):                                 return self._get("/projects")["projects"]

    # --- custom pipelines ---
    def custom_store(self, format, content, project="default", rules=None, name=None, traditional=True):
        return self._post("/custom/store", format=format, content=content, project=project, rules=rules, name=name, traditional=traditional)
    def custom_recall(self, project=None, shape="memories", **filt):
        return self._post("/custom/recall", project=project, shape=shape, **filt)["data"]
    def save_rules(self, project, format, name, rules):
        return self._post("/rules", project=project, format=format, name=name, rules=rules)

    # --- callbacks ---
    def set_callback(self, url, events=("*",)):          return self._post("/callback", url=url, events=list(events))


m = Memoception()

# 1) store a decision and recall it
m.store("We chose Postgres for the event store.", project="my-app", kind="decision")
for hit in m.recall("which database for events?", project="my-app"):
    print(round(hit["score"], 2), hit["title"])

# 2) build a research corpus from a list of URLs (scraped server-side)
for url in open("sources.txt"):
    url = url.strip()
    if url and not url.startswith("#"):
        m.add_link(url, project="memory-science")

# 3) index distilled reading notes in one call (incremental — re-runs add only what's new)
m.index_history("memory-science", entries=[
    {"title": "Fact: Tulving 1972", "content": "Endel Tulving distinguished episodic from semantic memory.", "kind": "fact"},
    {"title": "Fact: H.M. and the hippocampus", "content": "Patient H.M. (Milner, 1953) localized episodic encoding to the hippocampus.", "kind": "fact"},
])

# 4) structured data → the custom pipeline, then read it back as a table
m.custom_store("csv", "year,author,finding\n1885,Ebbinghaus,forgetting curve",
               project="memory-science", name="studies",
               rules={"map": {"title": "finding"}, "weights": {"year": 1}})
table = m.custom_recall(project="memory-science", format="csv", name="studies", shape="table")
print(table["columns"], table["rows"])

# 5) a knowledge graph via Cypher, read back as nodes + edges
m.custom_store("neo4j", "CREATE (a:Concept {name:'episodic'})-[:PART_OF]->(b:Concept {name:'declarative'})",
               project="memory-science", name="ontology")
graph = m.custom_recall(project="memory-science", format="neo4j", name="ontology", shape="graph")
print(graph["nodes"], graph["edges"])

# 6) export the project as fine-tuning JSONL
jsonl = m._post("/memory/export", project="memory-science", format="chatml")["result"]
open("memory-science.jsonl", "w").write(jsonl)

Handle rate limits by retrying on 429 after the retry-after header; the API allows 20 requests/second per key, so a small time.sleep(0.06) between tight-loop writes keeps you well under it.

JavaScript / TypeScript

Works in Node ≥ 18 and the browser (CORS is open) with the built-in fetch — no dependencies.

const BASE = "https://www.engli.org/api/v1";

class Memoception {
  constructor(key = process.env.MEMOCEPTION_KEY, base = BASE) { this.key = key; this.base = base; }

  async #call(method, path, body) {
    const res = await fetch(this.base + path, {
      method,
      headers: { "x-api-key": this.key, ...(body ? { "content-type": "application/json" } : {}) },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (res.status === 429) {                        // respect the 20 req/s limiter
      await new Promise((r) => setTimeout(r, (Number(res.headers.get("retry-after")) || 1) * 1000));
      return this.#call(method, path, body);
    }
    const data = await res.json();
    if (!data.ok) throw new Error(`${path}: ${data.error} (request_id=${data.request_id})`);
    return data;
  }

  // core memory
  store(content, project = "default", extra = {})   { return this.#call("POST", "/memory/store", { content, project, ...extra }); }
  recall(query, project, limit = 6)                 { return this.#call("POST", "/memory/recall", { query, project, limit }).then((d) => d.memories); }
  ingest(filename, content, project = "default")    { return this.#call("POST", "/memory/ingest", { filename, content, project }); }
  addLink(url, project = "default")                 { return this.#call("POST", "/memory/link", { url, project }); }
  indexHistory(project, entries)                    { return this.#call("POST", "/memory/history", { project, entries }); }
  forget(filter)                                    { return this.#call("POST", "/memory/forget", filter); }
  consolidate(project, threshold = 0.9)             { return this.#call("POST", "/memory/consolidate", { project, threshold }); }
  projects()                                        { return this.#call("GET", "/projects").then((d) => d.projects); }

  // custom pipelines
  customStore(format, content, opts = {})           { return this.#call("POST", "/custom/store", { format, content, ...opts }); }
  customRecall(opts)                                { return this.#call("POST", "/custom/recall", opts).then((d) => d.data); }
  saveRules(project, format, name, rules)           { return this.#call("POST", "/rules", { project, format, name, rules }); }

  // callbacks
  setCallback(url, events = ["*"])                  { return this.#call("POST", "/callback", { url, events }); }
}

const m = new Memoception();

// 1) store + recall
await m.store("User prefers dark mode.", "app", { kind: "preference" });
const hits = await m.recall("ui preferences", "app");
console.log(hits.map((h) => [h.score, h.title]));

// 2) research corpus from URLs (scraped server-side)
const urls = (await (await fetch("https://example.com/sources.txt")).text()).split("\n").filter(Boolean);
for (const url of urls) await m.addLink(url, "memory-science");

// 3) index distilled notes in one call
await m.indexHistory("memory-science", [
  { title: "Fact: Tulving 1972", content: "Endel Tulving distinguished episodic from semantic memory.", kind: "fact" },
  { title: "Fact: mental time travel", content: "Suddendorf & Corballis (2007) framed episodic memory as mental time travel.", kind: "fact", tags: ["evolution"] },
]);

// 4) custom pipeline: save reusable rules, then store CSV against them
await m.saveRules("memory-science", "csv", "studies", { map: { title: "finding" }, tagRules: [{ path: "author", contains: "Milner", tag: "hippocampus" }] });
await m.customStore("csv", "year,author,finding\n1953,Milner,patient H.M.", { project: "memory-science", name: "landmark", rulesName: "studies" });

// 5) read it back as a spreadsheet, and as a time series
const table  = await m.customRecall({ project: "memory-science", format: "csv", shape: "table" });
const series = await m.customRecall({ project: "metrics", format: "timescaledb", shape: "series", since: "2026-07-01" });
console.log(table.columns, series.points);

// 6) event-driven: register a signed callback
const { secret } = await m.setCallback("https://your-app.com/webhooks/memoception", ["memory.stored", "custom_store.completed"]);
console.log("store this signing secret:", secret);

Need a format, a higher rate limit, or a custom integration? Book a call — Enterprise plans remove the ceilings.