Files
ProjectE/docs/API.md
T
mbatchelder 8f55626e03 refactor: migrate to monorepo structure with Docker, PocketBase, and e2e tests
- Reorganize into apps/, packages/, docs/, e2e/, pocketbase/ directories
- Add Dockerfiles for web, worker, and PocketBase services
- Add docker-compose.yml for local orchestration
- Add turbo.json for monorepo task management
- Add Playwright e2e test infrastructure
- Add PocketBase backend with migrations
- Remove Vite/Next.js/ESLint/PostCSS config files
- Update package.json with workspace dependencies
- Add .env.example and .dockerignore
2026-07-16 06:19:58 -04:00

1028 lines
18 KiB
Markdown

# API Documentation
Project E exposes a REST API through Next.js API routes. All endpoints live under `/api/` and communicate via JSON.
## Table of Contents
- [Authentication](#authentication)
- [Request Format](#request-format)
- [Response Format](#response-format)
- [Error Handling](#error-handling)
- [Pagination](#pagination)
- [Filtering and Sorting](#filtering-and-sorting)
- [Endpoints](#endpoints)
- [Health](#health)
- [Auth](#auth)
- [Tasks](#tasks)
- [Habits](#habits)
- [Projects](#projects)
- [Notes](#notes)
- [Reports](#reports)
- [Milestones](#milestones)
- [Domains](#domains)
- [Tags](#tags)
- [Agents](#agents)
- [Webhooks](#webhooks)
- [Analytics](#analytics)
- [Realtime](#realtime)
- [MCP](#mcp)
## Authentication
All API endpoints (except `/api/health` and `/api/auth/login`) require authentication.
### Session-Based Auth
The web app uses cookie-based sessions. After login, the server sets an `httpOnly` cookie named `pb_auth` containing a PocketBase JWT token.
**Login:**
```bash
POST /api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "your-password"
}
```
**Response:**
```json
{
"user": {
"id": "user_id",
"email": "user@example.com",
"name": "User Name"
},
"token": "eyJhbGciOi..."
}
```
The `Set-Cookie` header includes the `pb_auth` cookie. Include this cookie in subsequent requests.
### Token Refresh
Tokens expire after 7 days. Refresh before expiry:
```bash
POST /api/auth/refresh
Cookie: pb_auth=eyJhbGciOi...
```
### Current User
```bash
GET /api/auth/me
Cookie: pb_auth=eyJhbGciOi...
```
**Response:**
```json
{
"id": "user_id",
"email": "user@example.com",
"name": "User Name"
}
```
### Logout
```bash
POST /api/auth/logout
Cookie: pb_auth=eyJhbGciOi...
```
Clears the `pb_auth` cookie.
## Request Format
All write endpoints (`POST`, `PATCH`, `PUT`) expect JSON bodies with `Content-Type: application/json`.
Request bodies are validated with Zod schemas from `@project-e/shared`. Invalid input returns a `400 VALIDATION_ERROR`.
## Response Format
Successful responses return JSON with appropriate HTTP status codes:
| Status | Meaning |
|--------|---------|
| `200` | Success |
| `201` | Created |
| `204` | No Content (delete) |
| `400` | Bad Request (validation error) |
| `401` | Unauthorized |
| `404` | Not Found |
| `500` | Internal Server Error |
List endpoints return paginated results:
```json
{
"items": [...],
"totalItems": 42,
"totalPages": 3,
"page": 1,
"perPage": 20
}
```
## Error Handling
Errors follow a consistent structure:
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{
"code": "invalid_string",
"message": "Title is required",
"path": ["title"]
}
]
}
}
```
Error codes:
| Code | Status | Description |
|------|--------|-------------|
| `UNAUTHORIZED` | 401 | Missing or invalid auth token |
| `VALIDATION_ERROR` | 400 | Request body failed Zod validation |
| `NOT_FOUND` | 404 | Resource does not exist |
| `FORBIDDEN` | 403 | Insufficient permissions |
| `INTERNAL_ERROR` | 500 | Unexpected server error |
## Pagination
List endpoints accept query parameters for pagination:
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page` | number | 1 | Page number (1-indexed) |
| `perPage` | number | 50 | Items per page (max 200) |
**Example:**
```bash
GET /api/tasks?page=2&perPage=20
```
## Filtering and Sorting
### Filtering
Use the `filter` query parameter with PocketBase filter syntax:
```bash
GET /api/tasks?filter=status = "todo" && priority = "high"
```
Filter operators:
| Operator | Description |
|----------|-------------|
| `=` | Equal |
| `!=` | Not equal |
| `>`, `>=`, `<`, `<=` | Comparison |
| `~` | Contains (case-insensitive) |
| `!~` | Not contains |
| `^` | Starts with |
| `@` | Full-text search |
| `&&` | AND |
| `\|\|` | OR |
### Sorting
Use the `sort` query parameter. Prefix with `-` for descending order:
```bash
GET /api/tasks?sort=-created
GET /api/tasks?sort=priority,-due_date
```
## Endpoints
### Health
#### `GET /api/health`
Returns server health status. No authentication required.
**Response:**
```json
{
"status": "ok",
"timestamp": "2024-01-15T10:30:00.000Z",
"version": "0.1.0"
}
```
### Auth
#### `POST /api/auth/login`
Authenticate a user and create a session.
**Request:**
```json
{
"email": "user@example.com",
"password": "your-password"
}
```
**Response (200):**
```json
{
"user": {
"id": "user_id",
"email": "user@example.com",
"name": "User Name"
},
"token": "eyJhbGciOi..."
}
```
#### `POST /api/auth/logout`
End the current session. Requires authentication.
#### `GET /api/auth/me`
Get the current authenticated user. Requires authentication.
#### `POST /api/auth/refresh`
Refresh the auth token. Requires authentication.
### Tasks
#### `GET /api/tasks`
List tasks with filtering, sorting, and pagination.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `page` | number | Page number |
| `perPage` | number | Items per page |
| `filter` | string | PocketBase filter expression |
| `sort` | string | Sort field(s) |
**Example:**
```bash
GET /api/tasks?filter=status = "todo"&sort=-priority
```
**Response:**
```json
{
"items": [
{
"id": "task_id",
"title": "Complete documentation",
"description": "Write API docs",
"status": "todo",
"priority": "high",
"due_date": "2024-01-20T00:00:00.000Z",
"project_id": "project_id",
"tags": ["docs", "api"],
"domain": "work",
"subtasks": [],
"attachments": [],
"created": "2024-01-15T10:00:00.000Z",
"updated": "2024-01-15T10:00:00.000Z"
}
],
"totalItems": 1,
"totalPages": 1,
"page": 1,
"perPage": 50
}
```
#### `POST /api/tasks`
Create a new task.
**Request:**
```json
{
"title": "Complete documentation",
"description": "Write API docs",
"status": "todo",
"priority": "high",
"due_date": "2024-01-20T00:00:00.000Z",
"project_id": "project_id",
"tags": ["docs", "api"],
"domain": "work"
}
```
**Required fields:** `title`, `domain`
**Response (201):** Returns the created task.
#### `GET /api/tasks/[id]`
Get a single task by ID.
**Response:** Returns the task object.
#### `PATCH /api/tasks/[id]`
Update a task. Send only the fields you want to change.
**Request:**
```json
{
"status": "in_progress",
"priority": "urgent"
}
```
**Response:** Returns the updated task.
#### `DELETE /api/tasks/[id]`
Delete a task. Returns `204 No Content`.
#### `POST /api/tasks/bulk`
Bulk operations on tasks.
**Request (bulk update):**
```json
{
"action": "update",
"task_ids": ["id1", "id2", "id3"],
"data": {
"status": "done"
}
}
```
**Request (bulk delete):**
```json
{
"action": "delete",
"task_ids": ["id1", "id2", "id3"]
}
```
### Habits
#### `GET /api/habits`
List habits with filtering and pagination.
**Query Parameters:** Same as tasks.
**Response:** Paginated list of habit objects.
#### `POST /api/habits`
Create a new habit.
**Request:**
```json
{
"title": "Morning meditation",
"description": "10 minutes of mindfulness",
"frequency": "daily",
"domain": "health",
"difficulty": "medium",
"target_count": 1,
"tags": ["wellness"]
}
```
**Required fields:** `title`, `domain`
**Response (201):** Returns the created habit.
#### `GET /api/habits/[id]`
Get a single habit by ID.
#### `PATCH /api/habits/[id]`
Update a habit.
#### `DELETE /api/habits/[id]`
Delete a habit.
#### `GET /api/habit-logs`
List habit completion logs.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `habit_id` | string | Filter by habit |
| `date` | string | Filter by date |
#### `POST /api/habit-logs`
Log a habit completion.
**Request:**
```json
{
"habit_id": "habit_id",
"date": "2024-01-15",
"mood": "good",
"quantity": 1,
"notes": "Felt great"
}
```
### Projects
#### `GET /api/projects`
List projects.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status (`active`, `paused`, `archived`) |
| `domain` | string | Filter by domain |
#### `POST /api/projects`
Create a new project.
**Request:**
```json
{
"name": "Website Redesign",
"description": "Complete overhaul of the company website",
"domain": "work",
"status": "active",
"color": "#3b82f6",
"tags": ["design", "frontend"]
}
```
**Required fields:** `name`, `domain`
#### `GET /api/projects/[id]`
Get a single project.
#### `PATCH /api/projects/[id]`
Update a project.
#### `DELETE /api/projects/[id]`
Delete a project.
### Notes
#### `GET /api/notes`
List notes.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `domain` | string | Filter by domain |
| `is_pinned` | boolean | Filter by pinned status |
| `is_archived` | boolean | Filter by archived status |
#### `POST /api/notes`
Create a new note.
**Request:**
```json
{
"title": "Meeting Notes",
"content": "# Meeting with design team\n\nDiscussed new layout...",
"domain": "work",
"tags": ["meetings"],
"is_pinned": false
}
```
**Required fields:** `title`, `domain`
#### `GET /api/notes/[id]`
Get a single note.
#### `PATCH /api/notes/[id]`
Update a note.
#### `DELETE /api/notes/[id]`
Delete a note.
### Reports
#### `GET /api/reports`
List reports.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `type` | string | Filter by type (`weekly`, `monthly`, `project`, `habit`, `custom`) |
| `domain` | string | Filter by domain |
#### `POST /api/reports`
Create a new report.
**Request:**
```json
{
"title": "Weekly Summary",
"report_type": "weekly",
"domain": "work",
"date_range_start": "2024-01-08T00:00:00.000Z",
"date_range_end": "2024-01-14T23:59:59.999Z",
"content": "# Week of Jan 8-14\n\nCompleted 15 tasks..."
}
```
**Required fields:** `title`, `report_type`, `domain`
#### `GET /api/reports/[id]`
Get a single report.
#### `PATCH /api/reports/[id]`
Update a report.
#### `DELETE /api/reports/[id]`
Delete a report.
### Milestones
#### `GET /api/milestones`
List milestones.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | string | Filter by project |
| `status` | string | Filter by status (`planned`, `in_progress`, `complete`) |
#### `POST /api/milestones`
Create a new milestone.
**Request:**
```json
{
"title": "Design Phase",
"description": "Complete all design mockups",
"project_id": "project_id",
"status": "in_progress",
"due_date": "2024-02-01T00:00:00.000Z",
"sort_order": 1
}
```
**Required fields:** `title`, `project_id`
#### `GET /api/milestones/[id]`
Get a single milestone.
#### `PATCH /api/milestones/[id]`
Update a milestone.
#### `DELETE /api/milestones/[id]`
Delete a milestone.
### Domains
#### `GET /api/domains`
List all domains.
**Response:**
```json
{
"items": [
{
"id": "domain_id",
"name": "Work",
"color": "#3b82f6",
"icon": "briefcase",
"sort_order": 1
}
],
"totalItems": 3,
"page": 1,
"perPage": 50
}
```
#### `POST /api/domains`
Create a new domain.
**Request:**
```json
{
"name": "Work",
"color": "#3b82f6",
"icon": "briefcase",
"sort_order": 1
}
```
**Required fields:** `name`
#### `GET /api/domains/[id]`
Get a single domain.
#### `PATCH /api/domains/[id]`
Update a domain.
#### `DELETE /api/domains/[id]`
Delete a domain.
### Tags
#### `GET /api/tags`
List all tags.
#### `POST /api/tags`
Create a new tag.
**Request:**
```json
{
"name": "urgent",
"color": "#ef4444"
}
```
**Required fields:** `name`
#### `GET /api/tags/[id]`
Get a single tag.
#### `PATCH /api/tags/[id]`
Update a tag.
#### `DELETE /api/tags/[id]`
Delete a tag.
### Agents
#### `GET /api/agents`
List agents.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status (`active`, `disabled`) |
#### `POST /api/agents`
Create a new agent.
**Request:**
```json
{
"name": "Code Assistant",
"description": "Helps with code reviews and suggestions",
"permission_tier": "read_only",
"webhook_url": "https://example.com/webhook",
"status": "active"
}
```
**Required fields:** `name`
The server generates an `api_key` automatically.
#### `GET /api/agents/[id]`
Get a single agent.
#### `PATCH /api/agents/[id]`
Update an agent.
#### `DELETE /api/agents/[id]`
Delete an agent.
#### `GET /api/agent-activity`
List recent agent activity.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `agent_id` | string | Filter by agent |
| `limit` | number | Max results |
#### `POST /api/agent-webhook`
Webhook endpoint for agent callbacks. Agents POST results here.
**Request:**
```json
{
"task_id": "agent_task_id",
"status": "completed",
"result": {
"summary": "Task completed successfully"
}
}
```
### Webhooks
#### `GET /api/webhooks`
List webhooks.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `active` | boolean | Filter by active status |
#### `POST /api/webhooks`
Create a new webhook.
**Request:**
```json
{
"name": "Task Notifications",
"url": "https://example.com/webhook",
"events": ["task.created", "task.updated", "task.completed"],
"secret": "your-webhook-secret",
"active": true
}
```
**Required fields:** `name`, `url`, `events`
#### `GET /api/webhooks/[id]`
Get a single webhook.
#### `PATCH /api/webhooks/[id]`
Update a webhook.
#### `DELETE /api/webhooks/[id]`
Delete a webhook.
#### `GET /api/webhook-deliveries`
List webhook delivery attempts.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `webhook_id` | string | Filter by webhook |
| `success` | boolean | Filter by success status |
### Analytics
#### `GET /api/analytics`
Get analytics data.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `period_days` | number | Number of days to analyze (default: 30) |
**Response:**
```json
{
"taskCompletionRate": 75,
"habitConsistency": 82,
"totalTimeMinutes": 1240,
"activeStreaks": 5,
"bestStreak": 21,
"period": 30
}
```
#### `GET /api/time-summary`
Get aggregated time tracking summary.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `start_date` | string | Start date (ISO 8601) |
| `end_date` | string | End date (ISO 8601) |
**Response:**
```json
{
"totalMinutes": 1240,
"byDomain": {
"work": 800,
"personal": 440
},
"byProject": {
"project_id_1": 600,
"project_id_2": 640
},
"byTag": {
"frontend": 300,
"backend": 500
}
}
```
### Realtime
#### `GET /api/realtime`
Server-sent events endpoint for realtime data updates. Requires authentication.
**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `collections` | string | Comma-separated list of collections to subscribe to |
**Example:**
```bash
GET /api/realtime?collections=tasks,habits
```
If no collections specified, subscribes to: `tasks`, `habits`, `projects`, `notes`, `reports`, `milestones`, `notifications`.
**Event format:**
```
data: {"type":"connected","collections":["tasks","habits"]}
data: {"type":"create","collection":"tasks","record":{"id":"...","title":"..."}}
data: {"type":"update","collection":"tasks","record":{"id":"...","status":"done"}}
data: {"type":"delete","collection":"tasks","record":{"id":"..."}}
:ping
```
The server sends a `:ping` comment every 30 seconds to keep the connection alive.
### MCP
#### `GET /api/mcp`
MCP server endpoint using Streamable HTTP transport. Requires API key authentication.
**Authentication:**
```bash
Authorization: Bearer your_agent_api_key
```
The server validates the API key against the `agents` collection.
**Session management:**
1. Client sends `GET /api/mcp` to establish a session
2. Server responds with `mcp-session-id` header
3. Client includes `mcp-session-id` in subsequent `POST` and `DELETE` requests
4. Client sends `DELETE /api/mcp` to end the session
See [MCP Documentation](MCP.md) for the full list of 61 available tools.
## Rate Limiting
The API does not enforce rate limiting at the application level. Configure rate limiting at the reverse proxy or infrastructure layer.
Recommended limits:
- Auth endpoints: 10 requests per minute per IP
- Write endpoints: 100 requests per minute per user
- Read endpoints: 500 requests per minute per user
## SDK Usage
### JavaScript/TypeScript
```typescript
const API_URL = 'http://localhost:3000/api';
// Login
const loginResponse = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com', password: 'password' }),
});
const { token } = await loginResponse.json();
// List tasks
const tasksResponse = await fetch(`${API_URL}/tasks?filter=status = "todo"`, {
headers: { Cookie: `pb_auth=${token}` },
});
const { items: tasks } = await tasksResponse.json();
// Create a task
const createResponse = await fetch(`${API_URL}/tasks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Cookie: `pb_auth=${token}`,
},
body: JSON.stringify({
title: 'New task',
domain: 'work',
priority: 'high',
}),
});
const task = await createResponse.json();
```
### cURL
```bash
# Login
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}' \
-c cookies.txt
# List tasks
curl http://localhost:3000/api/tasks \
-b cookies.txt
# Create a task
curl -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"title":"New task","domain":"work","priority":"high"}'
```