Install
openclaw skills install trilium-etapiUse when interacting with a Trilium Notes server via the ETAPI REST API - creating, reading, updating, searching, or deleting notes, branches, attributes, attachments, day/week/month notes; obtaining auth tokens; or scripting Trilium operations from the shell. Triggers on mentions of Trilium, ETAPI, noteId, branchId, or any /etapi/ URL.
openclaw skills install trilium-etapiETAPI is the external REST API for Trilium Notes (Trilium ≥ 0.50). All requests require token auth. Resources are addressed by 12-char IDs: noteId, branchId, attributeId, attachmentId.
Core concepts:
noteIdDo not use for scripts running inside Trilium — use the frontend/backend Script API directly, no HTTP needed.
All examples below assume:
export TRILIUM_URL="http://localhost:8080" # no trailing /etapi
export TRILIUM_TOKEN="<generate via Trilium → Options → ETAPI>"
If you only have a password (and the server allows password login), exchange it for a token:
curl -sX POST "$TRILIUM_URL/etapi/auth/login" \
-H 'Content-Type: application/json' \
-d '{"password":"YOUR_PASSWORD"}' | jq -r .authToken
Three auth styles (pick one, in the Authorization header):
Authorization: $TRILIUM_TOKEN — raw token (works on every version)Authorization: Bearer $TRILIUM_TOKEN — Bearer form (v0.93+)Authorization: Basic $(echo -n "etapi:$TRILIUM_TOKEN" | base64) — Basic auth (v0.56+)| Operation | Method | Path |
|---|---|---|
| Health / version | GET | /etapi/app-info |
| Search notes | GET | /etapi/notes?search=... |
| Read note metadata | GET | /etapi/notes/{noteId} |
| Read note content | GET | /etapi/notes/{noteId}/content |
| Write note content | PUT | /etapi/notes/{noteId}/content (text/plain) |
| Create note | POST | /etapi/create-note |
| Patch note metadata | PATCH | /etapi/notes/{noteId} |
| Delete note | DELETE | /etapi/notes/{noteId} |
| Export subtree as ZIP | GET | /etapi/notes/{noteId}/export?format=html|markdown |
| Import ZIP | POST | /etapi/notes/{noteId}/import |
| Create / move branch | POST | /etapi/branches |
| Create attribute | POST | /etapi/attributes |
| Create attachment | POST | /etapi/attachments |
| Day note (auto-create) | GET | /etapi/calendar/days/{YYYY-MM-DD} |
| Inbox | GET | /etapi/inbox/{YYYY-MM-DD} |
| Trigger DB backup | PUT | /etapi/backup/{name} |
Full endpoint, parameter, and schema reference: api-reference.md.
All snippets below assume TRILIUM_URL and TRILIUM_TOKEN are exported.
curl -s "$TRILIUM_URL/etapi/app-info" -H "Authorization: $TRILIUM_TOKEN" | jq
curl -sX POST "$TRILIUM_URL/etapi/create-note" \
-H "Authorization: $TRILIUM_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"parentNoteId": "root",
"title": "Hello from ETAPI",
"type": "text",
"content": "<p>Created via curl</p>"
}' | jq '{noteId: .note.noteId, branchId: .branch.branchId}'
Returns NoteWithBranch: the new note plus the branch mounting it.
Content lives on a separate endpoint and the body is text/plain even when the content is HTML:
curl -sX PUT "$TRILIUM_URL/etapi/notes/$NOTE_ID/content" \
-H "Authorization: $TRILIUM_TOKEN" \
-H 'Content-Type: text/plain' \
--data-binary @body.html
# fulltext + label
curl -sG "$TRILIUM_URL/etapi/notes" \
-H "Authorization: $TRILIUM_TOKEN" \
--data-urlencode 'search=tolkien #book' \
--data-urlencode 'limit=10' | jq '.results[] | {noteId, title}'
Search syntax matches the Trilium UI search bar. Common forms: #tag, #tag=value, note.content *= "...", ~relation.title = "...".
curl -sX POST "$TRILIUM_URL/etapi/attributes" \
-H "Authorization: $TRILIUM_TOKEN" \
-H 'Content-Type: application/json' \
-d "{
\"noteId\": \"$NOTE_ID\",
\"type\": \"label\",
\"name\": \"book\",
\"value\": \"\",
\"isInheritable\": false
}"
TODAY=$(date +%F)
curl -s "$TRILIUM_URL/etapi/calendar/days/$TODAY" \
-H "Authorization: $TRILIUM_TOKEN" | jq -r .noteId
curl -sX POST "$TRILIUM_URL/etapi/branches" \
-H "Authorization: $TRILIUM_TOKEN" \
-H 'Content-Type: application/json' \
-d "{
\"noteId\": \"$CHILD_ID\",
\"parentNoteId\": \"$NEW_PARENT_ID\",
\"prefix\": \"\",
\"notePosition\": 100,
\"isExpanded\": false
}"
curl -s "$TRILIUM_URL/etapi/notes/$NOTE_ID/export?format=markdown" \
-H "Authorization: $TRILIUM_TOKEN" -o subtree.zip
# Whole document: use noteId="root"
curl -sX PUT "$TRILIUM_URL/etapi/backup/now" \
-H "Authorization: $TRILIUM_TOKEN"
# Writes to dataDirectory/backup/backup-now.db
Bearer prefix to Authorization unless you're on v0.93+ and explicitly want Bearer. The raw token form works on every version.PUT /notes/{id}/content body is text/plain (NOT text/html). The OpenAPI spec is explicit on this.PATCH /notes/{id} only patches a subset: title, type, mime, dateCreated, utcDateCreated. Use PUT .../content to change content.PATCH /branches/{id} only patches prefix and notePosition. To re-parent a note you must DELETE the branch and POST a new one.PATCH /attributes/{id}: labels can only patch value and position; relations can only patch position. Anything else means delete + recreate.notePosition defaults to step 10 (10/20/30...). To insert in front, use 5; to push to the end, use a large value like 1000000. After bulk reorders, call POST /refresh-note-ordering/{parentNoteId} so connected clients refresh.[a-zA-Z0-9_]{4,32}. root is the special root noteId.{status, code, message}. Branch on the stable code constant (e.g. NOTE_IS_PROTECTED), not on the human-readable message./auth/login is rate-limited. Too many failures returns 429 and temporarily blacklists the client IP.| type | Use | mime required? |
|---|---|---|
text | Rich text (HTML) | no |
code | Source code | yes (e.g. text/x-python) |
file | Binary file | yes |
image | Image | yes (e.g. image/png) |
search | Saved search | no |
book | Folder-style container | no |
relationMap | Relation map | no |
render | Custom renderer | no |
You may also see these on read: noteMap, mermaid, webView, shortcut, doc, contentWidget, launcher.
A typical inbox / capture flow — append arbitrary text to today's day note:
TODAY=$(date +%F)
NOTE_ID=$(curl -s "$TRILIUM_URL/etapi/calendar/days/$TODAY" \
-H "Authorization: $TRILIUM_TOKEN" | jq -r .noteId)
OLD=$(curl -s "$TRILIUM_URL/etapi/notes/$NOTE_ID/content" \
-H "Authorization: $TRILIUM_TOKEN")
NEW="${OLD}<p>$(date +%H:%M) — $1</p>"
curl -sX PUT "$TRILIUM_URL/etapi/notes/$NOTE_ID/content" \
-H "Authorization: $TRILIUM_TOKEN" \
-H 'Content-Type: text/plain' \
--data-binary "$NEW"