# ALL OVER APPS - ADMIN API > Centralized administration API for all ALL OVER APPS applications. Manages multiple Shopify apps from a unified admin panel. Base: https://api.alloverapps.com Docs: [HTML](https://api.alloverapps.com/) | [OpenAPI JSON](https://api.alloverapps.com/openapi.json) ## Auth Methods - JWT: `Authorization: Bearer ` via POST /auth/login - KEY: `X-API-Key: ` (header) ## Conventions - Auth: JWT = Bearer token, KEY = API Key, JWT|KEY = either, NONE = no auth - `*` after field name = required, all fields string unless noted with `:type` - Common errors: 400/401/404 return `{success:false, error}` --- ## Auth Back Office authentication ### POST /auth/login - Back Office team login | NONE Body: `{email*, password*}` 200: `{success:boolean, token, user:{id, email, name, role}}` ### POST /auth/refresh - Refresh JWT token | JWT 200: `{success:boolean, token, user:{}}` ### POST /auth/hash-password - [DEV] Generate password hash | NONE Body: `{password*}` 200: `{hash}` ### GET /auth/me - Current user information | JWT 200: `{success:boolean, user:{id, email, name, role}}` ## Users Admin user management ### GET /users/ - List users | JWT Query: ?role ?isActive 200: `{success:boolean, data:[{_id, email, firstName, lastName, role, isActive:boolean, profile:{avatar, bio, phone, timezone, language}, settings:{notifications:{email:boolean, push:boolean}, theme, defaultApp}, lastLoginAt, loginCount:number, createdAt, updatedAt}]}` ### POST /users/ - Create user | JWT Body: `{email*, password*, firstName*, lastName*, role, profile:{avatar, bio, phone, timezone, language}, settings:{notifications:{email:boolean, push:boolean}, theme, defaultApp}}` 201: `{success:boolean, data:{}, message}` ### GET /users/{id} - Get user by ID | JWT Path: :id (User ID) 200: `{success:boolean, data:{}}` ### PATCH /users/{id} - Update user | JWT Path: :id Body: `{firstName, lastName, role, isActive:boolean, profile:{avatar, bio, phone, timezone, language}, settings:{notifications:{email:boolean, push:boolean}, theme, defaultApp}}` 200: `{success:boolean, data:{}, message}` ### DELETE /users/{id} - Delete user | JWT Path: :id 200: `{success:boolean, message}` ### PATCH /users/{id}/password - Update user password | JWT Path: :id Body: `{password*}` 200: `{success:boolean, message}` ## Apps Application management ### GET /apps - List of available applications | JWT 200: `{success:boolean, data:[{id, name, features:{canEditTemplates:boolean, canEditUsers:boolean, canViewMetrics:boolean}}]}` ## API Keys API key management for backend-to-backend authentication ### GET /api-keys/stats - API key statistics | JWT Query: ?appId 200: `{success:boolean, data:{total:integer, active:integer, inactive:integer, expired:integer, byApp:{}}}` ### GET /api-keys/ - List API keys | JWT Query: ?page:integer ?limit:integer ?appId ?isActive ?search ?sortBy ?sortOrder 200: `{success:boolean, data:[{_id, appId, name, description, allowedOrigins:string[], isActive:boolean, lastUsed, expiresAt, createdBy, createdAt, updatedAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### POST /api-keys/ - Create API key | JWT Body: `{appId*, name*, description, allowedOrigins:string[], expiresAt}` 201: `{success:boolean, data:{_id, key, appId, name, description, allowedOrigins:string[], isActive:boolean, expiresAt, createdBy, createdAt}, message}` ### GET /api-keys/{keyId} - Get API key | JWT Path: :keyId (API key ID) 200: `{success:boolean, data:{_id, key, appId, name, description, allowedOrigins:string[], isActive:boolean, lastUsed, expiresAt, createdBy, createdAt, updatedAt}}` ### PATCH /api-keys/{keyId} - Update API key | JWT Path: :keyId (API key ID) Body: `{name, description, allowedOrigins:string[], isActive:boolean, expiresAt}` 200: `{success:boolean, data:{}, message}` ### DELETE /api-keys/{keyId} - Delete API key | JWT Path: :keyId (API key ID) 200: `{success:boolean, message}` ### POST /api-keys/{keyId}/revoke - Revoke API key | JWT Path: :keyId (API key ID) 200: `{success:boolean, data:{}, message}` ### POST /api-keys/{keyId}/regenerate - Regenerate API key | JWT Path: :keyId (API key ID) 200: `{success:boolean, data:{_id, key, appId, name, isActive:boolean, createdAt}, message}` ## Shops Shopify shop management per app ### GET /{appId}/shops/stats - Shop statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:{total:integer, active:integer, inactive:integer, byPlan:{}}}` ### GET /{appId}/shops/domains - List of shop domains | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:string[]}` ### GET /{appId}/shops/ - List shops | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?isActive:boolean ?search ?sortBy ?sortOrder 200: `{success:boolean, data:object[], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /{appId}/shops/{shopId} - Get shop by ID | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :shopId (Shop ID) 200: `{success:boolean, data:{}}` ### PATCH /{appId}/shops/{shopId} - Update shop | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :shopId Body: `{email, name, isActive:boolean, settings:{}, plan:{type, activatedAt, trialEndsAt}, needsDataRefresh:boolean, needsAuthUpdate:boolean, timezone, storeLocale, auth:{corsEnabled:boolean, isApiActive:boolean, allowedOrigins:string[]}}` 200: `{success:boolean, data:{}, message}` ### PATCH /{appId}/shops/{shopId}/status - Change shop status | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :shopId Body: `{isActive*:boolean}` 200: `{success:boolean, data:{}, message}` ## Templates Email template management ### GET /{appId}/templates/categories - Template languages | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?shopId ### GET /{appId}/templates/ - List templates | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?shopId ?language ?isActive:boolean ?search ### POST /{appId}/templates/ - Create template | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{name*, language*, subject*, htmlTemplate*, textTemplate*, variables:string[], isActive:boolean, version:number, shopId}` ### GET /{appId}/templates/{templateId} - Get template by ID | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :templateId ### PUT /{appId}/templates/{templateId} - Update template (full) | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :templateId ### PATCH /{appId}/templates/{templateId} - Update template (partial) | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :templateId ### DELETE /{appId}/templates/{templateId} - Delete template | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :templateId ### PATCH /{appId}/templates/{templateId}/toggle - Toggle template active status | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :templateId Body: `{isActive*:boolean}` ### POST /{appId}/templates/{templateId}/duplicate - Duplicate template | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :templateId Body: `{name}` ## Metrics Metrics and analytics ### GET /metrics/aggregate/dashboard - Aggregated dashboard from all apps | JWT 200: `{success:boolean, data:{aggregate:{shops:{total:integer, active:integer, inactive:integer, newToday:integer, newThisWeek:integer, newThisMonth:integer, byPlan:...}}, byApp:object[]}}` ### GET /metrics/aggregate/shops-over-time - Shops over time (all apps) | JWT Query: ?days:integer 200: `{success:boolean, data:{aggregate:[{date, total:integer, byApp:{}}], byApp:object[]}}` ### GET /metrics/aggregate/top-shops - Top shops (all apps) | JWT Query: ?limit:integer 200: `{success:boolean, data:{aggregate:[{shop, name, domain, isActive:boolean, lastLogin, apps:[{appId, appName, plan, isActive:boolean}]}], byApp:object[]}}` ### GET /metrics/aggregate/activity - Recent activity (all apps) | JWT Query: ?limit:integer 200: `{success:boolean, data:[{type, appId, appName, shop:{name, email, domain, isActive:boolean}, timestamp}]}` ### GET /metrics/aggregate/summary - Quick summary (all apps) | JWT 200: `{success:boolean, data:{totals:{totalShops:integer, activeShops:integer, inactiveShops:integer, newToday:integer, newThisWeek:integer, newThisMonth:integer}, byApp:[{appId, appName, totalShops:integer, activeShops:integer, inactiveShops:integer, newToday:integer, newThisWeek:integer, newThisMonth:integer}]}}` ### GET /{appId}/metrics/dashboard - General dashboard | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:{}}` ### GET /{appId}/metrics/shops-over-time - Shops over time | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?days:integer ### GET /{appId}/metrics/top-shops - Top shops | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?limit:integer ### GET /{appId}/metrics/activity - Recent activity | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?limit:integer ### GET /{appId}/metrics/custom - Custom metrics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?type ?shop ?startDate ?endDate ?limit:integer ## Logs Application logs and debugging ### GET /{appId}/logs/stats - Log statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?startDate ?endDate ?shopDomain 200: `{success:boolean, data:{total:integer, byLevel:{debug:integer, info:integer, warn:integer, error:integer}, topModules:[{module, count:integer}]}}` ### GET /{appId}/logs/shop-domains - List of shop domains | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:string[]}` ### GET /{appId}/logs/modules - List of modules | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:string[]}` ### GET /{appId}/logs/over-time - Logs over time | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?days:integer ?level ?module ?shopDomain 200: `{success:boolean, data:[{date, debug:integer, info:integer, warn:integer, error:integer, total:integer}]}` ### GET /{appId}/logs/ - List logs | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?level ?module ?shopDomain ?startDate ?endDate ?search ?sortBy ?sortOrder 200: `{success:boolean, data:[{_id, shopDomain, module, level, message, data:{}, error:{name, message, stack, type, isErrorInstance:boolean}, createdAtTimestamp, createdAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### DELETE /{appId}/logs/ - Delete logs | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{olderThanDays:integer, level, module}` 200: `{success:boolean, data:{deletedCount:integer}, message}` ### GET /{appId}/logs/{logId} - Get log by ID | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :logId (Log ID) 200: `{success:boolean, data:{}}` ### DELETE /{appId}/logs/{logId} - Delete log by ID | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :logId (Log ID) 200: `{success:boolean, message}` ## Feedback User feedback management ### GET /feedback/ - List all feedback | JWT Query: ?page:integer ?limit:integer ?appId ?status ?type ?priority ?shopId ?rating ?startDate ?endDate ?search ?sortBy ?sortOrder 200: `{success:boolean, data:object[], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /feedback/{feedbackId} - Get feedback by ID | JWT Path: :feedbackId (Feedback ID) 200: `{success:boolean, data:{}}` ### PATCH /feedback/{feedbackId} - Update feedback | JWT Path: :feedbackId Body: `{status, priority}` 200: `{success:boolean, data:{}, message}` ### DELETE /feedback/{feedbackId} - Delete feedback | JWT Path: :feedbackId 200: `{success:boolean, message}` ### PATCH /feedback/{feedbackId}/status - Update feedback status | JWT Path: :feedbackId Body: `{status*}` 200: `{success:boolean, data:{}, message}` ### GET /{appId}/feedback/stats - Feedback statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?startDate ?endDate ?shopId 200: `{success:boolean, data:{total:integer, byStatus:{}, byType:{}, byPriority:{}, byRating:{}}}` ### POST /{appId}/feedback/ - Create feedback | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{shopId*, type*, subject*, message*, rating, ratingReason, canContact:boolean, priority}` 201: `{success:boolean, data:{}, message}` ### GET /{appId}/feedback/ - List feedback | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?status ?type ?priority ?shopId ?rating ?startDate ?endDate ?search ?sortBy ?sortOrder 200: `{success:boolean, data:object[], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ## Tickets Support tickets management ### GET /tickets/ - List all tickets | JWT Query: ?page:integer ?limit:integer ?appId ?status ?topic ?priority ?shopId ?email ?startDate ?endDate ?search ?sortBy ?sortOrder 200: `{success:boolean, data:object[], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /tickets/{messageId} - Get ticket by ID | JWT Path: :messageId (Ticket ID) 200: `{success:boolean, data:{}}` ### PATCH /tickets/{messageId} - Update ticket | JWT Path: :messageId Body: `{status, priority}` 200: `{success:boolean, data:{}, message}` ### DELETE /tickets/{messageId} - Delete ticket | JWT Path: :messageId 200: `{success:boolean, message}` ### PATCH /tickets/{messageId}/status - Update ticket status | JWT Path: :messageId Body: `{status*}` 200: `{success:boolean, data:{}, message}` ### GET /{appId}/tickets/stats - Ticket statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?startDate ?endDate ?shopId 200: `{success:boolean, data:{total:integer, byStatus:{}, byTopic:{}, byPriority:{}, recentActivity:[{_id, subject, email, status, priority, createdAt}], volumeOverTime:[{date, count:integer}], averageResolutionTime:number}}` ### POST /{appId}/tickets/ - Create ticket with attachments | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 201: `{success:boolean, data:{_id, appId, shopId, topic, email, subject, message, status, priority, attachments:[{url, key, filename, mimetype, size:number, uploadedAt}], createdAt, updatedAt}, message}` ### GET /{appId}/tickets/ - List tickets | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?status ?topic ?priority ?shopId ?email ?startDate ?endDate ?search ?sortBy ?sortOrder 200: `{success:boolean, data:object[], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### DELETE /{appId}/tickets/ - Bulk delete tickets | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{ids*:string[]}` 200: `{success:boolean, message, deletedCount:integer}` ### GET /{appId}/tickets/{messageId} - Get ticket by ID | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :messageId (Ticket ID) 200: `{success:boolean, data:{}}` ### PATCH /{appId}/tickets/{messageId} - Update ticket | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :messageId (Ticket ID) Body: `{status, priority, topic}` 200: `{success:boolean, data:{}, message}` ### DELETE /{appId}/tickets/{messageId} - Delete ticket | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :messageId (Ticket ID) 200: `{success:boolean, message}` ### PATCH /{appId}/tickets/{messageId}/status - Update ticket status | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :messageId (Ticket ID) Body: `{status*}` 200: `{success:boolean, data:{}, message}` ### POST /{appId}/tickets/{messageId}/messages - Add message to ticket | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :messageId (Ticket ID) Body: `{content*, isCustomer:boolean, authorEmail, authorName}` 201: `{success:boolean, data:{}, message}` ### GET /{appId}/tickets/{messageId}/messages - Get ticket messages | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :messageId (Ticket ID) 200: `{success:boolean, data:[{_id, content, isCustomer:boolean, authorEmail, authorName, createdAt}]}` ## Magic Link Passwordless authentication for end users ### POST /magic-link/ - Send magic link | NONE Body: `{email*, appId*}` 200: `{success:boolean, message}` ### POST /magic-link/verify - Verify magic link token | NONE Body: `{token*}` 200: `{success:boolean, token, user:{id, email, appId}}` ## Wants Feature requests with voting system ### GET /{appId}/wants/ - List wants | KEY Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?status ?category ?sort 200: `{success:boolean, data:[{_id, appId, title, description, category, status, voteCount:integer, hasVoted:boolean, authorEmail, createdAt, updatedAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### POST /{appId}/wants/ - Submit a want | KEY Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{title*, description*, category*}` 201: `{success:boolean, data:{_id, appId, title, description, category, status, voteCount:integer, hasVoted:boolean, authorEmail, createdAt, updatedAt}}` ### GET /{appId}/wants/{id} - Get want by ID | KEY Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feature request ID) 200: `{success:boolean, data:{_id, appId, title, description, category, status, voteCount:integer, hasVoted:boolean, authorEmail, createdAt, updatedAt}}` ### POST /{appId}/wants/{id}/vote - Vote on a want | KEY Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feature request ID) 200: `{success:boolean, data:{voteCount:integer, hasVoted:boolean}}` ### GET /admin/{appId}/wants/stats - Feature request statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:{total:integer, byStatus:{}, byCategory:{}, byPriority:{}}}` ### GET /admin/{appId}/wants/ - List wants (admin) | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?status ?category ?sort 200: `{success:boolean, data:[{_id, appId, title, description, category, status, priority, voteCount:integer, authorEmail, createdAt, updatedAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /admin/{appId}/wants/{id} - Get want by ID (admin) | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feature request ID) 200: `{success:boolean, data:{_id, appId, title, description, category, status, priority, voteCount:integer, authorEmail, createdAt, updatedAt}}` ### PATCH /admin/{appId}/wants/{id} - Update want | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feature request ID) Body: `{title, description, category}` 200: `{success:boolean, data:{_id, appId, title, description, category, status, priority, voteCount:integer, authorEmail, createdAt, updatedAt}}` ### DELETE /admin/{appId}/wants/{id} - Delete want | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feature request ID) 200: `{success:boolean, message}` ### PATCH /admin/{appId}/wants/{id}/status - Update want status | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feature request ID) Body: `{status*}` 200: `{success:boolean, data:{_id, appId, title, description, category, status, priority, voteCount:integer, authorEmail, createdAt, updatedAt}}` ### PATCH /admin/{appId}/wants/{id}/priority - Update want priority | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feature request ID) Body: `{priority*}` 200: `{success:boolean, data:{_id, appId, title, description, category, status, priority, voteCount:integer, authorEmail, createdAt, updatedAt}}` ## Newsletter Newsletter subscribers, campaigns, and templates ### POST /{appId}/newsletter/subscribe - Subscribe to newsletter | KEY Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{email*, name, language, referralUrl}` 201: `{success:boolean, data:{email, name}}` ### GET /{appId}/newsletter/unsubscribe/{token} - Unsubscribe from newsletter | KEY Path: :appId (Application ID (e.g.: banners-all-over)) :token (Unsubscribe token) 200: `string` ### POST /{appId}/newsletter/unsubscribe/{token} - Unsubscribe from newsletter (POST) | KEY Path: :appId (Application ID (e.g.: banners-all-over)) :token (Unsubscribe token) 200: `string` ### GET /admin/{appId}/newsletter/subscribers/stats - Subscriber statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:{total:integer, active:integer, unsubscribed:integer, bounced:integer, bySource:{}}}` ### GET /admin/{appId}/newsletter/subscribers - List subscribers | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?status ?search 200: `{success:boolean, data:[{_id, appId, email, name, language, referralUrl, status, source, subscribedAt, unsubscribedAt, createdAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### POST /admin/{appId}/newsletter/subscribers - Add subscriber manually | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{email*, name}` 201: `{success:boolean, data:{_id, appId, email, name, language, referralUrl, status, source, subscribedAt, unsubscribedAt, createdAt}}` ### POST /admin/{appId}/newsletter/subscribers/import - Import subscribers | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{subscribers*:[{email*, name}]}` 200: `{success:boolean, data:{imported:integer, skipped:integer}}` ### DELETE /admin/{appId}/newsletter/subscribers/{id} - Delete subscriber | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Subscriber ID) 200: `{success:boolean, message}` ### GET /admin/{appId}/newsletter/campaigns - List campaigns | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?status 200: `{success:boolean, data:[{_id, appId, name, subject, status, stats:{total:integer, sent:integer, failed:integer}, sentAt, completedAt, createdBy, createdAt, updatedAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### POST /admin/{appId}/newsletter/campaigns - Create campaign | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{name*, subject*, htmlContent*, textContent, templateId}` 201: `{success:boolean, data:{_id, appId, name, subject, status, stats:{total:integer, sent:integer, failed:integer}, sentAt, completedAt, createdBy, createdAt, updatedAt}}` ### GET /admin/{appId}/newsletter/campaigns/{id} - Get campaign | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Campaign ID) 200: `{success:boolean, data:{_id, appId, name, subject, status, stats:{total:integer, sent:integer, failed:integer}, sentAt, completedAt, createdBy, createdAt, updatedAt}}` ### PATCH /admin/{appId}/newsletter/campaigns/{id} - Update campaign | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Campaign ID) Body: `{name, subject, htmlContent, textContent}` 200: `{success:boolean, data:{_id, appId, name, subject, status, stats:{total:integer, sent:integer, failed:integer}, sentAt, completedAt, createdBy, createdAt, updatedAt}}` ### DELETE /admin/{appId}/newsletter/campaigns/{id} - Delete campaign | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Campaign ID) 200: `{success:boolean, message}` ### POST /admin/{appId}/newsletter/campaigns/{id}/send - Send campaign | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Campaign ID) 200: `{success:boolean, data:{status, jobId}}` ### POST /admin/{appId}/newsletter/campaigns/{id}/test - Send test email | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Campaign ID) Body: `{email*}` 200: `{success:boolean, message}` ### GET /admin/{appId}/newsletter/templates - List newsletter templates | JWT Path: :appId 200: `{success:boolean, data:[{_id, name, subject, htmlContent, textContent, isActive:boolean, createdBy, createdAt, updatedAt}]}` ### POST /admin/{appId}/newsletter/templates - Create newsletter template | JWT Path: :appId Body: `{name*, subject*, htmlContent*, textContent}` 201: `{success:boolean, data:{_id, name, subject, htmlContent, textContent, isActive:boolean, createdBy, createdAt, updatedAt}}` ### PATCH /admin/{appId}/newsletter/templates/{id} - Update newsletter template | JWT Path: :id (Template ID) Body: `{name, subject, htmlContent, textContent, isActive:boolean}` 200: `{success:boolean, data:{_id, name, subject, htmlContent, textContent, isActive:boolean, createdBy, createdAt, updatedAt}}` ### DELETE /admin/{appId}/newsletter/templates/{id} - Delete newsletter template | JWT Path: :id (Template ID) 200: `{success:boolean, message}` ## Contacts Shared contact model with per-category consent and GDPR compliance ### GET /admin/contacts/ - List contacts | JWT Query: ?page:integer ?limit:integer ?search ?category ?categoryStatus ?tag 200: `{success:boolean, data:object[], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /admin/contacts/{id} - Get contact by ID | JWT Path: :id (Contact ID) 200: `{success:boolean, data:{}}` ### DELETE /admin/contacts/{id} - GDPR delete contact | JWT Path: :id (Contact ID) 200: `{success:boolean, message}` ### PATCH /admin/contacts/{id}/consent - Update consent for a category | JWT Path: :id (Contact ID) Body: `{category*, status*:boolean, reason}` 200: `{success:boolean, data:{}, message}` ### POST /admin/contacts/{id}/opt-out - Global opt-out | JWT Path: :id (Contact ID) Body: `{reason}` 200: `{success:boolean, data:{}, message}` ### GET /admin/contacts/{id}/export - GDPR data export | JWT Path: :id (Contact ID) 200: `{success:boolean, data:{}}` ## Webhooks Email provider webhook receivers (Resend, cold-email) ### POST /webhooks/resend - Resend email webhook receiver | NONE Body: `{}` 200: `{success:boolean, processed:boolean, eventId}` ### POST /webhooks/cold-provider - Cold email provider webhook (stub) | NONE Body: `{}` 200: `{success:boolean, message}` ## Email Tracking Own tracking pixel and click redirect endpoints ### GET /t/o/{token} - Open tracking pixel | NONE Path: :token (Signed tracking token (may end with .gif)) 200: `string` ### GET /t/c/{token} - Click tracking redirect | NONE Path: :token (Signed tracking token) ## Email Events Email lifecycle event log and analytics ### GET /admin/email-events/ - List email events | JWT Query: ?page:integer ?limit:integer ?contactId ?campaignId ?messageId ?type ?provider ?appId 200: `{success:boolean, data:[{_id, contactId, messageId, campaignId, provider, type, createdAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /admin/email-events/message/{messageId} - Get events for a message | JWT Path: :messageId (Resend message ID) 200: `{success:boolean, data:[{_id, type, provider, createdAt}]}` ### GET /admin/email-events/campaign/{campaignId}/stats - Campaign event statistics | JWT Path: :campaignId (Campaign ID) 200: `{success:boolean, data:{}}` ## Email Analytics Per-campaign, per-contact, and global email engagement analytics ### GET /admin/email/analytics/campaigns/{campaignId} - Campaign analytics | JWT Path: :campaignId (Campaign ID) 200: `{success:boolean, data:{campaignId, sent:integer, delivered:integer, opened:integer, uniqueOpens:integer, clicked:integer, uniqueClicks:integer, bounced:integer, complained:integer, unsubscribed:integer, openRate:number, clickThroughRate:number, topLinks:[{url, clicks:integer}], timeline:[{date, type, count:integer}]}}` ### GET /admin/email/analytics/campaigns/{campaignId}/recipients - Campaign recipients breakdown | JWT Path: :campaignId (Campaign ID) Query: ?page:integer ?limit:integer 200: `{success:boolean, data:[{contactId, events:string[], lastEvent}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /admin/email/analytics/contacts/{contactId} - Contact engagement history | JWT Path: :contactId (Contact ID) Query: ?page:integer ?limit:integer 200: `{success:boolean, data:{contactId, totalSent:integer, totalOpened:integer, totalClicked:integer, totalBounced:integer, totalComplained:integer, events:[{_id, campaignId, messageId, type, createdAt}]}}` ### GET /admin/email/analytics/overview - Global email analytics overview | JWT Query: ?from ?to 200: `{success:boolean, data:{totalSent:integer, totalDelivered:integer, totalOpened:integer, totalUniqueOpens:integer, totalClicked:integer, totalUniqueClicks:integer, totalBounced:integer, totalComplained:integer, totalUnsubscribed:integer, openRate:number, clickThroughRate:number, bounceRate:number, complaintRate:number, timeline:[{date, sent:integer, delivered:integer, opened:integer, clicked:integer}]}}` ## Email Jobs Job queue monitoring and management ### GET /admin/email-jobs/ - List email jobs | JWT Query: ?status ?type ?campaignId 200: `{success:boolean, data:[{_id, type, status, priority:integer, payload:{}, result:{}, attempts:integer, maxAttempts:integer, lastError, scheduledAt, startedAt, completedAt, createdAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /admin/email-jobs/stats - Email job statistics | JWT Query: ?campaignId 200: `{success:boolean, data:{pending:integer, processing:integer, completed:integer, failed:integer, dead:integer, total:integer, progress:{total:integer, completed:integer, failed:integer, pending:integer, processing:integer, dead:integer, percentage:integer}}}` ### GET /admin/email-jobs/{id} - Get email job details | JWT Path: :id (Job ID) 200: `{success:boolean, data:{_id, type, status, priority:integer, payload:{}, result:{}, attempts:integer, maxAttempts:integer, lastError, scheduledAt, startedAt, completedAt, createdAt}}` ## Health Health checks ### GET /health - Server health check | NONE 200: `{status, timestamp, uptime:number, memory:{rss, heapUsed, heapTotal, external, arrayBuffers}, databases:{connected:integer, total:integer, failed:integer}}` ## Plans ### GET /{appId}/plans/stats - Plan statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:{total:integer, active:integer, inactive:integer, byInterval:{}}}` ### GET /{appId}/plans/active - List active plans | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:[{_id, id, name, price:number, interval, trialDays:integer, features:string[], isActive:boolean, createdAt, updatedAt}]}` ### GET /{appId}/plans/features - List all features | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:string[]}` ### GET /{appId}/plans/ - List plans | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?isActive:boolean ?search ?sortBy ?sortOrder 200: `{success:boolean, data:[{_id, id, name, price:number, interval, trialDays:integer, features:string[], isActive:boolean, createdAt, updatedAt}], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### POST /{appId}/plans/ - Create plan | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{id*, name*, price*:number, interval*, trialDays:integer, features:string[], isActive:boolean}` 201: `{success:boolean, data:{_id, id, name, price:number, interval, trialDays:integer, features:string[], isActive:boolean, createdAt, updatedAt}, message}` ### GET /{appId}/plans/{planId} - Get plan by ID | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :planId (Plan MongoDB _id) 200: `{success:boolean, data:{_id, id, name, price:number, interval, trialDays:integer, features:string[], isActive:boolean, createdAt, updatedAt}}` ### PATCH /{appId}/plans/{planId} - Update plan | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :planId (Plan MongoDB _id) Body: `{name, price:number, interval, trialDays:integer, features:string[], isActive:boolean}` 200: `{success:boolean, data:{_id, id, name, price:number, interval, trialDays:integer, features:string[], isActive:boolean, createdAt, updatedAt}, message}` ### DELETE /{appId}/plans/{planId} - Delete plan | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :planId (Plan MongoDB _id) 200: `{success:boolean, message}` ### PATCH /{appId}/plans/{planId}/toggle - Toggle plan status | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :planId (Plan MongoDB _id) 200: `{success:boolean, data:{_id, id, name, price:number, interval, trialDays:integer, features:string[], isActive:boolean, createdAt, updatedAt}, message}`