Back

API & MCP docs

Last updated: October 27, 2026

Memex has a REST API for your own scripts and an MCP server for AI assistants such as Claude and Cursor. Both work on the same notes as the app, with the same permissions: a token can only see and change the notes of the account that created it.

Getting started

1. Turn on API access

API access is off by default. Open Settings, find the API & MCP section and switch on API access. While it is off, every request with a token or MCP sign-in is refused with 403 api_access_disabled, even if the token is valid. Turning it off again cuts off all your tokens and MCP clients at once without revoking them.

2. Create a token

In the same section, choose New token, give it a name you will recognise later (“Backup script”, “Cursor”) and pick what it may do:

  • Read notes (vault:read): list, read and search notes, tags, folders and version history.
  • Edit notes (vault:write): create, edit, rename, move, delete and restore notes.

A token can have one scope or both; Read notes is selected by default. Give each script only what it needs: a backup job needs read, not write. Pick an expiry of 30 days, 90 days, 1 year, or never.

The token starts with mx_ and is shown once, right after you create it. Memex stores only a hash, so it can’t show it again. Copy it into your password manager or secret store. If you lose it, revoke it and create a new one. The token list shows each token’s first characters, scopes, expiry and when it was last used.

Treat a token like a password: anyone holding a write token can edit your notes. Revoke it in Settings the moment it leaks.

3. Make a request

Send the token in the Authorization header on every request. The base URL is https://memex-pied.vercel.app/api/v1. Browser session cookies are not accepted here, and tokens are not accepted as query parameters.

Request
curl https://memex-pied.vercel.app/api/v1/notes \
  -H "Authorization: Bearer mx_your_token_here"
Response 200
{
  "data": [
    {
      "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
      "title": "Meeting notes",
      "path": "projects/memex/meeting_notes.md",
      "folder": "Projects/Memex",
      "version": 4,
      "created_at": "2026-10-20T09:12:03.000Z",
      "updated_at": "2026-10-27T08:30:11.000Z",
      "deleted_at": null
    }
  ],
  "next_cursor": "eyJrIjoia3MiLCJ1IjoiMjAyNi0xMC0yN1QwODozMDoxMS4wMDBaIn0"
}

The examples below set the token once in your shell, so you can paste them as they are:

export MEMEX_TOKEN=mx_your_token_here

Request bodies are JSON (Content-Type: application/json) and every response is JSON. A machine-readable OpenAPI 3.1 description lives at https://memex-pied.vercel.app/api/v1/openapi.json and needs no token.

Notes, ids and paths

Every note has two handles:

  • id: a UUID that never changes, even when the note is renamed or moved. Store this if you need to find the note again later.
  • path: the folder path and title as a lowercase slug plus .md, for example projects/memex/meeting_notes.md for the note “Meeting notes” in the folder “Projects/Memex”. Handy for humans and scripts, but it changes whenever the title or folder does.

Lookups by path accept the slug with or without .md, and also the display form you see in the app (Projects/Memex/Meeting notes). Slugs drop accents and punctuation and turn spaces into underscores, so a title needs at least one Latin letter or digit.

Renaming or moving a note updates its path and rewrites [[wikilinks]] to it in your other notes. The old path does not redirect: a request for it returns 404. If a create, rename, move or restore would land on a path another note already uses (including two titles that differ only in case or punctuation), the request fails with 409 path_conflict. Nothing is renamed for you.

Locked notes can be read, but every write to them returns 403 note_locked. Unlock them in the app first.

Markdown and content_json

Memex stores each note as structured blocks (the editor’s native format). The API gives you two views of them:

  • content: Markdown generated from the blocks. Always present on a note.
  • content_json: the blocks themselves. Returned only when you add ?include=blocks, and accepted on create and update over REST (not MCP).

Markdown is lossy. Text colours, underline, callout styles and indentation outside lists have no Markdown form and are dropped from content. If you read content, change it and write it back, that formatting is gone from the note. For edits that must keep formatting, read with ?include=blocks and write content_json. Send either content or content_json in one request, not both. Every overwritten version stays in version history.

