# TriliumNext ETAPI Complete API Reference ## Overview ETAPI is TriliumNext's public/external REST API available since Trilium v0.50. **Base URLs:** - `http://localhost:37740/etapi` - `http://localhost:8080/etapi` **API Version:** 1.0.0 **License:** Apache 2.0 ## Authentication All operations require authentication using one of these methods: ### 1. ETAPI Token Authentication (Recommended) ```http GET /etapi/app-info Authorization: ``` OR (since v0.93.0): ```http GET /etapi/app-info Authorization: Bearer ``` ### 2. Basic Authentication (since v0.56) ```http GET /etapi/app-info Authorization: Basic ``` **Note:** Password must be the ETAPI token (NOT your Trilium password). ### 3. Get Token via API ```http POST /etapi/auth/login Content-Type: application/json { "password": "your_trilium_password" } ``` **Response:** ```json { "authToken": "Bc4bFn0Ffiok_4NpbVCDnFz7B2WU+pdhW8B5Ne3DiR5wXrEyqdjgRIsk=" } ``` --- ## Complete API Endpoints ### Authentication #### Login - **POST** `/auth/login` - **Description:** Get an ETAPI token based on password - **Security:** None (public endpoint) - **Request Body:** ```json { "password": "string" } ``` - **Responses:** - `201`: Auth token created - `429`: Client IP blacklisted (too many failed attempts) --- ### Application Information #### Get App Info - **GET** `/app-info` - **Description:** Get application information - **Response:** ```json { "appVersion": "0.91.0", "dbVersion": 231, "syncVersion": 25, "buildDate": "2022-02-09T22:52:36+01:00", "buildRevision": "23daaa2387a0655685377f0a541d154aeec2aae8", "dataDirectory": "/home/user/data", "clipperProtocolVersion": "1.0", "utcDateTime": "2022-03-07T21:54:25.277Z" } ``` #### Get Metrics - **GET** `/etapi/metrics` - **Description:** Get Prometheus-format metrics for monitoring - **Query Parameters:** - `format`: `json` or `prometheus` (default: prometheus) - **Response:** Metrics data including note counts, db stats, etc. --- ### Notes Management #### Create Note - **POST** `/create-note` - **Description:** Create a note and place it into the note tree - **Request Body:** ```json { "parentNoteId": "root", "title": "My Note", "type": "text", "mime": "text/html", "content": "

Hello World

