# API Specification

**Status:** In progress — foundation and auth routes implemented (Tasks 002–003). CRM routes planned per `/tasks`. Protected API routes require a valid NextAuth JWT session unless noted.

Base path: `/api` (Next.js Route Handlers under `src/app/api`).

## Conventions

- **Content-Type:** `application/json` unless file upload (`multipart/form-data`)
- **Errors:** `{ "error": string, "code"?: string, "details"?: unknown }`
- **Pagination:** `?page=1&limit=20` → `{ data: [], meta: { page, limit, total } }`
- **IDs:** MongoDB ObjectId strings in URLs
- **Versioning:** None initially; breaking changes documented in changelog

## Authentication (implemented — Task 003)

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| GET, POST | `/api/auth/[...nextauth]` | Public | NextAuth.js handlers (session, sign-in, sign-out, CSRF) |
| GET | `/api/health` | Public | Health check |
| POST | `/api/admin/bootstrap` | `x-bootstrap-secret` header | Create initial admin if env configured |

**Credentials sign-in:** `POST` to NextAuth sign-in with `email` and `password` (handled by NextAuth credentials provider).

**Session (JWT):** includes `user.id`, `user.email`, `user.name`, `user.role` (`admin` | `manager` | `sales` | `marketing` | `viewer`).

**Protected APIs:** middleware returns `401` `{ "error": "Unauthorized" }` for `/api/*` except `/api/health`, `/api/auth/*`, `/api/admin/bootstrap`, `/api/email-tracking/*`.

**Permission guards (Task 018):** routes may return `403` when role lacks permission (`src/lib/permissions.ts`, `src/lib/api-guards.ts`). Viewer is read-only. Admin-only: `/api/settings/**` (except public `GET /api/settings/branding`), `/api/admin/data-cleanup/**`. Public: `GET /api/branding/assets/*`.

Implementation: `src/lib/auth.ts`, `src/app/api/auth/[...nextauth]/route.ts`, `src/middleware.ts`.

### Health (public)

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/health` | Liveness: `status`, `version`, `database`, `smtp`/`imap` presence, `ai` status — no secrets |

See [20-production-readiness.md](./20-production-readiness.md).

## Admin bootstrap (implemented)

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/admin/bootstrap` | Header `x-bootstrap-secret` must match `BOOTSTRAP_SECRET` | Runs `createInitialAdminIfMissing()` |

**Request headers:**

```
x-bootstrap-secret: <BOOTSTRAP_SECRET>
```

**Response 200:**

```json
{
  "status": "created" | "already_exists" | "missing_env",
  "message": "Human-readable status (no password returned)"
}
```

| status | Meaning |
|--------|---------|
| `created` | New admin user created from `INITIAL_ADMIN_*` env vars |
| `already_exists` | User with `INITIAL_ADMIN_EMAIL` already exists |
| `missing_env` | `INITIAL_ADMIN_NAME`, `INITIAL_ADMIN_EMAIL`, or `INITIAL_ADMIN_PASSWORD` not set |

**Response 401:** invalid or missing bootstrap secret.

**Response 403:** `BOOTSTRAP_SECRET` not configured (bootstrap disabled).

**Security:** Local setup only; disable or rotate `BOOTSTRAP_SECRET` in production. Never returns passwords.

Implementation: `src/app/api/admin/bootstrap/route.ts`.

## Notifications (Task 015)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/notifications` | List (`status`, `priority`, `page`, `limit`) |
| GET | `/api/notifications/unread-count` | Unread count for badge |
| PATCH | `/api/notifications/[id]/read` | Mark notification read |
| POST | `/api/notifications/read-all` | Mark all visible notifications read |
| DELETE | `/api/notifications/[id]` | Delete notification |

Implementation: `src/server/services/notification.service.ts`, `src/server/services/automation-rules.service.ts`.

## Dashboard (Task 014)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/dashboard` | Aggregated analytics: market, imports, campaigns, inbox, AI, leads, opportunities, tasks, recent activities, top sectors/cities, pipeline summary |

Requires authenticated session. Implementation: `src/server/services/dashboard.service.ts`.

## Health (implemented)

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| GET | `/api/health` | None | Liveness and optional MongoDB connectivity check |

**Response 200** (success):

