REST API

API Reference

All endpoints accept and return JSON. Base URL: https://memory.wiki

Rate limit: 10 requests/min per IP. Max document size: 500KB.

POST/api/docs

Create a new document. Returns document ID, edit token, and creation timestamp.

Parameters

markdownREQUIREDstringThe Markdown content of the document.
titlestringDocument title. If not provided, extracted from the first heading.
isDraftbooleanDraft status. Default: false. Draft documents are only visible to the owner.
sourcestringSource identifier: api, web, vscode, mcp, cli, or github:<owner>/<repo> for GitHub imports.
editModestringWho can edit: owner (default for signed-in users), token (anyone with editToken), view (read-only for everyone but the owner).
folderIdstringPlace the document in a specific folder.

Request - curl

bash
curl -X POST https://memory.wiki/api/docs \
  -H "Content-Type: application/json" \
  -d '{
    "markdown": "# Hello World\nThis is my document.",
    "title": "My Document",
    "isDraft": false
  }'

Request - JavaScript

javascript
const res = await fetch("https://memory.wiki/api/docs", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    markdown: "# Hello World\nThis is my document.",
    title: "My Document",
    isDraft: false,
  }),
});
const data = await res.json();

Request - Python

python
import requests

res = requests.post("https://memory.wiki/api/docs", json={
    "markdown": "# Hello World\nThis is my document.",
    "title": "My Document",
    "isDraft": False,
})
data = res.json()

Response 200

json
{
  "id": "abc123",
  "editToken": "tok_aBcDeFgHiJkLmNoP",
  "created_at": "2026-04-15T00:00:00Z"
}
GET/api/docs/{id}

Read a document by ID. Draft documents and email-restricted shares require owner or invitee authentication.

Headers (optional)

x-user-idstringUser UUID for ownership verification.
x-user-emailstringUser email for identification (matches against the doc's allowed_emails / allowed_editors).
AuthorizationstringBearer token for OAuth-authenticated requests.

Request - curl

bash
curl https://memory.wiki/api/docs/abc123

Request - JavaScript

javascript
const res = await fetch("https://memory.wiki/api/docs/abc123");
const doc = await res.json();

Request - Python

python
import requests

res = requests.get("https://memory.wiki/api/docs/abc123")
doc = res.json()

Response 200

json
{
  "id": "abc123",
  "title": "My Document",
  "markdown": "# Hello World\nThis is my document.",
  "created_at": "2026-04-15T00:00:00Z",
  "updated_at": "2026-04-15T01:00:00Z",
  "view_count": 42,
  "is_draft": false,
  "editMode": "owner",
  "isOwner": true,
  "editToken": "tok_..."
}
PATCH/api/docs/{id}

Update a document. Requires edit token or owner authentication. Supports multiple actions: update content, soft-delete, rotate-token, change-edit-mode.

Parameters

editTokenREQUIREDstringThe edit token returned at creation (required for token mode).
markdownstringNew Markdown content.
titlestringNew document title.
isDraftbooleanToggle between draft and published state.
actionstringSpecial action: soft-delete, rotate-token.
changeSummarystringVersion note describing the change.
editModestringChange edit mode: owner, token, or view.

Request - curl (update content)

bash
curl -X PATCH https://memory.wiki/api/docs/abc123 \
  -H "Content-Type: application/json" \
  -d '{
    "editToken": "tok_aBcDeFgH",
    "markdown": "# Updated Content",
    "changeSummary": "Fixed typos"
  }'

Request - curl (soft delete)

bash
curl -X PATCH https://memory.wiki/api/docs/abc123 \
  -H "Content-Type: application/json" \
  -d '{
    "editToken": "tok_aBcDeFgH",
    "action": "soft-delete"
  }'

Request - JavaScript

javascript
const res = await fetch("https://memory.wiki/api/docs/abc123", {
  method: "PATCH",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    editToken: "tok_aBcDeFgH",
    markdown: "# Updated Content",
  }),
});

Request - Python

python
import requests

res = requests.patch("https://memory.wiki/api/docs/abc123", json={
    "editToken": "tok_aBcDeFgH",
    "markdown": "# Updated Content",
})

Response 200

json
{
  "success": true,
  "id": "abc123",
  "updated_at": "2026-04-15T02:00:00Z"
}
HEAD/api/docs/{id}

Check when a document was last updated. Returns x-updated-at response header. Useful for sync polling without downloading full content.

Request - curl

bash
curl -I https://memory.wiki/api/docs/abc123

# Response headers:
# x-updated-at: 2026-04-15T01:00:00Z
# x-content-length: 1234

Request - JavaScript

javascript
const res = await fetch("https://memory.wiki/api/docs/abc123", {
  method: "HEAD",
});
const updatedAt = res.headers.get("x-updated-at");

Request - Python

python
import requests

res = requests.head("https://memory.wiki/api/docs/abc123")
updated_at = res.headers["x-updated-at"]
GET/api/user/documents

List all documents owned by a user. Requires user identification via header.

Headers

