mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 08:56:41 +00:00
441 lines
20 KiB
Markdown
Executable file
441 lines
20 KiB
Markdown
Executable file
# APIs
|
||
|
||
This reference is the entrypoint for working with Super Productivity's APIs. The app exposes **three API systems**: the **Sync Server REST API** (for data synchronization), the **Local REST API** (for controlling the desktop app from local scripts), and the **Plugin API** (for extending the app). This reference summarizes all three; full request/response schemas and examples live in the repository (Sync Server: `packages/super-sync-server/`; Plugin API: `packages/plugin-api/` and `docs/plugin-development.md`; Local REST API: `src/app/core/electron/local-rest-api-handler.service.ts`).
|
||
|
||
## 1. Sync Server REST API
|
||
|
||
The Sync Server is a custom, operation-based synchronization service (event-sourcing style). It is **not** WebDAV. It is intended for use with the built-in SuperSync sync provider. SuperSync is very new and is still in beta. See [[2.08-Choose-Sync-Backend]] and [[2.09-Configure-Sync-Backend]] for user-facing sync options.
|
||
|
||
### Authentication and Authorization
|
||
|
||
All sync endpoints require **JWT Bearer** authentication: `Authorization: Bearer <token>`.
|
||
|
||
- **Token expiry:** 7 days.
|
||
- **Environment:** Server requires `JWT_SECRET` (minimum 32 characters).
|
||
- **Token contents:** `userId`, `email`, `tokenVersion` (for invalidation).
|
||
- Invalid or missing token returns 401.
|
||
|
||
### Authentication Endpoints
|
||
|
||
**Traditional:**
|
||
|
||
- `POST /api/register` — User registration (email verification required).
|
||
- `POST /api/login` — Password login.
|
||
- `POST /api/verify-email` — Email verification.
|
||
- `POST /api/replace-token` — Replace compromised JWT (requires current auth).
|
||
|
||
**Passkey (WebAuthn):**
|
||
|
||
- `POST /api/register/passkey/options` — Registration options.
|
||
- `POST /api/register/passkey/verify` — Verify registration.
|
||
- `POST /api/login/passkey/options` — Authentication options.
|
||
- `POST /api/login/passkey/verify` — Verify authentication.
|
||
- `POST /api/recover/passkey` — Request passkey recovery (e.g. magic link).
|
||
- `POST /api/recover/passkey/options` — Recovery registration options.
|
||
- `POST /api/recover/passkey/complete` — Complete recovery.
|
||
|
||
**Magic link:**
|
||
|
||
- `POST /api/login/magic-link` — Request magic link email.
|
||
- `POST /api/login/magic-link/verify` — Verify magic link token.
|
||
|
||
**Account:**
|
||
|
||
- `DELETE /api/account` — Delete user and all data (requires auth).
|
||
|
||
### Synchronization Endpoints
|
||
|
||
All require authentication.
|
||
|
||
**Operations:**
|
||
|
||
- `POST /api/sync/ops` — Upload operations (incremental). Request: `ops[]`, `clientId`, optional `lastKnownServerSeq`, optional `requestId` (deduplication).
|
||
- `GET /api/sync/ops?sinceSeq={seq}&limit={limit}&excludeClient={clientId}` — Download operations. Query: `sinceSeq` (required), `limit` (default 500, max 1000), `excludeClient` (optional).
|
||
|
||
**Snapshots:**
|
||
|
||
- `GET /api/sync/snapshot` — Get full state snapshot.
|
||
- `POST /api/sync/snapshot` — Upload full state (backup/repair/migration). Body limit 30 MB (compressed).
|
||
|
||
**Status and maintenance:**
|
||
|
||
- `GET /api/sync/status` — Sync status and storage info.
|
||
- `DELETE /api/sync/data` — Delete all sync data for the user (e.g. encryption password change).
|
||
- `GET /api/sync/restore-points?limit={limit}` — List restore points (limit 1–100).
|
||
- `GET /api/sync/restore/{serverSeq}` — Get state snapshot at a specific sequence.
|
||
|
||
**Health:**
|
||
|
||
- `GET /health` — Database connectivity check (no auth). Returns 200 with `{ status: 'ok', db: 'connected' }` or 503 on failure.
|
||
|
||
### Request/Response Overview
|
||
|
||
- **Upload ops:** Body is an array of operations; each has `id`, `clientId`, `actionType`, `opType`, `entityType`, `entityId`/`entityIds`, `payload`, `vectorClock`, `timestamp`, `schemaVersion`, optional `isPayloadEncrypted`. Response: `results[]` (per-op accepted/rejected + `serverSeq`), optional `newOps`, `latestSeq`, optional `hasMorePiggyback`, optional `deduplicated`.
|
||
- **Download ops:** Response: `ops`, `hasMore`, `latestSeq`, optional `gapDetected`, optional `latestSnapshotSeq`, optional `snapshotVectorClock`, `serverTime`.
|
||
- **Snapshot:** Response: `state`, `serverSeq`, `generatedAt`. Upload snapshot body: `state`, `clientId`, `reason`, `vectorClock`, `schemaVersion`, optional `isPayloadEncrypted`.
|
||
- **Status:** Response: `latestSeq`, `devicesOnline`, optional `snapshotAge`, `storageUsedBytes`, `storageQuotaBytes`.
|
||
|
||
Exact schemas (Zod on server, TypeScript types in repo) are in `packages/super-sync-server/src/sync/sync.types.ts` and `sync.routes.ts`.
|
||
|
||
### Error Codes
|
||
|
||
Structured error codes (for client handling) include: `VALIDATION_FAILED`, `INVALID_OP_ID`, `INVALID_OP_TYPE`, `INVALID_ENTITY_TYPE`, `INVALID_ENTITY_ID`, `INVALID_PAYLOAD`, `PAYLOAD_TOO_LARGE`, `INVALID_VECTOR_CLOCK`, `INVALID_CLIENT_ID`, `CONFLICT_CONCURRENT`, `CONFLICT_SUPERSEDED`, `DUPLICATE_OPERATION`, `RATE_LIMITED`, `STORAGE_QUOTA_EXCEEDED`, `ENCRYPTED_OPS_NOT_SUPPORTED`, `SYNC_IMPORT_EXISTS`, `INTERNAL_ERROR`. Defined in `sync.types.ts` (`SYNC_ERROR_CODES`).
|
||
|
||
### Rate Limits (per endpoint)
|
||
|
||
- Email verification: 20 / 15 min.
|
||
- Token replacement: 5 / 15 min.
|
||
- Account deletion: 3 / 15 min.
|
||
- Passkey registration: 10 / 15 min.
|
||
- Passkey login: 20 / 15 min.
|
||
- Passkey recovery (request/options/complete): 5–10 / 15 min.
|
||
- Magic link request: 5 / 15 min; verify: 10 / 15 min.
|
||
- Upload ops: 100 / 1 min.
|
||
- Download ops: 200 / 1 min.
|
||
- Snapshot (GET): 10 / 5 min.
|
||
|
||
### Storage Quotas (Sync Server)
|
||
|
||
- **Per user:** Default 100 MB. Enforced before accepting uploads; automatic cleanup (restore points and old operations) when over quota.
|
||
- **Upload limits:** Max payload 20 MB; max operations per upload 100. Compressed ops body: 10 MB; compressed snapshot: 30 MB; decompressed cap (zip-bomb protection): 100 MB.
|
||
|
||
### Compression
|
||
|
||
- **Request:** Gzip via `Content-Encoding: gzip`. Android can send base64-encoded gzip with `Content-Transfer-Encoding: base64`.
|
||
- **Response:** Server may send compressed responses where configured.
|
||
|
||
### CORS
|
||
|
||
Configurable via environment/config. Typical allowed headers include `Authorization`, `Content-Type`, `Content-Encoding`, `X-Expected-Rev`, `X-Force-Overwrite`, `X-Requested-With`. Credentials supported.
|
||
|
||
### Data Retention
|
||
|
||
Operations, devices, and related validation data are retained for **45 days**. Older data is purged.
|
||
|
||
### Request Deduplication
|
||
|
||
Upload ops accepts optional `requestId`. Repeated requests with the same `requestId` receive cached results (and fresh piggybacked ops if applicable), so clients can retry safely without duplicating operations.
|
||
|
||
### End-to-End Encryption
|
||
|
||
Payloads can be encrypted client-side; server stores them as opaque blobs. Restore-at-sequence is not supported when operations are encrypted (`ENCRYPTED_OPS_NOT_SUPPORTED`).
|
||
|
||
---
|
||
|
||
## 2. Plugin API
|
||
|
||
The Plugin API is exposed to plugins via a global `PluginAPI` object. Plugins run in a sandboxed environment (VM or iframe). See [[3.05-Web-App-vs-Desktop]] for web limitations (e.g. Node-only plugins disabled, iframe API restrictions). Full types and the development guide: `packages/plugin-api/src/types.ts`, `docs/plugin-development.md`.
|
||
|
||
### Plugin API Categories
|
||
|
||
**Data — Tasks:**
|
||
|
||
- `getTasks()`, `getArchivedTasks()`, `getCurrentContextTasks()` — Read tasks.
|
||
- `addTask(taskData)`, `updateTask(taskId, updates)`, `deleteTask(taskId)` — Create/update/delete.
|
||
- `batchUpdateForProject(request)` — Batch create/update/delete/reorder for a project.
|
||
- `reorderTasks(taskIds, contextId, contextType)` — Reorder tasks.
|
||
|
||
**Data — Projects:**
|
||
|
||
- `getAllProjects()`, `addProject(projectData)`, `updateProject(projectId, updates)`.
|
||
|
||
**Data — Tags:**
|
||
|
||
- `getAllTags()`, `addTag(tagData)`, `updateTag(tagId, updates)`.
|
||
|
||
**Data — Simple counters:**
|
||
|
||
- `setCounter(id, value)`, `getCounter(id)`, `incrementCounter(id, incrementBy)`, `decrementCounter(id, decrementBy)`, `deleteCounter(id)`, `getAllCounters()`.
|
||
|
||
**UI:**
|
||
|
||
- `showSnack(snackCfg)`, `notify(notifyCfg)` — Notifications.
|
||
- `openDialog(dialogCfg)` — Opens a plugin dialog and resolves to the clicked button label, or `undefined` if the dialog is dismissed.
|
||
- `showIndexHtmlAsView()` — Shows plugin UI.
|
||
|
||
**Registration (main plugin context only; not in iframe):**
|
||
|
||
- `registerHeaderButton(config)`, `registerMenuEntry(config)`, `registerShortcut(config)`, `registerSidePanelButton(config)`, `registerHook(hook, handler)`.
|
||
|
||
**Persistence:**
|
||
|
||
- `persistDataSynced(dataStr)`, `loadSyncedData()`, `getConfig()` — Plugin-specific storage and config.
|
||
|
||
**Advanced:**
|
||
|
||
- `executeNodeScript(request)` — Run Node.js scripts (Electron only, `nodeExecution` permission and user consent).
|
||
- `dispatchAction(action)` — Dispatch NgRx actions (allowed subset).
|
||
- `downloadFile(filename, data)`, `isWindowFocused()`, `onWindowFocusChange(handler)`.
|
||
|
||
### Hooks (Events)
|
||
|
||
Plugins can register handlers for: `taskCreated`, `taskComplete`, `taskUpdate`, `taskDelete`, `currentTaskChange`, `finishDay`, `languageChange`, `persistedDataChanged`, `action`, `anyTaskUpdate`, `projectListUpdate`. Payload types are defined in `plugin-api` types (`HookPayloadMap`).
|
||
|
||
`persistedDataChanged` fires after the initial boot load on any change to this plugin's persisted data (local write, remote sync, bulk import). The payload is `void`; re-call `loadSyncedData(key?)` for fresh data. No replay-on-register, no ordering guarantee across rapid changes — handlers must be idempotent.
|
||
|
||
### Plugin Data Types
|
||
|
||
Core types (Task, Project, Tag, ProjectFolder, etc.) and batch types (`BatchUpdateRequest`, `BatchUpdateResult`, `BatchOperation`, etc.) are in `packages/plugin-api/src/types.ts`.
|
||
|
||
### Plugin Manifest
|
||
|
||
Plugins require `manifest.json`: `name`, `id`, `manifestVersion`, `version`, `minSupVersion`, optional `description`, `hooks`, `permissions`, optional `iFrame`, `isSkipMenuEntry`, `type`, `assets`, `icon`, `nodeScriptConfig`, `sidePanel`, `jsonSchemaCfg`. See `PluginManifest` in the repo.
|
||
|
||
Plugin packages normally include `plugin.js` for host-side behavior. Iframe-only plugins may omit `plugin.js` when
|
||
`iFrame: true` is set and `index.html` is included.
|
||
|
||
### Plugin Permissions
|
||
|
||
- **nodeExecution:** Required for `executeNodeScript()` (Electron only; user consent). Main-process grants are currently issued only for packaged built-in plugins whose manifest can be verified by the desktop app. See [[3.05-Web-App-vs-Desktop]] and [[3.06-User-Data]] for file-system and desktop-only behavior.
|
||
- Other permissions may gate specific API methods; see the plugin development guide.
|
||
|
||
### Iframe Restrictions
|
||
|
||
When a plugin uses an iframe UI, the following are **not** available: `registerHeaderButton`, `registerMenuEntry`, `registerSidePanelButton`, `registerShortcut`, `registerHook`, `execNodeScript`. Iframe content is subject to CSP (e.g. no external scripts; same-origin or inlined only).
|
||
|
||
---
|
||
|
||
## 3. Local REST API
|
||
|
||
The Local REST API allows external scripts and tools to interact with a running Super Productivity desktop app. It runs on `http://127.0.0.1:3876` and is disabled by default. Enable it in **Settings → Misc → Enable local REST API**.
|
||
|
||
For development scripts, `SP_FORCE_LOCAL_REST_API=1 npm start` starts the local REST API without changing the persisted user setting. This override only works in `NODE_ENV=DEV`.
|
||
|
||
### Prerequisites
|
||
|
||
- **Electron desktop app only** (not available on web or mobile)
|
||
- Must be enabled in settings, unless using the development override
|
||
- App must be running with renderer ready
|
||
|
||
### Authentication
|
||
|
||
No authentication required. The API only accepts connections from localhost (`127.0.0.1`).
|
||
|
||
### Base URL
|
||
|
||
```text
|
||
http://127.0.0.1:3876
|
||
```
|
||
|
||
### Response Format
|
||
|
||
All responses are JSON with a consistent envelope:
|
||
|
||
```typescript
|
||
// Success
|
||
{ "ok": true, "data": <response data> }
|
||
|
||
// Error
|
||
{ "ok": false, "error": { "code": "<ERROR_CODE>", "message": "<description>" } }
|
||
```
|
||
|
||
### Health Check
|
||
|
||
| Method | Path | Description |
|
||
| ------ | --------- | ------------------------------------------------ |
|
||
| GET | `/health` | Check if server is running and renderer is ready |
|
||
|
||
**Response:**
|
||
|
||
```json
|
||
{ "ok": true, "data": { "server": "up", "rendererReady": true } }
|
||
```
|
||
|
||
---
|
||
|
||
### Task Endpoints
|
||
|
||
| Method | Path | Description |
|
||
| ------ | -------------------- | ---------------------------------- |
|
||
| GET | `/tasks` | List tasks (with optional filters) |
|
||
| GET | `/tasks/:id` | Get task by ID |
|
||
| POST | `/tasks` | Create task |
|
||
| PATCH | `/tasks/:id` | Update task |
|
||
| DELETE | `/tasks/:id` | Delete task |
|
||
| POST | `/tasks/:id/start` | Start task (set as current) |
|
||
| POST | `/tasks/:id/archive` | Archive task |
|
||
| POST | `/tasks/:id/restore` | Restore archived task |
|
||
|
||
**GET /tasks Query Parameters:**
|
||
|
||
| Parameter | Type | Description |
|
||
| ------------- | ------- | ----------------------------------------------------------- |
|
||
| `query` | string | Filter by title (case-insensitive, contains) |
|
||
| `projectId` | string | Filter by project ID |
|
||
| `tagId` | string | Filter by tag ID. Use `TODAY` for tasks scheduled for today |
|
||
| `includeDone` | boolean | Include completed tasks (default: false) |
|
||
| `source` | string | `"active"` \| `"archived"` \| `"all"` (default: `"active"`) |
|
||
|
||
**Examples:**
|
||
|
||
```bash
|
||
# List all active tasks
|
||
curl http://127.0.0.1:3876/tasks
|
||
|
||
# List archived tasks
|
||
curl "http://127.0.0.1:3876/tasks?source=archived"
|
||
|
||
# Search tasks containing "meeting"
|
||
curl "http://127.0.0.1:3876/tasks?query=meeting"
|
||
|
||
# List tasks scheduled for today
|
||
curl "http://127.0.0.1:3876/tasks?tagId=TODAY"
|
||
|
||
# Create a task
|
||
curl -X POST http://127.0.0.1:3876/tasks \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"title": "Buy groceries", "projectId": "INBOX_PROJECT"}'
|
||
|
||
# Create a subtask (parent must be a top-level task; the subtask inherits
|
||
# the parent's projectId and cannot have its own tags)
|
||
curl -X POST http://127.0.0.1:3876/tasks \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"title": "milk", "parentId": "PARENT_TASK_ID"}'
|
||
|
||
# Update a task
|
||
curl -X PATCH http://127.0.0.1:3876/tasks/task-id \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"title": "Updated title"}'
|
||
|
||
# Archive a task
|
||
curl -X POST http://127.0.0.1:3876/tasks/task-id/archive
|
||
```
|
||
|
||
**POST /tasks body fields:**
|
||
|
||
| Field | Notes |
|
||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `title` (required) | Non-empty string |
|
||
| `parentId` | Create the task as a subtask of this top-level task. Returns 404 if parent missing, 400 if parent is itself a subtask. The new subtask inherits the parent's `projectId` and never has its own tags — supplying `projectId` or `tagIds` together with `parentId` returns 400 `UNSUPPORTED_FIELD`. |
|
||
| `subTaskIds` | Not supported on create — returns 400 `UNSUPPORTED_FIELD`. Create the parent first, then create each child with `parentId`. |
|
||
| other allowed fields | `notes`, `isDone`, `timeEstimate`, `timeSpent`, `projectId`, `tagIds`, `dueDay`, `dueWithTime`, `plannedAt` |
|
||
|
||
**PATCH /tasks/:id — restricted fields:**
|
||
|
||
`parentId` and `subTaskIds` cannot be set via `PATCH` (returns 400 `UNSUPPORTED_FIELD`). Re-parenting an existing task is not supported by this API; delete and recreate the task instead.
|
||
|
||
---
|
||
|
||
### Task Control Endpoints
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ----------------------- | ------------------------------- |
|
||
| GET | `/status` | Get current task and task count |
|
||
| GET | `/task-control/current` | Get current task |
|
||
| POST | `/task-control/current` | Set current task |
|
||
| POST | `/task-control/stop` | Stop current task |
|
||
|
||
**POST /task-control/current Body:**
|
||
|
||
```json
|
||
{ "taskId": "task-id" }
|
||
// or to clear:
|
||
{ "taskId": null }
|
||
```
|
||
|
||
**Examples:**
|
||
|
||
```bash
|
||
# Get current task
|
||
curl http://127.0.0.1:3876/task-control/current
|
||
|
||
# Set current task
|
||
curl -X POST http://127.0.0.1:3876/task-control/current \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"taskId": "task-id"}'
|
||
|
||
# Stop current task
|
||
curl -X POST http://127.0.0.1:3876/task-control/stop
|
||
```
|
||
|
||
---
|
||
|
||
### Project Endpoints
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ----------- | ------------- |
|
||
| GET | `/projects` | List projects |
|
||
|
||
**GET /projects Query Parameters:**
|
||
|
||
| Parameter | Type | Description |
|
||
| --------- | ------ | -------------------------------------------- |
|
||
| `query` | string | Filter by title (case-insensitive, contains) |
|
||
|
||
**Example:**
|
||
|
||
```bash
|
||
# List all projects
|
||
curl http://127.0.0.1:3876/projects
|
||
```
|
||
|
||
---
|
||
|
||
### Tag Endpoints
|
||
|
||
| Method | Path | Description |
|
||
| ------ | ------- | ----------- |
|
||
| GET | `/tags` | List tags |
|
||
|
||
**GET /tags Query Parameters:**
|
||
|
||
| Parameter | Type | Description |
|
||
| --------- | ------ | -------------------------------------------- |
|
||
| `query` | string | Filter by title (case-insensitive, contains) |
|
||
|
||
**Example:**
|
||
|
||
```bash
|
||
# List all tags
|
||
curl http://127.0.0.1:3876/tags
|
||
```
|
||
|
||
---
|
||
|
||
### Local REST API Error Codes
|
||
|
||
| Code | HTTP Status | Description |
|
||
| ---------------- | ----------- | --------------------- |
|
||
| `TASK_NOT_FOUND` | 404 | Task does not exist |
|
||
| `INVALID_INPUT` | 400 | Invalid request body |
|
||
| `NOT_FOUND` | 404 | Route not found |
|
||
| `INTERNAL_ERROR` | 500 | Internal server error |
|
||
|
||
---
|
||
|
||
### Local REST API Notes
|
||
|
||
- **Electron only:** The Local REST API is only available in the desktop app.
|
||
- **No auth:** Security relies on localhost-only binding.
|
||
- **Port:** Fixed at `3876`; not configurable in v1.
|
||
- **Timeout:** 15 seconds for renderer responses.
|
||
|
||
---
|
||
|
||
## 4. Versioning and Compatibility
|
||
|
||
### Schema Versioning (Sync)
|
||
|
||
- **Current schema version:** 1 (shared schema in `packages/shared-schema`).
|
||
- **Minimum supported:** 1.
|
||
- **Max version skip:** 3 (if remote data is more than 3 versions ahead, client should update).
|
||
- Operations and snapshots carry `schemaVersion`; server validates and may reject unsupported versions.
|
||
|
||
### Vector Clocks
|
||
|
||
Sync uses vector clocks for conflict resolution. Server validates/sanitizes clocks: max 50 entries; keys max 255 chars; values 0–10,000,000. Invalid entries are stripped.
|
||
|
||
### API Validation (Sync Server)
|
||
|
||
- Operation IDs: 1–255 characters. Client IDs: alphanumeric, underscore, hyphen; max 255.
|
||
- Entity types: 1–255 characters. Schema version: 1–100.
|
||
- Payload validation by op type (CRT, UPD, DEL, MOV, BATCH, SYNC_IMPORT, etc.); see `validatePayload` in `sync.types.ts`.
|
||
|
||
---
|
||
|
||
## General Notes
|
||
|
||
- **Sync Server:** Production-oriented (JWT, rate limiting, CORS, Helmet). Multi-instance deployment has limitations (e.g. passkey challenge storage in memory; snapshot generation locks); single-instance is the typical deployment.
|
||
- **Plugin API:** Sandboxed; iframe and Electron have different capabilities. See [[3.05-Web-App-vs-Desktop]] and the repo plugin guide.
|
||
- **Local REST API:** Desktop-only, localhost-bound, no authentication. For automation and scripting. See [[3.05-Web-App-vs-Desktop]] for platform differences.
|
||
- **Further documentation:** Sync Server README and auth docs in `packages/super-sync-server/`; Plugin API and examples in `packages/plugin-api/`, `docs/plugin-development.md`, and example plugins under `packages/plugin-dev/`; Local REST API types in `electron/shared-with-frontend/local-rest-api.model.ts`.
|