```json
{
  "status": "ok",
  "app": "Anwal Growth Platform",
  "timestamp": "2026-06-02T12:00:00.000Z",
  "database": "connected"
}
```

`database` values:

| Value | Meaning |
|-------|---------|
| `connected` | Mongoose connected successfully |
| `not_checked` | Reserved; URI not evaluated |
| `error` | Connection failed or not ready |

**Response 500** (failure):

```json
{
  "status": "error",
  "app": "Anwal Growth Platform",
  "timestamp": "2026-06-02T12:00:00.000Z",
  "database": "error",
  "message": "Health check failed. Verify environment and database connectivity."
}
```

- Dynamic route (`force-dynamic`); not cached.
- Does not return secrets or connection strings.
- Requires valid server env when DB check runs (`getEnv()`).

Implementation: `src/app/api/health/route.ts`.

## Imports (implemented — Task 005)

All routes require authenticated session. Upload via `multipart/form-data` with field `file`.

| Method | Endpoint | Body fields | Description |
|--------|----------|-------------|-------------|
| POST | `/api/imports/detect` | `file` | Detect simple vs processed; for simple, returns `suggestedMapping`, `mappingMatches`, `unmappedHeaders` |
| POST | `/api/imports/preview` | `file`, `importType?`, `mapping?` (optional JSON) | Preview counts; auto-maps simple import when mapping omitted |
| POST | `/api/imports/execute` | Same as preview | Starts import (`202`); returns `importLogId`; runs in background; blocks concurrent imports per user |
| GET | `/api/imports/status/:importLogId` | — | Progress + `resultStats` while processing or after completion |
| GET | `/api/imports/history` | — | Recent logs with summary counters (limit 25) |

**Detect response:** `{ sheetNames, headers, detectedType, confidence, reason, suggestedMapping?, mappingConfidence?, mappingMatches?, unmappedHeaders? }`

**Preview response (simple):** `ImportPreviewResult` plus `mappingUsed`, `mappingAutoDetected`, `mappingMatches?`.

**Execute response (simple):** `ImportExecutionResult` plus `mappingUsed`, `mappingAutoDetected`.

Implementation: `src/server/services/import-mapping.service.ts` — `detectSimpleImportMapping()`, `resolveSimpleImportMapping()`.

**File rules:** `.xlsx`, `.xls`, `.csv`; max 15MB. Safe error messages only.

See [18-import-system.md](./18-import-system.md).

## Market Database (implemented — Task 006)

All routes require authenticated session. Pagination: `?page=1&limit=25` (max 100). Response shape: `{ data: [], pagination: { page, limit, total, totalPages } }` unless noted.

### Companies

| Method | Endpoint | Query params | Description |
|--------|----------|--------------|-------------|
| GET | `/api/market/companies` | `q`, `entityType`, `sector`, `city`, `priority`, `dataConfidence`, `hasEmail`, `hasMobile`, `page`, `limit`, `sortBy`, `sortOrder` | List with search, filters, facets |
| GET | `/api/market/companies/:id` | — | Detail: company, people, contactPoints, serviceMatches, campaignRecipients, emailReplies, leads, opportunities, activities |
| GET | `/api/market/people/:id` | — | Detail: person, contactPoints, campaignRecipients, emailReplies, leads, opportunities, activities |

**List response extras:** `facets: { entityTypes, sectors, cities, priorities, confidenceLevels }`.

**Search:** `q` matches `nameAr`, `nameEn`, `sector`, `activity`, `city`. `hasEmail`/`hasMobile` check `ContactPoint` records.

### People

| Method | Endpoint | Query params | Description |
|--------|----------|--------------|-------------|
| GET | `/api/market/people` | `q`, `companyId`, `jobTitle`, `seniorityLevel`, `isDecisionMaker`, `hasEmail`, `hasMobile`, `page`, `limit` | Paginated people list |

### Contact points

| Method | Endpoint | Query params | Description |
|--------|----------|--------------|-------------|
| GET | `/api/market/contact-points` | `q`, `contactType`, `entityType`, `isVerified`, `dataConfidence`, `page`, `limit` | Paginated contact points with `linkedCompanyId/Name`, `linkedPersonId/Name`, `campaignCount`, `replyCount` |

**Removed API routes (UI retired):** `/api/market/duplicate-reviews`, `/api/market/data-quality-issues`. Models `DuplicateReview` and `DataQualityIssue` remain in MongoDB for legacy records.