", "notePosition": 10, "prefix": "", "isExpanded": false, "noteId": "customId123", "branchId": "customBranchId", "utcDateCreated": "2021-12-31 19:18:11.930Z", "utcDateModified": "2021-12-31 19:18:11.930Z" } ``` - **Required Fields:** `parentNoteId`, `title`, `type`, `content` - **Optional Fields:** `notePosition`, `prefix`, `isExpanded`, `noteId`, `branchId`, timestamps - **Note Types:** - `text` - Rich text notes - `code` - Code notes (requires `mime`) - `file` - File attachments (requires `mime`) - `image` - Image notes (requires `mime`) - `search` - Saved search - `book` - Book/container note - `relationMap` - Relation map - `render` - Render note - `noteMap` - Note map - `mermaid` - Mermaid diagrams - `webView` - Web view - `shortcut` - Shortcut - `doc` - Document - `contentWidget` - Content widget - `launcher` - Launcher - `canvas` - Canvas note - **Response:** `201` with `NoteWithBranch` object #### Search Notes - **GET** `/notes` - **Description:** Search notes using query syntax - **Query Parameters:** - `search` (required): Search query string - `ancestorNoteId`: Search in subtree only - `fastSearch`: Boolean for fast search mode - `includeArchivedNotes`: Include archived notes (default: false) - `orderBy`: Field to order by (e.g., `title`, `dateModified`) - `orderDirection`: `asc` or `desc` - `limit`: Maximum results (default: 10) - `debug`: Enable debug info - **Response:** Array of note objects #### Get Note - **GET** `/notes/{noteId}` - **Description:** Get note metadata by ID - **Path Parameters:** - `noteId`: Note ID - **Response:** Note object with metadata #### Get Note Content - **GET** `/notes/{noteId}/content` - **Description:** Get note content (HTML/text for text notes, binary for files/images) - **Path Parameters:** - `noteId`: Note ID - **Response:** Note content (content-type varies by note type) #### Update Note Content - **PUT** `/notes/{noteId}/content` - **Description:** Update note content - **Path Parameters:** - `noteId`: Note ID - **Request Body:** Raw content (HTML for text notes, binary for files) - **Response:** `204` No Content #### Update Note Metadata - **PATCH** `/notes/{noteId}` - **Description:** Update note metadata (title, type, mime, etc.) - **Path Parameters:** - `noteId`: Note ID - **Request Body:** ```json { "title": "Updated Title", "type": "text", "mime": "text/html" } ``` - **Response:** `200` with updated note object #### Delete Note - **DELETE** `/notes/{noteId}` - **Description:** Delete note and all its branches - **Path Parameters:** - `noteId`: Note ID - **Response:** `204` No Content - **Note:** Deletes all clones/branches of the note #### Export Note - **GET** `/notes/{noteId}/export` - **Description:** Export note as ZIP file (with optional subtree) - **Path Parameters:** - `noteId`: Note ID (use "root" to export entire tree) - **Query Parameters:** - `format`: `html` or `markdown`/`md` - **Response:** ZIP file download --- ### Branches Management Branches represent note clones/placements in the tree. A single note can exist in multiple locations via different branches. #### Create Branch - **POST** `/branches` - **Description:** Create a branch (clone a note to another location) - **Request Body:** ```json { "noteId": "existingNoteId", "parentNoteId": "targetParentId", "prefix": "Branch Prefix", "notePosition": 10, "isExpanded": false, "branchId": "customBranchId" } ``` - **Required Fields:** `noteId`, `parentNoteId` - **Response:** `201` with Branch object #### Get Branch - **GET** `/branches/{branchId}` - **Description:** Get branch by ID - **Path Parameters:** - `branchId`: Branch ID - **Response:** Branch object #### Update Branch - **PATCH** `/branches/{branchId}` - **Description:** Update branch (prefix, notePosition) - **Path Parameters:** - `branchId`: Branch ID - **Request Body:** ```json { "prefix": "New Prefix", "notePosition": 20, "isExpanded": true } ``` - **Response:** `200` with updated branch - **Note:** Only `prefix`, `notePosition`, and `isExpanded` can be updated. For other properties, delete and recreate. #### Set Branch Prefix - **PATCH** `/branches/{branchId}/set-prefix` - **Description:** Set branch prefix - **Path Parameters:** - `branchId`: Branch ID - **Request Body:** ```json { "prefix": "New Prefix" } ``` #### Move Branch to Parent - **POST** `/branches/{branchId}/set-note-to-parent` - **Description:** Move branch to a different parent - **Path Parameters:** - `branchId`: Branch ID - **Request Body:** ```json { "parentNoteId": "newParentId" } ``` #### Delete Branch - **DELETE** `/branches/{branchId}` - **Description:** Delete branch (removes note from this tree location) - **Path Parameters:** - `branchId`: Branch ID - **Response:** `204` No Content - **Note:** If this is the last branch of the note, the note itself is deleted #### Refresh Note Ordering - **PATCH** `/refresh-note-ordering/{parentNoteId}` - **Description:** Push notePosition changes to connected clients - **Path Parameters:** - `parentNoteId`: Parent note ID - **Note:** Call this after updating branch notePositions to sync changes to clients --- ### Attributes Management Attributes include labels (key-value metadata) and relations (links between notes). #### Create Attribute - **POST** `/attributes` - **Description:** Create an attribute - **Request Body:** ```json { "noteId": "targetNoteId", "type": "label", "name": "priority", "value": "high", "position": 10, "isInheritable": false, "attributeId": "customAttributeId" } ``` - **Attribute Types:** - `label`: Key-value metadata - `relation`: Link to another note (value is target noteId) - **Required Fields:** `noteId`, `type`, `name` - **Optional Fields:** `value`, `position`, `isInheritable`, `attributeId` - **Response:** `201` with Attribute object #### Create Attribute for Note - **POST** `/notes/{noteId}/attributes` - **Description:** Create attribute for specific note - **Path Parameters:** - `noteId`: Note ID - **Request Body:** Same as Create Attribute (noteId not required) #### Get Attribute - **GET** `/attributes/{attributeId}` - **Description:** Get attribute by ID - **Path Parameters:** - `attributeId`: Attribute ID - **Response:** Attribute object #### Get Note Attributes - **GET** `/notes/{noteId}/attributes` - **Description:** Get all attributes for a note - **Path Parameters:** - `noteId`: Note ID - **Response:** Array of attribute objects #### Update Attribute - **PATCH** `/attributes/{attributeId}` - **Description:** Update attribute (name, value, position) - **Path Parameters:** - `attributeId`: Attribute ID - **Request Body:** ```json { "name": "newName", "value": "newValue", "position": 20, "isInheritable": true } ``` - **Response:** `200` with updated attribute #### Delete Attribute - **DELETE** `/attributes/{attributeId}` - **Description:** Delete attribute - **Path Parameters:** - `attributeId`: Attribute ID - **Response:** `204` No Content --- ### Attachments Management #### Create Attachment - **POST** `/attachments` - **Description:** Create attachment for a note - **Request Body:** Multipart form data with file ```json { "ownerId": "noteId", "role": "image", "mime": "image/png", "title": "Screenshot", "position": 10, "attachmentId": "customAttachmentId" } ``` - **Required Fields:** `ownerId`, file data - **Optional Fields:** `role`, `mime`, `title`, `position`, `attachmentId` - **Response:** `201` with Attachment object #### Create Attachment for Note - **POST** `/notes/{noteId}/attachments` - **Description:** Create attachment (alternative endpoint) - **Path Parameters:** - `noteId`: Note ID - **Request Body:** Same as Create Attachment (ownerId not required) #### Get Attachment - **GET** `/attachments/{attachmentId}` - **Description:** Get attachment metadata - **Path Parameters:** - `attachmentId`: Attachment ID - **Response:** Attachment object #### Get Attachment Content - **GET** `/attachments/{attachmentId}/content` - **Description:** Get attachment binary content - **Path Parameters:** - `attachmentId`: Attachment ID - **Response:** Binary content with appropriate MIME type #### Get Note Attachments - **GET** `/notes/{noteId}/attachments` - **Description:** Get all attachments for a note - **Path Parameters:** - `noteId`: Note ID - **Response:** Array of attachment objects #### Update Attachment Content - **PUT** `/attachments/{attachmentId}/content` - **Description:** Update attachment binary content - **Path Parameters:** - `attachmentId`: Attachment ID - **Request Body:** Binary file data - **Response:** `204` No Content #### Update Attachment Metadata - **PATCH** `/attachments/{attachmentId}` - **Description:** Update attachment metadata - **Path Parameters:** - `attachmentId`: Attachment ID - **Request Body:** ```json { "title": "New Title", "role": "image", "mime": "image/jpeg", "position": 20 } ``` - **Response:** `200` with updated attachment #### Delete Attachment - **DELETE** `/attachments/{attachmentId}` - **Description:** Delete attachment - **Path Parameters:** - `attachmentId`: Attachment ID - **Response:** `204` No Content --- ### Special Purpose Endpoints #### Get Inbox Note - **GET** `/inbox/{date}` - **Description:** Get or create inbox note for specific date - **Path Parameters:** - `date`: Date in format `YYYY-MM-DD` - **Response:** Note object - **Behavior:** - Returns fixed inbox note (marked with `#inbox` label) if configured - Otherwise returns/creates day note in journal for the specified date #### Get Day Note - **GET** `/calendar/days/{date}` - **Description:** Get or create day note - **Path Parameters:** - `date`: Date in format `YYYY-MM-DD` (e.g., `2022-12-31`) - **Response:** Note object - **Note:** Creates note if it doesn't exist #### Get Month Note - **GET** `/calendar/months/{month}` - **Description:** Get or create month note - **Path Parameters:** - `month`: Month in format `YYYY-MM` (e.g., `2022-12`) - **Response:** Note object - **Note:** Creates note if it doesn't exist #### Get Year Note - **GET** `/calendar/years/{year}` - **Description:** Get or create year note - **Path Parameters:** - `year`: Year in format `YYYY` (e.g., `2022`) - **Response:** Note object - **Note:** Creates note if it doesn't exist --- ### Backup #### Create Backup - **PUT** `/backup/{backupName}` - **Description:** Create a database backup - **Path Parameters:** - `backupName`: Backup filename (without extension) - **Example:** `PUT /backup/now` creates `backup-now.db` - **Response:** `204` No Content --- ## Data Types and Schemas ### Common Field Types - **EntityId**: 12-character alphanumeric string (e.g., `evnnmvHTCgIn`) - **LocalDateTime**: `YYYY-MM-DD HH:mm:ss.SSS±ZZZZ` (e.g., `2021-12-31 20:18:11.930+0100`) - **UtcDateTime**: `YYYY-MM-DD HH:mm:ss.SSSZ` (e.g., `2021-12-31 19:18:11.930Z`) ### Note Position - Normal ordering: 10, 20, 30, 40... - First position: use value < 10 (e.g., 5) - Last position: use large value (e.g., 1000000) - Between existing: use value between their positions ### Branch Prefix Branch-specific title prefix displayed in the tree. Useful when same note appears in multiple locations with slightly different context. --- ## Error Responses All endpoints may return these error responses: ### Standard Error Object ```json { "status": 400, "code": "NOTE_IS_PROTECTED", "message": "Note 'evnnmvHTCgIn' is protected and cannot be modified through ETAPI" } ``` ### Common HTTP Status Codes - `200`: Success - `201`: Resource created - `204`: Success (no content) - `400`: Bad request (validation error) - `401`: Unauthorized (invalid token) - `404`: Not found - `429`: Too many requests (rate limited/blacklisted) - `500`: Internal server error ### Common Error Codes - `NOTE_IS_PROTECTED`: Protected note cannot be modified - `INVALID_TOKEN`: Invalid or expired ETAPI token - `VALIDATION_ERROR`: Request validation failed - `NOT_FOUND`: Resource not found - `RATE_LIMITED`: Too many requests --- ## Search Query Syntax The `/notes` search endpoint supports Trilium's query language: ### Basic Search ``` python # Search in title and content #todo # Find notes with label "todo" ~project # Find notes with relation "project" ``` ### Advanced Operators ``` note.title =* "meeting" # Title contains "meeting" note.title %= ".*2022.*" # Regex in title #priority = "high" # Label with specific value ~template = "someNoteId" # Relation to specific note #created >= MONTH-1 # Created in last month note.dateModified >= "2022-01-01" # Modified after date ``` ### Combining Queries ``` #todo AND #urgent # Both labels #work OR #personal # Either label #project AND note.title =* "Q1" # Label AND title condition ``` ### Hierarchical Queries ``` note.parents.title = "Work" # Direct parent title note.ancestors.title = "Archive" # Any ancestor title note.children.title =* "Chapter" # Direct children ``` See Trilium Search Documentation for complete syntax. --- ## Rate Limiting - Failed authentication attempts can result in IP blacklist - Blacklisted IPs receive `429` response - Wait period required before retry - Use valid tokens to avoid blacklisting --- ## Configuration Notes ### Upload Size Limits - Default: 250MB - Disable limit: Set `TRILIUM_NO_UPLOAD_LIMIT=true` - Custom limit: Set `MAX_ALLOWED_FILE_SIZE_MB=` ### Network Configuration ETAPI accessible through: - Local interface: `http://localhost:8080/etapi` - Network interface: Configure reverse proxy (nginx/Apache) - SSL/TLS: Recommended for production use --- ## Best Practices 1. **Always use ETAPI tokens** (not passwords) for authentication 2. **Store tokens securely** - they provide full access to your Trilium instance 3. **Use notePosition strategically** - leave gaps (10, 20, 30) for easy insertion 4. **Handle branches carefully** - deleting last branch deletes the note 5. **Check for protected notes** - they cannot be modified via ETAPI 6. **Implement rate limiting** in your client to avoid blacklisting 7. **Use search efficiently** - leverage fastSearch for better performance 8. **Call refresh-note-ordering** after bulk branch position updates 9. **Validate data before submission** - reduce error responses 10. **Handle errors gracefully** - check status codes and error messages --- ## Example Workflows ### Create a Note with Attributes ```bash # 1. Create note NOTE_RESPONSE=$(curl -X POST "$SERVER/etapi/create-note" \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "parentNoteId": "root", "title": "Project TODO", "type": "text", "content": "

