# 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)`, `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**.

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`.
