obsidian-rest-api
Call the Obsidian Local REST API directly (over HTTP) for vault operations the mcp__obsidian__* tools do NOT expose — move/rename a note, overwrite a whole file atomically (PUT), act on the currently-open active file, run an Obsidian command, open a note in the UI, list all tags,
Install
npx skills add https://github.com/davepoon/buildwithclaude/tree/main/plugins/all-skills/skills/obsidian-rest-api
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install davepoon-buildwithclaude@llmmart
git clone https://github.com/davepoon/buildwithclaude.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole davepoon/buildwithclaude collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Obsidian Local REST API
The connected obsidian MCP server exposes only a subset of the Obsidian
Local REST API
(plugin obsidian-local-rest-api). This skill provides the full API surface plus
an authenticated request wrapper, so a missing MCP method is called over HTTP
instead of being worked around with hacks (e.g. delete+recreate to rename a note).
When to Use This Skill
Use the mcp__obsidian__* tools first for read, append, patch, delete, and search.
Fall back to this skill only for operations that have no MCP tool:
- Move / rename a note (preserves history, updates internal links)
- Overwrite a whole file atomically (PUT) instead of delete+recreate
- Act on the currently-open "active" file in the Obsidian UI
- Run an Obsidian command from the command palette
- Open / focus a note in the UI
- List all vault tags with counts
- Create/update/delete date-specific periodic notes
What This Skill Does
- Resolves the API host, port, and key from the connected obsidian MCP server
config (
~/.claude.json) orOBSIDIAN_*env vars — no hardcoded secrets. - Handles the plugin's self-signed TLS certificate.
- Exposes every endpoint of the Local REST API (see
references/api_reference.md), with the header enums (Operation, Target-Type, Target-Scope), the customMOVEcontract, and the search (JsonLogic/Dataview) formats.
How to Use
Call the wrapper scripts/olrapi.sh <METHOD> <path> [curl args...]:
S=scripts/olrapi.sh # adjust to the skill's install path
# rename/move a note (the most common reason to reach for this skill)
"$S" MOVE "/vault/Path/To/Old Name.md" -H 'Destination: Path/To/New Name.md'
# move into a folder, keeping the filename (trailing slash on Destination)
"$S" MOVE "/vault/Inbox/todo.md" -H 'Destination: Archive/'
# atomically overwrite a whole note
"$S" PUT "/vault/Path/Note.md" -H 'Content-Type: text/markdown' --data-binary @/tmp/body.md
# read a note as structured JSON (frontmatter + tags + stat)
"$S" GET "/vault/Path/Note.md" -H 'Accept: application/vnd.olrapi.note+json'
# list tags, run a command, open a note in the UI
"$S" GET /tags/
"$S" POST "/commands/editor:toggle-bold/"
"$S" POST "/open/Path/Note.md?newLeaf=true"
The wrapper prints <<HTTP nnn>> after the body. Success: 200/204.
On MOVE, 409 means the destination exists — add -H 'Allow-Overwrite: true' to force.
For non-trivial calls, load references/api_reference.md.
Path & encoding rules
{filename}is vault-relative (no leading slash on the vault path).- Percent-encode non-ASCII in URL paths and in the
MOVEDestinationheader (e.g.r%C3%A9sum%C3%A9.md).Destinationrejects absolute (/…) paths. - Target a sub-part of a note with
Target-Type(heading|block|frontmatter)Targetheaders on GET/PATCH/POST.
Example
User: "Rename 3-Resources/Draft.md to 3-Resources/Final.md in my vault."
Output:
scripts/olrapi.sh MOVE "/vault/3-Resources/Draft.md" \
-H 'Destination: 3-Resources/Final.md'
# <<HTTP 204>> — moved, history preserved, internal links updated
Tips
- Regenerate the reference against the live plugin if it was updated:
scripts/olrapi.sh GET /openapi.yaml.GET /shows the plugin version. - Prefer
PUTover delete+recreate for whole-file overwrites — it is atomic and keeps the file's identity. - The API serves HTTPS on port 27124 (self-signed →
curl -k) and HTTP on 27123.
Files (buildwithclaude)
-
references
-
api_reference.md 7.7 KB
# Obsidian Local REST API — full endpoint reference Plugin: **Local REST API with MCP** (`obsidian-local-rest-api`), spec `openapi 3.2.0`, plugin v4.1.x. 16 paths. This file is the authoritative surface to fall back on when an MCP tool is missing. ## Connection & auth - Transport: HTTPS on port **27124** (self-signed cert → `curl -k`). Plain HTTP is served on **27123**. - Auth: `Authorization: Bearer <API_KEY>` on every request (except `/` and `/obsidian-local-rest-api.crt`). - Credentials live in the connected MCP server env (`~/.claude.json` → obsidian server → `OBSIDIAN_API_KEY` / `OBSIDIAN_HOST` / `OBSIDIAN_PORT`). `scripts/olrapi.sh` resolves them automatically. - Servers template: `https://{host}:{port}`. ## Common headers (content-targeting) Many read/patch endpoints accept these headers to target a sub-part of a note instead of the whole file: - `Target-Type`: `heading` | `block` | `frontmatter` - `Target`: the section to target (heading text, block id, or frontmatter key). Required when `Target-Type` set. - `Target-Delimiter`: nested-heading delimiter, default `::` (e.g. `Heading 1::Subheading`). - `Target-Scope` (PATCH only): `content` | `marker` | `markerAndContent`. PATCH-specific control headers: - `Operation` (required): `append` | `prepend` | `replace` - `Create-Target-If-Missing`: `true` | `false` - `Reject-If-Content-Preexists`: `true` | `false` - `Trim-Target-Whitespace`: `true` | `false` Body content types: `text/markdown` for content; `application/json` for structured PATCH (e.g. frontmatter values); PUT accepts `*/*` (arbitrary file bytes) or `text/markdown`. --- ## Endpoints ### Server / meta | Method | Path | Purpose | Auth | |---|---|---|---| | GET | `/` | Server status + plugin/obsidian versions + cert info | no | | GET | `/openapi.yaml` | This OpenAPI spec | yes | | GET | `/obsidian-local-rest-api.crt` | The API's TLS certificate (for pinning/trust) | no | ### Active file (the note currently open in the Obsidian UI) | Method | Path | Purpose | |---|---|---| | GET | `/active/` | Read active file content (supports Target-* headers) | | PUT | `/active/` | Replace active file content (body `*/*` or `text/markdown`) | | POST | `/active/` | Append to active file (body `text/markdown`, supports targeting) | | PATCH | `/active/` | Partial update (Operation + Target-* required) | | DELETE | `/active/` | Delete the active file | ### Vault files | Method | Path | Purpose | |---|---|---| | GET | `/vault/` | List files in vault root | | GET | `/vault/{pathToDirectory}/` | List files in a directory (trailing slash) | | GET | `/vault/{filename}` | Read a file. Default returns raw markdown; send `Accept: application/vnd.olrapi.note+json` for NoteJson (content+frontmatter+tags+stat+path). Target-* headers supported. | | PUT | `/vault/{filename}` | **Create OR overwrite** a file (body `*/*` or `text/markdown`). The clean way to replace a whole note. | | POST | `/vault/{filename}` | Append to a new/existing file (body `text/markdown`) | | PATCH | `/vault/{filename}` | Partial update (Operation + Target-* required) — insert under a heading, set a frontmatter key, replace a block | | DELETE | `/vault/{filename}` | Delete a file | | **MOVE** | `/vault/{filename}` | **Rename/move** a file (see below). Custom HTTP method. | #### MOVE (rename / move) — the headline REST-only capability Custom HTTP method `MOVE` on `/vault/{filename}`. Preserves file history and updates internal links. - `Destination` header (**required**): new vault-relative path. `..` allowed if result stays in vault; absolute `/`-paths rejected. Trailing slash keeps source filename (e.g. `archive/` moves `notes/todo.md` → `archive/todo.md`). Percent-encode non-ASCII (`r%C3%A9sum%C3%A9.md`). - `Allow-Overwrite` header: `true` | `false` (default `false` → `409` if destination exists). - Responses: `204` success (with `Content-Location` header = new path), `400` bad/missing Destination or path escapes vault, `404` source not found, `409` destination exists. ```bash scripts/olrapi.sh MOVE "/vault/3-Resources/Investigate/Thread-Keeper/Old.md" \ -H 'Destination: 3-Resources/Investigate/Thread-Keeper/New.md' ``` ### Periodic notes (`{period}` = `daily` | `weekly` | `monthly` | `quarterly` | `yearly`) | Method | Path | Purpose | |---|---|---| | GET/PUT/POST/PATCH/DELETE | `/periodic/{period}/` | CRUD on the CURRENT periodic note for that period | | GET/PUT/POST/PATCH/DELETE | `/periodic/{period}/{year}/{month}/{day}/` | CRUD on the periodic note for a SPECIFIC date | Same body/targeting semantics as vault endpoints. ### Search | Method | Path | Purpose | |---|---|---| | POST | `/search/simple/` | Text search. Query params: `query` (required), `contextLength` (optional). | | POST | `/search/` | Advanced search. Body content type selects the engine: `application/vnd.olrapi.jsonlogic+json` (JsonLogic, supports `glob`/`regexp` ops over `frontmatter.*`, `tags`, `content`) — also Dataview DQL via its own content type. Returns matching filenames + results. | JsonLogic example (notes whose frontmatter.url matches): ```json { "or": [ {"===": [{"var": "frontmatter.url"}, "https://x/"]}, {"glob": [{"var": "frontmatter.url-glob"}, "https://x/*"]} ]} ``` ### Tags | Method | Path | Purpose | |---|---|---| | GET | `/tags/` | List all tags in the vault with counts. | ### Commands (Obsidian command palette) | Method | Path | Purpose | |---|---|---| | GET | `/commands/` | List available command IDs + names. | | POST | `/commands/{commandId}/` | Execute a command by id (e.g. `editor:toggle-bold`, plugin commands). | ### Open in UI | Method | Path | Purpose | |---|---|---| | POST | `/open/{filename}` | Open a file in the Obsidian UI. Query `newLeaf=true` opens in a new pane. | ### MCP passthrough (usually ignore — we already have MCP tools) | Method | Path | Purpose | |---|---|---| | POST | `/mcp/` | JSON-RPC 2.0 to the plugin's own MCP server. | | GET | `/mcp/` | SSE stream for an MCP session (`Mcp-Session-Id` header). | --- ## MCP-tool ↔ REST coverage (when to fall back) Already covered by `mcp__obsidian__*` tools (prefer these): `list_files_in_vault` → GET /vault/ · `list_files_in_dir` → GET /vault/{dir}/ · `get_file_contents` → GET /vault/{file} · `batch_get_file_contents` → N× GET · `append_content` → POST /vault/{file} · `patch_content` → PATCH /vault/{file} · `delete_file` → DELETE /vault/{file} · `simple_search` → POST /search/simple/ · `complex_search` → POST /search/ · `get_periodic_note`/`get_recent_periodic_notes` → GET /periodic/... · `get_recent_changes` (plugin helper). **REST-only (no MCP tool → use `olrapi.sh`):** - **MOVE `/vault/{filename}`** — rename/move a note (history + link updates). No MCP equivalent. - **PUT `/vault/{filename}`** — atomic whole-file create/overwrite. MCP only appends/patches; overwriting via MCP needs delete+recreate. Use PUT instead. - **Active-file ops** (`/active/` GET/PUT/POST/PATCH/DELETE) — act on the note open in the UI. - **`/commands/` + POST `/commands/{id}/`** — run Obsidian commands. - **POST `/open/{filename}`** — open/focus a note in the UI. - **GET `/tags/`** — vault-wide tag list with counts. - **Periodic PUT/POST/PATCH/DELETE** and **specific-date periodic** endpoints. ## Response notes - NoteJson (with `Accept: application/vnd.olrapi.note+json`): `{ path, content, frontmatter, tags, stat:{ctime,mtime,size} }`. - Errors: `{ "errorCode": <int>, "message": <str> }` (schema `Error`). - Success without body: `204`. Bad targeting/headers: `400`. Missing file: `404`. Method not allowed on target: `405`. MOVE destination exists: `409`. ## Source of truth Regenerate against the live instance anytime: `scripts/olrapi.sh GET /openapi.yaml`. The plugin version and any `apiExtensions` show in `GET /`.
-
-
scripts
-
olrapi.sh 1.9 KB
#!/usr/bin/env bash # olrapi.sh — authenticated wrapper around the Obsidian Local REST API. # Resolves host/port/API-key from the connected obsidian MCP server config # (~/.claude.json), so no secrets are hardcoded. # # Usage: # olrapi.sh <METHOD> <path> [extra curl args...] # olrapi.sh GET /tags/ # olrapi.sh GET /vault/Note.md # olrapi.sh PUT /vault/New.md --data-binary @file.md -H 'Content-Type: text/markdown' # olrapi.sh MOVE "/vault/old/Note.md" -H 'Destination: archive/Note.md' # # Prints the HTTP status to stderr and the body to stdout. set -euo pipefail CFG="${OLRAPI_CONFIG:-$HOME/.claude.json}" read -r HOST PORT KEY < <(python3 - "$CFG" <<'PY' import json, sys cfg = sys.argv[1] def walk(o): if isinstance(o, dict): if 'OBSIDIAN_API_KEY' in o: yield o for v in o.values(): yield from walk(v) elif isinstance(o, list): for v in o: yield from walk(v) try: d = json.load(open(cfg)) except Exception: d = {} env = next(walk(d), {}) host = (env.get('OBSIDIAN_HOST') or 'http://127.0.0.1').replace('https://','').replace('http://','') port = env.get('OBSIDIAN_PORT') or '27123' key = env.get('OBSIDIAN_API_KEY') or '' # env vars override config (useful on other machines) import os host = os.environ.get('OBSIDIAN_HOST', host).replace('https://','').replace('http://','') port = os.environ.get('OBSIDIAN_PORT', port) key = os.environ.get('OBSIDIAN_API_KEY', key) print(host, port, key) PY ) if [[ -z "${KEY:-}" ]]; then echo "olrapi: could not resolve OBSIDIAN_API_KEY (checked $CFG and env)" >&2 exit 2 fi METHOD="$1"; PATH_="$2"; shift 2 # 27124 is the TLS port for the Local REST API; -k because it uses a self-signed cert. BASE="https://${HOST}:27124" [[ "$PORT" == "27123" || "$PORT" == "27124" ]] || BASE="https://${HOST}:${PORT}" curl -sk -X "$METHOD" "${BASE}${PATH_}" \ -H "Authorization: Bearer ${KEY}" \ -w '\n<<HTTP %{http_code}>>\n' \ "$@"
-
-
SKILL.md 4 KB
--- name: obsidian-rest-api category: development-code license: MIT description: Call the Obsidian Local REST API directly (over HTTP) for vault operations the mcp__obsidian__* tools do NOT expose — move/rename a note, overwrite a whole file atomically (PUT), act on the currently-open active file, run an Obsidian command, open a note in the UI, list all tags, or do date-specific periodic-note CRUD. Prefer the mcp__obsidian__* tools for plain read/append/patch/delete/search; fall back to this skill only when the required method is missing from MCP. --- # Obsidian Local REST API The connected `obsidian` MCP server exposes only a subset of the Obsidian [Local REST API](https://coddingtonbear.github.io/obsidian-local-rest-api/) (plugin `obsidian-local-rest-api`). This skill provides the full API surface plus an authenticated request wrapper, so a missing MCP method is called over HTTP instead of being worked around with hacks (e.g. delete+recreate to rename a note). ## When to Use This Skill Use the `mcp__obsidian__*` tools first for read, append, patch, delete, and search. Fall back to this skill only for operations that have **no MCP tool**: - Move / rename a note (preserves history, updates internal links) - Overwrite a whole file atomically (PUT) instead of delete+recreate - Act on the currently-open "active" file in the Obsidian UI - Run an Obsidian command from the command palette - Open / focus a note in the UI - List all vault tags with counts - Create/update/delete date-specific periodic notes ## What This Skill Does 1. Resolves the API host, port, and key from the connected obsidian MCP server config (`~/.claude.json`) or `OBSIDIAN_*` env vars — no hardcoded secrets. 2. Handles the plugin's self-signed TLS certificate. 3. Exposes every endpoint of the Local REST API (see `references/api_reference.md`), with the header enums (Operation, Target-Type, Target-Scope), the custom `MOVE` contract, and the search (JsonLogic/Dataview) formats. ## How to Use Call the wrapper `scripts/olrapi.sh <METHOD> <path> [curl args...]`: ```bash S=scripts/olrapi.sh # adjust to the skill's install path # rename/move a note (the most common reason to reach for this skill) "$S" MOVE "/vault/Path/To/Old Name.md" -H 'Destination: Path/To/New Name.md' # move into a folder, keeping the filename (trailing slash on Destination) "$S" MOVE "/vault/Inbox/todo.md" -H 'Destination: Archive/' # atomically overwrite a whole note "$S" PUT "/vault/Path/Note.md" -H 'Content-Type: text/markdown' --data-binary @/tmp/body.md # read a note as structured JSON (frontmatter + tags + stat) "$S" GET "/vault/Path/Note.md" -H 'Accept: application/vnd.olrapi.note+json' # list tags, run a command, open a note in the UI "$S" GET /tags/ "$S" POST "/commands/editor:toggle-bold/" "$S" POST "/open/Path/Note.md?newLeaf=true" ``` The wrapper prints `<<HTTP nnn>>` after the body. Success: `200`/`204`. On `MOVE`, `409` means the destination exists — add `-H 'Allow-Overwrite: true'` to force. For non-trivial calls, load `references/api_reference.md`. ### Path & encoding rules - `{filename}` is vault-relative (no leading slash on the vault path). - Percent-encode non-ASCII in URL paths and in the `MOVE` `Destination` header (e.g. `r%C3%A9sum%C3%A9.md`). `Destination` rejects absolute (`/…`) paths. - Target a sub-part of a note with `Target-Type` (`heading`|`block`|`frontmatter`) + `Target` headers on GET/PATCH/POST. ## Example **User**: "Rename `3-Resources/Draft.md` to `3-Resources/Final.md` in my vault." **Output**: ```bash scripts/olrapi.sh MOVE "/vault/3-Resources/Draft.md" \ -H 'Destination: 3-Resources/Final.md' # <<HTTP 204>> — moved, history preserved, internal links updated ``` ## Tips - Regenerate the reference against the live plugin if it was updated: `scripts/olrapi.sh GET /openapi.yaml`. `GET /` shows the plugin version. - Prefer `PUT` over delete+recreate for whole-file overwrites — it is atomic and keeps the file's identity. - The API serves HTTPS on port 27124 (self-signed → `curl -k`) and HTTP on 27123.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.