# 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 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)`, `showIndexHtmlAsView()` — Dialogs and 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`, `persistedDataUpdate`, `action`, `anyTaskUpdate`, `projectListUpdate`. Payload types are defined in `plugin-api` types (`HookPayloadMap`). ### 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 Permissions - **nodeExecution:** Required for `executeNodeScript()` (Electron only; user consent). 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**. ### Prerequisites - **Electron desktop app only** (not available on web or mobile) - Must be enabled in settings - 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": } // Error { "ok": false, "error": { "code": "", "message": "" } } ``` ### 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 | | `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" # 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`; any supplied `projectId`/`tagIds` are ignored by the reducer. | | `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`.