Base URL: http://localhost:3000
GET /api/notesResponse:
[
{
"id": "20260205-123456",
"title": "My Note",
"path": "notes/20260205-123456.md",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:34:56Z"
}
]POST /api/notes
Content-Type: application/json
{
"title": "Optional Title",
"content": "# My Note\n\nContent here"
}Response: 201 Created
{
"id": "20260205-123456",
"title": "Optional Title",
"path": "notes/20260205-123456.md",
"content": "# My Note\n\nContent here",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:34:56Z"
}GET /api/notes/:idResponse:
{
"id": "20260205-123456",
"title": "My Note",
"path": "notes/20260205-123456.md",
"content": "# My Note\n\nFull content...",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:34:56Z"
}PUT /api/notes/:id
Content-Type: application/json
{
"content": "# Updated Content\n\nNew content here"
}Response:
{
"id": "20260205-123456",
"title": "Updated Content",
"path": "notes/20260205-123456.md",
"content": "# Updated Content\n\nNew content here",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:35:00Z"
}DELETE /api/notes/:idResponse: 200 OK
Note: The note is moved to archive/, not permanently deleted.
GET /api/projectsResponse:
[
{
"id": "ferrite",
"title": "Ferrite",
"description": "A Rust project",
"path": "projects/ferrite",
"created": "2026-02-04T10:00:00Z",
"updated": "2026-02-05T12:00:00Z"
}
]POST /api/projects
Content-Type: application/json
{
"title": "New Project",
"description": "Project description"
}Response: 201 Created
{
"id": "new-project",
"title": "New Project",
"description": "Project description",
"path": "projects/new-project",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:34:56Z"
}GET /api/projects/:idResponse:
{
"id": "ferrite",
"title": "Ferrite",
"description": "A Rust project",
"path": "projects/ferrite",
"created": "2026-02-04T10:00:00Z",
"updated": "2026-02-05T12:00:00Z"
}GET /api/projects/:id/contentResponse:
{
"content": "# Ferrite\n\nProject overview content..."
}PUT /api/projects/:id/content
Content-Type: application/json
{
"content": "# Updated Overview\n\nNew content..."
}GET /api/projects/:id/notesResponse:
[
{
"id": "20260205-123456",
"title": "Project Note",
"path": "projects/ferrite/notes/20260205-123456.md",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:34:56Z"
}
]POST /api/projects/:id/notes
Content-Type: application/json
{
"title": "New Note",
"content": "Note content..."
}GET /api/projects/:id/notes/:noteIdPUT /api/projects/:id/notes/:noteId
Content-Type: application/json
{
"content": "Updated content..."
}DELETE /api/projects/:id/notes/:noteIdGET /api/projects/:id/notes-titlesReturns all notes in the project with their frontmatter IDs and titles. Used by the /link slash command dropdown.
Response:
{
"notes": [
{ "id": "ferrite-index", "title": "Ferrite", "path": "projects/ferrite/index.md" },
{ "id": "ferrite-20260227-153000", "title": "Meeting Notes", "path": "projects/ferrite/notes/20260227-153000.md" }
]
}GET /api/projects/:id/notes-search?q=meeting&limit=10Filter notes by partial title or ID match within a project.
Response:
{
"query": "meeting",
"results": [
{ "id": "ferrite-20260227-153000", "title": "Meeting Notes", "path": "projects/ferrite/notes/20260227-153000.md" }
]
}See backlinks.md for full feature documentation.
Returns both backlinks (incoming) and forward links (outgoing).
GET /api/backlinks/notes/:noteId/linksResponse:
{
"note_id": "ferrite-20260227-153000",
"backlinks": [
{
"source_id": "ferrite-20260226-100000",
"source_title": "Project Overview",
"source_path": "projects/ferrite/notes/20260226-100000.md",
"context": "See [Meeting Notes](ferrite-20260227-153000) for details",
"line_number": 5
}
],
"forward_links": [
{
"target_id": "ferrite-20260225-090000",
"target_title": "Architecture Notes",
"context": "Based on the [Architecture Notes](ferrite-20260225-090000)",
"line_number": 12
}
]
}GET /api/backlinks/notes/:noteId/backlinksResponse:
{
"note_id": "ferrite-20260227-153000",
"backlinks": [...],
"count": 1
}GET /api/backlinks/notes/:noteId/forward-linksResponse:
{
"note_id": "ferrite-20260227-153000",
"forward_links": [...],
"count": 2
}Force a full rescan of all note files and rebuild the in-memory link index.
POST /api/backlinks/links/rebuildResponse:
{
"success": true,
"indexed_notes": 42,
"message": "Indexed 42 notes"
}GET /api/backlinks/links/statsResponse:
{
"total_links": 15,
"unique_targets": 8
}GET /api/projects/:id/tasksResponse:
[
{
"id": "task-20260205-123456",
"title": "Implement feature X",
"completed": false,
"section": "Active",
"priority": "high",
"due_date": "2026-02-10",
"is_active": true,
"tags": ["backend", "api"],
"parent_id": null,
"recurrence": null,
"recurrence_interval": null,
"project_id": "ferrite",
"last_comment": "API endpoint done, moving to frontend",
"path": "projects/ferrite/tasks/task-20260205-123456.md",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:34:56Z"
}
]POST /api/projects/:id/tasks
Content-Type: application/json
{
"title": "New Task",
"content": "Task description..."
}GET /api/projects/:id/tasks/:taskIdPUT /api/projects/:id/tasks/:taskId
Content-Type: application/json
{
"content": "Updated task description..."
}PUT /api/projects/:id/tasks/:taskId/meta
Content-Type: application/json
{
"title": "New Title",
"is_active": false,
"section": "Backlog",
"priority": "low",
"due_date": "2026-02-15"
}PUT /api/projects/:id/tasks/:taskId/toggleResponse:
{
"completed": true
}DELETE /api/projects/:id/tasks/:taskIdPOST /api/projects/:id/tasks/:taskId/comments
Content-Type: application/json
{
"text": "Started work on this — API integration is in progress."
}Response: 201 Created
{
"id": "task-20260216-120000",
"title": "Implement feature X",
"completed": false,
"section": "Active",
"is_active": true,
"comments": [
{
"date": "2026-02-16T10:30:00+00:00",
"text": "Created initial spec"
},
{
"date": "2026-02-16T12:00:00+00:00",
"text": "Started work on this — API integration is in progress."
}
],
"content": "## Requirements\n\n- Item 1\n- Item 2",
"...": "other task fields"
}Comments are stored as a YAML sequence in the task's frontmatter. The response returns the full TaskWithContent object with all comments.
DELETE /api/projects/:id/tasks/:taskId/comments/:commentIndexRemoves the comment at the given zero-based index.
Response:
{
"id": "task-20260216-120000",
"comments": [],
"...": "full TaskWithContent"
}When listing tasks (GET /api/projects/:id/tasks or GET /api/tasks), each task includes a last_comment field with the text of the most recent comment (or null if no comments exist). This enables showing a quick status summary without loading the full task.
{
"id": "task-20260216-120000",
"title": "Implement feature X",
"last_comment": "Started work on this — API integration is in progress.",
"...": "other task fields"
}GET /api/tasksReturns tasks from all projects, useful for global task views.
GET /api/dailyResponse:
[
{
"date": "2026-02-05",
"path": "daily/2026-02-05.md",
"created": "2026-02-05T08:00:00Z",
"updated": "2026-02-05T12:00:00Z"
}
]GET /api/daily/todayCreates the daily note if it doesn't exist.
Response:
{
"date": "2026-02-05",
"content": "# 2026-02-05\n\n## Todo\n\n- [ ] Task 1",
"path": "daily/2026-02-05.md",
"created": "2026-02-05T08:00:00Z",
"updated": "2026-02-05T12:00:00Z"
}GET /api/daily/:date
POST /api/daily/:dateDate format: YYYY-MM-DD
POST /api/assets/upload
Content-Type: multipart/form-data
project: ferrite
file: (binary data)Response:
{
"url": "/api/assets/ferrite/image-20260205-123456.png",
"filename": "image-20260205-123456.png"
}GET /api/assets/:project/:filenameReturns the binary file with appropriate Content-Type header.
GET /api/search?q=search+termResponse:
{
"results": [
{
"path": "notes/20260205-123456.md",
"title": "My Note",
"matches": [
{
"line": 5,
"text": "This is a **search term** example"
}
]
}
]
}GET /api/git/statusResponse:
{
"branch": "main",
"ahead": 2,
"behind": 0,
"staged": [],
"modified": ["notes/20260205-123456.md"],
"untracked": [],
"has_conflicts": false
}POST /api/git/commit
Content-Type: application/json
{
"message": "Update notes"
}POST /api/git/pushPOST /api/git/fetchGET /api/git/log?limit=20Response:
[
{
"id": "abc123...",
"message": "Update notes",
"author": "User Name",
"date": "2026-02-05T12:34:56Z",
"files_changed": 3
}
]GET /api/git/diffResponse:
{
"diff": "diff --git a/notes/... "
}GET /api/git/diff/:commitIdGET /api/git/remoteResponse:
{
"name": "origin",
"url": "git@github.com:user/repo.git",
"ahead": 2,
"behind": 0
}GET /api/git/conflictsResponse:
{
"has_conflicts": false,
"files": []
}WS /ws
Lock File:
{
"type": "lock_file",
"path": "notes/20260205-123456.md",
"lock_type": "editor"
}Unlock File:
{
"type": "unlock_file",
"path": "notes/20260205-123456.md"
}File Locked:
{
"type": "file_locked",
"path": "notes/20260205-123456.md",
"client_id": "client-123"
}File Unlocked:
{
"type": "file_unlocked",
"path": "notes/20260205-123456.md"
}File Modified (broadcast):
{
"type": "file_modified",
"path": "notes/20260205-123456.md"
}Git Status Update:
{
"type": "git_status",
"status": { ... }
}All endpoints return errors in this format:
{
"error": "Human-readable error message",
"code": "ERROR_CODE"
}| Code | HTTP Status | Description |
|---|---|---|
NOT_FOUND |
404 | Resource doesn't exist |
BAD_REQUEST |
400 | Invalid request data |
CONFLICT |
409 | Resource conflict (e.g., Git) |
INTERNAL_ERROR |
500 | Server error |