- Expose Obsidian CLI property commands via the n8n Obsidian API node - Add a new Property resource with operations: Set, Read, Remove, List - Implement correct args mapping for property:set/property:read/property:remove/properties - Return raw CLI stdout for property read/list for easier downstream parsing - Update node README with frontmatter/property usage examples - Bump package version to 0.2.5
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-cliexposed asobsidian - 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:
{
"args": ["read", "path=Inbox/Note.md"]
}
Rejected:
{
"command": "read path=Inbox/Note.md"
}
{
"mode": "shell",
"args": ["ls -la"]
}
The service always runs the configured binary directly with exec semantics:
obsidian <args...>
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:
export OBSIDIAN_JWT_SECRET="$(openssl rand -base64 48)"
Generate a token with the same secret:
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:
OBSIDIAN_JWT_SECRET=change-this-long-random-secret
JWT claims:
{
"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 timestampnbf: optional not-before timestampiat: optional issued-at timestamp
Use ["*"] for unrestricted access in a dimension.
Example request:
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
cp .env.example .env
Edit:
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:
OBSIDIAN_VAULT_PATH=/home/daniel/Documents/Obsidian/Main
For more vaults, mount a parent folder and register container paths:
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:
{
"args": ["vault=work", "read", "path=Inbox/Note.md"]
}
If no vault=<name> prefix is provided, the first vault in OBSIDIAN_VAULTS is used as the default.
Start:
docker compose up -d --build
Logs:
docker compose logs -f
API
Health
curl http://localhost:8080/health
Run official Obsidian CLI command
curl -X POST http://localhost:8080/commands \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"args":["version"]}'
Create a note:
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:
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:
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:
sdk/typescript
Install locally:
npm install ./sdk/typescript
Use in TypeScript / Node / Prism:
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 - Code node / HTTP examples:
docs/n8n.md
Raw escape hatch, still safe because it sends an args array:
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:
uv run pytest -q
TypeScript SDK typecheck:
cd sdk/typescript
npm install
npm run typecheck