Task list

" }') NOTE_ID=$(echo $NOTE_RESPONSE | jq -r '.note.noteId') # 2. Add label curl -X POST "$SERVER/etapi/attributes" \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"noteId\": \"$NOTE_ID\", \"type\": \"label\", \"name\": \"priority\", \"value\": \"high\" }" ``` ### Clone Note to Multiple Locations ```bash # Clone note to another parent curl -X POST "$SERVER/etapi/branches" \ -H "Authorization: $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "noteId": "existingNoteId", "parentNoteId": "anotherParentId", "prefix": "Reference: " }' ``` ### Daily Journal Entry ```bash # Get or create today's note TODAY=$(date +%Y-%m-%d) curl "$SERVER/etapi/calendar/days/$TODAY" \ -H "Authorization: $TOKEN" ``` --- ## Client Libraries ### Python - **trilium-py**: Full-featured client with extended functionality - **PyTrilium**: Lightweight wrapper matching OpenAPI spec - **trilium-alchemy**: SQLAlchemy-style SDK with CLI toolkit ### Node.js - **trilium-etapi**: TypeScript wrapper with type safety ### Other Tools - **trilium-mcp-server**: Model Context Protocol server for LLMs - **openapi-mcp-generator**: Generate MCP servers from OpenAPI specs --- ## Version Compatibility - ETAPI introduced: Trilium v0.50 - Basic Auth support: v0.56 - Bearer token format: v0.93.0 - TriliumNext fork: Compatible with Trilium API, ongoing development Check `/app-info` endpoint for version details of your instance. --- ## Additional Resources - **Official Documentation**: https://docs.triliumnotes.org/ - **GitHub Repository**: https://github.com/TriliumNext/Trilium - **Search Syntax Guide**: https://github.com/zadam/trilium/wiki/Search - **Community Resources**: https://github.com/Nriver/awesome-trilium --- **License:** Apache 2.0 **Maintainer:** TriliumNext Community **Contact:** https://github.com/TriliumNext/Trilium/discussions