Implementation: `src/server/services/market.service.ts`, `src/app/api/market/**`.

## Companies (planned — manual CRUD)

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/companies` | List with search and filters |
| POST | `/api/companies` | Create company |
| GET | `/api/companies/:id` | Get by id |
| PATCH | `/api/companies/:id` | Update |
| DELETE | `/api/companies/:id` | Soft delete |

## People

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/people` | List; filter by `companyId` |
| POST | `/api/people` | Create |
| GET | `/api/people/:id` | Get |
| PATCH | `/api/people/:id` | Update |
| DELETE | `/api/people/:id` | Soft delete |

## Contact points

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/contact-points` | List; filter by company/person |
| POST | `/api/contact-points` | Create |
| PATCH | `/api/contact-points/:id` | Update |
| DELETE | `/api/contact-points/:id` | Delete |

## Leads (Task 013)

All routes require authenticated session.

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/leads` | List (`q`, `status`, `temperature`, `priority`, `assignedTo`, `page`, `limit`) |
| POST | `/api/leads` | Manual create lead |
| GET | `/api/leads/:id` | Lead detail + classification, email reply, opportunities, tasks, activities |
| PATCH | `/api/leads/:id` | Update lead fields |
| POST | `/api/leads/from-email-reply` | Convert classified reply → lead + opportunity + task (`emailReplyId`, `force?`) |
| POST | `/api/leads/:id/create-opportunity` | Create opportunity from lead |

Implementation: `src/server/services/lead.service.ts`, `src/app/api/leads/**`.

## Opportunities (Task 013)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/opportunities` | List (`q`, `stage`, `assignedTo`, `companyId`, `page`, `limit`) |
| POST | `/api/opportunities` | Create opportunity |
| GET | `/api/opportunities/pipeline` | Opportunities grouped by stage (kanban) |
| GET | `/api/opportunities/:id` | Detail + activities |
| PATCH | `/api/opportunities/:id` | Update fields |
| POST | `/api/opportunities/:id/move-stage` | Body `{ "stage": "proposal" }` — updates probability |
| POST | `/api/opportunities/:id/close-won` | Set stage `won` |
| POST | `/api/opportunities/:id/close-lost` | Set stage `lost` |

Implementation: `src/server/services/opportunity.service.ts`, `src/app/api/opportunities/**`.

## Tasks (Task 013)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/tasks` | List (`status`, `priority`, `assignedTo`, `page`, `limit`) — default open |
| POST | `/api/tasks` | Create task |
| GET | `/api/tasks/:id` | Get task |
| PATCH | `/api/tasks/:id` | Update task |
| POST | `/api/tasks/:id/complete` | Mark completed |

Implementation: `src/server/services/task.service.ts`, `src/app/api/tasks/**`.

## Segments

All routes require authenticated session.

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/segments` | List segments (`q`, `targetType`, `status`, `type`, `page`, `limit`) |
| POST | `/api/segments` | Create segment (`name`, `description`, `targetType`, `targetChannel`, `type`, `filters`, `status`) |
| GET | `/api/segments/:id` | Get segment detail |
| PATCH | `/api/segments/:id` | Update segment |
| DELETE | `/api/segments/:id` | Soft delete (set status `inactive`) |
| POST | `/api/segments/preview` | Preview dynamic filters (`targetChannel`, email validation filters); batched dedup + paginated rows + dedup stats |
| GET | `/api/segments/:id/members` | Paginated members: dynamic = filter match; static = `staticIds` |
| GET | `/api/segments/filter-options` | `targetType=companies\|people\|contact_points` — distinct filter values from DB |
| GET | `/api/segments/static-list` | `targetType` — active static segments for bulk add UI |
| POST | `/api/segments/:id/members` | Body `{ "ids": ["..."] }` — append to `staticIds` (static only) |
| DELETE | `/api/segments/:id/members` | Body `{ "ids": ["..."] }` — remove from `staticIds` (static only) |

**Filter options response (companies example):**

```json
{
  "entityTypes": [],
  "sectors": [],
  "cities": [],
  "priorities": [],
  "confidenceLevels": [],
  "recommendedServices": []
}
```

**Preview response:**

```json
{
  "rawCount": 150,
  "uniqueCount": 123,
  "duplicatesRemoved": 27,
  "validEmails": 100,
  "roleBasedEmails": 15,
  "count": 123,
  "sample": [],
  "targetType": "companies",
  "targetChannel": "email",
  "filters": {}
}
```

## Timeline (Task 022)

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/timeline` | Entity-scoped feed — `companyId`, `personId`, `leadId`, `campaignId`, `sequenceId`, `category`, `q`, `dateFrom`, `dateTo`, `page`, `limit` |
| GET | `/api/activities` | Global activity feed (same query params, no entity required) |
| GET | `/api/timeline/counts` | Summary counters (`emailsSent`, `replies`, `campaigns`, `sequences`, `tasks`, `notes`) |
| POST | `/api/timeline/notes` | Create manual note — `{ entityType, entityId, text }` → `note_created` |

