Trilium
by @yanickxia
Use when interacting with a Trilium Notes server via the ETAPI REST API - creating, reading, updating, searching, or deleting notes, branches, attributes, at...
clawhub install trilium-etapiπ About This Skill
name: trilium-etapi description: Use 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.
Trilium ETAPI
Overview
ETAPI 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:
noteIdWhen to Use
Do not use for scripts running *inside* Trilium β use the frontend/backend Script API directly, no HTTP needed.
Setup
All examples below assume:
export TRILIUM_URL="http://localhost:8080" # no trailing /etapi
export TRILIUM_TOKEN=""
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):
1. Authorization: $TRILIUM_TOKEN β raw token (works on every version)
2. Authorization: Bearer $TRILIUM_TOKEN β Bearer form (v0.93+)
3. Authorization: Basic $(echo -n "etapi:$TRILIUM_TOKEN" | base64) β Basic auth (v0.56+)
Quick Reference
| 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.
Core Patterns (curl + jq)
All snippets below assume TRILIUM_URL and TRILIUM_TOKEN are exported.
1. Health check
curl -s "$TRILIUM_URL/etapi/app-info" -H "Authorization: $TRILIUM_TOKEN" | jq
2. Create a text note
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": "Created via curl
"
}' | jq '{noteId: .note.noteId, branchId: .branch.branchId}'
Returns NoteWithBranch: the new note plus the branch mounting it.
3. Replace note content
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
4. Search
# 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 = "...".
5. Tag a note
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
}"
6. Day note (auto-created on demand)
TODAY=$(date +%F)
curl -s "$TRILIUM_URL/etapi/calendar/days/$TODAY" \
-H "Authorization: $TRILIUM_TOKEN" | jq -r .noteId
7. Clone a note to another location (branch)
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
}"
8. Export a subtree as ZIP
curl -s "$TRILIUM_URL/etapi/notes/$NOTE_ID/export?format=markdown" \
-H "Authorization: $TRILIUM_TOKEN" -o subtree.zip
Whole document: use noteId="root"
9. Trigger a server-side backup
curl -sX PUT "$TRILIUM_URL/etapi/backup/now" \
-H "Authorization: $TRILIUM_TOKEN"
Writes to dataDirectory/backup/backup-now.db
Common Pitfalls
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.Note Types
| 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.
Workflow: Append to today's day note
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}
$(date +%H:%M) β $1
"curl -sX PUT "$TRILIUM_URL/etapi/notes/$NOTE_ID/content" \
-H "Authorization: $TRILIUM_TOKEN" \
-H 'Content-Type: text/plain' \
--data-binary "$NEW"
When You Need More
β‘ When to Use
βοΈ Configuration
All examples below assume:
export TRILIUM_URL="http://localhost:8080" # no trailing /etapi
export TRILIUM_TOKEN=""
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):
1. Authorization: $TRILIUM_TOKEN β raw token (works on every version)
2. Authorization: Bearer $TRILIUM_TOKEN β Bearer form (v0.93+)
3. Authorization: Basic $(echo -n "etapi:$TRILIUM_TOKEN" | base64) β Basic auth (v0.56+)