Concurrent edits

Every note has a version number that goes up by one on each change. Reading a note returns it in the body and as an ETag header ("v4"). To update, tell Memex which version you edited, either with an If-Match: "v4" header or an expected_version field in the body:

  • The version matches: the update is applied and the version becomes 5.
  • Someone changed the note in the meantime (you in the app, the assistant, another script): 409 version_conflict with the current version in details.current_version. Read the note again, re-apply your change and retry.
  • You sent neither: 428 precondition_required. Updates never overwrite blindly.
Response 409
{
  "error": {
    "code": "version_conflict",
    "message": "Note changed since version 4",
    "details": {
      "current_version": 5
    }
  }
}

DELETE checks If-Match only when you send it. Restoring a version always needs it, the same way as an update.

Delete, restore and version history

Deleting a note moves it to the trash; nothing is erased. A deleted note disappears from lists, search, tags, folders and path lookups, but you can still reach it by id to restore it or read its history. Restoring puts it back at its old path, unless another note has taken that path since (409 path_conflict; rename or move the other note first).

Memex keeps a snapshot of the previous content before every API or MCP write and on every delete, from any surface. Edits in the app are snapshotted less often (assistant edits always, manual saves at most every 10 minutes). Restoring a version writes the old content back as a new change; the current content is snapshotted first, so a restore can itself be undone. History is never deleted by a restore.

Pagination

Every list returns the same envelope:

{ "data": [ ... ], "next_cursor": "eyJrIjoia3Mi..." }

Pass next_cursor back as ?cursor= to get the next page. When it is null you have everything. Cursors are opaque: don’t build or edit them. limit sets the page size (1 to 100, default 50 for notes and versions, 20 for search). Tags and folders are not paginated and always return null.

Note lists are ordered by last update, newest first. A note edited while you page through moves to the front, so a long export can skip or repeat it; fetch by id if you need an exact copy.

Fetch the next page
curl "https://memex-pied.vercel.app/api/v1/notes?limit=100&cursor=eyJrIjoia3Mi..." \
  -H "Authorization: Bearer $MEMEX_TOKEN"

Endpoints

All paths are relative to https://memex-pied.vercel.app/api/v1. Endpoints marked vault:read work with a read or a read-write token; vault:write needs a token with Edit notes.

GET/notes

Scope: vault:read

Lists your notes, most recently updated first. Filter with folder (display or slug path) or tag (without the #). Page with limit and cursor. List items carry no content; read a note to get it.

Request
curl "https://memex-pied.vercel.app/api/v1/notes?folder=Projects/Memex&tag=work&limit=20" \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "data": [
    {
      "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
      "title": "Meeting notes",
      "path": "projects/memex/meeting_notes.md",
      "folder": "Projects/Memex",
      "version": 4,
      "created_at": "2026-10-20T09:12:03.000Z",
      "updated_at": "2026-10-27T08:30:11.000Z",
      "deleted_at": null
    }
  ],
  "next_cursor": "eyJrIjoia3MiLCJ1IjoiMjAyNi0xMC0yN1QwODozMDoxMS4wMDBaIn0"
}

POST/notes

Scope: vault:write

Creates a note. title is required and can’t contain /. folder is optional (root when omitted) and missing folders are created. Content is optional, as content (Markdown) or content_json (blocks). Returns 201 with the new note.

Request
curl -X POST https://memex-pied.vercel.app/api/v1/notes \
  -H "Authorization: Bearer $MEMEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Meeting notes", "folder": "Projects/Memex", "content": "# Agenda\n\n- Ship the public API #work\n- [ ] Write the docs"}'
Response 201
{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 1,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-20T09:12:03.000Z",
  "deleted_at": null,
  "content": "# Agenda\n\n- Ship the public API #work\n- [ ] Write the docs",
  "tags": [
    "work"
  ],
  "locked": false
}

GET/notes/{id}

Scope: vault:read

Reads one note with its Markdown content, tags and lock state. The ETag header holds the version for your next update. Add ?include=blocks to also get content_json.