See [27-crm-timeline-completion.md](./27-crm-timeline-completion.md).

## Sequences (Task 019)

All routes require authenticated session.

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/sequences` | List sequences (`q`, `status`, `page`, `limit`) |
| POST | `/api/sequences` | Create sequence |
| GET | `/api/sequences/:id` | Sequence detail |
| PATCH | `/api/sequences/:id` | Update sequence |
| POST | `/api/sequences/preview-recipients` | Preview recipients from segment |
| POST | `/api/sequences/:id/generate-recipients` | Generate sequence recipients |
| POST | `/api/sequences/:id/activate` | Activate sequence |
| POST | `/api/sequences/:id/pause` | Pause sequence |
| POST | `/api/sequences/:id/resume` | Resume sequence |
| POST | `/api/sequences/:id/archive` | Archive sequence |
| POST | `/api/sequences/:campaignId/from-campaign` | Create sequence draft from campaign |

Recipient generation uses **`getAllDedupedSegmentMembers`** (batched `segment-dedup-accumulator`) for large segments.

## Cron jobs

Public cron endpoints require header `x-cron-secret` matching `CRON_SECRET`. Each run writes a `CronRunLog`.

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/cron/run-sequences` | `x-cron-secret` | Process due sequence steps |
| POST | `/api/cron/sync-mailboxes` | `x-cron-secret` | IMAP sync all active mailboxes |

**Admin cron (session + admin role):**

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/admin/cron/summary` | Last run per job |
| GET | `/api/admin/cron/logs` | Paginated `CronRunLog` list |
| POST | `/api/admin/cron/sync-mailboxes` | Manual mailbox sync |
| POST | `/api/admin/cron/run-sequences` | Manual sequence worker |

See [24-cpanel-cron.md](./24-cpanel-cron.md).

## Campaigns

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/campaigns` | List (`q`, `status`, `page`, `limit`) |
| POST | `/api/campaigns` | Create draft campaign |
| GET | `/api/campaigns/:id` | Campaign detail |
| PATCH | `/api/campaigns/:id` | Update campaign |
| DELETE | `/api/campaigns/:id` | Archive campaign |
| POST | `/api/campaigns/preview-recipients` | Preview recipients from segment/template |
| POST | `/api/campaigns/:id/generate-recipients` | Generate and save campaign recipients |
| GET | `/api/campaigns/:id/recipients` | List generated recipients (paginated) |
| GET | `/api/campaigns/:id/stats` | Opens/clicks/sends |
| GET | `/api/campaigns/:id/check-send-duplicates` | Legacy duplicate check for pending recipients |
| POST | `/api/campaigns/:id/duplicate-send-check` | Pre-send duplicate analysis (`mode`, `recipientId?`, `limit?`) |
| POST | `/api/campaigns/:id/send-batch` | Unified send (`mode`, `limit`/`batchSize`, `duplicateDecision`, `applyDuplicateDecisionToAll`). Returns **409** with `requiresDuplicateDecision: true` when policy is `warn` and duplicates exist |
| POST | `/api/campaigns/:id/send-one` | Send one recipient (`recipientId`, `duplicateDecision?`) |

**Campaign preview recipients response:**

```json
{
  "totalCandidates": 100,
  "validRecipients": 80,
  "skipped": 20,
  "sample": []
}
```

## Email templates (implemented — Task 008)

