- tasks.list: replace status filter with state_group (backlog/unstarted/started/completed/cancelled) - tasks.create: remove obsolete status param (tasks now use state_id FK) - tasks.update: remove obsolete status param - Implement state_group filtering via EXISTS subquery on states table - Update API.md example to use state_group instead of status
1114 lines
38 KiB
Markdown
1114 lines
38 KiB
Markdown
# API Documentation
|
|
|
|
Project E exposes a REST API built with [Hono](https://hono.dev/) running on [Bun](https://bun.sh/). All endpoints live under `/api/` and exchange JSON.
|
|
|
|
The API server listens on port 3001 (Docker) and is proxied from the Vite dev server on port 3000. In development, `http://localhost:3000/api/...` reaches the same routes as `http://localhost:3001/api/...`.
|
|
|
|
## Table of Contents
|
|
|
|
- [Overview](#overview)
|
|
- [Authentication](#authentication)
|
|
- [Request and Response Format](#request-and-response-format)
|
|
- [Error Handling](#error-handling)
|
|
- [Pagination and Sorting](#pagination-and-sorting)
|
|
- [Domain Scoping](#domain-scoping)
|
|
- [Health](#health)
|
|
- [Auth](#auth)
|
|
- [Domains](#domains)
|
|
- [Tasks](#tasks)
|
|
- [Habits](#habits)
|
|
- [Projects](#projects)
|
|
- [Notes](#notes)
|
|
- [Search](#search)
|
|
- [Calendar](#calendar)
|
|
- [Graph](#graph)
|
|
- [Dashboard](#dashboard)
|
|
- [Agents](#agents)
|
|
- [Webhooks](#webhooks)
|
|
- [Canvas](#canvas)
|
|
- [Daily Notes](#daily-notes)
|
|
- [Tags](#tags)
|
|
- [Custom Fields](#custom-fields)
|
|
- [Error Log](#error-log)
|
|
- [Analytics](#analytics)
|
|
- [Notifications](#notifications)
|
|
- [Export and Import](#export-and-import)
|
|
- [Realtime (SSE)](#realtime-sse)
|
|
- [MCP](#mcp)
|
|
|
|
## Overview
|
|
|
|
| Item | Value |
|
|
|------|-------|
|
|
| Framework | Hono, served by Bun (`bun run dev` / the API Docker image) |
|
|
| Base path | `/api` |
|
|
| Body format | JSON (`Content-Type: application/json`) |
|
|
| Auth | Cookie session, JWT bearer token, or API key |
|
|
| Database | PostgreSQL via Drizzle ORM |
|
|
|
|
All routes mount through `apps/api/src/index.ts`:
|
|
|
|
```
|
|
/api/auth /api/tasks /api/habits /api/projects
|
|
/api/notes /api/search /api/calendar /api/graph
|
|
/api/dashboard /api/agents /api/webhooks /api/canvas
|
|
/api/daily-notes /api/tags /api/custom-fields
|
|
/api/error-log /api/analytics /api/notifications
|
|
/api/export /api/import /api/realtime /api/mcp
|
|
/api/health
|
|
```
|
|
|
|
## Authentication
|
|
|
|
Every endpoint except `GET /api/health` requires authentication. The API accepts three credential forms, in this order of resolution:
|
|
|
|
1. **Session cookie** named `session`, set by `POST /api/auth/credentials`. The cookie is `httpOnly`, `SameSite=Lax`, and persists for 30 days. The JWT inside it expires after 7 days.
|
|
2. **Bearer token** via the `Authorization: Bearer <token>` header. The token can be the same JWT issued at login, or an API key.
|
|
3. **API key** in the `Authorization: Bearer <apiKey>` header. Keys are validated against the `api_keys` table, which stores only the SHA-256 hash of the key.
|
|
|
|
API keys are the required credential for `POST /api/mcp` (JSON-RPC). The MCP endpoint does not accept cookie or JWT auth.
|
|
|
|
### Credentials login
|
|
|
|
`POST /api/auth/credentials` with `{ email, password }`. On success the server sets the `session` cookie and returns the user plus the JWT:
|
|
|
|
```bash
|
|
curl -X POST http://localhost:3000/api/auth/credentials \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"user@example.com","password":"your-password"}' \
|
|
-c cookies.txt
|
|
```
|
|
|
|
```json
|
|
{
|
|
"user": {
|
|
"id": "a1b2c3d4-...",
|
|
"email": "user@example.com",
|
|
"name": "User Name"
|
|
},
|
|
"token": "eyJhbGciOiJIUzI1NiJ9..."
|
|
}
|
|
```
|
|
|
|
First login auto-creates the admin account. When the `users` table is empty, the request is accepted only if `email` matches `INITIAL_ADMIN_EMAIL` and `password` matches `INITIAL_ADMIN_PASSWORD` from the environment. The created account uses `INITIAL_ADMIN_NAME` or the email as its display name.
|
|
|
|
Invalid credentials return `401 UNAUTHORIZED`.
|
|
|
|
### Session check
|
|
|
|
`GET /api/auth/session` returns the current session state. It never errors on an unauthenticated request:
|
|
|
|
```json
|
|
{ "authenticated": true, "user": { "id": "...", "email": "...", "name": "..." } }
|
|
```
|
|
|
|
```json
|
|
{ "authenticated": false, "user": null }
|
|
```
|
|
|
|
### Current user
|
|
|
|
`GET /api/auth/me` returns the authenticated user or `401 UNAUTHORIZED`:
|
|
|
|
```json
|
|
{ "user": { "id": "...", "email": "...", "name": "..." } }
|
|
```
|
|
|
|
### Logout
|
|
|
|
`POST /api/auth/logout` clears the `session` cookie and returns `{ "success": true }`.
|
|
|
|
### Removed features
|
|
|
|
Passkey authentication was removed. The legacy passkey routes issued a session without verifying the WebAuthn signature, which was an authentication bypass. Do not re-add passkey endpoints without full WebAuthn challenge/attestation verification.
|
|
|
|
## Request and Response Format
|
|
|
|
Write endpoints (`POST`, `PATCH`, `PUT`) expect a JSON body with `Content-Type: application/json`. Bodies are validated with Zod schemas. Invalid input returns `400 VALIDATION_ERROR`.
|
|
|
|
Success status codes:
|
|
|
|
| Status | Meaning |
|
|
|--------|---------|
|
|
| `200` | Success |
|
|
| `201` | Created |
|
|
| `204` | Deleted (no body) |
|
|
|
|
Responses use camelCase field names (`domainId`, `createdAt`, `deletedAt`). Timestamps are ISO 8601 strings with timezone.
|
|
|
|
## Error Handling
|
|
|
|
Errors use a consistent envelope:
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "VALIDATION_ERROR",
|
|
"message": "Invalid input",
|
|
"details": [
|
|
{
|
|
"code": "invalid_string",
|
|
"message": "Title is required",
|
|
"path": ["title"]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
`details` appears only when present (mostly Zod validation issues). Error codes:
|
|
|
|
| Code | Status | Meaning |
|
|
|------|--------|---------|
|
|
| `UNAUTHORIZED` | 401 | Missing or invalid credentials |
|
|
| `VALIDATION_ERROR` | 400 | Request body or params failed validation |
|
|
| `NOT_FOUND` | 404 | Resource does not exist |
|
|
| `FORBIDDEN` | 403 | Missing workspace ID, or the workspace belongs to someone else |
|
|
| `CONFLICT` | 409 | State conflict (reserved; not currently returned by routes) |
|
|
| `INTERNAL_ERROR` | 500 | Unexpected server error |
|
|
|
|
Two endpoints deviate from the envelope:
|
|
|
|
- `GET /api/realtime` returns `401` with `{ "error": "Unauthorized" }` when unauthenticated.
|
|
- `POST /api/mcp` returns JSON-RPC error objects (see [MCP](#mcp)).
|
|
|
|
## Pagination and Sorting
|
|
|
|
Most list endpoints paginate with `page` and `perPage`:
|
|
|
|
| Parameter | Type | Default | Max | Description |
|
|
|-----------|------|---------|-----|-------------|
|
|
| `page` | number | 1 | - | Page number, 1-indexed |
|
|
| `perPage` | number | 50 | 100 | Items per page |
|
|
|
|
Response shape:
|
|
|
|
```json
|
|
{
|
|
"items": [],
|
|
"totalItems": 42,
|
|
"totalPages": 3,
|
|
"page": 1,
|
|
"perPage": 50
|
|
}
|
|
```
|
|
|
|
Tasks, habits, projects, and notes also accept `limit` and `offset` (default limit 50, max 200). These four endpoints echo both styles in the response (`page`, `perPage`, `limit`, `offset`).
|
|
|
|
Sorting uses `sort=<field>` with a leading `-` for descending:
|
|
|
|
```bash
|
|
GET /api/tasks?sort=-due_date
|
|
```
|
|
|
|
Some endpoints (tasks, habits, projects, notes) accept a separate `order=asc|desc` parameter that flips the sort direction.
|
|
|
|
Endpoints that return everything for a domain (calendar events, graph nodes, custom fields, notifications, agent activity) skip pagination and return `{ items, totalItems }` or `{ items, count }`.
|
|
|
|
## Domain Scoping
|
|
|
|
Every entity belongs to a domain (workspace). All by-ID routes verify the caller owns the entity's domain through `requireWorkspaceAccess` and return `403 FORBIDDEN` (missing or foreign workspace) or `404 NOT_FOUND` (no such workspace) otherwise.
|
|
|
|
List endpoints accept `?domain=<id>` to scope results. When omitted, they resolve the user's active domain: the user's first domain by `sortOrder` then `createdAt`, or a "Personal" domain auto-created on first use.
|
|
|
|
## Health
|
|
|
|
#### `GET /api/health`
|
|
|
|
No authentication required. Pings the database and reports runtime state:
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"timestamp": "2026-08-10T10:30:00.000Z",
|
|
"version": "0.1.0",
|
|
"runtime": "bun",
|
|
"uptime": 12345.6,
|
|
"database": {
|
|
"connected": true,
|
|
"ping_ms": 4
|
|
}
|
|
}
|
|
```
|
|
|
|
`status` is `ok` when the DB ping succeeds and `degraded` when it fails. `database.ping_ms` is `-1` on failure.
|
|
|
|
## Auth
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `POST` | `/api/auth/credentials` | Log in with email and password; sets `session` cookie |
|
|
| `GET` | `/api/auth/session` | Return `{ authenticated, user }` |
|
|
| `GET` | `/api/auth/me` | Return the current user |
|
|
| `POST` | `/api/auth/logout` | Clear the session cookie |
|
|
|
|
## Domains
|
|
|
|
Domains are workspaces. They are owned by a single user (`ownerId`).
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/domains` | List the caller's domains |
|
|
| `POST` | `/api/domains` | Create a domain |
|
|
| `GET` | `/api/domains/:id` | Get a domain |
|
|
| `PATCH` | `/api/domains/:id` | Update a domain |
|
|
| `DELETE` | `/api/domains/:id` | Delete a domain |
|
|
|
|
**List query parameters:** `page`, `perPage`, `sort` (`name`, `slug`, `sort_order`, `created_at`, `updated_at`; default `sort_order`), `filter` (case-insensitive match on name or slug).
|
|
|
|
**Create:**
|
|
|
|
```bash
|
|
curl -X POST http://localhost:3000/api/domains \
|
|
-H "Content-Type: application/json" \
|
|
-b cookies.txt \
|
|
-d '{"name":"Work","slug":"work","color":"#3b82f6"}'
|
|
```
|
|
|
|
`name` is required. `slug` defaults to a slugified name. Response (`201`):
|
|
|
|
```json
|
|
{
|
|
"id": "b2c3d4e5-...",
|
|
"name": "Work",
|
|
"slug": "work",
|
|
"color": "#3b82f6",
|
|
"icon": null,
|
|
"ownerId": "a1b2c3d4-...",
|
|
"parentId": null,
|
|
"sortOrder": 0,
|
|
"customFields": {},
|
|
"createdAt": "2026-08-10T10:30:00.000Z",
|
|
"updatedAt": "2026-08-10T10:30:00.000Z"
|
|
}
|
|
```
|
|
|
|
`PATCH /api/domains/:id` accepts any subset of `name`, `slug`, `color`, `icon`, `parentId`. `DELETE` returns `204` and hard-deletes the row (domains have no `deleted_at` column).
|
|
|
|
## Tasks
|
|
|
|
Task statuses: `todo`, `in_progress`, `done`, `cancelled`. Priorities: `low`, `medium`, `high`, `urgent`.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/tasks` | List tasks |
|
|
| `POST` | `/api/tasks` | Create a task |
|
|
| `POST` | `/api/tasks/reorder` | Persist Kanban column ordering |
|
|
| `GET` | `/api/tasks/:id` | Get a task with subtasks, tags, dependencies |
|
|
| `PATCH` | `/api/tasks/:id` | Update a task |
|
|
| `DELETE` | `/api/tasks/:id` | Soft-delete a task |
|
|
| `POST` | `/api/tasks/:id/status` | Change task status (Kanban drag) |
|
|
| `POST` | `/api/tasks/:id/tags` | Assign a tag |
|
|
| `DELETE` | `/api/tasks/:id/tags/:tagId` | Remove a tag |
|
|
| `GET` | `/api/tasks/:id/history` | Status change log (from activity feed) |
|
|
| `GET` | `/api/tasks/:id/comments` | List comments |
|
|
| `POST` | `/api/tasks/:id/comments` | Add a comment |
|
|
| `GET` | `/api/tasks/:id/attachments` | List attachment metadata |
|
|
|
|
### List tasks
|
|
|
|
**Query parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `domain` | string | Workspace ID; defaults to active domain |
|
|
| `search` | string | Case-insensitive match on title |
|
|
| `filter` | string | Case-insensitive match on title or description |
|
|
| `status` | string | Comma-separated statuses |
|
|
| `priority` | string | Comma-separated priorities |
|
|
| `tag` | string | Comma-separated tag IDs (task must have all) |
|
|
| `parent_id` | string | Filter by parent; `null` for root tasks |
|
|
| `project_id` | string | Filter by project |
|
|
| `section_id` | string | Filter by project section |
|
|
| `sort` | string | `created`, `updated`, `title`, `status`, `priority`, `order`, `due_date` (default `-created`) |
|
|
| `order` | string | `asc` or `desc` |
|
|
| `page` / `perPage` | number | Pagination (default 1 / 50) |
|
|
| `limit` / `offset` | number | Alternative pagination (max limit 200) |
|
|
|
|
**Example:**
|
|
|
|
```bash
|
|
curl "http://localhost:3000/api/tasks?status=todo,in_progress&sort=due_date&domain=<domainId>" \
|
|
-b cookies.txt
|
|
```
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"id": "c3d4e5f6-...",
|
|
"title": "Complete documentation",
|
|
"description": "Write API docs",
|
|
"status": "todo",
|
|
"priority": "high",
|
|
"domainId": "b2c3d4e5-...",
|
|
"projectId": null,
|
|
"sectionId": null,
|
|
"parentId": null,
|
|
"dueDate": "2026-08-20T00:00:00.000Z",
|
|
"completedAt": null,
|
|
"estimatedMinutes": null,
|
|
"trackedMinutes": 0,
|
|
"recurrenceRule": null,
|
|
"order": 0,
|
|
"customFields": {},
|
|
"createdAt": "2026-08-10T10:30:00.000Z",
|
|
"updatedAt": "2026-08-10T10:30:00.000Z",
|
|
"tags": []
|
|
}
|
|
],
|
|
"totalItems": 1,
|
|
"totalPages": 1,
|
|
"page": 1,
|
|
"perPage": 50,
|
|
"limit": 50,
|
|
"offset": 0
|
|
}
|
|
```
|
|
|
|
### Create a task
|
|
|
|
`title` is required. `domain` defaults to the active domain. Other accepted fields: `description`, `status`, `priority`, `projectId`, `sectionId`, `parentId`, `dueDate` (ISO 8601), `estimatedMinutes`, `order`, `customFields`, `recurrenceRule` (RRule string; the worker spawns the next occurrence), `tagIds`.
|
|
|
|
```json
|
|
{
|
|
"title": "Ship the release",
|
|
"description": "Cut the v0.2.0 tag",
|
|
"status": "in_progress",
|
|
"priority": "urgent",
|
|
"dueDate": "2026-08-20T17:00:00.000Z",
|
|
"tagIds": ["f1a2b3c4-..."]
|
|
}
|
|
```
|
|
|
|
Response (`201`) is the created task. Setting `parentId` requires the parent to exist and not be deleted (`404 NOT_FOUND` otherwise).
|
|
|
|
### Reorder
|
|
|
|
`POST /api/tasks/reorder` with `{ "orderedIds": ["id1", "id2", "id3"], "domain": "<domainId>" }`. `domain` is optional; it resolves from the first task when omitted. Every ID must exist in the workspace and be non-deleted, or the request returns `404`. Tasks are assigned `order` 0..n in transaction. Response:
|
|
|
|
```json
|
|
{ "success": true, "orderedIds": ["id1", "id2", "id3"] }
|
|
```
|
|
|
|
### Get a single task
|
|
|
|
`GET /api/tasks/:id` returns the task enriched with `subtasks`, `tags` (array of `{ id, name, color }`), `dependencies` (tasks this task blocks on), and `dependents` (tasks that block on this one).
|
|
|
|
### Update
|
|
|
|
`PATCH /api/tasks/:id` accepts any subset of the create fields. A task cannot be its own parent, and `parentId` cycles are rejected with `400 VALIDATION_ERROR`.
|
|
|
|
### Delete
|
|
|
|
`DELETE /api/tasks/:id` soft-deletes the task (sets `deletedAt`) and returns `204`. Recurring spawns for the task are stopped.
|
|
|
|
### Change status
|
|
|
|
`POST /api/tasks/:id/status` with `{ "status": "done" }`. Marking a task `done` also sets `completedAt`.
|
|
|
|
### Tags
|
|
|
|
`POST /api/tasks/:id/tags` with `{ "tagId": "<uuid>" }` returns `201 { "success": true }`. Re-assigning the same tag is a no-op, not an error. `DELETE /api/tasks/:id/tags/:tagId` returns `204`.
|
|
|
|
### Comments and attachments
|
|
|
|
Comments and attachment metadata are stored in the activity feed with entity types `comment` and `attachment`. `POST /api/tasks/:id/comments` takes `{ "content": "..." }` and returns `201 { "success": true }`. `GET` endpoints return `{ items, totalItems }`, newest first.
|
|
|
|
## Habits
|
|
|
|
Habit frequencies: `daily`, `weekly`, `custom`. Difficulties: `easy`, `medium`, `hard`.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/habits` | List habits |
|
|
| `POST` | `/api/habits` | Create a habit |
|
|
| `GET` | `/api/habits/:id` | Get a habit with recent completions, tags |
|
|
| `PATCH` | `/api/habits/:id` | Update a habit |
|
|
| `DELETE` | `/api/habits/:id` | Soft-delete a habit |
|
|
| `POST` | `/api/habits/:id/complete` | Log a completion for today |
|
|
| `GET` | `/api/habits/:id/completions` | Completion history |
|
|
| `POST` | `/api/habits/:id/tags` | Assign a tag |
|
|
| `DELETE` | `/api/habits/:id/tags/:tagId` | Remove a tag |
|
|
|
|
### List habits
|
|
|
|
**Query parameters:** `domain`, `active` (`true`/`false`), `frequency`, `difficulty`, `tag` (comma-separated tag IDs), `search`/`filter` (match on name), `sort` (`created`, `updated`, `name`, `frequency`, `difficulty`, `streak_count`; default `-created`), `order`, `page`/`perPage`, `limit`/`offset`.
|
|
|
|
### Create
|
|
|
|
`name` is required. Other fields: `description`, `domain` (defaults to active), `frequency`, `difficulty`, `goalPerPeriod` (default 1), `unit`, `reminderTime`, `skipDays` (array of 0-6 weekday numbers), `moodTracking`, `active` (default true), `tagIds`.
|
|
|
|
### Complete
|
|
|
|
`POST /api/habits/:id/complete` with:
|
|
|
|
```json
|
|
{ "value": 1, "mood": 5, "notes": "Felt great" }
|
|
```
|
|
|
|
`value` is required (default 1). `mood` is 1-5, `notes` optional. Response (`201`) includes the stored completion plus updated streaks:
|
|
|
|
```json
|
|
{
|
|
"completion": {
|
|
"id": "d4e5f6a7-...",
|
|
"habitId": "e5f6a7b8-...",
|
|
"date": "2026-08-10T12:00:00.000Z",
|
|
"value": 1,
|
|
"mood": 5,
|
|
"notes": "Felt great",
|
|
"createdAt": "2026-08-10T12:00:00.000Z"
|
|
},
|
|
"streakCount": 3,
|
|
"bestStreak": 5
|
|
}
|
|
```
|
|
|
|
### Completions
|
|
|
|
`GET /api/habits/:id/completions` supports `from` and `to` (ISO timestamps), `limit` (default 365, max 1000), `offset`, and `order` (`asc`/`desc`). Returns `{ items, totalItems, limit, offset }`.
|
|
|
|
## Projects
|
|
|
|
Project statuses: `active`, `paused`, `completed`, `archived`.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/projects` | List projects |
|
|
| `POST` | `/api/projects` | Create a project |
|
|
| `GET` | `/api/projects/:id` | Get a project with sections, tasks, progress |
|
|
| `PATCH` | `/api/projects/:id` | Update a project |
|
|
| `DELETE` | `/api/projects/:id` | Soft-delete a project |
|
|
| `GET` | `/api/projects/:id/sections` | List sections |
|
|
| `POST` | `/api/projects/:id/sections` | Create a section |
|
|
| `GET` | `/api/projects/:id/sections/:sid` | Get a section |
|
|
| `PATCH` | `/api/projects/:id/sections/:sid` | Update a section |
|
|
| `DELETE` | `/api/projects/:id/sections/:sid` | Delete a section |
|
|
| `GET` | `/api/projects/:id/members` | List members (from activity feed) |
|
|
| `POST` | `/api/projects/:id/members` | Add a member |
|
|
| `DELETE` | `/api/projects/:id/members/:uid` | Remove a member |
|
|
|
|
### List projects
|
|
|
|
**Query parameters:** `domain`, `status` (comma-separated), `search`/`filter` (match on name), `sort` (`created`, `updated`, `name`, `status`, `target_date`; default `-created`), `order`, `page`/`perPage`, `limit`/`offset`.
|
|
|
|
Each item carries `tags`, `taskCount`, `completedCount`, and `progress` (0-100).
|
|
|
|
### Create
|
|
|
|
`name` is required. Other fields: `description`, `domain` (defaults to active), `status` (default `active`), `color`, `icon`, `targetDate`, `tagIds`.
|
|
|
|
### Sections
|
|
|
|
Sections group tasks within a project. `kind` is `section` (default) or `milestone`; `status` is `planned`, `in_progress`, or `complete`.
|
|
|
|
Create with `{ "name": "Design phase", "kind": "milestone", "status": "in_progress", "targetDate": "...", "sortOrder": 1 }`. `sortOrder` defaults to one past the current max. Sections are hard-deleted (`204`); they have no `deleted_at` column.
|
|
|
|
### Members
|
|
|
|
Members are recorded in the activity feed with entity type `member`. `POST /api/projects/:id/members` takes `{ "userId": "<uuid>", "role": "member" }` (role optional, default `member`) and returns `201 { "success": true }`. Remove with `DELETE /api/projects/:id/members/:uid` (`204`).
|
|
|
|
## Notes
|
|
|
|
Notes store Tiptap HTML in `content`. Wikilinks in the content (Obsidian-style links to other notes and entities) are parsed and kept in sync on create and update.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/notes` | List notes |
|
|
| `POST` | `/api/notes` | Create a note |
|
|
| `GET` | `/api/notes/:id` | Get a note with tags, backlinks, outgoing links |
|
|
| `PATCH` | `/api/notes/:id` | Update a note |
|
|
| `DELETE` | `/api/notes/:id` | Soft-delete a note |
|
|
| `GET` | `/api/notes/:id/backlinks` | Notes that link to this one |
|
|
| `GET` | `/api/notes/:id/versions` | Edit history (from activity feed) |
|
|
| `POST` | `/api/notes/:id/tags` | Assign a tag |
|
|
| `DELETE` | `/api/notes/:id/tags/:tagId` | Remove a tag |
|
|
|
|
### List notes
|
|
|
|
**Query parameters:** `domain`, `pinned` (`true`), `archived` (`true`/`false`/`all`; default excludes archived), `tag` (comma-separated tag IDs), `search`/`filter` (match on title), `sort` (`title`, `created_at`, `updated_at`, `is_pinned`; default `-updated_at`), `order`, `page`/`perPage`, `limit`/`offset`.
|
|
|
|
### Create
|
|
|
|
`title` is required. Other fields: `content` (Tiptap HTML), `domain` (defaults to active), `isPinned`, `isArchived`, `tagIds`.
|
|
|
|
## Search
|
|
|
|
Full-text search across tasks, notes, projects, habits, and domains. Results are scoped to the caller's active domain. Backed by Postgres `search_vector` columns with `ts_headline` snippets.
|
|
|
|
#### `GET /api/search`
|
|
|
|
**Query parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `q` | string | Search query (required; empty returns `{ results: [], totalCount: 0 }`) |
|
|
| `types` | string | Comma-separated entity types; defaults to all five |
|
|
| `limit` | number | Max results (default 20, max 50) |
|
|
| `offset` | number | Offset into the ranked results (default 0) |
|
|
|
|
Entity types: `task`, `note`, `project`, `habit`, `domain`.
|
|
|
|
```json
|
|
{
|
|
"results": [
|
|
{
|
|
"id": "c3d4e5f6-...",
|
|
"type": "task",
|
|
"title": "Complete documentation",
|
|
"snippet": "Complete <mark>documentation</mark> for the API...",
|
|
"score": 0.42,
|
|
"workspaceId": "b2c3d4e5-...",
|
|
"link": "/tasks/c3d4e5f6-..."
|
|
}
|
|
],
|
|
"totalCount": 1,
|
|
"query": "documentation"
|
|
}
|
|
```
|
|
|
|
Two stubs exist: `GET /api/search/recent` returns `{ items: [], totalItems: 0 }` and `POST /api/search/index` returns `{ success: true, message: "Reindex triggered" }`. Neither performs work yet.
|
|
|
|
## Calendar
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/calendar/events` | List events in a time range |
|
|
| `POST` | `/api/calendar/events` | Create an event |
|
|
| `PATCH` | `/api/calendar/events/:id` | Update an event |
|
|
| `DELETE` | `/api/calendar/events/:id` | Delete an event |
|
|
| `GET` | `/api/calendar/upcoming` | Events in the next N days |
|
|
|
|
`GET /events` takes `from` and `to` (ISO timestamps, matched against `startTime`), plus `domain`. Returns events ordered by `startTime` ascending.
|
|
|
|
**Create:**
|
|
|
|
```json
|
|
{
|
|
"title": "Sprint review",
|
|
"description": "Demo the new dashboard",
|
|
"startTime": "2026-08-14T15:00:00.000Z",
|
|
"endTime": "2026-08-14T16:00:00.000Z",
|
|
"allDay": false,
|
|
"color": "#3b82f6",
|
|
"entityType": "task",
|
|
"entityId": "c3d4e5f6-...",
|
|
"recurrenceRule": null
|
|
}
|
|
```
|
|
|
|
`title`, `startTime`, and `domain` are required. `GET /upcoming?days=7` (default 7, max 365) returns events from now through `days` days ahead. Events are hard-deleted (`204`).
|
|
|
|
## Graph
|
|
|
|
The knowledge graph of a domain: nodes for notes, tasks, habits, projects, sections, tags, and the domain itself; edges for note links, note-to-entity links, task dependencies, and domain membership.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/graph/nodes` | All nodes for a domain |
|
|
| `GET` | `/api/graph/edges` | All edges for a domain |
|
|
| `POST` | `/api/graph/edges` | Create a relationship |
|
|
| `DELETE` | `/api/graph/edges/:id` | Delete a relationship |
|
|
|
|
`domain` is required on both `GET` endpoints; omitting it returns `400 VALIDATION_ERROR`.
|
|
|
|
Node shape: `{ id, label, type, color }` with types `task`, `habit`, `project`, `note`, `section`, `tag`, `domain`. Edge shape: `{ source, target, type }`.
|
|
|
|
**Create an edge** with `{ "sourceId": "<uuid>", "targetId": "<uuid>", "type": "note_link" }`. Supported types:
|
|
|
|
| Type | Meaning |
|
|
|------|---------|
|
|
| `note_link` | Note to note (source and target are notes) |
|
|
| `note_entity` | Note to task (target is a task) |
|
|
| `task_dependency` | Task depends on task (source depends on target) |
|
|
|
|
Both endpoints must belong to the caller's domain, verified before any write. Response (`201`): `{ "success": true }`.
|
|
|
|
**Delete an edge** with `DELETE /api/graph/edges/:id` where `:id` is `"<sourceId>-<targetId>"`. The server resolves the source entity type, deletes from `note_links` or `task_dependencies`, and returns `204`.
|
|
|
|
## Dashboard
|
|
|
|
Per-user dashboard widgets, scoped to a domain.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/dashboard/widgets` | List the caller's widgets for a domain |
|
|
| `POST` | `/api/dashboard/widgets` | Add a widget |
|
|
| `PATCH` | `/api/dashboard/widgets/:id` | Update a widget |
|
|
| `DELETE` | `/api/dashboard/widgets/:id` | Remove a widget |
|
|
|
|
**Create:**
|
|
|
|
```json
|
|
{
|
|
"type": "task_progress",
|
|
"title": "Sprint progress",
|
|
"config": { "projectId": "..." },
|
|
"layout": { "x": 0, "y": 0, "w": 4, "h": 2 }
|
|
}
|
|
```
|
|
|
|
`type` is required. `layout` defaults to `{ x: 0, y: 0, w: 2, h: 2 }`.
|
|
|
|
## Agents
|
|
|
|
Agents are API-authenticated assistants scoped to a domain. Creating one generates an `apiKey` (a UUID) that the agent presents as a bearer token. The MCP endpoint accepts the same key.
|
|
|
|
Permission tiers: `full_access`, `read_only`, `content_creator`, `task_manager`, `custom`.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/agents` | List agents |
|
|
| `POST` | `/api/agents` | Create an agent |
|
|
| `GET` | `/api/agents/:id` | Get an agent |
|
|
| `PATCH` | `/api/agents/:id` | Update an agent |
|
|
| `DELETE` | `/api/agents/:id` | Delete an agent |
|
|
| `POST` | `/api/agents/:id/permissions` | Set permission tier and custom permissions |
|
|
| `GET` | `/api/agents/:id/permissions` | Get permissions |
|
|
| `GET` | `/api/agents/activity` | Activity across all agents in a domain |
|
|
| `GET` | `/api/agents/:id/activity` | Activity for one agent (`_all` for all) |
|
|
|
|
### List
|
|
|
|
**Query parameters:** `domain`, `q` (match on name), `sort` (`created`, `updated`, `name`; default `-created`), `page`, `perPage`.
|
|
|
|
### Create
|
|
|
|
```json
|
|
{
|
|
"name": "Code Assistant",
|
|
"description": "Reviews code and suggests changes",
|
|
"status": "active",
|
|
"permissionTier": "read_only",
|
|
"customPermissions": [],
|
|
"tags": ["assistant"],
|
|
"config": {}
|
|
}
|
|
```
|
|
|
|
`name` and `domain` are required. Response (`201`) includes the generated `apiKey`. Store it; the API returns it only at creation.
|
|
|
|
### Activity
|
|
|
|
`GET /api/agents/activity` and `GET /api/agents/:id/activity` accept `action`, `from`, `to` (ISO timestamps; a bare `YYYY-MM-DD` bounds the whole day), `limit` (default 100, max 500), and `domain`. `GET /api/agents/_all/activity` is equivalent to `/api/agents/activity`. Activity rows carry `agentId`, `action`, `entityType`, `entityId`, `details`, `success`, `errorMessage`, `createdAt`.
|
|
|
|
## Webhooks
|
|
|
|
Webhooks are domain-scoped outgoing notifications. Creating or updating tasks, habits, projects, and notes enqueues deliveries for matching webhooks. A background worker performs the HTTP delivery and records the result in `webhook_deliveries`.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/webhooks` | List webhooks |
|
|
| `POST` | `/api/webhooks` | Create a webhook |
|
|
| `PATCH` | `/api/webhooks/:id` | Update a webhook |
|
|
| `DELETE` | `/api/webhooks/:id` | Delete a webhook |
|
|
| `POST` | `/api/webhooks/:id/test` | Enqueue a test delivery |
|
|
|
|
### Create
|
|
|
|
```json
|
|
{
|
|
"name": "Task Notifications",
|
|
"url": "https://example.com/hook",
|
|
"events": ["task.created", "task.updated", "task.completed"],
|
|
"secret": "your-webhook-secret",
|
|
"active": true,
|
|
"retryCount": 3
|
|
}
|
|
```
|
|
|
|
`name`, `url`, `events`, and `domain` are required. `secret` enables payload signing. Event names follow `<entity>.<action>` with entities `task`, `habit`, `project`, `note`; the `test` event fires from the test endpoint.
|
|
|
|
### Delivery
|
|
|
|
The worker POSTs to the webhook URL with:
|
|
|
|
- `Content-Type: application/json`
|
|
- `X-Event-Type: <event>`
|
|
- `X-ProjectE-Signature: <hex HMAC-SHA256>` when a `secret` is set, computed over the raw JSON body
|
|
|
|
Payload:
|
|
|
|
```json
|
|
{
|
|
"event": "task.created",
|
|
"entity_type": "task",
|
|
"entity_id": "c3d4e5f6-...",
|
|
"data": { "title": "Complete documentation" },
|
|
"timestamp": "2026-08-10T10:30:00.000Z",
|
|
"workspace_id": "b2c3d4e5-..."
|
|
}
|
|
```
|
|
|
|
Deliveries use a 10 second timeout and are recorded in `webhook_deliveries` with status, status code, response body (first 1000 chars), and attempt count.
|
|
|
|
`POST /api/webhooks/:id/test` returns `{ "success": true, "message": "Test webhook queued" }` immediately; the worker performs the delivery.
|
|
|
|
## Canvas
|
|
|
|
Freeform or graph-mode whiteboards. A canvas has cards positioned on a viewport.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/canvas` | List canvases |
|
|
| `POST` | `/api/canvas` | Create a canvas |
|
|
| `GET` | `/api/canvas/:id` | Get a canvas with cards and connections |
|
|
| `PATCH` | `/api/canvas/:id` | Update a canvas |
|
|
| `DELETE` | `/api/canvas/:id` | Delete a canvas |
|
|
| `POST` | `/api/canvas/:id/cards` | Create one card |
|
|
| `PUT` | `/api/canvas/:id/cards` | Bulk-replace all cards (primary save path) |
|
|
| `PATCH` | `/api/canvas/cards/:cardId` | Update one card |
|
|
| `DELETE` | `/api/canvas/cards/:cardId` | Delete one card |
|
|
|
|
**Create:**
|
|
|
|
```json
|
|
{
|
|
"name": "Brainstorm",
|
|
"description": "Q3 roadmap ideas",
|
|
"mode": "freeform",
|
|
"viewport": { "x": 0, "y": 0, "zoom": 1 },
|
|
"tags": ["ideas"],
|
|
"background": null
|
|
}
|
|
```
|
|
|
|
`name` and `domain` are required. `GET /api/canvas/:id` returns the canvas plus `cards` (ordered by `zIndex`) and `connections`.
|
|
|
|
**Create a card** with `{ "type": "note", "title": "Idea", "content": "…", "x": 0, "y": 0, "width": 200, "height": 150, "zIndex": 1 }`. Defaults: `type` `note`, `width` 200, `height` 150, `zIndex` one past the current max.
|
|
|
|
**Bulk save** `PUT /api/canvas/:id/cards` replaces all cards in one transaction:
|
|
|
|
```json
|
|
{
|
|
"cards": [
|
|
{ "id": "optional-existing-id", "type": "note", "content": "First card", "x": 0, "y": 0 },
|
|
{ "type": "note", "content": "Second card", "x": 240, "y": 0 }
|
|
]
|
|
}
|
|
```
|
|
|
|
Cards without an `id` are created; `zIndex` defaults to the array index. Response is the canvas plus the saved cards. Canvases, cards, and connections are hard-deleted (`204`).
|
|
|
|
## Daily Notes
|
|
|
|
One note per calendar day per domain (unique on `date` + `domain_id`).
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/daily-notes` | Get a day's note or list all for the domain |
|
|
| `POST` | `/api/daily-notes` | Create a note for a date |
|
|
| `PATCH` | `/api/daily-notes/:id` | Update a note |
|
|
| `DELETE` | `/api/daily-notes/:id` | Delete a note |
|
|
|
|
`GET /api/daily-notes?date=2026-08-10` returns the note object or `null`. Without `date`, it returns `{ items, totalItems }` ordered newest first.
|
|
|
|
**Create:**
|
|
|
|
```json
|
|
{
|
|
"date": "2026-08-10",
|
|
"content": "Deep work on the API docs.",
|
|
"mood": 8,
|
|
"energy": 7,
|
|
"customFields": {}
|
|
}
|
|
```
|
|
|
|
`date` must be `YYYY-MM-DD`, `mood` and `energy` are 1-10. Daily notes are hard-deleted (`204`).
|
|
|
|
## Tags
|
|
|
|
Tags are global (no domain column). They attach to tasks, habits, projects, and notes through junction tables.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/tags` | List tags |
|
|
| `POST` | `/api/tags` | Create a tag |
|
|
| `GET` | `/api/tags/:id` | Get a tag |
|
|
| `PATCH` | `/api/tags/:id` | Update a tag |
|
|
| `DELETE` | `/api/tags/:id` | Delete a tag |
|
|
|
|
**List query parameters:** `page`, `perPage`, `sort` (`name`, `created`, `updated`; default `name`), `filter` (exact match on scope: `global`, `tasks`, `habits`, `projects`, `notes`).
|
|
|
|
**Create:**
|
|
|
|
```json
|
|
{ "name": "urgent", "color": "#ef4444", "scope": "global" }
|
|
```
|
|
|
|
`name` is required, `scope` defaults to `global`. Tags are hard-deleted (`204`).
|
|
|
|
## Custom Fields
|
|
|
|
Per-domain field definitions attached to entities.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/custom-fields` | List fields, optionally for one entity type |
|
|
| `POST` | `/api/custom-fields` | Create a field |
|
|
| `PATCH` | `/api/custom-fields/:id` | Update a field |
|
|
| `DELETE` | `/api/custom-fields/:id` | Delete a field |
|
|
|
|
`GET /api/custom-fields?entity=task` filters by entity type (`task`, `habit`, `project`, `note`, and so on). Results are ordered by `sortOrder`, then `name`.
|
|
|
|
**Create:**
|
|
|
|
```json
|
|
{
|
|
"name": "Client",
|
|
"type": "text",
|
|
"entityType": "task",
|
|
"required": false,
|
|
"options": [],
|
|
"defaultValue": null,
|
|
"sortOrder": 0
|
|
}
|
|
```
|
|
|
|
`name`, `entityType`, and `domain` are required. Fields are hard-deleted (`204`).
|
|
|
|
## Error Log
|
|
|
|
Server-side error log, useful for admin screens.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/error-log` | List recent errors |
|
|
| `DELETE` | `/api/error-log` | Clear all error logs |
|
|
|
|
`GET /api/error-log?level=error&limit=50` filters by level and caps at 200 rows (default 50). Rows carry `level`, `source`, `message`, `stackTrace`, `metadata`, `resolved`, timestamps. `DELETE` returns `{ "deleted": <count> }`.
|
|
|
|
## Analytics
|
|
|
|
All analytics endpoints accept `range` (days, default 30) and `domain`. Responses include a short `Cache-Control` header. None paginate.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/analytics/productivity` | Task completion rate over the period |
|
|
| `GET` | `/api/analytics/habits` | Habit consistency, active streaks, best streak |
|
|
| `GET` | `/api/analytics/projects` | Per-project task totals and progress |
|
|
| `GET` | `/api/analytics/daily` | Daily task created/completed time series |
|
|
|
|
**Productivity:**
|
|
|
|
```json
|
|
{
|
|
"taskCompletionRate": 75,
|
|
"totalTasks": 40,
|
|
"completedTasks": 30,
|
|
"period": 30
|
|
}
|
|
```
|
|
|
|
**Habits:**
|
|
|
|
```json
|
|
{
|
|
"habitConsistency": 82,
|
|
"totalHabits": 6,
|
|
"totalLogs": 148,
|
|
"activeStreaks": 5,
|
|
"bestStreak": 21,
|
|
"period": 30
|
|
}
|
|
```
|
|
|
|
**Projects:**
|
|
|
|
```json
|
|
{
|
|
"projects": [
|
|
{ "id": "...", "name": "Website Redesign", "totalTasks": 12, "completedTasks": 9, "progress": 0.75 }
|
|
],
|
|
"totalProjects": 3,
|
|
"period": 30
|
|
}
|
|
```
|
|
|
|
**Daily** returns `{ items: [{ date: "2026-07-12", created: 2, completed: 1 }, ...], period }` with one bucket per day across the range.
|
|
|
|
## Notifications
|
|
|
|
The notification bell feed. Backed by `activity_feed`; there is no read/unread state yet, so `count` serves as the unread badge. `graph_edge` rows are excluded.
|
|
|
|
#### `GET /api/notifications`
|
|
|
|
**Query parameters:** `workspace_id` (defaults to active domain), `limit` (default 20, max 100).
|
|
|
|
Returns activity from the last 7 days, newest first:
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"id": "...",
|
|
"actor": "User Name",
|
|
"action": "completed",
|
|
"entityType": "task",
|
|
"entityId": "c3d4e5f6-...",
|
|
"changes": { "previousStatus": "in_progress", "newStatus": "done" },
|
|
"workspaceId": "b2c3d4e5-...",
|
|
"createdAt": "2026-08-10T12:00:00.000Z"
|
|
}
|
|
],
|
|
"count": 3
|
|
}
|
|
```
|
|
|
|
## Export and Import
|
|
|
|
JSON backup and restore, scoped to one domain.
|
|
|
|
#### `GET /api/export`
|
|
|
|
Lists the exportable collections without exporting anything:
|
|
|
|
```json
|
|
{
|
|
"collections": [
|
|
{ "name": "tasks", "label": "Tasks" },
|
|
{ "name": "habits", "label": "Habits" },
|
|
{ "name": "projects", "label": "Projects" },
|
|
{ "name": "notes", "label": "Notes" },
|
|
{ "name": "tags", "label": "Tags" },
|
|
{ "name": "agents", "label": "Agents" },
|
|
{ "name": "webhooks", "label": "Webhooks" }
|
|
]
|
|
}
|
|
```
|
|
|
|
#### `POST /api/export`
|
|
|
|
Body `{ "collections": ["tasks", "notes"], "domain": "<domainId>" }`. `collections` defaults to all seven; `domain` defaults to the active domain. Returns a JSON dump:
|
|
|
|
```json
|
|
{
|
|
"version": "1.0",
|
|
"exportedAt": "2026-08-10T10:30:00.000Z",
|
|
"tasks": [],
|
|
"notes": []
|
|
}
|
|
```
|
|
|
|
Tags are exported only when used by the domain's entities. Soft-deleted rows are excluded. A failed collection exports as an empty array rather than failing the whole request.
|
|
|
|
#### `POST /api/import`
|
|
|
|
Body `{ "version": "1.0", "domain": "<domainId>", "tasks": [...], "habits": [...], ... }`. `version` is required. The target domain comes from `body.domain` or `body.domain_id` (or the active domain) and every imported entity is forced into it; payload-supplied `domain_id` values on individual rows are ignored.
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"imported": 12,
|
|
"failed": 0,
|
|
"results": [
|
|
{ "collection": "tasks", "imported": 10, "failed": 0, "errors": [] },
|
|
{ "collection": "notes", "imported": 2, "failed": 0, "errors": [] }
|
|
]
|
|
}
|
|
```
|
|
|
|
`success` is true when nothing failed. Per-collection `errors` hold up to 5 messages.
|
|
|
|
## Realtime (SSE)
|
|
|
|
#### `GET /api/realtime`
|
|
|
|
Server-sent events stream backed by PostgreSQL `LISTEN/NOTIFY` on the `project_e_events` channel. Requires authentication.
|
|
|
|
**Query parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `workspace_id` | string | Only forward events for this workspace. When omitted, all workspaces' events are forwarded. |
|
|
|
|
The ownership check is an IDOR guard: when `workspace_id` is supplied, the caller must own that workspace, or the request is rejected before the stream opens.
|
|
|
|
On connect the server sends a `connected` event, then forwards activity events:
|
|
|
|
```
|
|
data: {"type":"connected","workspace_id":"b2c3d4e5-..."}
|
|
|
|
data: {"type":"task","action":"created","id":"c3d4e5f6-...","workspace_id":"b2c3d4e5-..."}
|
|
```
|
|
|
|
A `:ping` comment arrives every 30 seconds to keep the connection alive. Events are plain `data:` frames, not named events. Unauthenticated requests get `401` with `{ "error": "Unauthorized" }`.
|
|
|
|
## MCP
|
|
|
|
Model Context Protocol over JSON-RPC 2.0. `POST /api/mcp` is a stateless HTTP endpoint; there are no sessions to establish. Requires an API key in the `Authorization: Bearer <apiKey>` header. The key is validated against the `api_keys` table (SHA-256 hash comparison). JWT and cookie auth are not accepted here.
|
|
|
|
Requests and responses use the JSON-RPC 2.0 envelope:
|
|
|
|
```json
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"method": "tools/call",
|
|
"params": { "name": "tasks.list", "arguments": { "domain_id": "b2c3d4e5-...", "state_group": "unstarted" } },
|
|
"id": 1
|
|
}
|
|
```
|
|
|
|
```json
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"result": { "content": [{ "type": "text", "text": "{\"items\":[...],\"total\":3}" }] },
|
|
"id": 1
|
|
}
|
|
```
|
|
|
|
Tool results are JSON strings inside `content[0].text`. Tool calls that pass `domain_id` or `workspace_id` are ownership-checked before execution.
|
|
|
|
**Methods:**
|
|
|
|
| Method | Purpose |
|
|
|--------|---------|
|
|
| `initialize` | Negotiate protocol version `2024-11-05`, server `project-e` v1.0.0 |
|
|
| `tools/list` | List available tools and schemas |
|
|
| `tools/call` | Invoke a tool |
|
|
| `resources/list` | List resource URIs |
|
|
| `resources/read` | Read a resource URI (stub) |
|
|
| `server/discover` | Legacy discovery: server info plus all tools |
|
|
|
|
**Tools:**
|
|
|
|
| Tool | Description |
|
|
|------|-------------|
|
|
| `tasks.list` | List tasks with optional filters |
|
|
| `tasks.create` | Create a task |
|
|
| `tasks.update` | Update a task |
|
|
| `tasks.delete` | Delete a task |
|
|
| `tasks.complete` | Mark a task done |
|
|
| `habits.list` | List habits |
|
|
| `habits.create` | Create a habit |
|
|
| `habits.complete` | Log a habit completion |
|
|
| `projects.list` | List projects |
|
|
| `projects.create` | Create a project |
|
|
| `notes.list` | List notes |
|
|
| `notes.create` | Create a note |
|
|
| `notes.update` | Update a note |
|
|
| `notes.search` | Search notes |
|
|
| `domains.list` | List domains |
|
|
| `domains.create` | Create a domain |
|
|
| `search.query` | Cross-entity search |
|
|
| `activity.list` | List activity feed entries |
|
|
|
|
**Errors** use JSON-RPC codes:
|
|
|
|
| Code | Meaning |
|
|
|------|---------|
|
|
| `-32700` | Parse error |
|
|
| `-32600` | Invalid request |
|
|
| `-32601` | Method or tool not found |
|
|
| `-32602` | Invalid params (including missing required args) |
|
|
| `-32603` | Internal error |
|
|
| `-32001` | Unauthorized (missing or invalid API key, HTTP 401) |
|
|
|
|
`GET /api/mcp` returns HTTP 405; the endpoint only accepts POST.
|
|
|
|
## Rate Limiting
|
|
|
|
The API does not enforce rate limiting at the application level. Configure limits at the reverse proxy or infrastructure layer.
|