Request
curl -i https://memex-pied.vercel.app/api/v1/notes/0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
ETag: "v4"

{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 4,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-27T08:30:11.000Z",
  "deleted_at": null,
  "content": "# Agenda\n\n- Ship the public API #work\n- [ ] Write the docs",
  "tags": [
    "work"
  ],
  "locked": false
}
With ?include=blocks (excerpt)
{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 4,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-27T08:30:11.000Z",
  "deleted_at": null,
  "content": "...",
  "tags": [
    "work"
  ],
  "locked": false,
  "content_json": [
    {
      "id": "a1",
      "type": "heading",
      "props": {
        "level": 1
      },
      "content": [
        {
          "type": "text",
          "text": "Agenda",
          "styles": {}
        }
      ],
      "children": []
    }
  ]
}

GET/notes/by-path?path=

Scope: vault:read

Same as reading by id, but looks the note up by path. URL-encode the path. Accepts projects/memex/meeting_notes.md, the same without .md, or Projects/Memex/Meeting notes.

Request
curl -G https://memex-pied.vercel.app/api/v1/notes/by-path \
  --data-urlencode "path=Projects/Memex/Meeting notes" \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 4,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-27T08:30:11.000Z",
  "deleted_at": null,
  "content": "# Agenda\n\n- Ship the public API #work\n- [ ] Write the docs",
  "tags": [
    "work"
  ],
  "locked": false
}

PATCH/notes/{id}

Scope: vault:write

Changes content, title, folder, or any mix of them in one step; the version goes up by one. Changing title renames the note and changing folder moves it ("" is the root). Requires If-Match or expected_version; see Concurrent edits.

Request
curl -X PATCH https://memex-pied.vercel.app/api/v1/notes/0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c \
  -H "Authorization: Bearer $MEMEX_TOKEN" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "v4"' \
  -d '{"content": "# Agenda\n\n- Ship the public API #work\n- [x] Write the docs"}'
Response 200
{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 5,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-27T09:02:45.000Z",
  "deleted_at": null,
  "content": "# Agenda\n\n- Ship the public API #work\n- [x] Write the docs",
  "tags": [
    "work"
  ],
  "locked": false
}
Rename and move, version in the body
curl -X PATCH https://memex-pied.vercel.app/api/v1/notes/0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c \
  -H "Authorization: Bearer $MEMEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"expected_version": 5, "title": "Kickoff", "folder": "Archive"}'

DELETE/notes/{id}

Scope: vault:write

Moves the note to the trash (a soft delete) and returns its summary with deleted_at set. If-Match is optional; when sent, it must match.

Request
curl -X DELETE https://memex-pied.vercel.app/api/v1/notes/0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 5,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-27T08:30:11.000Z",
  "deleted_at": "2026-10-27T09:10:00.000Z"
}

POST/notes/{id}/restore

Scope: vault:write

Takes a deleted note out of the trash at its old path and returns it. Fails with 409 path_conflict if another note uses that path now.

Request
curl -X POST https://memex-pied.vercel.app/api/v1/notes/0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c/restore \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 6,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-27T09:02:45.000Z",
  "deleted_at": null,
  "content": "# Agenda\n\n- Ship the public API #work\n- [x] Write the docs",
  "tags": [
    "work"
  ],
  "locked": false
}

GET/notes/{id}/versions

Scope: vault:read

Lists saved snapshots, newest first, without their content. Works on deleted notes too. version is the note version the snapshot captured (null for snapshots taken before the API existed); kind is update, delete or restore; source says what made the change (for example api, mcp, agent, manual).

Request
curl https://memex-pied.vercel.app/api/v1/notes/0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c/versions \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "data": [
    {
      "id": "c41a8e2f-6b7d-4c9e-a1f3-2d4b6e8a0c1f",
      "note_id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
      "version": 4,
      "title": "Meeting notes",
      "kind": "update",
      "source": "api",
      "created_at": "2026-10-27T09:02:45.000Z"
    }
  ],
  "next_cursor": null
}