x-user-idREQUIREDstringUser UUID. Alternatively use x-user-email or Authorization: Bearer.

Request - curl

bash
curl https://memory.wiki/api/user/documents \
  -H "x-user-id: user-uuid-here"

Request - JavaScript

javascript
const res = await fetch("https://memory.wiki/api/user/documents", {
  headers: { "x-user-id": "user-uuid-here" },
});
const { documents } = await res.json();

Request - Python

python
import requests

res = requests.get("https://memory.wiki/api/user/documents",
    headers={"x-user-id": "user-uuid-here"})
documents = res.json()["documents"]

Response 200

json
{
  "documents": [
    {
      "id": "abc123",
      "title": "My Document",
      "created_at": "2026-04-15T00:00:00Z",
      "updated_at": "2026-04-15T01:00:00Z",
      "is_draft": false,
      "view_count": 42
    }
  ]
}
POST/api/upload

Upload an image file. Returns a public URL. Accepts multipart form-data with a file field.

Request - curl

bash
curl -X POST https://memory.wiki/api/upload \
  -F "file=@screenshot.png"

Request - JavaScript

javascript
const form = new FormData();
form.append("file", fileBlob, "screenshot.png");

const res = await fetch("https://memory.wiki/api/upload", {
  method: "POST",
  body: form,
});
const { url } = await res.json();

Request - Python

python
import requests

with open("screenshot.png", "rb") as f:
    res = requests.post("https://memory.wiki/api/upload",
        files={"file": f})
url = res.json()["url"]

Response 200

json
{
  "url": "https://storage.memory.wiki/uploads/screenshot.png"
}
POST/api/import/github

Import every .md file from a GitHub repo, folder, or single file. Caps: 80 files, 200KB per file. Creates one doc per file plus a bundle that groups them.

Parameters

urlREQUIREDstringGitHub URL — repo home, /tree/branch/path, /blob/branch/path, or raw.githubusercontent.com link.

Request - curl

bash
curl -X POST https://memory.wiki/api/import/github \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $JWT" \
  -d '{ "url": "https://github.com/owner/repo/tree/main/docs" }'

Response 200

json
{
  "imported": 12,
  "skipped": 0,
  "deduplicated": 2,
  "failed": 0,
  "docs": [
    {
      "id": "abc123",
      "title": "README",
      "path": "README.md",
      "sourceUrl": "https://github.com/owner/repo/blob/main/README.md"
    }
  ]
}
POST/api/import/obsidian

Import every .md file from an Obsidian vault uploaded as a .zip (multipart/form-data, file field). Caps: 10MB zipped, 80 files, 200KB per file. Skips dot-prefixed folders (.obsidian, .git, …) and macOS resource forks.

Multipart fields

fileREQUIREDfileA .zip containing your vault. The file name (minus extension) is recorded as the vault label.

Request - curl

bash
curl -X POST https://memory.wiki/api/import/obsidian \
  -H "Authorization: Bearer $JWT" \
  -F "file=@MyVault.zip"

Response 200

json
{
  "imported": 12,
  "skipped": 0,
  "deduplicated": 2,
  "failed": 0,
  "docs": [
    {
      "id": "abc123",
      "title": "Daily Note",
      "path": "notes/Daily Note.md"
    }
  ]
}
POST/api/import/notion

Import a single Notion page using the caller's internal integration token. v1 — no OAuth; the user pastes the token per call. Same dedup contract as docs/PDF/GitHub/Obsidian so re-calling is idempotent.

Parameters

tokenREQUIREDstringNotion internal integration token (secret_… or ntn_…). The integration must have access to the page.
pageUrlstringNotion page URL — anything from notion.so/… with a UUID in it.
pageIdstringAlternative to pageUrl. Hyphenated UUID or bare 32-char id.

Request - curl

bash
curl -X POST https://memory.wiki/api/import/notion \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $JWT" \
  -d '{
    "token": "secret_xxx",
    "pageUrl": "https://www.notion.so/My-Page-abcdef0123456789abcdef0123456789"
  }'

Response 200

json
{
  "imported": 1,
  "deduplicated": 0,
  "failed": 0,
  "docs": [
    {
      "id": "abc123",
      "title": "My Page",
      "notionPageId": "abcdef01-2345-6789-abcd-ef0123456789",
      "pageUrl": "https://www.notion.so/My-Page-abcdef0123456789abcdef0123456789"
    }
  ]
}
POST/api/hub/{slug}/recall

Hybrid retrieval over the public docs in a hub. Combines vector + keyword search; pass rerank: true to reorder candidates with a Haiku-based cross-encoder for higher precision.

Parameters

queryREQUIREDstringThe natural-language question or search phrase.
knumberNumber of results to return. Default 8.
rerankbooleanWhen true, fetch k * 4 candidates first then rerank with Anthropic Haiku. Slower but more precise.

Request - curl

bash
curl -X POST https://memory.wiki/api/hub/your-slug/recall \
  -H "Content-Type: application/json" \
  -d '{ "query": "how do bundles work?", "k": 6, "rerank": true }'

Response 200

