New features: - Task comments with date-stamped entries and last-comment summary - Recurring tasks expanded on calendar (daily/weekly/monthly/yearly) - System tray mode replacing CMD window (Windows/macOS/Linux) - Ironpad logo as exe icon, tray icon, favicon, and header logo Technical changes: - Backend restructured for dual-mode: dev (API-only) / prod (tray + server) - tray-item crate for cross-platform tray, winresource for icon embedding - Calendar view refactored with CalendarEntry interface for recurring merging - Added CHANGELOG.md, build-local.ps1, version bumped to 0.2.0 Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
Ironpad API Reference
Base URL: http://localhost:3000
Notes
List Notes
GET /api/notes
Response:
[
{
"id": "20260205-123456",
"title": "My Note",
"path": "notes/20260205-123456.md",
"created": "2026-02-05T12:34:56Z",
"updated": "2026-02-05T12:34:56Z"
}
]
Create Note
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 Note
GET /api/notes/:id
Response:
{
"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"
}
Update Note
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 (Archive) Note
DELETE /api/notes/:id
Response: 200 OK
Note: The note is moved to archive/, not permanently deleted.
Projects
List Projects
GET /api/projects
Response:
[
{
"id": "ferrite",
"title": "Ferrite",
"description": "A Rust project",
"path": "projects/ferrite",
"created": "2026-02-04T10:00:00Z",
"updated": "2026-02-05T12:00:00Z"
}
]
Create Project
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 Project
GET /api/projects/:id
Response:
{
"id": "ferrite",
"title": "Ferrite",
"description": "A Rust project",
"path": "projects/ferrite",
"created": "2026-02-04T10:00:00Z",
"updated": "2026-02-05T12:00:00Z"
}
Get Project Content
GET /api/projects/:id/content
Response:
{
"content": "# Ferrite\n\nProject overview content..."
}
Update Project Content
PUT /api/projects/:id/content
Content-Type: application/json
{
"content": "# Updated Overview\n\nNew content..."
}
Project Notes
List Project Notes
GET /api/projects/:id/notes
Response:
[
{
"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"
}
]
Create Project Note
POST /api/projects/:id/notes
Content-Type: application/json
{
"title": "New Note",
"content": "Note content..."
}
Get Project Note
GET /api/projects/:id/notes/:noteId
Update Project Note
PUT /api/projects/:id/notes/:noteId
Content-Type: application/json
{
"content": "Updated content..."
}
Delete Project Note
DELETE /api/projects/:id/notes/:noteId
Project Tasks
List Project Tasks
GET /api/projects/:id/tasks
Response:
[
{
"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"
}
]
Create Task
POST /api/projects/:id/tasks
Content-Type: application/json
{
"title": "New Task",
"content": "Task description..."
}
Get Task
GET /api/projects/:id/tasks/:taskId
Update Task Content
PUT /api/projects/:id/tasks/:taskId
Content-Type: application/json
{
"content": "Updated task description..."
}
Update Task Metadata
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"
}
Toggle Task Completion
PUT /api/projects/:id/tasks/:taskId/toggle
Response:
{
"completed": true
}
Delete Task
DELETE /api/projects/:id/tasks/:taskId
Add Comment
POST /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 Comment
DELETE /api/projects/:id/tasks/:taskId/comments/:commentIndex
Removes the comment at the given zero-based index.
Response:
{
"id": "task-20260216-120000",
"comments": [],
"...": "full TaskWithContent"
}
Comment in List Views
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"
}
All Tasks
List All Tasks (across projects)
GET /api/tasks
Returns tasks from all projects, useful for global task views.
Daily Notes
List Daily Notes
GET /api/daily
Response:
[
{
"date": "2026-02-05",
"path": "daily/2026-02-05.md",
"created": "2026-02-05T08:00:00Z",
"updated": "2026-02-05T12:00:00Z"
}
]
Get Today's Note
GET /api/daily/today
Creates 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/Create Daily Note by Date
GET /api/daily/:date
POST /api/daily/:date
Date format: YYYY-MM-DD
Assets
Upload Asset
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 Asset
GET /api/assets/:project/:filename
Returns the binary file with appropriate Content-Type header.
Search
Search Content
GET /api/search?q=search+term
Response:
{
"results": [
{
"path": "notes/20260205-123456.md",
"title": "My Note",
"matches": [
{
"line": 5,
"text": "This is a **search term** example"
}
]
}
]
}
Git Operations
Get Status
GET /api/git/status
Response:
{
"branch": "main",
"ahead": 2,
"behind": 0,
"staged": [],
"modified": ["notes/20260205-123456.md"],
"untracked": [],
"has_conflicts": false
}
Commit Changes
POST /api/git/commit
Content-Type: application/json
{
"message": "Update notes"
}
Push to Remote
POST /api/git/push
Fetch from Remote
POST /api/git/fetch
Get Commit Log
GET /api/git/log?limit=20
Response:
[
{
"id": "abc123...",
"message": "Update notes",
"author": "User Name",
"date": "2026-02-05T12:34:56Z",
"files_changed": 3
}
]
Get Working Directory Diff
GET /api/git/diff
Response:
{
"diff": "diff --git a/notes/... "
}
Get Commit Diff
GET /api/git/diff/:commitId
Get Remote Info
GET /api/git/remote
Response:
{
"name": "origin",
"url": "git@github.com:user/repo.git",
"ahead": 2,
"behind": 0
}
Check for Conflicts
GET /api/git/conflicts
Response:
{
"has_conflicts": false,
"files": []
}
WebSocket
Connect
WS /ws
Messages (Client → Server)
Lock File:
{
"type": "lock_file",
"path": "notes/20260205-123456.md",
"lock_type": "editor"
}
Unlock File:
{
"type": "unlock_file",
"path": "notes/20260205-123456.md"
}
Messages (Server → Client)
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": { ... }
}
Error Responses
All endpoints return errors in this format:
{
"error": "Human-readable error message",
"code": "ERROR_CODE"
}
Common Error Codes
| 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 |