All routes require authenticated session.

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/email-templates` | List templates (`q`, `status`, `language`, `category`, `page`, `limit`) |
| POST | `/api/email-templates` | Create template (variables auto-extracted/validated) |
| GET | `/api/email-templates/:id` | Get template detail |
| PATCH | `/api/email-templates/:id` | Update template (re-extract variables) |
| DELETE | `/api/email-templates/:id` | Archive template (status `archived`) |
| POST | `/api/email-templates/preview` | Preview unsaved input with `sampleData` |
| POST | `/api/email-templates/:id/preview` | Preview saved template with `sampleData` |

**Preview response:**

```json
{
  "subject": "…",
  "htmlContent": "…",
  "textContent": "…",
  "variables": ["company_name", "person_name"]
}
```

## Email sending (Task 010)

All routes require authenticated session except tracking.

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/email/verify-smtp` | Verify SMTP connection using env config |
| POST | `/api/email/test-send` | Send test email (`to`, `subject`, `htmlContent?`, `textContent?`) |
| GET | `/api/email/verify-imap` | Verify IMAP connection using env config |

## Email tracking (Task 010)

Public routes (no session) for recipient engagement.

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/email-tracking/open/:trackingId` | Returns transparent pixel and marks opened |
| GET | `/api/email-tracking/click/:trackingId?url=...` | Marks clicked and redirects to validated URL |

## Inbox

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/inbox` | List inbox emails (legacy per-reply list) |
| GET | `/api/inbox/:id` | Inbox email detail (legacy) |
| PATCH | `/api/inbox/:id` | Update inbox status |
| POST | `/api/inbox/sync` | Sync recent IMAP emails and create/link `EmailReply` records |
| GET | `/api/inbox/threads` | List conversation threads (`filter`, `q`, `page`, `limit`) |
| GET | `/api/inbox/threads/:id` | Thread detail + messages |
| POST | `/api/inbox/threads/:id/reply` | Send reply from system |
| POST | `/api/inbox/threads/:id/forward` | Forward thread |
| POST | `/api/inbox/threads/:id/archive` | Archive thread |
| POST | `/api/inbox/threads/:id/read` | Mark thread read |
| POST | `/api/inbox/threads/:id/unread` | Mark thread unread |
| POST | `/api/inbox/threads/:id/classify` | AI classify latest inbound |
| POST | `/api/inbox/threads/:id/create-task` | Create follow-up task |
| POST | `/api/inbox/threads/:id/stop-sequence` | Stop linked sequence |
| POST | `/api/inbox/threads/:id/suppress` | Suppress sender |
| POST | `/api/admin/maintenance/backfill-inbox-threads` | Backfill threads for existing replies |

Implementation: `inbox.service.ts`, `email-thread.service.ts`, `src/app/api/inbox/**`. See [25-professional-inbox.md](./25-professional-inbox.md).

---

## AI classification (Task 012)

| Method | Path | Body | Description |
|--------|------|------|-------------|
| GET | `/api/ai/status` | — | Active provider, availability, model, fallback flag |
| POST | `/api/ai/classify-email-reply` | `{ "emailReplyId": "..." }` | Classify one reply; upsert `AiClassification`; update `EmailReply` |
| POST | `/api/ai/classify-email-replies/bulk` | `{ "limit": 20 }` optional | Bulk classify unclassified replies (`status: new`) |
| POST | `/api/ai/classify-preview` | `ClassifyEmailReplyInput` (e.g. `subject`, `bodyText`) | Classify without saving |

All routes require authenticated session (`401` if missing).

Implementation: `src/server/services/ai-classification.service.ts`, `src/server/ai/**`, `src/app/api/ai/**`.

## AI content generation (Task 021 Phase 2)

Extends classification with content generation, scoring, and recommendations. See [26-ai-content-generation.md](./26-ai-content-generation.md).