json
{
  "matches": [
    {
      "doc_id": "abc123",
      "title": "Bundles overview",
      "passage": "A bundle groups related docs into…",
      "score": 0.84
    }
  ],
  "meta": { "reranked": true }
}

Raw + /llms.txt

Every public Memory.Wiki URL also exposes a clean-markdown variant for AI agents. Append ?compact or ?digest to cut tokens — the answer is the same; the bill is smaller.

/raw/{id}GETPlain markdown for a single document.
/raw/b/{bundleId}GETBundle digest: frontmatter + the canvas analysis (themes, insights, concept sub-graph) + a numbered link list of member docs. Add ?full=1 to inline every doc body, ?graph=0 to drop the analysis. See Bundle URL anatomy below.
/raw/hub/{slug}GETWhole-hub markdown. ?digest=1 returns a concept-clustered summary.
/raw/hub/{slug}/c/{concept}GETPer-concept passage page across all docs in the hub.
/hub/{slug}/llms.txtGETManifest the agent can fetch first to understand what's available.
/hub/{slug}/llms-full.txtGETDense whole-hub bundle (default 80k tokens, override with ?cap=).

Bundle URL anatomy

Pasting a bundle URL (memory.wiki/b/{id}) into Claude, ChatGPT, or Cursor returns more than a doc list. The default response is a digest that also carries the canvas's cross-document analysis — themes, insights, gaps, takeaways, notable connections, and a concept sub-graph — so the consuming AI inherits the prior AI's work instead of redoing it.

Vector embeddings stay server-side (they're numeric and unusable to an LLM). What ships over the wire is the textual output the analysis already produced.

Response shape

markdown
---
mw_bundle: 1
id: <bundleId>
title: "..."
url: https://memory.wiki/b/<id>
document_count: N
updated: <ISO>
analysis_generated_at: <ISO>   # present when the canvas has run
analysis_stale: true           # only when a member doc was edited after that run
source: "Memory.Wiki"
---

# <Bundle title>
> <description>
**Intent:** <intent>

> ⚠ _Analysis may be stale — re-run the canvas to refresh._   (stale only)

## Summary
<canvas summary>

## Themes
- ...

## Cross-document insights
- ...

## Key takeaways
- ...

## Open questions / gaps
- ...

## Notable connections
- **doc A title** ↔ **doc B title** — <relationship>

## Concepts (this bundle)
- **concept label** (from **doc title**)

## Concept relations
- **conceptA** ↔ **conceptB** — <edge label>

1. [Doc 1 title](https://memory.wiki/<docId>) — annotation
2. [Doc 2 title](https://memory.wiki/<docId>) — annotation

Query parameters

fullboolean?full=1 inlines every member doc's full markdown body after the doc-list section. Default (omitted) returns the link list only — the AI follows links on demand.
graphboolean?graph=0 (also false / off) drops the canvas-analysis sections and returns the legacy doc-list-only digest. Useful when the analysis is heavy or the caller only needs the inventory.
compactboolean?compact strips redundant whitespace and trims long quoted blocks across the whole payload. Combines with full and graph.

Two ontology layers

Memory.Wiki ships two distinct ontology layers, and each lives on its own URL:

  • concept_index — hub-wide. Concept labels + the docs each appears in. Already embedded in /raw/hub/{slug}.
  • ai_graph — bundle-scoped. Themes, insights, gaps, connections, and a concept sub-graph produced by the canvas. Embedded in /raw/b/{id} as of this release.

Staleness model

The canvas analysis is a snapshot. If any member doc's updated_at is later thangraph_generated_at, the response sets analysis_stale: true in frontmatter and prepends a warning blockquote so the consuming AI can weight the analysis appropriately.

Authentication

Memory.Wiki uses progressive authentication. Basic operations require no auth. Advanced features use edit tokens or user identity.

No auth required

POST /api/docs and GET /api/docs/{id} work without authentication. The returned editToken is your proof of ownership.

Edit tokens

Every document gets an editToken at creation. Include it in PATCH requests to update or delete. MCP server and CLI manage tokens automatically.

User identity

For user-scoped actions (list, ownership), provide x-user-id header, x-user-email header, or Authorization: Bearer JWT token.

MCP / CLI auth

Both MCP server and CLI use JWT from Memory.Wiki login. Run: npm install -g memory-wiki-cli && Memory.Wiki login

Rate Limits

All endpoints are rate-limited to 10 requests per minute per IP. Exceeding the limit returns 429 Too Many Requests. The response includes Retry-After header indicating seconds to wait. Maximum document size is 500KB.

Errors

400errorBad Request. Missing required fields or invalid parameters.
401errorUnauthorized. Invalid or missing edit token / credentials.
403errorForbidden. Insufficient permissions for this resource.
404errorNot Found. Document does not exist or has been deleted.
409errorConflict. Anti-template guard refused the write (would overwrite real content with boilerplate).
429errorToo Many Requests. Rate limit exceeded.
500errorInternal Server Error. Please retry or contact support.

Error Response Format

json
{
  "error": "Document not found",
  "status": 404
}