# Published Feeds Source: https://docs.dexi.net/api/feeds Read a published Dexi feed as JSON or RSS — no authentication Dexi users can publish curated, tag-filtered feeds of their notes. Feeds shared as **public** or **link-only** are readable by anyone who has the feed's UUID — no credentials; private feeds return `404` (existence is never confirmed). These are consumption endpoints for feed readers and site embeds, not a general read API. ## RSS ``` GET https://api.dexi.net/api/v2/publisher/feeds/{feed_id}/rss ``` Returns standard RSS 2.0 (`application/rss+xml`) — point any feed reader at it. This is the URL shown on a feed's public page at `app.dexi.net/feeds/{feed_id}`. ## JSON ``` GET https://api.dexi.net/api/v2/publisher/feeds/{feed_id}/public ``` ```json Example response theme={null} { "id": "d6a1b8c2-4e5f-4a7b-9c3d-1e2f3a4b5c6d", "title": "Signals in AI Infrastructure", "description": "What I'm reading on the picks-and-shovels layer.", "author": "Julian Gay", "rss_url": "https://api.dexi.net/api/v2/publisher/feeds/d6a1b8c2-4e5f-4a7b-9c3d-1e2f3a4b5c6d/rss", "entries": [ { "id": "2982e9b4-19fe-40fd-9a69-e23d45e53b13", "title": "Attention Is All You Need", "text": "Reading notes on the transformer paper — the key insight is…", "updated": "2026-08-02T18:40:11+00:00" } ] } ``` Entries are the publisher's notes matching the feed's tag filter, newest first. `updated` may be `null` on rare legacy notes. ### Fields | Field | Type | Notes | | - | - | - | | `id` | UUID | The feed id — the same one in the URL | | `title` | string | Feed title as the publisher set it | | `description` | string or `null` | Optional blurb | | `author` | string | Publisher's display name; may be empty | | `rss_url` | URL | The RSS 2.0 rendering of the same feed | | `entries[].id` | UUID | Note id (stable across fetches) | | `entries[].title` | string | `Untitled` when the note has no title | | `entries[].text` | string | Entry body as plain text — formatting, wiki links and tags are flattened | | `entries[].updated` | ISO 8601 or `null` | Last edit time, UTC | ## Access and visibility A feed's publisher chooses one of three access levels in the app. **Public** and **link-only** feeds behave identically on these endpoints — the UUID is the only credential, so treat a link-only URL as a secret. **Private** feeds answer `404` to everyone but the signed-in owner, and the two routes never reveal whether an id exists. Changing a feed's access, or the tag filter behind it, takes effect on the next request; nothing is cached server-side. ## Polling Both routes are rate-limited per IP (120 requests per minute) and return the `RateLimit-*` headers described in [Versioning](/api/versioning). Feed readers that poll every 15–60 minutes are well inside the cap. There are no conditional-request headers (`ETag`, `Last-Modified`) today — compare `entries[].updated` to detect changes, or just re-render the list. ## Embedding The JSON route is not open to cross-origin browser requests from arbitrary sites, so fetch it from your build step or server (a static-site generator, an edge function, a cron job) and render the entries into your page. For a no-code option, hand the RSS URL to any feed-to-HTML widget or newsletter tool that accepts RSS 2.0 — that is what the public share page at `app.dexi.net/feeds/{feed_id}` links to. ## Errors | Status | Meaning | | - | - | | `404` | Unknown feed id, or a private feed you don't own | # API Overview Source: https://docs.dexi.net/api/overview Base URL, authentication, and the shape of Dexi's REST surface ``` https://api.dexi.net ``` Dexi's public REST surface is deliberately small — two endpoint families: | | Auth | Purpose | | - | - | - | | [`POST /api/v1/bookmarks/save`](/api/save-bookmark) | `dxi_` [API token](/authentication) | Save a page into your workspace | | [`GET /api/v2/publisher/feeds/{id}/…`](/api/feeds) | none | Read someone's published feed (JSON or RSS) | Everything else under `api.dexi.net` belongs to the Dexi app's own session-authenticated API; personal API tokens deliberately don't work there. If you want programmatic **read** access to a workspace — search, browse, full note text — that's the [MCP server](/mcp/overview), not REST. For bulk data, the app's export (Settings → Export) produces ZIP/CSV/markdown. ## Conventions * Requests and responses are JSON (`Content-Type: application/json`); the RSS route returns `application/rss+xml`. * Timestamps are ISO 8601 with timezone offsets. IDs are UUIDs. * Authentication uses a bearer header: `Authorization: Bearer dxi_…`. * Validation failures return `422` with FastAPI's standard `detail` array. Missing/invalid credentials return `401`. Resources you can't see return `404` — never `403`. Structured errors carry a machine-readable `detail.code` — see [Versioning & Deprecation](/api/versioning) for the error model. * Responses carry `RateLimit-*` headers; a `429` includes `Retry-After`. Self-throttle from these — details in [Versioning & Deprecation](/api/versioning). * A machine-readable [OpenAPI 3 spec](https://dexi.net/openapi.json) describes this whole surface, typed responses and errors included. # Save Bookmark Source: https://docs.dexi.net/api/save-bookmark POST /api/v1/bookmarks/save Save a page into your Dexi workspace The one endpoint personal API tokens authenticate — the write-only capture path used by the iOS share-sheet Shortcut. The saved page becomes a bookmark-backed note: searchable (full-text and semantic), taggable, and connected like everything else in your workspace. The page URL. Must start with `http://` or `https://` — anything else is rejected with `422`. Page title. Optional; Dexi falls back to the URL when absent. Selected text to save with the page — added to the note body as a quoted highlight (`> `-prefixed lines, rendered as a blockquote). Optional note on the highlight — lands as the paragraph directly below the quoted `selection`. Hashtags typed here become tags on the note. The page's meta description, if you have it. On a first save it seeds the note body (above the source line) as an editable starting point; values longer than 500 characters are truncated. Ignored when the URL is already in your workspace. When omitted, Dexi scrapes the description from the page in the background instead. Explicitly request append behavior when the URL is already saved. Saving a **new URL** creates a bookmark and its note. Saving a URL **already in your workspace** doesn't duplicate it — the existing note is reused, and any `selection` is appended as another quoted highlight (so repeatedly sharing or highlighting the same article accumulates your annotations on one note). ```bash Example request theme={null} curl -X POST https://api.dexi.net/api/v1/bookmarks/save \ -H "Authorization: Bearer dxi_2f7c1a9e4b8d3f6a0c5e9b2d7f4a8c1e3b6d9f2a" \ -H "Content-Type: application/json" \ -d '{ "url": "https://arxiv.org/abs/1706.03762", "title": "Attention Is All You Need", "selection": "The dominant sequence transduction models are based on complex recurrent networks.", "comment": "The opening line of the transformer paper #ml" }' ``` ```json Example response theme={null} { "bookmark": { "id": "7c1f3e58-2ab4-4b6f-9d21-8f4e5c6a7b90", "note_id": "2982e9b4-19fe-40fd-9a69-e23d45e53b13", "title": "Attention Is All You Need", "url": "https://arxiv.org/abs/1706.03762", "icon": "https://api.dexi.net/media/icons/arxiv.org.png", "created": "2026-08-05T14:20:00+00:00", "updated": "2026-08-05T14:20:00+00:00" }, "note_id": "2982e9b4-19fe-40fd-9a69-e23d45e53b13", "is_new_bookmark": true, "selection_appended": false } ``` `icon` is `null` until the favicon fetch completes in the background. ## Errors | Status | Meaning | | - | - | | `401` | Missing, revoked, or malformed token | | `402` | Free-plan note limit reached — machine-readable body below | | `422` | Invalid body (e.g. non-http(s) `url`) | ```json 402 response theme={null} { "detail": { "code": "note_limit", "message": "You've reached the free plan limit of 1,000 notes.", "used": 1000, "limit": 1000 } } ``` ## iOS Shortcut The share-sheet Shortcut is a single **Get Contents of URL** action posting this shape with your token in the `Authorization` header. Step-by-step setup: [Automate capture with API tokens](https://dexi.net/kb/save-with-api-tokens). # Versioning & Deprecation Source: https://docs.dexi.net/api/versioning How Dexi's API is versioned, how deprecations are announced, and how to self-throttle with rate-limit headers ## Versioning The API is versioned in the URL path: `/api/v1/…` and `/api/v2/…`. The version prefix is part of the contract — endpoints documented on this site are stable within their major version: * **No breaking changes within a version.** Fields may be *added* to responses; existing fields keep their names and types. Treat unknown response fields as forward-compatible additions. * **Breaking changes ship as a new path version.** The old version keeps working through its deprecation window. * Undocumented `/api/*` endpoints belong to the Dexi app's own session-authenticated API and carry **no stability promise** — build only against what's documented here and in the [OpenAPI spec](https://dexi.net/openapi.json). ## Deprecation policy When a documented endpoint is scheduled for removal: 1. Its responses gain a `Deprecation` header, and a `Sunset` header ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)) with the removal date — **at least 6 months out**. 2. This page lists the deprecation, its replacement, and a migration note. 3. After the sunset date the endpoint returns `410 Gone` with a JSON `detail` pointing at the replacement. Nothing is currently deprecated. ## Rate limits Rate-limited endpoints return the draft-RFC rate-limit headers ([draft-ietf-httpapi-ratelimit-headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/)) on **every** response, so clients can self-throttle instead of discovering limits by hitting them: | Header | Meaning | | - | - | | `RateLimit-Limit` | Requests allowed in the current window | | `RateLimit-Remaining` | Requests left in the current window | | `RateLimit-Reset` | Seconds until the window resets | | `RateLimit-Policy` | The policy, e.g. `"120;w=60"` — 120 requests per 60-second window | Exceeding a limit returns `429` with a `Retry-After` header (seconds) and a JSON body: ```json theme={null} { "detail": { "code": "rate_limited", "message": "Too many requests — slow down and try again shortly.", "retry_after": 12 } } ``` Wait `Retry-After` seconds before retrying; don't retry in a tight loop. ## Errors All errors are JSON with a top-level `detail` — a plain string on simple errors, or a structured object with a machine-readable `code`, human-readable `message`, and context fields: ```json theme={null} { "detail": { "code": "not_found", "message": "No such API endpoint.", "hint": "The public REST surface is documented at https://docs.dexi.net …" } } ``` Known codes: `not_found`, `rate_limited`, and `note_limit` (see [Save a bookmark](/api/save-bookmark) for the `note_limit` shape). Validation failures return `422` with FastAPI's standard `detail` array. The full error model is typed in the [OpenAPI spec](https://dexi.net/openapi.json). # Authentication Source: https://docs.dexi.net/authentication MCP OAuth connections and personal API tokens Dexi has two credential kinds, for two different jobs. ## MCP connections (OAuth) MCP clients never handle long-lived secrets. When a client first connects to `https://mcp.dexi.net/mcp`, it registers itself, opens your browser to a Dexi consent page, and receives short-lived tokens after you approve — the standard OAuth 2.1 flow, handled automatically by the client. Two scopes exist, and the consent page shows exactly what a client requested: | Scope | Grants | | - | - | | `notes:read` | Search and read your notes | | `notes:write` | Create, edit, and delete notes | Beyond scopes, the consent page lets you restrict a connection to notes in one folder, one tag, or both — see [access restrictions](/mcp/oauth#access-restrictions). Review and disconnect clients any time in **Settings → Connected apps** — revoking kills the client's access immediately, and **Edit access** changes a connection's boundary live. Protocol details for client builders are on the [OAuth page](/mcp/oauth). ## Personal API tokens Tokens are minted in [Settings → API tokens](https://app.dexi.net/dashboard/settings): 44 characters starting with `dxi_`, shown **once** at creation and stored hashed — copy it then or mint a new one. Up to 10 active tokens; revoke any of them from the same screen. ```bash theme={null} curl -X POST https://api.dexi.net/api/v1/bookmarks/save \ -H "Authorization: Bearer dxi_2f7c1a9e4b8d3f6a0c5e9b2d7f4a8c1e3b6d9f2a" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/article"}' ``` A token authenticates **exactly one endpoint**: [`POST /api/v1/bookmarks/save`](/api/save-bookmark). That's deliberate — tokens live in Shortcuts, scripts, and other places that leak, so a leaked token can add pages to your workspace but can never read, change, or delete anything in it. Full data access belongs to MCP connections, which you approve and revoke individually. Everything else under `api.dexi.net` is the Dexi app's own session-authenticated API and isn't usable with tokens. # Introduction Source: https://docs.dexi.net/introduction Connect AI assistants and capture tools to your Dexi workspace Dexi is a lifelong learning system: notes, bookmarks, newsletters, and RSS feeds collected into one searchable workspace. Everything you save becomes a note, so one search — full-text or semantic — covers your whole reading life. This site documents the two ways software connects to Dexi: The primary integration. Claude, ChatGPT, Cursor, and any other MCP client can search, read, and write your notes over an OAuth-secured connection. Full read/write access through eighteen tools. A single write-only REST endpoint for saving pages from anywhere — authenticated with a personal API token. This is what the iOS share-sheet Shortcut uses. A word on scope, so you don't go looking for endpoints that aren't here: Dexi's REST API is deliberately small. Personal API tokens authenticate only the capture endpoint — they cannot read your notes. Full programmatic access to your workspace (search, read, create, edit, review) goes through the [MCP server](/mcp/overview), where each connection is approved in the browser and can be revoked at any time. Your [published feeds](/api/feeds) are readable without any credentials. Keep API tokens and MCP connections to services you trust. A connected MCP client with the write scope can create, edit, and delete notes. ## Base URLs | Surface | URL | | - | - | | REST API | `https://api.dexi.net` | | MCP server | `https://mcp.dexi.net/mcp` | | App | `https://app.dexi.net` | ## Getting help Questions or problems: [dexi.net/support](https://dexi.net/support), or email [team@dexi.net](mailto:team@dexi.net). The [knowledge base](https://dexi.net/kb) covers the product side of everything documented here. # Claude Code plugin Source: https://docs.dexi.net/mcp/claude-code-plugin One-command install for Claude Code and Cowork — the MCP server plus capture, recall, and review skills The **dexi** plugin packages the [Dexi MCP server](/mcp/overview) with three skills, so Claude Code (and Claude Cowork) get the connection and the know-how in one install. It's the recommended way to connect from Claude Code; adding the server by hand with `claude mcp add` works too and gives you the same tools without the skills. Source and issues: [github.com/dexi/claude-plugin](https://github.com/dexi/claude-plugin) (MIT). ## Install ```bash theme={null} claude plugin marketplace add dexi/claude-plugin claude plugin install dexi@dexi ``` Or from inside a session: `/plugin marketplace add dexi/claude-plugin`, then `/plugin install dexi@dexi`. Run `/mcp` and select **dexi** to authenticate — your browser opens Dexi's consent page. Sign in, choose what the connection may access (everything, or one folder / tag), and approve. **Cowork tab → Customize → Plugins → Add from a repository**, enter `https://github.com/dexi/claude-plugin`, then install **Dexi**. The first Dexi tool call triggers the same browser consent flow. ```bash theme={null} git clone https://github.com/dexi/claude-plugin claude --plugin-dir ./claude-plugin ``` ## What you get * **The Dexi MCP server** — all [eighteen tools](/mcp/tools): keyword and semantic search, note create/update/delete, bases (run and manage), tags, folders, saved bases, spaced-repetition reviews. Claude uses them on its own once connected. * **`/dexi:capture`** — saves a finding, decision, or snippet from the current session as a Dexi note: distilled rather than dumped, a specific noun-phrase title, one to three of your existing `#hashtags` (it checks `list_tags` first), `[[Wiki Links]]` where a related note exists. Claude also invokes it itself when you say "save that to my notes". * **`/dexi:recall`** — runs `search_notes` *and* `semantic_search` (they surface different notes), reads the bodies that matter (`full_text: true` or `get_note`), uses `list_tags` / `list_folders` + filtered `list_notes` when the question maps to how you organize things, and answers with note titles linked to `app.dexi.net`. Also model-invoked: "did I save anything about X?" * **`/dexi:review`** — a spaced-repetition session over your due cards: question first (title only), then reveal, then your self-assessment mapped to a 1–4 grade via `grade_review`. Runs only when you ask for it. Every skill passes the optional `intent` argument on each tool call — one sentence on what it's doing for you, used only in aggregate to improve the tools; see [Tools](/mcp/tools). ## Access control The plugin's tools carry exactly the access you approved on the consent page. Manage, narrow (to one folder or tag), or revoke the connection any time in Dexi under **Settings → Connected apps**; changes apply on the next tool call. ## Troubleshooting | Symptom | Fix | | - | - | | `/mcp` shows dexi but tools don't appear | Select **dexi** in `/mcp` and complete the browser sign-in; tools register once authenticated. | | Consent page says the request expired | Approval requests are valid for ten minutes — re-run `/mcp` and approve promptly. | | A skill doesn't trigger | Skills are namespaced: `/dexi:capture`, `/dexi:recall`, `/dexi:review`. Capture and recall are also model-invoked; review is user-invoked only. | | Note limit error on capture | Free accounts hold 1,000 notes (more with referrals). The error message carries the real cap; upgrade at [dexi.net/pricing](https://dexi.net/pricing). | The plugin's skill guidance mirrors the tool contract in this documentation; when tools change, both are updated together. Plugin version history lives in the mirror repo. # Examples Source: https://docs.dexi.net/mcp/examples Flows assistants run well with the Dexi tools Prompts to try (or to steal for your own agent's instructions). Each shows the tool sequence a capable assistant settles into. ## Research recall > What have I saved about vector databases? Anything related I've forgotten about? 1. `semantic_search("vector databases")` — meaning-based, so notes about pgvector or embeddings match without the literal phrase 2. `get_note(id)` on the strongest hits for full text 3. `find_similar(id)` on the best note to surface adjacent reading saved months ago Semantic first, keyword (`search_notes`) when the user gives exact words or a title fragment. ## Capture during work > Save a note: the standup decision was to ship folders behind a flag — tag it project-dexi and mention Sarah. 1. `create_note(title="Standup decision: folders behind a flag", text="… #project-dexi @sarah")` Tags go **inline in the text** — `#project-dexi` and `@sarah` become tags automatically; there is no separate tags parameter. To add to an existing note instead, `search_notes` for it, then `update_note(mode="append")` — append is the safe default; replace destroys rich formatting. ## Folder triage > What's piling up unfiled? Suggest where things should go. 1. `list_folders()` — folder names, counts, and the unfiled count 2. `list_notes(folder="unfiled", size=20)` — the backlog 3. Suggest destinations from the existing folder names; the user files notes in the app Folder assignment isn't exposed as a tool yet, so the assistant recommends and the user moves. ## Review session > Quiz me on my review deck. 1. `get_due_reviews()` — due cards with `due_count` 2. For each card: show the **title only**, let the user attempt recall, then reveal the body 3. `grade_review(note_id, grade)` — 1 forgot / 2 hard / 3 good / 4 easy; the schedule updates immediately (Again comes back in \~10 minutes) The assistant should never reveal the answer before the user attempts recall — that's the point of the exercise. # Getting Started Source: https://docs.dexi.net/mcp/getting-started Connect an MCP client to Dexi Every client uses the same server URL and the same browser-consent flow: add the server, your browser opens a Dexi approval page, click **Approve**, done. No Dexi account yet? The approval page offers to create one (Google or email) and brings you straight back to finish connecting. Longer walkthroughs with screenshots live at [dexi.net/mcp](https://dexi.net/mcp). ``` https://mcp.dexi.net/mcp ``` ## Claude Code The dexi plugin registers the server plus a usage skill in one step: ```bash theme={null} claude plugin marketplace add dexi/claude-plugin claude plugin install dexi@dexi ``` Or add the server directly (use `--scope user` so it applies to every project, not just the current one): ```bash theme={null} claude mcp add --transport http --scope user dexi https://mcp.dexi.net/mcp ``` Either way, run `/mcp` inside Claude Code and select **dexi** to authenticate. Plugin details (skills, Cowork, troubleshooting): [Claude Code plugin](/mcp/claude-code-plugin). ## Claude (claude.ai and Desktop) **Settings → Connectors → Add custom connector**, paste the server URL, then approve in the browser. Custom connectors require a paid Claude plan. ## Cursor Add to `~/.cursor/mcp.json`: ```json theme={null} { "mcpServers": { "dexi": { "url": "https://mcp.dexi.net/mcp" } } } ``` ## VS Code (Copilot) Run **MCP: Add Server…** from the command palette and choose HTTP, or add to `.vscode/mcp.json`: ```json theme={null} { "servers": { "dexi": { "type": "http", "url": "https://mcp.dexi.net/mcp" } } } ``` ## ChatGPT Enable **Developer mode** under **Settings → Connectors → Advanced**, then create a connector with the server URL and OAuth authentication. ## Goose Add a **Streamable HTTP** remote extension with the endpoint URL — in Goose Desktop's settings, or via `goose configure` in the CLI. ## Hermes Agent Add to `~/.hermes/config.yaml`: ```yaml theme={null} mcp_servers: dexi: url: "https://mcp.dexi.net/mcp" auth: oauth ``` Restart Hermes; the first tool call prints an authorize URL (and opens your browser when it can). On a headless VPS, open the URL elsewhere, approve, and paste the redirect URL back when prompted — or run `hermes mcp login dexi`. Tools appear as `mcp_dexi_`. Or install Dexi as Hermes' **memory provider** (auto-recall before each turn, skills, optional session digest — takes Hermes' single external-provider slot, so use it *instead of* the entry above): ```bash theme={null} hermes plugins install dexi/hermes-plugin --enable hermes memory setup # choose: dexi hermes dexi login ``` Details, config keys, and what data leaves the device: [Hermes Agent plugin](/mcp/hermes-plugin). ## OpenClaw Add Dexi as a remote MCP server (streamable HTTP + OAuth), then sign in once: ```bash theme={null} openclaw mcp add dexi --url https://mcp.dexi.net/mcp --transport streamable-http --auth oauth openclaw mcp login dexi # prints the authorize URL; --code for headless paste-back ``` Tools appear as `dexi__` (e.g. `dexi__search_notes`). Or install Dexi as OpenClaw's **memory plugin** (auto-recall before each turn, `/remember` and `/recall`, skills, optional session digest — takes the exclusive memory slot, so use it *instead of* the entry above): ```bash theme={null} openclaw plugins install openclaw-dexi openclaw plugins enable openclaw-dexi # claims the memory slot openclaw dexi setup # hook permissions + tool allowlist, then signs you in openclaw gateway restart ``` Details, config keys, and what data leaves the device: [OpenClaw plugin](/mcp/openclaw-plugin). ## Verify the connection Ask your assistant: > What did I save in Dexi this week? You should see it call `list_notes` and answer from your workspace. If authentication didn't trigger, remove and re-add the server — and check **Settings → Connected apps** in Dexi to see whether the grant was created. # Hermes Agent plugin Source: https://docs.dexi.net/mcp/hermes-plugin Dexi as Hermes Agent's memory provider — recall before every turn, dexi_* tools, optional session digest [Hermes Agent](https://github.com/NousResearch/hermes-agent) can use Dexi two ways. As a plain **MCP server** it gets the eighteen tools (see [Getting Started → Hermes Agent](/mcp/getting-started#hermes-agent)). The **dexi memory-provider plugin** on this page goes further: it recalls relevant notes before every turn, teaches the agent how to use your notes, and can leave a digest note per session. Hermes allows one external memory provider at a time, so pick this *instead of* the `mcp_servers` entry when Dexi is your memory — configuring both duplicates the tools. Source, issues, and version history: [github.com/dexi/hermes-plugin](https://github.com/dexi/hermes-plugin) (MIT). | | MCP server (`mcp_servers.dexi`) | Memory provider (this plugin) | | - | - | - | | Tools | all eighteen as `mcp_dexi_*` | compact `dexi_*` set (search / get / list / save / append / tags / folders / bases / reviews) | | Auto-recall before each turn (``) | — | ✓ | | System-prompt guidance | — | ✓ | | Session digest note (opt-in) | — | ✓ | | Skills `dexi:capture` / `dexi:recall` / `dexi:review` | — | ✓ | | Works alongside another memory provider (Honcho, Mem0, …) | ✓ | ✗ | | Auth | OAuth, browser or paste-back | same — shares the token cache | ## Install ```bash theme={null} hermes plugins install dexi/hermes-plugin --enable hermes memory setup # pick "dexi" (or: memory.provider: dexi in ~/.hermes/config.yaml) hermes dexi login # one-time OAuth hermes memory status ``` Or as a pip package (entry-point discovery — handy in a container image), then set `memory.provider: dexi`: ```bash theme={null} pip install git+https://github.com/dexi/hermes-plugin.git ``` Requires `mcp>=1.26,<2` and `httpx`, both present in a Hermes install with the `mcp` extra. `hermes dexi login` prints an authorize URL and opens your browser. Sign in to Dexi, optionally restrict the connection to one folder or tag, and approve. Hermes' local callback completes the flow; you'll see "Authorization Successful — return to Hermes." Same command; open the printed URL in any browser, approve, then paste the final redirect URL — or just its `?code=…&state=…` part — back into the terminal when Hermes prompts. SSH port-forwarding the callback port also works. Tokens are cached in `~/.hermes/mcp-tokens/dexi*.json`, so a gateway started later reuses them silently. If you already had an `mcp_servers.dexi` entry, the provider reuses its token — no second approval. Verify with `hermes memory status` (shows **dexi** as the active provider) and `hermes dexi status` (config plus a live probe). ## What it does **Recall (`prefetch`)** — before each non-trivial turn, the plugin runs `semantic_search` over your notes for the incoming message (plus a keyword pass when it contains a `#hashtag` or a "quoted phrase") and injects hits above the similarity floor as a `` block of titles and snippets. Never full bodies — the model calls `dexi_get` or `full_text: true` when it wants one. Best-effort with a \~2.5 s budget; failures inject nothing. Hermes shows it in the recall indicator ("🧠 Dexi — recalled 5 memories"). **Tools** — each forwards to the Dexi MCP tool of the same purpose and accepts an optional `intent` sentence: | Tool | Forwards to | Notes | | - | - | - | | `dexi_search` | `search_notes` + `semantic_search` | `mode`: `hybrid` (default, merged and de-duplicated, each item marked `keyword` / `semantic` / `both`), `keyword`, or `semantic`; `full_text` | | `dexi_get` | `get_note` | full body | | `dexi_list` | `list_notes` | `source`, `tag`, `folder`, `period`, `since`, `sort`, `page`, `size`, `full_text` | | `dexi_save` | `create_note` | returns `id`, `title`, `url` | | `dexi_append` | `update_note` (`mode: append`) | keeps the note's formatting | | `dexi_tags` / `dexi_folders` | `list_tags` / `list_folders` | | | `dexi_bases` / `dexi_query_base` | `list_bases` / `query_base` | the user's saved [bases](/properties-and-bases); `name`, `view`, `page`, `size`, `full_text`; items carry `properties` | | `dexi_reviews_due` / `dexi_review_grade` | `get_due_reviews` / `grade_review` | spaced repetition | Every result item carries a `url` (`https://app.dexi.net/dashboard/notes/`) so the agent can cite notes. **Session digest (off by default)** — with `session_digest: true`, one note per session is written at session end, session switch, pre-compaction, or shutdown — never per turn. It holds the questions asked, the last answer, and the session id, tagged `#hermes` (configurable). Deterministic (no LLM call in the plugin) and idempotent per session. **Skills** — `dexi:capture`, `dexi:recall`, `dexi:review` mirror the [Claude Code plugin's](/mcp/claude-code-plugin) with the `dexi_*` tool names; load one explicitly, or schedule `dexi:review` with a Hermes cron for a daily quiz over Telegram. **Deliberately not done** — no note per turn, no mirroring of Hermes' `MEMORY.md`/`USER.md`, no rules/provenance/rollback layer. Dexi is your notes app; the agent is a reader and an occasional, deliberate writer. ## What leaves your device Only when the corresponding feature runs, and only to `mcp.dexi.net` under your own account: | Feature | Data sent | | - | - | | Auto-recall (`auto_recall`, default on) | the current user message (≤ 500 chars) as a search query | | Tools | the arguments the model passes (a query, note text you asked it to save) | | Session digest (`session_digest`, default **off**) | your session's user messages (first \~240 chars each, up to 12) + the last answer (\~1,200 chars) | Never full transcripts, `MEMORY.md`, or tool-call history. Everything lands in your Dexi account, visible and deletable in the app. Set `read_only: true` and the connection cannot write at all: Hermes requests only the `notes:read` scope and the write tools disappear from the agent. ## Configuration `~/.hermes/dexi.json` — created by `hermes memory setup`; every key optional; `DEXI_` environment variables override: | Key | Default | Meaning | | - | - | - | | `auto_recall` | `true` | inject relevant notes before each turn | | `recall_results` | `5` | max notes injected | | `recall_min_similarity` | `0.55` | semantic similarity floor (0–1) | | `session_digest` | `false` | write one digest note per session | | `digest_tag` | `#hermes` | tag on digest notes (word characters only — Dexi's hashtag syntax) | | `read_only` | `false` | request `notes:read` only; hide write tools | | `prefetch_timeout` | `2.5` | seconds; recall is skipped past this | | `tool_timeout` | `30` | seconds per explicit tool call | | `mcp_url` | `https://mcp.dexi.net/mcp` | override for a self-hosted or dev server | CLI (available while `memory.provider` is `dexi`): `hermes dexi status`, `hermes dexi login`, `hermes dexi logout` (clears the cached tokens on the Hermes side), `hermes dexi config`. Manage, narrow, or revoke the grant itself in Dexi under **Settings → Connected apps** — the connection appears there as *Hermes Agent (Dexi memory)*; a folder/tag restriction set there applies on the next request. ## Troubleshooting | Symptom | Fix | | - | - | | `hermes memory status` doesn't list dexi | Confirm the plugin dir exists at `~/.hermes/plugins/dexi/` (`hermes plugins list`), then `hermes plugins enable dexi`. | | Tool calls return `unauthorized (401)` | The cached token is missing, expired, or revoked — run `hermes dexi login`. | | Authorize URL expired | Dexi's approval request is valid for ten minutes; run `hermes dexi login` again and approve promptly. | | Non-interactive environment error at first use | A gateway with no TTY can't run the browser flow; authorize once from a terminal with `hermes dexi login`, then restart the gateway — the cached tokens are reused. | | Duplicate tools (`mcp_dexi_*` and `dexi_*`) | You have both an `mcp_servers.dexi` entry and the provider active; remove one (or `exclude` the MCP side's tools). | | Digest never appears | It's opt-in: `session_digest: true` in `~/.hermes/dexi.json`, and the connection must not be `read_only`. | # OAuth Source: https://docs.dexi.net/mcp/oauth Protocol details for MCP client builders Dexi's MCP server is a full OAuth 2.1 authorization server. Off-the-shelf MCP clients handle all of this automatically — this page is for people building their own client or debugging a connection. ## Discovery Standard metadata at the domain root: ``` https://mcp.dexi.net/.well-known/oauth-authorization-server (RFC 8414) https://mcp.dexi.net/.well-known/oauth-protected-resource/mcp (RFC 9728) https://mcp.dexi.net/.well-known/openid-configuration (OpenID discovery) ``` Endpoints: `/authorize`, `/token`, `/register`, `/revoke`, `/userinfo`. The OpenID discovery document is the RFC 8414 metadata plus `userinfo_endpoint`: `GET /userinfo` with a Dexi access token (`Authorization: Bearer dxm_…`) returns the token owner's `sub` (user id), `email`, `email_verified`, and `name`/`given_name`/`family_name` when set. It exists for clients that verify a connection's account identity (ChatGPT workspace domain restrictions); any live access token can read its own owner's identity, no extra scope needed. Dexi does not issue ID tokens. ## Client registration Dynamic client registration (RFC 7591) is open — `POST /register` with your metadata. Redirect URIs must match **exactly** and be either HTTPS or a localhost loopback (`localhost`, `127.0.0.1`, `[::1]`; any port). Client secrets are stored hashed. ## Authorization * **PKCE S256 is required** on every authorization request. * `/authorize` does not itself log the user in: it parks the request and redirects to Dexi's consent page (`app.dexi.net/account/authorize`), which is login-gated. On approval the user agent returns to your redirect URI with `code` + `state`; denial returns `error=access_denied`. * Pending authorization requests expire after 10 minutes; authorization codes (`dxc_…`) expire after 5 minutes and are single-use — replaying one fails. ## Scopes `notes:read` and `notes:write`. Both are valid and requested by default when unspecified; `notes:read` is the minimum every tool call requires. The OpenID identity scopes `openid`, `email` and `profile` are also accepted (never defaulted) — they grant nothing inside Dexi and only exist so OIDC-aware clients that request them aren't rejected. Request only what you need — the consent page shows the user exactly what was asked, and **the user can grant read-only even when `notes:write` was requested**. Check the `scope` field of the token response rather than assuming: a read-only grant makes the write tools (`create_note`, `update_note`, `delete_note`, `grade_review`) return a permission error. ## Access restrictions Beyond scopes, the consent page lets the user limit the connection to notes in one folder, notes with one tag, or both — and edit that choice later in **Settings → Connected apps**. Three properties matter to client builders: * **Latest consent wins.** The restriction is stored per user + client, not per token. Re-authorizing the same client replaces the previous restriction, and the new boundary governs every outstanding token for that client. * **Edits apply live.** The boundary is re-read on every request, so a settings change takes effect on the connection's next call — no token refresh or re-authorization involved. * **Folder deletion fails closed.** If the restricted folder is deleted, the connection loses all access (every tool call errors with an explanation) until the user edits or disconnects it. Access never silently widens. Under any restriction the connection sees only the user's own notes; [tool behavior inside a boundary](/mcp/tools#access-restrictions) is documented per tool. ## Tokens | Token | Prefix | Lifetime | | - | - | - | | Access | `dxm_` | 60 minutes | | Refresh | `dxr_` | 30 days | Tokens are opaque (not JWTs) and stored hashed server-side. **Refresh rotation is strict**: exchanging a refresh token revokes that token *and* every outstanding access token for the same client + user. Don't reuse old refresh tokens; store the new pair from each refresh response. ## Revocation * RFC 7009 `POST /revoke` — revoking either token kind kills the entire grant for that client + user. * Users can disconnect any client in **Settings → Connected apps**, with the same effect. Expect calls to start failing with 401 at any time; re-run the authorization flow to reconnect. ## Compatibility note `https://api.dexi.net/mcp` serves the same server (one service, two domains) and remains supported for connections created before the `mcp.dexi.net` domain existed. Use `mcp.dexi.net` for anything new. # OpenClaw plugin Source: https://docs.dexi.net/mcp/openclaw-plugin Dexi as OpenClaw's memory plugin — recall before every turn, dexi_* tools, /remember and /recall, optional session digest [OpenClaw](https://openclaw.ai) can use Dexi two ways. As a plain **remote MCP server** it gets the eighteen tools with no install (see [Getting Started → OpenClaw](/mcp/getting-started#openclaw)). The **`openclaw-dexi` memory plugin** on this page goes further: it recalls relevant notes before every turn, teaches the agent how to use your notes, adds `/remember` and `/recall`, and can leave a digest note per conversation. It takes OpenClaw's exclusive **memory slot**, so pick it *instead of* the built-in `memory-core` (local `memory_search`/`memory_get`, dreaming and the markdown memory flush are off while Dexi is the memory plugin) and instead of an `mcp.servers.dexi` entry (configuring both duplicates the tools). Source, issues, and version history: [github.com/dexi/openclaw-plugin](https://github.com/dexi/openclaw-plugin) (MIT). npm: [`openclaw-dexi`](https://www.npmjs.com/package/openclaw-dexi). | | Native MCP server (`openclaw mcp add dexi …`) | Memory plugin (this page) | | - | - | - | | Tools | all eighteen as `dexi__*` | compact `dexi_*` set (search / get / list / save / append / tags / folders / bases / reviews) | | Auto-recall before each turn (``) | — | ✓ | | Memory prompt section | — | ✓ | | `/remember` · `/recall` · `/dexi-digest` commands | — | ✓ | | Session digest note (opt-in) | — | ✓ | | Skills `dexi-capture` / `dexi-recall` / `dexi-review` | — | ✓ | | Works alongside OpenClaw's built-in memory (`memory-core`) | ✓ | ✗ (exclusive memory slot) | | Auth | OAuth (`openclaw mcp login dexi`) | OAuth (`openclaw dexi login`), browser or paste-back | ## Install ```bash theme={null} openclaw plugins install openclaw-dexi openclaw plugins enable openclaw-dexi # claims the memory slot (unlocks the `openclaw dexi` CLI) openclaw dexi setup # hook permissions + tool allowlist, then signs you in openclaw gateway restart ``` `setup` writes `plugins.entries.openclaw-dexi` (with the `hooks.allowConversationAccess` / `allowPromptInjection` permissions the memory hooks need), `plugins.slots.memory = "openclaw-dexi"`, and the `dexi_*` tool allowlist into `~/.openclaw/openclaw.json`, then runs `openclaw dexi login`. Flags: `--read-only`, `--session-digest`, `--digest-tag `, `--mcp-url `, `--no-login`, `--no-browser`. Requires OpenClaw ≥ 2026.5.7 (Node 22+). `openclaw dexi login` prints an authorize URL and opens your browser. Sign in to Dexi, optionally restrict the connection to one folder or tag, and approve. The plugin's loopback listener on `127.0.0.1` completes the flow; you'll see "Dexi is connected to OpenClaw" in the tab and `✓ Connected to Dexi` in the terminal. Same command (add `--no-browser`); open the printed URL in any browser, approve, then paste the final redirect URL — or just its `?code=…&state=…` part — back into the terminal (or run `openclaw dexi login --code ''`). SSH port-forwarding the callback port also works. Tokens are stored in `~/.openclaw/dexi/oauth.json` (mode 0600); the gateway reuses them silently after a restart. Verify with `openclaw dexi status` (config plus a live probe) and `openclaw status` (the Memory row reads *enabled (plugin openclaw-dexi)*). ## What it does **Recall (`before_prompt_build`)** — before each interactive turn, the plugin runs `semantic_search` over your notes for the incoming message (channel metadata stripped; plus a keyword pass when it contains a `#hashtag` or a "quoted phrase") and injects hits above the similarity floor as a `` block of titles and snippets. Never full bodies — the model calls `dexi_get` or `full_text: true` when it wants one. Best-effort with a \~2.5 s budget; failures inject nothing. Heartbeat and cron runs are skipped. **Tools** — each forwards to the Dexi MCP tool of the same purpose and accepts an optional `intent` sentence: | Tool | Forwards to | Notes | | - | - | - | | `dexi_search` | `search_notes` + `semantic_search` | `mode`: `hybrid` (default, merged and de-duplicated, each item marked `keyword` / `semantic` / `both`), `keyword`, or `semantic`; `full_text` | | `dexi_get` | `get_note` | full body | | `dexi_list` | `list_notes` | `source`, `tag`, `folder`, `period`, `since`, `sort`, `page`, `size`, `full_text` | | `dexi_save` | `create_note` | returns the note's `url` | | `dexi_append` | `update_note` (`mode: append`) | keeps the note's formatting | | `dexi_tags` / `dexi_folders` | `list_tags` / `list_folders` | | | `dexi_bases` / `dexi_query_base` | `list_bases` / `query_base` | the user's saved [bases](/properties-and-bases); `name`, `view`, `page`, `size`, `full_text`; items carry `properties` | | `dexi_reviews_due` / `dexi_review_grade` | `get_due_reviews` / `grade_review` | spaced repetition | Every result item carries a `url` (`https://app.dexi.net/dashboard/notes/`) so the agent can cite notes. **Commands** — `/remember ` saves a note without involving the model (first line becomes the title, `#hashtags` become tags), `/recall ` runs a hybrid search, `/dexi-digest` writes the current session's digest now. **Session digest (off by default)** — with `sessionDigest: true`, one note per conversation is written when the session ends (`/new`, `/reset`, idle or daily rotation), before compaction, after `digestIdleMinutes` of quiet, or at gateway shutdown — never per turn. It holds the questions asked, the last answer, and the session key, tagged `#openclaw` (configurable). Later flushes *append* to the same note; the session→note mapping is persisted in `~/.openclaw/dexi/digests.json`, so gateway restarts and one-shot `openclaw agent` runs don't create duplicates. Deterministic — no LLM call in the plugin. **Skills** — `dexi-capture`, `dexi-recall`, `dexi-review` mirror the [Claude Code plugin's](/mcp/claude-code-plugin) with the `dexi_*` tool names and load while the plugin is enabled; schedule `dexi-review` with an OpenClaw cron job for a daily quiz over Telegram or Discord. **Deliberately not done** — no note per turn, no mirroring of transcripts or `MEMORY.md`, no LLM-written summaries. Dexi is your notes app; the agent is a reader and an occasional, deliberate writer. ## What leaves your device Only when the corresponding feature runs, and only to `mcp.dexi.net` under your own account: | Feature | Data sent | | - | - | | Auto-recall (`autoRecall`, default on) | the current user message (≤ 500 chars) as a search query | | Tools / commands | the arguments the model (or you) pass — a query, note text you asked it to save | | Session digest (`sessionDigest`, default **off**) | your session's user messages (first \~240 chars each, up to 12) + the last answer (\~1,200 chars) | Never full transcripts, local memory files, or tool-call history. Everything lands in your Dexi account, visible and deletable in the app. Set `readOnly: true` and the connection cannot write at all: the plugin requests only the `notes:read` scope, and the write tools, `/remember`, and the digest disappear. ## Configuration `plugins.entries.openclaw-dexi.config` in `~/.openclaw/openclaw.json` — written by `openclaw dexi setup`; every key optional; `DEXI_` environment variables override (e.g. `DEXI_MCP_URL`): | Key | Default | Meaning | | - | - | - | | `autoRecall` | `true` | inject relevant notes before each turn | | `recallResults` | `5` | max notes injected (1–20) | | `recallMinSimilarity` | `0.55` | semantic similarity floor (0–1) | | `sessionDigest` | `false` | write one digest note per session | | `digestTag` | `#openclaw` | tag on digest notes (word characters only — Dexi's hashtag syntax) | | `digestIdleMinutes` | `30` | also flush the digest after this much quiet | | `readOnly` | `false` | request `notes:read` only; hide write tools | | `recallTimeoutMs` | `2500` | recall is skipped past this | | `toolTimeoutMs` | `30000` | per explicit tool call | | `mcpUrl` | `https://mcp.dexi.net/mcp` | override for a self-hosted or dev server | | `debug` | `false` | verbose plugin logs | CLI: `openclaw dexi setup`, `openclaw dexi login [--read-only] [--no-browser] [--code ]`, `openclaw dexi logout` (revokes the token and forgets it locally), `openclaw dexi status`, `openclaw dexi search `, `openclaw dexi save `. Manage, narrow, or revoke the grant itself in Dexi under **Settings → Connected apps** — the connection appears there as *OpenClaw (Dexi memory)*; a folder/tag restriction set there applies on the next request. ## Troubleshooting | Symptom | Fix | | - | - | | `openclaw dexi` says "does not know the command" / Memory row doesn't show *plugin openclaw-dexi* | A `kind: memory` plugin stays disabled until it owns the slot: run `openclaw plugins enable openclaw-dexi` (switches `plugins.slots.memory` from `memory-core`), then `openclaw dexi setup` and `openclaw gateway restart`. `openclaw plugins list` must show **Dexi** as enabled. | | Tools answer "Dexi is not connected" | The token is missing, expired, or revoked — run `openclaw dexi login` on the machine running the gateway, then restart the gateway. | | Authorize URL expired | Dexi's approval request is valid for ten minutes; run `openclaw dexi login` again and approve promptly. | | The browser callback can't reach the gateway host | Use the paste-back flow: open the URL on your own machine, approve, and paste the final redirect URL (or `--code`) into the terminal. | | Duplicate tools (`dexi__*` and `dexi_*`) | You have both an `mcp.servers.dexi` entry and the plugin active; remove one (`openclaw mcp unset dexi`). | | I want OpenClaw's built-in memory back | `openclaw config set plugins.slots.memory memory-core` and restart the gateway; the Dexi plugin can stay installed. | | Digest never appears | It's opt-in: `sessionDigest: true` in the plugin config, and the connection must not be `readOnly`. | # MCP Overview Source: https://docs.dexi.net/mcp/overview Your notes, searchable and writable by AI assistants Dexi runs a [Model Context Protocol](https://modelcontextprotocol.io) server — the same notes, search, and capture engine as the app, exposed as tools any MCP client can call. Connect Claude, ChatGPT, Cursor, or your own agent and it can answer from what you've actually read and saved, file new notes as you work, and quiz you on your review deck. ``` https://mcp.dexi.net/mcp ``` Transport is **streamable HTTP**; auth is **OAuth 2.1** with browser consent — no API keys to paste. Every content kind is covered: typed notes, saved bookmarks, forwarded emails, and feed articles are all note-backed, so one `search_notes` call spans your whole workspace. * [Getting Started](/mcp/getting-started) — connect from Claude, ChatGPT, Cursor, VS Code, Goose, Hermes Agent, OpenClaw * [Tools](/mcp/tools) — all eighteen tools with parameters and return shapes * [Examples](/mcp/examples) — flows agents run well * [OAuth](/mcp/oauth) — protocol details for MCP client builders * [Claude Code plugin](/mcp/claude-code-plugin), [Hermes Agent plugin](/mcp/hermes-plugin), and [OpenClaw plugin](/mcp/openclaw-plugin) — one-command installs that bundle the server with skills (and, for Hermes and OpenClaw, memory hooks: recall before every turn, optional session digest) Don't confuse the two MCP endpoints: `mcp.dexi.net/mcp` is **your notes** (this page's subject, OAuth-gated). `docs.dexi.net/mcp` is a separate read-only server that searches **this documentation site** — Mintlify hosts one for every docs site, no account needed. ## Scopes | Scope | Grants | | - | - | | `notes:read` | search\_notes, semantic\_search, list\_notes, get\_note, find\_similar, list\_tags, list\_folders, get\_due\_reviews | | `notes:write` | create\_note, update\_note, delete\_note, grade\_review | Clients typically request both; the consent page shows what was asked. Disconnect any client in **Settings → Connected apps**. Scopes say what a connection may *do*; the consent page also lets you limit what it may *touch* — everything, one folder, one tag, or a tag within a folder — per connection, editable any time in Settings → Connected apps, applied on the connection's next request. Details: [access restrictions](/mcp/oauth#access-restrictions). ## Teams and privacy If you're on a team, read tools see your own notes **plus teammates' team-shared notes** (marked `"shared": true` with an `owner` name in results). Write tools work only on your own notes — a connected assistant can never modify a teammate's note. Your folder assignments stay private; teammates' shared notes carry no folder information. # Tools Source: https://docs.dexi.net/mcp/tools The eighteen Dexi MCP tools, their parameters, and return shapes Ten read tools (scope `notes:read`), six write tools (`notes:write`), and two spaced-repetition tools. Errors come back as MCP tool errors with a plain-language message (`Note not found`, `Folder not found: X`, …). Every tool also accepts an optional `intent` parameter (string, ≤ 300 chars, omitted from the per-tool tables below): one short sentence on what the agent is trying to accomplish for the user. It never changes the tool's behavior — Dexi records it, along with the tool name and call outcome, to understand how the tools are used in aggregate and improve them. Tool arguments and note content are not recorded. Keep personal details out of it. ## Access restrictions A connection may be [restricted](/mcp/oauth#access-restrictions) to notes in one folder, notes with one tag, or both. Inside a boundary: * Every read tool sees only the user's **own** in-boundary notes (teammates' shared notes are excluded); out-of-boundary notes behave exactly like nonexistent ones (`Note not found`, `Folder not found`). * `list_tags` and `list_folders` cover only in-boundary content — folder names and tags outside the boundary never appear. * `create_note` keeps writes inside the boundary automatically: notes are filed into the restricted folder, and the restricted tag is appended to the text when missing. * `update_note` refuses edits that would remove the restricted tag from a note (the error names the tag); deletes work only on in-boundary notes. * Review tools cover only in-boundary cards. ## The note item List and search tools return items in one shared shape: ```json Note item theme={null} { "id": "2982e9b4-19fe-40fd-9a69-e23d45e53b13", "title": "Attention is all you need", "snippet": "Reading notes on the transformer paper — the key insight is…", "tags": ["@vaswani", "#ml", "#papers"], "source": "bookmark", "source_url": "https://arxiv.org/abs/1706.03762", "created": "2026-07-14T09:12:00+00:00", "updated": "2026-08-02T18:40:11+00:00" } ``` * `snippet` is the first 300 characters of the body (`…` appended when truncated). * `source` is `note`, `bookmark`, `email`, or `feed`; `source_url` is set for bookmarks and feed articles. * Conditional keys: `folder` (name, only on your own filed notes), `properties` (the note's inline `key:: value` fields as raw JSON values — see [Bases](#bases); only when the note has any), `shared: true` + `owner` (only on a teammate's team-shared note — read-only for you), and `similarity` (0–1, only from `semantic_search` and `find_similar`). * With `full_text: true` (accepted by `search_notes`, `semantic_search`, and `list_notes`), `snippet` is replaced by `text` — the body capped at 4,000 characters, with `text_truncated: true` when it was cut — and `size` is capped at 10. Use it to read a handful of results in one call instead of one `get_note` per item. `get_note`, `create_note`, and `update_note` return the same shape with `snippet` replaced by `text` — the full plain-text body. ## Read tools ### search\_notes Full-text search over titles and body text, ranked by relevance. Supports web search syntax: `"quoted phrases"`, `OR` between terms, and `-word` to exclude; plain terms must all match. When nothing matches every term, the search retries with any-term matching and sets `relaxed: true` on the response. Covers bookmarks, saved emails, and feed articles too — every item in Dexi is backed by a note. Use `semantic_search` instead when looking for concepts rather than exact words. A query wrapped in slashes runs as a **regex search** instead: `/colou?r/` matches titles and bodies with a case-insensitive POSIX regular expression (Postgres syntax). Regex results are unranked — newest matches come first — and never trigger the any-term retry. An invalid or pathologically slow pattern returns a tool error naming the problem. | Parameter | Type | Constraints | Default | | - | - | - | - | | `query` | string | 1–200 chars | required | | `page` | int | ≥ 1 | 1 | | `size` | int | 1–50 (≤ 10 with `full_text`) | 10 | | `full_text` | bool | bodies inline instead of snippets | `false` | Returns `{items, total, page, relaxed}`. ### semantic\_search Find notes by meaning using vector embeddings — surfaces conceptually related content even when no words are shared with the query (e.g. "machine learning" matches notes about neural networks). Each result carries a `similarity` score; only results above \~0.55 similarity are returned, so an empty list means nothing conceptually close exists — fall back to `search_notes` for exact words. | Parameter | Type | Constraints | Default | | - | - | - | - | | `query` | string | 1–500 chars | required | | `size` | int | 1–50 (≤ 10 with `full_text`) | 10 | | `full_text` | bool | bodies inline instead of snippets | `false` | Returns `{items}` (no total/pagination — it's a ranked window). ### list\_notes Browse newest-first with filters. The right tool for "what did I save this week?" or "what changed since yesterday 9am?"-style questions. | Parameter | Type | Constraints | Default | | - | - | - | - | | `page` | int | ≥ 1 | 1 | | `size` | int | 1–50 (≤ 10 with `full_text`) | 10 | | `source` | enum | `all` `bookmark` `email` `feed` `note` | `all` | | `tag` | string | `"#hashtag"`, `"@mention"`, or a bare word matching either | — | | `folder` | string | folder name (case-insensitive) or `"unfiled"`; unknown name errors — call `list_folders` first | — | | `period` | enum | `today` `yesterday` `week` | — | | `since` | string | ISO 8601 date or datetime (`2026-08-13`, `2026-08-13T09:00:00Z`); inclusive lower bound on `created`, or on `updated` when `sort` is `updated`. A value without a timezone is read in your profile timezone. Composable with the other filters | — | | `sort` | enum | `created` `updated` | `created` | | `full_text` | bool | bodies inline instead of snippets | `false` | Returns `{items, total, page}`. ### get\_note Fetch one note in full: complete body text, title, tags, source kind, and source URL. | Parameter | Type | Constraints | | - | - | - | | `note_id` | string | note UUID (from search or list results) | Returns a full note object (with `text`). Invisible or unknown ids error with `Note not found`. ### find\_similar The notes most semantically similar to a given note — related reading, duplicates, connected ideas. Each result carries `similarity`. | Parameter | Type | Constraints | Default | | - | - | - | - | | `note_id` | string | note UUID | required | | `size` | int | 1–50 | 10 | Returns `{items}`. ### list\_tags The user's tags with usage counts, most-used first. Hashtags (`#topic`) categorize subjects; mentions (`@name`) reference people or entities. Tags are parsed automatically from note text — they are not set directly. | Parameter | Type | Constraints | Default | | - | - | - | - | | `kind` | enum | `hashtag` `mention` | `hashtag` | | `limit` | int | 1–200 | 50 | Returns `{tags: [{tag, count}]}`. ### list\_folders The user's folders with note counts, plus how many notes are unfiled. Folders are personal organization — a note lives in at most one folder (tags handle cross-cutting topics). Pass a folder's name to `list_notes` to browse its contents. No parameters. Returns `{folders: [{name, note_count}], unfiled_count}`. ## Bases A base is a saved database-style view over notes: Obsidian-style filters (`status == "reading"`, `file.hasTag("book")`, `file.mtime > now() - "1 week"`) over a note's inline properties, tags, folder, source and dates, plus one or more named views (table, cards, list or map) with their own filters, columns and sort. Users build them in the app (Bases in the sidebar) or import Obsidian `.base` files; `list_bases` and `query_base` let an assistant run one instead of re-implementing a filter the user already saved, and `get_base` / `create_base` / `update_base` / `delete_base` let it manage the definitions themselves ("make me a reading list grouped by topic"). Inline properties are lines of the form `key:: value` in a note's body (`status:: reading`, `due:: 2026-09-10`, `rating:: 4`, `authors:: [A, B]`). Dexi parses them on every save — including notes written through `create_note` / `update_note` — into typed values (number, boolean, ISO date, list, `[[link]]`, `#tag`, else string). ### list\_bases The user's saved bases with their view names and types. No parameters. Returns `{bases: [{name, views: [{name, type}]}]}`. ### query\_base Run a saved base: the base's filters (and the chosen view's own filters) applied to every note the user can see, in the view's sort order. | Parameter | Type | Constraints | Default | | - | - | - | - | | `name` | string | base name, case-insensitive | required | | `view` | string | view name within the base | the first view | | `page` | int | ≥ 1 | 1 | | `size` | int | 1–50 | 10 | | `full_text` | bool | see [the note item](#the-note-item) | `false` | The filter language, property syntax and `.base` format are documented in [Properties and bases](/properties-and-bases). Returns `{base, view, columns: [key…], items: [note item…], total, page}` — `columns` are the view's configured column keys (`file.name`, `rating`, `formula.score`, …), each item carries `properties` when the note has any, and `formulas` (name → computed value) when the base defines formulas. A grouped view adds `group` (the group's label) to each item and a top-level `groups: [{label, count, summaries?}]` in display order; a view with column summaries adds `summaries` (column key → value over the whole result). Errors: `Base not found: X` (call `list_bases`), `Unknown view: X`, or a message when the base's filter is invalid or timed out. Under an [access restriction](#access-restrictions), results are limited to in-boundary notes and definitions are never returned. ### get\_base A base's full definition — filters, formulas, property display names and views — as JSON and as Obsidian `.base` YAML. Read it before `update_base`. | Parameter | Type | Constraints | | - | - | - | | `name` | string | base name, case-insensitive | Returns `{name, definition, yaml}`. ### create\_base Save a new base. Scope `notes:write`. | Parameter | Type | Constraints | Default | | - | - | - | - | | `name` | string | 1–100 chars, unique per user (case-insensitive) | required | | `definition` | object | the `.base` document as JSON: `{filters, formulas, properties, views: [{type, name, filters, order, sort, groupBy, summaries, limit, …}]}` — exactly one of `definition` / `yaml` | — | | `yaml` | string | the same as Obsidian `.base` YAML (≤ 64 KB); unsupported pieces are dropped and listed in `warnings` | — | Returns `{name, views: [{name, type}], warnings: [string]}`. Every expression is validated exactly as in the app; an error reads `Invalid expression at views.0.filters: … (at character N)` so the leaf can be fixed, and a structural problem `Invalid base definition at views.0.type: …`. A duplicate name errors (`update_base` to change the existing one). ### update\_base Replace a base's definition and/or rename it. Scope `notes:write`. The definition is replaced whole — `get_base`, change the piece the user asked about, send the full document back. | Parameter | Type | Constraints | Default | | - | - | - | - | | `name` | string | current name, case-insensitive | required | | `definition` / `yaml` | object / string | as for `create_base` | — | | `new_name` | string | 1–100 chars | — | At least one of `definition`, `yaml`, `new_name`. Returns `{name, views, warnings}`. ### delete\_base Delete a saved base (the notes it showed are untouched). Scope `notes:write`. `{name}` → `{deleted: name}`. These four tools refuse on an [access-restricted](#access-restrictions) connection — definitions are neither read nor written there; `list_bases` and `query_base` keep working. ## Write tools ### create\_note Create a new note from plain text. Include `#hashtags` and `@mentions` inline — Dexi turns them into tags automatically. A line of the form `key:: value` (e.g. `status:: reading`, `due:: 2026-09-10`) becomes a structured property that [bases](#bases) can filter and sort on. | Parameter | Type | Constraints | Default | | - | - | - | - | | `title` | string | ≤ 500 chars | `""` | | `text` | string | plain text body | `""` | Returns the created note (full shape with `text`). On a free plan at the note cap, errors with the limit message instead of creating. ### update\_note Update a note's title and/or body. `mode: "append"` adds text to the end of the existing body — safe for adding thoughts to an existing note, or for setting a property with a `key:: value` line. `mode: "replace"` overwrites the entire body, and any rich formatting (headings, links, lists) is lost — read the note first and prefer append unless a rewrite was asked for. | Parameter | Type | Constraints | Default | | - | - | - | - | | `note_id` | string | note UUID; must be your own note | required | | `title` | string | ≤ 500 chars; omit to keep | — | | `text` | string | omit to keep | — | | `mode` | enum | `replace` `append` | `replace` | Returns the updated note. Omitting both `title` and `text` in replace mode errors with `Nothing to update`. ### delete\_note Permanently delete a note. Cannot be undone, and also removes the attached bookmark, saved email, or feed-article record if the note came from one. The tool is flagged destructive; assistants should confirm with the user before deleting anything not explicitly asked for. | Parameter | Type | Constraints | | - | - | - | | `note_id` | string | note UUID; must be your own note | Returns `{deleted: true, id, title}`. ## Spaced repetition Notes tagged with the user's review deck tags (Settings → Review) enroll in an SM-2 review schedule. These tools let an assistant run a review session in conversation. ### get\_due\_reviews Cards due for review. Quiz the user on the title/topic before revealing the body, then call `grade_review`. | Parameter | Type | Constraints | Default | | - | - | - | - | | `limit` | int | 1–50 | 10 | Returns `{items: [{id, title, text, due_at, interval_days, repetitions, is_new}], due_count}` — `is_new` means never graded. ### grade\_review Record recall quality and reschedule the card. Requires scope `notes:write`. | Parameter | Type | Constraints | | - | - | - | | `note_id` | string | note UUID | | `grade` | int | 1=Again (forgot — back in \~10 min), 2=Hard, 3=Good, 4=Easy | Returns `{id, grade, due_at, interval_days, ease_factor, repetitions}`. A note not enrolled in review errors with `Note is not in review`. # Properties and bases Source: https://docs.dexi.net/properties-and-bases The key:: value property syntax, the filter language bases understand, and the .base file format for import and export Dexi notes have no frontmatter and no properties panel. A property is a line you type in the body, and a **base** is a saved view — table, cards, list or map — over every note whose properties and attributes match a filter. This page is the reference for both; the product side is in the [knowledge base](https://dexi.net/kb/properties-and-bases). Agents reach bases through the MCP tools [`list_bases` and `query_base`](/mcp/tools#list_bases). ## Property syntax One property per line, at the start of the line, optionally after a list marker: ```text theme={null} status:: reading - due:: 2026-09-10 Due Date:: 2026-09-10 authors:: [Kahneman, Tversky] ref:: [[Thinking, Fast and Slow]] topic:: #pricing #psychology ``` Rules: * The key is 1–3 words (letters, digits, spaces, `_`, `-`), at most 50 characters, followed by `::` and then whitespace or the end of the line. Keys are normalised: `Due Date` → `due_date`, and matching is case-insensitive. * Deliberate non-matches: `std::vector` and `a::b` (no whitespace after `::`), URLs and times (`10::30` — a key can't contain `:` or `/`), `::` mid-sentence (the key would exceed three words), and `> quoted:: text` (blockquotes are someone else's words). * Properties are parsed on every write, whichever path created the note — editor, MCP `create_note`/`update_note`, bookmarklet, email-in, feeds — and stored on the note. They stay in full-text and semantic search. * Limits: 50 properties per note, values up to 500 characters, lists up to 50 items. ### Value types Types are inferred from the shape of the value; there is nothing to declare. | You write | Stored as | | - | - | | `true` / `false` | boolean (`yes`/`no` stay strings — an answer of "no" must not become `false`) | | `4`, `-3`, `2.5` | number (leading zeros such as `007` stay strings) | | `2026-09-10`, `2026-09-10T14:30` | date / datetime (ISO 8601; naive values read in your profile timezone) | | `[[Title]]`, `[[Title\|alias]]` | link | | `#tag` | tag | | `[a, b, c]`, several links or tags, or the same key on several lines | list | | anything else | string (a bare comma never splits: `summary:: fast, cheap, good` is prose) | The API returns raw JSON values on `NoteOut.properties` and on the MCP note item; repeated keys on one note merge into a list. ## Bases A base has global `filters`, optional `formulas` (see below), optional display names for properties, and one or more views. Each view is `table`, `cards`, `list` or `map` with its own extra filters, property `order`, up to five `sort` keys, an optional `groupBy` and per-column `summaries` (see [Grouping and summaries](#grouping-and-summaries)), an optional `limit` (≤ 500); for cards a `cardSize` (100–600 px) and an `imageProperty`; for a list, `markers` (`bullets`, `numbers` or `none`), a `separator` between the inline properties (default `, `) and `indent` — the properties rendered as nested sub-items under each note instead of inline. Obsidian's list view has the same three settings but doesn't document their YAML keys, so a list view exported from Obsidian imports with its own keys reported as unknown and Dexi's defaults applied. A map view takes `coordinates` — the property or formula holding `lat, lng` text or a `[lat, lng]` list (the two shapes Obsidian's map view reads; with separate `latitude` and `longitude` properties, point the map at a formula `[latitude, longitude]`, as Obsidian's guide does) — an optional `markerColor` property (hex, `rgb()`/`hsl()` or a color keyword), and an optional starting `center` (`[lat, lng]`) and `zoom` (0–20); without them the map fits its markers. Notes whose coordinates don't parse are counted below the map rather than plotted; a map shows at most 500 markers. Tiles come from OpenFreeMap (vector, keyless). In a table, click a property cell on one of your own notes to edit it in place: type the value as you would in the note (`done`, `4`, `2026-09-10`, `[a, b]`, `[[Title]]`), Enter saves, Escape cancels, a blank value removes the property, and boolean cells toggle with one click. Dexi rewrites that `key:: value` line inside the note (adding one after the note's last property line when the key is new) and leaves the rest of the note untouched; file attributes, formulas and teammates' notes aren't editable. Bases are personal — teammates never see them — but they run over every note you can see, including notes shared with you. The app manages all of this at **Bases** in the sidebar — and the notes list's **Save as base** button turns its active filters (type, tag, folder, team, period, sort) into a new base in one step; the definitions are also reachable over the [MCP server](/mcp/tools#query_base). ### Filter language Filters are a subset of the expression language Obsidian uses in `.base` files. Every expression compiles fully to SQL, so pagination and totals are exact; anything outside the subset is rejected with a clear error naming the construct (and the position inside the expression) rather than approximated. **Operands** * `note.` or bare `` — a property. * `formula.` — one of the base's formulas (below). * `file.name` (title), `file.ctime`, `file.mtime`, `file.tags` (hashtags and @mentions), `file.links`, `file.folder` (your own notes only), `file.size`, plus Dexi's `file.source` (`note`, `bookmark`, `email`, `feed`), `file.url`, `file.domain`, `file.description`, `file.preview`, `file.visibility`, `file.shared`, `file.owner`. * Literals: strings, numbers, `true`/`false`, lists such as `["reading", "done"]` or `[latitude, longitude]`, `date("2026-09-10")`, `now()`, `today()`, durations such as `"1 week"` or `"3d"`, `"2026-09-10"` compared against a real date is coerced to one. A literal list of constants works anywhere (`["reading", "done"].contains(status)`); one built from properties is a display value for a formula column — it can't be filtered or sorted on. **Operators and functions** | Kind | Available | | - | - | | Comparison | `==`, `!=`, `<`, `<=`, `>`, `>=` (typed: number vs number, date vs date, string vs string) | | Logic | `&&`, `\|\|`, `!`, parentheses | | Arithmetic | `+`, `-`, `*`, `/`, `%`; dates ± durations | | Globals | `date()`, `now()`, `today()`, `duration()`, `if()`, `link()`, `number()`, `min()`, `max()` | | File tests | `file.hasTag("a", …)`, `file.hasLink("Title")`, `file.inFolder("Name")`, `file.hasProperty("key")` — constant arguments only | | Any value | `.isEmpty()`, `.toString()` | | Strings | `.contains()`, `.containsAny()`, `.containsAll()`, `.startsWith()`, `.endsWith()`, `.lower()`, `.trim()`, `.length` | | Numbers | `.abs()`, `.floor()`, `.ceil()`, `.round(n)` (half-up), `.toFixed(n)` (text with `n` decimals) | | Dates | `.date()`, `.year`, `.month`, `.day`, `.hour`, `.minute`, `.second` | | Lists | `.contains()`, `.containsAny()`, `.containsAll()`, `.join()`, `.length` | Truthiness follows Obsidian: empty strings, `0`, `false`, empty lists and missing properties are falsy, so `!note.done` matches notes that never had the key. A property holding a value of the wrong shape (a string where a number is expected) simply doesn't match; it never errors the query. **Not supported yet** (rejected with a message): `this.*` (Dexi has no embedding note), regular expressions (`matches`), `list.map/filter/reduce/sort/unique/flat`, `slice`, `reverse`, `string.replace/split/repeat/title`, `date.format/relative/time`, `isType`, `isTruthy`, `file()`, `list()`, `random()`, `image()`, `icon()`, HTML helpers, object values. ### Formulas A formula is a named expression computed for every note, declared under `formulas` and referenced as `formula.`: ```yaml theme={null} formulas: score: rating * 2 overdue: due < today() ppu: (price / age).toFixed(2) status_label: if(formula.overdue, "late", status) ``` Formulas use the same language as filters, can reference other formulas (no cycles), and return whatever the expression returns — a number, text, a date, a boolean or a list. Once declared, a formula is a column (`formula.score` in a view's `order`, renamable through `properties`), a sort key, and a filter operand (`formula.score >= 8`) — the Obsidian behavior. A base may hold 20 formulas; names are letters, digits and underscores. Values are computed per note when a view runs; the API returns them on `BaseQueryOut.formulas` (note id → name → value) and the MCP note item carries `formulas`. A formula that fails on one note — a strict `date()` over a value that isn't a date — reads as empty for that note only. One rule keeps totals exact: a formula used in a filter, sort, group or summary must have a SQL form. Almost every expression does; the exceptions are constructs the row evaluator can compute but SQL can't, such as `if()` with branches of different types. Such a formula is **display-only**: it still shows in columns, and using it in a filter, sort, group or summary is rejected with a message naming the formula. ### Grouping and summaries A view can group its rows by one property, file attribute or formula (`groupBy: {property, direction}`), and summarize any column (`summaries: {column: Name}`) — Obsidian's semantics: ```yaml theme={null} views: - type: table name: By status groupBy: property: status direction: ASC summaries: rating: Average formula.score: Sum file.mtime: Latest ``` Groups are ordered by their value in `direction` (numbers, then dates, then text — the same order a sort uses), and notes without a value form a **None** group at the end. Dates group by calendar day in your timezone — a `due` property or `file.mtime` gives one group per day, not one per timestamp. Grouping is a leading sort, so the view's own `sort` keys order the rows within each group. The built-in summaries are exactly Obsidian's names: | Column kind | Summaries | | - | - | | Number | `Average`, `Min`, `Max`, `Sum`, `Range`, `Median`, `Stddev` | | Date | `Earliest`, `Latest`, `Range` (milliseconds between them) | | Boolean | `Checked`, `Unchecked` | | Any | `Empty`, `Filled` (the count), `Unique` | Summaries are computed over the rows the view shows — the whole filtered result, capped by the view's `limit` — never just the current page; a grouped view carries each group's summaries on the group plus the grand total. Custom summary formulas (Obsidian's top-level `summaries:` using `values`) are not supported yet and are dropped with a warning on import. The API returns `groups` (`[{key, label, count, summaries}]` in display order), `group_keys` (parallel to `items`) and `summaries` on `BaseQueryOut`; the MCP `query_base` result carries `groups`, `summaries`, and `group` on each item. A view with more than 500 groups is rejected rather than run. Examples: ```text theme={null} status != "done" && file.hasTag("pricing") due <= today() + "1 week" && !done rating >= 4 || file.mtime > now() - "30 days" file.source == "bookmark" && file.domain.endsWith("substack.com") authors.contains("Kahneman") && year.toString().startsWith("20") ``` Limits: 1,000 characters and 256 tokens per expression, 50 filter conditions per base, 20 views, 5 sort keys, `limit` ≤ 500. Queries run under a statement timeout and report a timeout rather than a partial page. ### The `.base` file format Export produces, and import accepts, Obsidian's `.base` YAML: ```yaml theme={null} filters: and: - 'status != "done"' - file.hasTag("reading") properties: status: displayName: Status rating: displayName: Rating views: - type: table name: Queue order: - file.name - status - rating - file.mtime sort: - property: file.mtime direction: DESC limit: 50 - type: cards name: Wall imageProperty: file.preview cardSize: 240 ``` Import is lossy only where Dexi lacks the feature, and says so: custom `summaries` formulas are dropped with a warning, plugin view types (kanban, …) become tables with a warning, unknown keys and unknown summary names are reported, and every unsupported expression — in filters, sort keys, formulas, `groupBy` or `summaries` — is listed at once instead of stopping at the first. `formulas`, per-view `groupBy`, built-in `summaries`, and `list`/`map` views import as-is (their view settings under Dexi's keys). Files are capped at 64 KB. Export of an imported file round-trips: `import(export(x)) == x` for everything Dexi keeps, emitted in Obsidian's key order. In the app: **Import…** and **Export .base** on a base's toolbar. Over the API (JWT-authenticated app routes, not part of the public REST surface): `POST /api/v2/bases/import` and `GET /api/v2/bases/{id}/export`. # Quickstart Source: https://docs.dexi.net/quickstart Connect an assistant or save your first bookmark in two minutes You need a Dexi account — sign up free at [app.dexi.net](https://app.dexi.net/account/register). ## Path 1: connect Claude (MCP) From a terminal with [Claude Code](https://claude.com/claude-code) installed: ```bash theme={null} claude mcp add --transport http --scope user dexi https://mcp.dexi.net/mcp ``` Then run `/mcp` inside Claude Code and pick **dexi** to authenticate — your browser opens a Dexi consent page; approve it and you're connected. Verify it works by asking: > What did I save in Dexi this week? Claude calls the `list_notes` tool and answers from your actual workspace. Using claude.ai, Cursor, ChatGPT, or another client instead? See [Getting Started with MCP](/mcp/getting-started). ## Path 2: save a bookmark (REST) Create a token in [Settings → API tokens](https://app.dexi.net/dashboard/settings) — it's shown once, starting with `dxi_`. Then: ```bash theme={null} curl -X POST https://api.dexi.net/api/v1/bookmarks/save \ -H "Authorization: Bearer dxi_YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/article", "title": "Worth reading"}' ``` The page appears in your workspace as a bookmark-backed note, searchable alongside everything else. Full request/response details: [Save Bookmark](/api/save-bookmark). ## Where to go next * [Authentication](/authentication) — how the two credential kinds work and differ * [MCP tools](/mcp/tools) — everything a connected assistant can do * [Examples](/mcp/examples) — agent flows worth stealing