# Obsidian API Docker service that exposes an HTTP API for the **official Obsidian CLI**. It is meant for: - n8n custom nodes, HTTP Request nodes, and Code nodes - Prism / TypeScript agent harnesses - local automation against a mounted Obsidian vault ## Important: official CLI architecture This uses the real Obsidian CLI from `obsidian.md/help/cli`. That CLI is not an npm package. It ships inside the Obsidian desktop app and requires the app to be running. Therefore this container runs: - official Obsidian desktop app - Xvfb virtual display - official `/opt/Obsidian/obsidian-cli` exposed as `obsidian` - this Starlette HTTP API Because Electron is required, the image is Debian-based and larger than Alpine. ## Security model The HTTP API does **not** accept shell commands and does **not** accept plain command strings. Allowed: ```json { "args": ["read", "path=Inbox/Note.md"] } ``` Rejected: ```json { "command": "read path=Inbox/Note.md" } ``` ```json { "mode": "shell", "args": ["ls -la"] } ``` The service always runs the configured binary directly with `exec` semantics: ```bash obsidian ``` No shell is involved. ### JWT access policies Auth uses signed HS256 JWT bearer tokens. The token itself contains the policy claims. Create a signing secret: ```bash export OBSIDIAN_JWT_SECRET="$(openssl rand -base64 48)" ``` Generate a token with the same secret: ```bash python scripts/create_jwt.py \ --sub n8n \ --vault work \ --path Inbox/n8n/ \ --path Automation.md \ --command create \ --command read \ --command append \ --command search \ --command search:context \ --days 365 ``` Set the same secret for the API: ```env OBSIDIAN_JWT_SECRET=change-this-long-random-secret ``` JWT claims: ```json { "sub": "n8n", "vaults": ["work"], "paths": ["Inbox/n8n/", "Automation.md"], "commands": ["create", "read", "append", "search", "search:context"], "iat": 1760000000, "exp": 1791536000 } ``` Policy claims: - `vaults`: allowed Obsidian vault names, e.g. `["work"]` - `paths`: allowed note/path prefixes, e.g. `["Inbox/n8n/", "Automation.md"]` - `commands`: allowed official CLI commands, e.g. `["read", "create", "append"]` - `exp`: token expiry timestamp - `nbf`: optional not-before timestamp - `iat`: optional issued-at timestamp Use `["*"]` for unrestricted access in a dimension. Example request: ```bash JWT="$(python scripts/create_jwt.py --sub n8n --vault work --path Inbox/n8n/ --command create --days 365)" curl -X POST http://localhost:8080/commands \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"args":["vault=work","create","path=Inbox/n8n/Event.md","content=Hello","overwrite"]}' ``` Path restrictions are enforced before the Obsidian CLI process starts. If a token has path restrictions, commands that read/write notes must include an explicit `path=`, `file=`, `folder=`, `name=` or `to=` argument. ## Start ```bash cp .env.example .env ``` Edit: ```env OBSIDIAN_JWT_SECRET=change-this-long-random-secret OBSIDIAN_VAULT_PATH=/absolute/path/to/your/vault OBSIDIAN_API_PORT=8080 ``` ### Multiple vaults Single vault is the default: ```env OBSIDIAN_VAULT_PATH=/home/daniel/Documents/Obsidian/Main ``` For more vaults, mount a parent folder and register container paths: ```env OBSIDIAN_VAULT_PATH=/home/daniel/Documents/Obsidian/Main OBSIDIAN_VAULTS_ROOT=/home/daniel/Documents/Obsidian OBSIDIAN_VAULTS=main=/vault,work=/vaults/Work,personal=/vaults/Personal ``` Then target a vault with the official CLI prefix: ```json { "args": ["vault=work", "read", "path=Inbox/Note.md"] } ``` If no `vault=` prefix is provided, the first vault in `OBSIDIAN_VAULTS` is used as the default. Start: ```bash docker compose up -d --build ``` Logs: ```bash docker compose logs -f ``` ## API ### Health ```bash curl http://localhost:8080/health ``` ### Run official Obsidian CLI command ```bash curl -X POST http://localhost:8080/commands \ -H "Authorization: Bearer change-me" \ -H "Content-Type: application/json" \ -d '{"args":["version"]}' ``` Create a note: ```bash curl -X POST http://localhost:8080/commands \ -H "Authorization: Bearer change-me" \ -H "Content-Type: application/json" \ -d '{"args":["create","path=Inbox/Hello.md","content=# Hello","overwrite"]}' ``` Read a note: ```bash curl -X POST http://localhost:8080/commands \ -H "Authorization: Bearer change-me" \ -H "Content-Type: application/json" \ -d '{"args":["read","path=Inbox/Hello.md"]}' ``` Search: ```bash curl -X POST http://localhost:8080/commands \ -H "Authorization: Bearer change-me" \ -H "Content-Type: application/json" \ -d '{"args":["search","query=Hello","format=json"]}' ``` ## TypeScript SDK SDK path: ```text sdk/typescript ``` Install locally: ```bash npm install ./sdk/typescript ``` Use in TypeScript / Node / Prism: ```ts import { ObsidianCliClient } from '@obsidian-api/sdk'; const obsidian = new ObsidianCliClient({ baseUrl: 'http://localhost:8080', token: 'change-me', }); await obsidian.create({ path: 'Inbox/Hello.md', content: '# Hello from TypeScript', overwrite: true, }); const note = await obsidian.read({ path: 'Inbox/Hello.md' }); console.log(note.stdout); const results = await obsidian.search('Hello', { format: 'json' }); console.log(JSON.parse(results.stdout)); // Multi-vault: scoped client const work = obsidian.inVault('work'); await work.create({ path: 'Inbox/Work.md', content: 'Hello work', overwrite: true }); ``` n8n options: - real community-node package: [`packages/n8n-nodes-obsidian-api`](packages/n8n-nodes-obsidian-api) - Code node / HTTP examples: [`docs/n8n.md`](docs/n8n.md) Raw escape hatch, still safe because it sends an args array: ```ts await obsidian.runRaw('daily:append', 'content=- [ ] Buy groceries'); ``` ## Official CLI command examples The SDK covers the official command groups from `obsidian.md/help/cli`, including: - General: `help`, `version`, `reload`, `restart` - Files: `create`, `read`, `append`, `prepend`, `move`, `rename`, `delete`, `files`, `folders` - Daily notes: `daily`, `daily:path`, `daily:read`, `daily:append`, `daily:prepend` - Search: `search`, `search:context`, `search:open` - Links: `backlinks`, `links`, `unresolved`, `orphans`, `deadends` - Tags/tasks/properties/templates - Plugins/themes/snippets - Sync/Publish commands - Workspace/tabs/recents - Developer commands: `dev:*`, `eval` ## Development Python API tests: ```bash uv run pytest -q ``` TypeScript SDK typecheck: ```bash cd sdk/typescript npm install npm run typecheck ```