POST/notes/{id}/versions/{versionId}/restore

Scope: vault:write

Writes a snapshot’s content back as a new change and returns the updated note. Needs If-Match or { "expected_version": n } in the body. Restore a deleted note first.

Request
curl -X POST https://memex-pied.vercel.app/api/v1/notes/0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c/versions/c41a8e2f-6b7d-4c9e-a1f3-2d4b6e8a0c1f/restore \
  -H "Authorization: Bearer $MEMEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"expected_version": 5}'
Response 200
{
  "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
  "title": "Meeting notes",
  "path": "projects/memex/meeting_notes.md",
  "folder": "Projects/Memex",
  "version": 6,
  "created_at": "2026-10-20T09:12:03.000Z",
  "updated_at": "2026-10-27T08:30:11.000Z",
  "deleted_at": null,
  "content": "# Agenda\n\n- Ship the public API #work\n- [ ] Write the docs",
  "tags": [
    "work"
  ],
  "locked": false
}

GET/search?q=

Scope: vault:read

Full-text search over your notes. q uses web-search syntax: "exact phrase", -exclude, this OR that. Results are ranked by relevance and include a short snippet.

Request
curl -G https://memex-pied.vercel.app/api/v1/search \
  --data-urlencode 'q="public api" -draft' \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "data": [
    {
      "id": "0b6f7c1e-4a8d-4f2e-9c3a-5d1e2f3a4b5c",
      "title": "Meeting notes",
      "path": "projects/memex/meeting_notes.md",
      "folder": "Projects/Memex",
      "version": 5,
      "created_at": "2026-10-20T09:12:03.000Z",
      "updated_at": "2026-10-27T09:02:45.000Z",
      "snippet": "Ship the public **API**"
    }
  ],
  "next_cursor": null
}

GET/tags

Scope: vault:read

Every tag on your notes with how many notes use it, most used first.

Request
curl https://memex-pied.vercel.app/api/v1/tags \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "data": [
    {
      "tag": "work",
      "count": 12
    },
    {
      "tag": "ideas",
      "count": 4
    }
  ],
  "next_cursor": null
}

GET/folders

Scope: vault:read

Every folder with its display path, parent and the number of notes in it. Use the path as folder when listing or creating notes.

Request
curl https://memex-pied.vercel.app/api/v1/folders \
  -H "Authorization: Bearer $MEMEX_TOKEN"
Response 200
{
  "data": [
    {
      "id": "7d2e9a40-1b3c-4e5f-8a6b-9c0d1e2f3a4b",
      "title": "Memex",
      "path": "Projects/Memex",
      "parent_id": "3e8f1a2b-4c5d-4e6f-9a0b-1c2d3e4f5a6b",
      "note_count": 7
    }
  ],
  "next_cursor": null
}

GET/openapi.json

No token needed

The OpenAPI 3.1 description of this API, for client generators and API tools.

Request
curl https://memex-pied.vercel.app/api/v1/openapi.json

Errors

Every error has the same shape: a stable code to branch on, a human-readable message that may change, and optional details.

{
  "error": {
    "code": "version_conflict",
    "message": "Note changed since version 4",
    "details": {
      "current_version": 5
    }
  }
}
CodeStatusMeaning
unauthorized401Token missing, malformed, unknown, revoked or expired.
api_access_disabled403API access is switched off in Settings.
insufficient_scope403The token lacks the scope this endpoint or tool needs.
note_locked403The note is locked in the app; unlock it to write.
not_found404No such note, version or path (or it is in the trash).
validation_failed400The request is malformed; details lists each problem.
version_conflict409The note changed since the version you sent.
path_conflict409Another note already uses the resulting path.
precondition_required428An update was sent without If-Match or expected_version.
rate_limited429Too many requests; wait for Retry-After seconds.
internal500Something failed on our side. Safe to retry reads.

Rate limits

Each token (or each MCP client you signed in) gets its own limits:

  • 120 requests per minute in total.
  • 30 writes per minute (create, update, delete, restore).

