# 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:**

- `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 (diagnostic — not used by the production client).
- `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.
- `getSelectedTask()` — Read the task selected in the task detail panel, or `null`.
- `getFocusedTask()` — Read the currently focused task row, or `null`. Row focus is transient and is cleared when focus moves elsewhere, including into iframe side panels; use `getSelectedTask()` for persistent side-panel context.
- `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

Iframe plugins receive a filtered `window.PluginAPI` object injected into `index.html`. The iframe can use the injected task/project/tag APIs, dialog and notification APIs, navigation helpers, persistence helpers, registration helpers, counters, and action dispatch. APIs not injected into the iframe are unavailable, even if they exist on the host-side plugin bridge. `executeNodeScript` is proxied through the host bridge when the desktop app grants the plugin `nodeExecution` permission. Iframe content runs in a sandboxed blob document without `allow-same-origin` and communicates with the host through the filtered `postMessage` bridge.

---

## 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, or a field has the wrong value type (see `error.details`) |
| `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`.