| Method | Path | Body | Description |
|--------|------|------|-------------|
| GET | `/api/ai/guard` | — | AI enabled state; warning when provider key missing |
| GET, PATCH | `/api/settings/ai/brand-voice` | Brand voice fields | Company tone/context for all generation |
| POST | `/api/ai/email-writer` | `action`, `goal`, `service`, `subject`, `body`, … | Generate or rewrite email content |
| POST | `/api/ai/template-generator` | `templateType`, `language`, `tone`, … | Full template (subject, body, CTA) |
| POST | `/api/ai/sequence-generator` | `goal`, `numberOfEmails`, … | Multi-step sequence plan |
| POST | `/api/ai/segment-builder` | `{ "query": "..." }` | Natural language → segment filters (preview only) |
| POST | `/api/ai/lead-scoring` | `{ entityType, entityId }` or `{ entityType, entityIds[] }` | Score lead/company/person |
| GET | `/api/ai/next-best-action` | `?entityType=&entityId=` | Recommended CRM action |
| POST | `/api/ai/follow-up` | `lastReply`, `conversationHistory`, context fields | Suggested reply HTML |

When `aiEnabled` is false, routes return fallback content with `error` message (Arabic). When API key is missing, rule-based fallback is used with warning — never crashes.

Implementation: `ai-content.service.ts`, `ai-lead-scoring.service.ts`, `ai-next-best-action.service.ts`, `ai-guard.service.ts`, `src/components/ai/**`.

## Import

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/import/excel` | Upload processed Excel |
| GET | `/api/import/batches` | List import history |
| GET | `/api/import/batches/:id` | Batch detail and errors |

## Webhooks

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/webhooks/n8n` | Inbound from n8n (authenticated via secret header) |

## Dashboard

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/dashboard/summary` | KPIs: leads, pipeline, campaigns |
| GET | `/api/dashboard/activity` | Recent activity feed |

## Settings (Task 016 — admin only)

All routes return `403` unless session role has `settings.manage` (admin). Implementation: `requireAdminApiSession()`, `src/app/api/settings/**`.

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/settings/branding` | Public read — platform name, logos, colors, support contacts |
| PATCH | `/api/settings/branding` | Admin — update brand settings |
| POST | `/api/settings/branding/upload` | Admin — multipart `assetType` (`logoLight` \| `logoDark` \| `favicon`) + `file` |
| GET | `/api/branding/assets/:filename` | Public — serve uploaded branding file |
| GET, PATCH | `/api/settings/system` | App name, company, locale, default sender |
| GET, PATCH | `/api/settings/email-policy` | `duplicateWindowDays` (0–365), `duplicateAction` (`warn` \| `skip` \| `allow`) |
| GET, PATCH | `/api/settings/imports` | Duplicate threshold, match fields, auto flags |
| GET, PATCH | `/api/settings/ai` | Provider/model view; PATCH updates DB overrides (not API keys) |
| GET, PATCH | `/api/settings/ai/brand-voice` | Brand voice for AI content generation |
| POST | `/api/settings/ai` | Test classification (`subject`, `bodyText`) |
| GET, POST | `/api/settings/mailboxes` | List / create mailboxes |
| GET, PATCH, DELETE | `/api/settings/mailboxes/:id` | Mailbox CRUD |
| POST | `/api/settings/mailboxes/:id/verify-smtp` | Verify SMTP credentials |
| POST | `/api/settings/mailboxes/:id/verify-imap` | Verify IMAP credentials |
| POST | `/api/settings/mailboxes/:id/set-default` | Set default mailbox |
| GET, POST | `/api/settings/users` | List / create users |
| GET, PATCH | `/api/settings/users/:id` | Update user; `active` toggles status |
| POST | `/api/settings/users/:id/reset-password` | `{ "password": "..." }` |

See [18-settings-administration.md](./18-settings-administration.md).

## Data cleanup (Task 017 — admin only)

Destructive reset tools. Session + admin role required (`403` for non-admin). See [19-data-cleanup-reset.md](./19-data-cleanup-reset.md).

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/admin/data-cleanup/preview?scope=` | Count documents per collection for scope; `includeUsers=true` only for `everything` |
| POST | `/api/admin/data-cleanup/execute` | Body: `{ scope, confirmation, includeUsers? }` — confirmation phrase must match scope |
| POST | `/api/admin/maintenance/backfill-crm-foundation` | Normalize/classify emails, link CRM entities, backfill timeline activities |

**Scopes:** `market_data`, `imports`, `campaigns`, `inbox`, `leads_pipeline`, `notifications`, `operational_all`, `everything`.

## Outbound n8n (not REST on this app)

The application **calls** n8n webhook URLs from env when events occur. See [09-n8n-integration.md](./09-n8n-integration.md).

## Update policy

Any new route or change to request/response shape must update this file in the same task PR/commit.