Windows are fixed minutes, not sliding. Every response tells you where you stand:

HeaderValue
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetWhen the window resets, in Unix seconds.
Retry-AfterOn 429 only: seconds to wait before retrying.

Versioning

The API lives under /api/v1. Within v1 changes are additive only: new endpoints, new optional parameters, new fields in responses. Your client should ignore fields it doesn’t know. Anything that would break an existing client (removing or renaming a field, changing a type or a default) ships as /api/v2, and v1 keeps working alongside it.

MCP: use Memex from Claude, Cursor and other AI tools

Memex runs a remote Model Context Protocol server, so AI assistants can search, read and edit your notes directly. The server URL is:

https://memex-pied.vercel.app/mcp

It uses the Streamable HTTP transport and the same rules as the REST API: API access must be on in Settings, the same scopes and rate limits apply, and the same error codes come back as tool errors.

There are two ways to connect:

  • A token in a header. Create a token in Settings and paste it into the client config. Works in any client that lets you set headers (Claude Code, Cursor) and is always available.
  • OAuth sign-in. The client opens a Memex page where you sign in and approve access; no token to copy. Used by Claude.ai and Claude Desktop. OAuth sign-in for MCP clients works once it is enabled on the Memex server; if the client can’t finish signing in, use a token with a client that supports headers instead.

Claude Code

claude mcp add --transport http memex https://memex-pied.vercel.app/mcp \
  --header "Authorization: Bearer mx_your_token_here"

Run /mcp inside Claude Code to check the connection. Add --scope user to make it available in every project.

Claude.ai and Claude Desktop

  1. Open Settings > Connectors and choose Add custom connector.
  2. Name it “Memex” and paste https://memex-pied.vercel.app/mcp as the URL. Leave the OAuth client fields empty.
  3. Choose Connect, sign in to Memex if asked, and approve access.

An OAuth connection can read and write: the sign-in screen asks for both, because per-scope approval isn’t supported yet. If you want an assistant that can only read, use a token with just Read notes in Claude Code or Cursor. Connected apps are listed in Settings, where you can disconnect them.

Cursor

Add Memex to .cursor/mcp.json in your project, or to ~/.cursor/mcp.json for every project:

{
  "mcpServers": {
    "memex": {
      "url": "https://memex-pied.vercel.app/mcp",
      "headers": {
        "Authorization": "Bearer mx_your_token_here"
      }
    }
  }
}

Keep the token out of version control: add the project file to .gitignore or use the global file.

Other clients

Any MCP client that supports remote Streamable HTTP servers works. Point it at https://memex-pied.vercel.app/mcp and send Authorization: Bearer mx_..., or let it discover OAuth: a request without a token gets a 401 whose WWW-Authenticate header links to the sign-in metadata.

Tools

ToolScopeInputWhat it does
list_notesvault:readpath? (folder), tag?, cursor?List notes, newest first.
read_notevault:readid or pathRead one note as Markdown, with its version.
search_notesvault:readquery, limit?Full-text search.
list_tagsvault:readnoneTags with note counts.
create_notevault:writepath (Folder/Title), content?Create a note.
update_notevault:writeid or path, expected_version, content?, title?Edit a note. Fails if the note changed since expected_version.
move_notevault:writeid, expected_version, folder?, title?Move or rename a note.
delete_notevault:writeid, expected_version?Move a note to the trash. Restorable with restore_note.
restore_notevault:writeidTake a note out of the trash.
list_versionsvault:readid, cursor?Version history of a note.

Deletes from MCP are soft, the same as over REST: delete_note moves the note to the trash and restore_note brings it back, so an assistant can’t erase anything for good. Edits made through MCP are saved in version history with the source mcp, so you can see and undo what an assistant changed. MCP reads and writes Markdown only; see Markdown and content_json for what that drops.

An MCP connection needs a token with Read notes, because a client lists and reads notes to do anything useful; a token with only Edit notes gets 403. With a Read-only token, write tools still appear in the tool list, but calling one returns a tool error with code insufficient_scope, which the assistant can read and explain to you.