# X24 LABS - ADMIN API > Centralized administration API for all X24 Labs applications. Base: https://api.x24labs.io Docs: [HTML](https://api.x24labs.io/) | [OpenAPI JSON](https://api.x24labs.io/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}}]}` ### GET /admin/apps/ - List apps (admin) | JWT Query: ?activeOnly 200: `{success:boolean, data:[{_id, id, name, description, features:{}, slackChannels:{}, allowedOrigins:string[], publicKey, active:boolean, createdAt, updatedAt}]}` ### POST /admin/apps/ - Create app | JWT Body: `{id*, name*, description, features:{}, slackChannels:{}, allowedOrigins:string[], active:boolean}` 201: `{success:boolean, data:{_id, id, name, description, features:{}, slackChannels:{}, allowedOrigins:string[], publicKey, active:boolean, createdAt, updatedAt}}` ### GET /admin/apps/{id} - Get app by id | JWT Path: :id 200: `{success:boolean, data:{_id, id, name, description, features:{}, slackChannels:{}, allowedOrigins:string[], publicKey, active:boolean, createdAt, updatedAt}}` ### PATCH /admin/apps/{id} - Update app | JWT Path: :id Body: `{name, description, features:{}, slackChannels:{}, allowedOrigins:string[], active:boolean}` 200: `{success:boolean, data:{_id, id, name, description, features:{}, slackChannels:{}, allowedOrigins:string[], publicKey, active:boolean, createdAt, updatedAt}}` ### DELETE /admin/apps/{id} - Delete app | JWT Path: :id 200: `{success:boolean, message}` ### POST /admin/apps/{id}/rotate-key - Rotate app publicKey | JWT Path: :id 200: `{success:boolean, data:{id, publicKey}}` ### POST /admin/apps/{id}/toggle - Toggle app active flag | JWT Path: :id 200: `{success:boolean, data:{_id, id, name, description, features:{}, slackChannels:{}, allowedOrigins:string[], publicKey, active:boolean, createdAt, updatedAt}}` ## 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}` ## 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}]}` ## Health Health checks ### GET /health - Server health check | NONE 200: `{status, timestamp, uptime:number, memory:{rss, heapUsed, heapTotal}, database:{connected:boolean, modelsCount:integer}}` ## Magic Link Passwordless end-user authentication ### POST /auth/magic-link/ - Send magic link | NONE Body: `{email*, appId*}` 200: `{success:boolean, message}` ### POST /auth/magic-link/verify - Verify magic link token | NONE Body: `{token*}` 200: `{success:boolean, token, user:{id, email, appId}}` ## Wants Feature request board with voting and roadmap ### 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}}` ## Feedback Quick anonymous feedback submission ### POST /{appId}/feedback/ - Submit feedback | NONE Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{type*, subject*, message*, email}` 201: `{success:boolean, data:{_id, appId, type, subject, message, email, status, priority, createdAt, updatedAt}}` ### GET /admin/{appId}/feedback/stats - Feedback statistics | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 200: `{success:boolean, data:{total:integer, byType:{}, byStatus:{}}}` ### GET /admin/{appId}/feedback/ - List feedback | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Query: ?page:integer ?limit:integer ?type ?status ?priority ?sortBy ?sortOrder 200: `{success:boolean, data:object[], pagination:{page:integer, limit:integer, total:integer, pages:integer}}` ### GET /admin/{appId}/feedback/{id} - Get feedback by ID | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feedback ID) 200: `{success:boolean, data:{}}` ### PATCH /admin/{appId}/feedback/{id} - Update feedback | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feedback ID) Body: `{type, subject, message}` 200: `{success:boolean, data:{}}` ### DELETE /admin/{appId}/feedback/{id} - Delete feedback | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feedback ID) 200: `{success:boolean, message}` ### PATCH /admin/{appId}/feedback/{id}/status - Update feedback status | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feedback ID) Body: `{status*}` 200: `{success:boolean, data:{}}` ### PATCH /admin/{appId}/feedback/{id}/priority - Update feedback priority | JWT Path: :appId (Application ID (e.g.: banners-all-over)) :id (Feedback ID) Body: `{priority*}` 200: `{success:boolean, data:{}}` ## Joinlist ### POST /joinlist - Public newsletter signup | NONE Body: `{appId, email*, firstName, locale, source, referralUrl, website}` 200: `{success:boolean, alreadySubscribed:boolean, unsubscribeUrl}` ### GET /unsubscribe/{token} - Public unsubscribe confirmation | NONE Path: :token 200: `string` ## Email Tracking ### GET /o/{token} - Open tracking pixel | NONE Path: :token (Signed tracking token (may end with .gif)) 200: `string` ### GET /c/{token} - Click tracking redirect | NONE Path: :token (Signed tracking token) ## Webhooks ### POST /email-events/resend - Resend email webhook receiver | NONE Body: `{}` 200: `{success:boolean, processed:boolean, eventId}` ### POST /email-events/cold-provider - Cold email provider webhook (stub) | NONE Body: `{}` 200: `{success:boolean, message}` ## Contacts ### 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:{}}` ## Slack Routing ### GET /admin/slack-routing/ - List routing rules | JWT Query: ?appId 200: `{success:boolean, data:[{_id, appId, channel, webhookUrl, active:boolean, createdAt}]}` ### POST /admin/slack-routing/ - Create routing rule | JWT Body: `{appId*, channel*, webhookUrl*}` 201: `{success:boolean, data:{}}` ### PATCH /admin/slack-routing/{id} - Update routing rule | JWT Path: :id Body: `{webhookUrl, active:boolean}` 200: `{success:boolean, data:{}}` ### DELETE /admin/slack-routing/{id} - Delete routing rule | JWT Path: :id 200: `{success:boolean}` ## Templates ### GET /admin/templates/categories - Template languages | JWT Query: ?appId ### GET /admin/templates/ - List templates | JWT Query: ?page:integer ?limit:integer ?appId ?language ?isActive:boolean ?search ### POST /admin/templates/ - Create template | JWT Body: `{appId:string,null, name*, language*, subject*, htmlTemplate*, textTemplate*, variables:string[], isActive:boolean, version:number, sender:{name, email}}` ### GET /admin/templates/{templateId} - Get template by ID | JWT Path: :templateId Query: ?appId ### PUT /admin/templates/{templateId} - Update template (full) | JWT Path: :templateId ### PATCH /admin/templates/{templateId} - Update template (partial) | JWT Path: :templateId ### DELETE /admin/templates/{templateId} - Delete template | JWT Path: :templateId Query: ?appId ### PATCH /admin/templates/{templateId}/toggle - Toggle template active status | JWT Path: :templateId Body: `{appId:string,null, isActive*:boolean}` ### POST /admin/templates/{templateId}/duplicate - Duplicate template | JWT Path: :templateId Body: `{appId:string,null, name}` ## Email Jobs ### GET /admin/email-jobs/ - List email jobs | JWT Query: ?page:integer ?limit:integer ?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}}` ## Email Events ### 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:{sent:integer, delivered:integer, opened:integer, clicked:integer, bounced:integer, openRate:number, clickRate:number}}` ## Email Analytics ### GET /admin/email-events/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-events/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-events/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-events/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}]}}` ## Newsletter ### POST /{appId}/newsletter/subscribe - Subscribe to newsletter | KEY Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{email*, firstName, locale, referralUrl}` 201: `{success:boolean, data:{email, firstName}}` ### 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, email, firstName, lastName, locale, subscriptions:[{appId, status, source, referralUrl, 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*, firstName, locale}` 201: `{success:boolean, data:{_id, email, firstName, lastName, locale, subscriptions:[{appId, status, source, referralUrl, subscribedAt, unsubscribedAt}], createdAt}}` ### POST /admin/{appId}/newsletter/subscribers/import - Import subscribers | JWT Path: :appId (Application ID (e.g.: banners-all-over)) Body: `{subscribers*:[{email*, firstName, locale}]}` 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, data:{jobId}}` ### GET /admin/{appId}/newsletter/templates - List newsletter templates | JWT Path: :appId (Application ID (e.g.: banners-all-over)) 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 (Application ID (e.g.: banners-all-over)) 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: :appId (Application ID (e.g.: banners-all-over)) :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: :appId (Application ID (e.g.: banners-all-over)) :id (Template ID) 200: `{success:boolean, message}`