title, description, icon, tags
title
description
icon
tags
API Reference
Complete REST API reference for both the Express API (port 4000) and Fastify Media API (port 4100).
material/api
API Reference
Changemaker Lite exposes two REST APIs sharing a single PostgreSQL database.
Server
Framework
Port
Purpose
Main API
Express.js
4000
Auth, campaigns, map, shifts, canvassing, pages, email, settings
Media API
Fastify
4100
Video library, analytics, playlists, reactions, comments
Both APIs use JWT Bearer authentication and return JSON. All request/response bodies are application/json unless noted otherwise.
Authentication
Token Flow
sequenceDiagram
participant Client
participant API
participant DB
Client->>API: POST /api/auth/login {email, password}
API->>DB: Verify credentials
DB-->>API: User record
API-->>Client: {accessToken, refreshToken}
Note over Client: Store tokens
Client->>API: GET /api/campaigns (Authorization: Bearer <accessToken>)
API-->>Client: 200 OK
Note over Client: Access token expires (15 min)
Client->>API: POST /api/auth/refresh {refreshToken}
API->>DB: Rotate token (atomic transaction)
DB-->>API: New token pair
API-->>Client: {accessToken, refreshToken}
All authenticated requests require:
Authorization: Bearer <accessToken>
The Media API also accepts tokens via query parameter for SSE streams:
GET /api/public/:id/chat-stream?token=<accessToken>
Roles
Role
Access Level
SUPER_ADMIN
Full platform access
INFLUENCE_ADMIN
Campaign and advocacy management
MAP_ADMIN
Map, locations, shifts, canvassing
USER
Volunteer portal, public features
TEMP
Limited access (auto-created on public shift signup)
Middleware Reference
Middleware
Effect
authenticate
Requires valid JWT. Sets req.user with id, email, role. Returns 401 if missing or invalid.
optionalAuth
Same as authenticate but continues without user if token is absent.
requireRole(...roles)
Checks user role against allowed list. Returns 403 if not authorized.
requireNonTemp
Blocks TEMP users. Returns 403.
validate(schema, source)
Validates request body/query/params against a Zod schema. Returns 400 on failure.
Error Responses
All errors follow a consistent format:
{
"error" : {
"message" : "Human-readable error description" ,
"code" : "ERROR_CODE" ,
"statusCode" : 400
}
}
Status
Code
Meaning
400
VALIDATION_ERROR
Request body/query failed schema validation
401
UNAUTHORIZED
Missing or invalid access token
403
FORBIDDEN
Valid token but insufficient role
404
NOT_FOUND
Resource does not exist
429
RATE_LIMITED
Too many requests (see Rate Limits)
500
INTERNAL_ERROR
Unexpected server error
!!! note "Enumeration Prevention"
Auth endpoints (/login, /register, /forgot-password) return generic success messages to prevent user enumeration. A 401 from /api/auth/me does not reveal whether the user exists.
Rate Limits
Rate limits are Redis-backed and keyed by IP address.
Endpoint Group
Window
Max Requests
Redis Prefix
Auth (login, register, refresh)
15 min
10
rl:auth:
Email sending
1 hour
30
rl:email:
Response submission
1 hour
10
rl:response:
Shift signup
1 hour
10
rl:shift-signup:
Canvass visits
1 min
30
rl:canvass-visit:
Canvass bulk visits
1 min
5
rl:canvass-visit-bulk:
GPS tracking
1 min
6
rl:gps-tracking:
Canvass geocode
1 min
10
rl:canvass-geocode:
Observability
1 min
20
rl:observability:
Health/metrics
1 min
30
rl:health-metrics:
Global (all other)
Configurable
Configurable
rl:global:
When rate-limited, the API returns:
{
"error" : {
"message" : "Too many requests, please try again later" ,
"code" : "RATE_LIMITED" ,
"statusCode" : 429
}
}
Main API (Express — Port 4000)
Health & Metrics
Method
Path
Auth
Description
GET
/api/health
:material-close:
Health check — PostgreSQL + Redis ping
GET
/api/metrics
:material-close:
Prometheus metrics (text/plain)
??? example "Health response"
json { "status": "healthy", "checks": { "database": "ok", "redis": "ok" } }
Auth
Prefix: /api/auth
Method
Path
Auth
Rate Limited
Description
POST
/api/auth/login
:material-close:
:material-check:
Email + password login
POST
/api/auth/register
:material-close:
:material-check:
Create account (always USER role)
POST
/api/auth/verify-email
:material-close:
:material-check:
Verify email with token
POST
/api/auth/resend-verification
:material-close:
:material-check:
Resend verification email
POST
/api/auth/forgot-password
:material-close:
:material-check:
Send password reset email
POST
/api/auth/reset-password
:material-close:
:material-check:
Set new password with reset token
POST
/api/auth/refresh
:material-close:
:material-check:
Rotate refresh token → new token pair
POST
/api/auth/logout
:material-close:
:material-check:
Invalidate refresh token
GET
/api/auth/me
:material-check:
:material-close:
Current user profile
??? example "Login request & response"
Request:
json { "email": "admin@example.com", "password": "SecurePass123!" }
Response:
json { "accessToken": "eyJhbG...", "refreshToken": "eyJhbG...", "user": { "id": "uuid", "email": "admin@example.com", "name": "Admin", "role": "SUPER_ADMIN" } }
!!! info "Password Policy"
Passwords must be at least 12 characters with at least one uppercase letter, one lowercase letter, and one digit.
Users
Prefix: /api/users · Auth: All routes require authentication
Method
Path
Role
Description
GET
/api/users
Admin
Paginated user list with search, role, and status filters
GET
/api/users/:id
Admin or self
Single user profile
POST
/api/users
Admin
Create user
PUT
/api/users/:id
Admin or self
Update user (non-admins cannot change role/status)
POST
/api/users/:id/approve
Admin
Approve pending user; sends approval email
POST
/api/users/:id/reject
Admin
Reject pending user
DELETE
/api/users/:id
Admin
Delete user
Query parameters for GET /api/users:
Param
Type
Description
page
number
Page number (default 1)
limit
number
Items per page (default 20)
search
string
Search by name or email
role
string
Filter by role
status
string
Filter by status
Dashboard
Prefix: /api/dashboard · Auth: Admin roles required
Method
Path
Role
Description
GET
/api/dashboard/summary
Any admin
Platform-wide counts (users, campaigns, locations, shifts)
GET
/api/dashboard/system
SUPER_ADMIN
Hardware + OS info (CPU, memory, disk)
GET
/api/dashboard/containers
SUPER_ADMIN
Docker container statuses
GET
/api/dashboard/weather
Any admin
Current weather at map center coordinates
GET
/api/dashboard/api-metrics
SUPER_ADMIN
Prometheus API performance metrics
GET
/api/dashboard/time-series
SUPER_ADMIN
Prometheus time-series data
GET
/api/dashboard/container-resources
SUPER_ADMIN
cAdvisor CPU/memory/network per container
Query parameters for GET /api/dashboard/time-series:
Param
Type
Description
metrics
string
Comma-separated metric keys (whitelist-validated)
range
string
Time range (e.g., 1h, 24h, 7d)
step
string
Sample interval (e.g., 5m, 1h)
Campaigns
Admin CRUD
Prefix: /api/campaigns · Auth: Admin roles
Method
Path
Description
GET
/api/campaigns
Paginated campaign list
GET
/api/campaigns/:id
Single campaign detail
POST
/api/campaigns
Create campaign
PUT
/api/campaigns/:id
Update campaign
DELETE
/api/campaigns/:id
Delete campaign
Public
Method
Path
Auth
Description
GET
/api/campaigns/public
:material-close:
List all active campaigns
GET
/api/campaigns/:slug/details
:material-close:
Campaign detail by slug (ACTIVE only)
User Submissions
Auth: Authenticated, non-TEMP users
Method
Path
Description
POST
/api/campaigns/user/submit
Submit campaign for moderation (5/hour limit)
GET
/api/campaigns/user/my-campaigns
List own submitted campaigns
PUT
/api/campaigns/user/:id
Edit own pending campaign
Moderation
Auth: Admin roles
Method
Path
Description
GET
/api/campaigns/moderation/queue
Campaigns pending moderation
GET
/api/campaigns/moderation/stats
Moderation queue statistics
PATCH
/api/campaigns/moderation/:id
Approve or reject campaign
Campaign Emails
Method
Path
Auth
Description
POST
/api/campaigns/:slug/send-email
:material-close:
Send advocacy email to representatives (rate limited: 30/hour)
POST
/api/campaigns/:slug/track-mailto
:material-close:
Track mailto link click
GET
/api/campaigns/:id/emails
Admin
Paginated emails for campaign
GET
/api/campaigns/:id/email-stats
Admin
Email statistics
Responses
Prefix: /api/campaigns (public) and /api/responses (admin + actions)
Public
Method
Path
Auth
Description
GET
/api/campaigns/:slug/responses
:material-close:
List approved public responses
GET
/api/campaigns/:slug/response-stats
:material-close:
Response statistics
POST
/api/campaigns/:slug/responses
:material-close:
Submit response (rate limited: 10/hour)
POST
/api/responses/:id/upvote
Optional
Upvote a response
DELETE
/api/responses/:id/upvote
Optional
Remove upvote
GET
/api/responses/:id/verify/:token
:material-close:
Verify response via email link
Admin
Auth: Admin roles
Method
Path
Description
GET
/api/responses
All responses with filters
PATCH
/api/responses/:id/status
Approve or reject response
POST
/api/responses/:id/resend-verification
Resend verification email
DELETE
/api/responses/:id
Delete response
Representatives
Prefix: /api/representatives
Method
Path
Auth
Description
GET
/api/representatives/by-postal/:postalCode
:material-close:
Lookup representatives by postal code (cache-first)
GET
/api/representatives/test-connection
:material-close:
Represent API health check
GET
/api/representatives/cache-stats
Admin
Cache statistics
GET
/api/representatives
Admin
Paginated cached representatives
GET
/api/representatives/:id
Admin
Single cached representative
DELETE
/api/representatives/by-postal/:postalCode
Admin
Clear cache for postal code
DELETE
/api/representatives/:id
Admin
Delete cached representative
Query parameters for postal code lookup:
Param
Type
Description
refresh
boolean
Force API call, bypass cache
Email Queue
Prefix: /api/email-queue · Auth: Admin roles
Method
Path
Description
GET
/api/email-queue/stats
BullMQ queue statistics (waiting, active, completed, failed)
POST
/api/email-queue/pause
Pause email processing
POST
/api/email-queue/resume
Resume email processing
POST
/api/email-queue/clean
Clean completed jobs
Locations
Prefix: /api/map/locations
Public
Method
Path
Description
GET
/api/map/locations/public
All geocoded locations for map (no PII); optional ?bounds=
Admin
Auth: SUPER_ADMIN or MAP_ADMIN
Method
Path
Description
GET
/api/map/locations
Paginated locations with filters
GET
/api/map/locations/stats
Location statistics
GET
/api/map/locations/all
All geocoded locations for admin map
GET
/api/map/locations/export-csv
CSV export
GET
/api/map/locations/:id
Single location
GET
/api/map/locations/:id/history
Edit history
POST
/api/map/locations
Create location
PUT
/api/map/locations/:id
Update location
DELETE
/api/map/locations/:id
Delete location
POST
/api/map/locations/bulk-delete
Bulk delete
POST
/api/map/locations/geocode
Geocode single address
POST
/api/map/locations/geocode-missing
Batch geocode all ungeocoded
POST
/api/map/locations/reverse-geocode
Reverse geocode lat/lng to address
POST
/api/map/locations/import-csv
Import from CSV (10 MB limit)
POST
/api/map/locations/import-bulk
Bulk NAR or standard CSV import (100 MB limit)
Bulk Geocode
Prefix: /api/map/locations/bulk-geocode · Auth: Map admins
Method
Path
Description
POST
/api/map/locations/bulk-geocode
Start BullMQ bulk geocoding job
GET
/api/map/locations/bulk-geocode/:jobId
Poll job status
GET
/api/map/locations/bulk-geocode/stats
Queue statistics
NAR Import
Prefix: /api/map/nar-import · Auth: Map admins
Method
Path
Description
GET
/api/map/nar-import/datasets
Available NAR datasets by province
POST
/api/map/nar-import
Start province import (fire-and-forget)
GET
/api/map/nar-import/status/:importId
Poll import progress
??? info "NAR Import body"
json { "provinceCode": "24", "filterType": "city", "filterCity": "Edmonton", "residentialOnly": true, "deduplicateRadius": 10, "batchSize": 500 }
Area Import
Prefix: /api/map/area-import · Auth: Map admins
Method
Path
Description
POST
/api/map/area-import/preview
Preview bounds + estimated record counts
POST
/api/map/area-import
Start area import (fire-and-forget)
GET
/api/map/area-import/status/:importId
Poll import progress
Cuts (Polygons)
Prefix: /api/map/cuts
Method
Path
Auth
Description
GET
/api/map/cuts/public
:material-close:
All public cuts as GeoJSON
GET
/api/map/cuts
Map admin
Paginated cuts list
GET
/api/map/cuts/:id
Map admin
Single cut
POST
/api/map/cuts
Map admin
Create cut (polygon GeoJSON)
PUT
/api/map/cuts/:id
Map admin
Update cut
DELETE
/api/map/cuts/:id
Map admin
Delete cut
GET
/api/map/cuts/:id/locations
Map admin
All locations within cut polygon
GET
/api/map/cuts/:id/statistics
Map admin
Support level breakdown
GET
/api/map/cuts/export-geojson
Map admin
All cuts as GeoJSON FeatureCollection
GET
/api/map/cuts/:id/export-geojson
Map admin
Single cut as GeoJSON Feature
POST
/api/map/cuts/import-geojson
Map admin
Import cuts from GeoJSON file
Shifts
Prefix: /api/map/shifts
Public
Method
Path
Description
GET
/api/map/shifts/public
List upcoming public shifts
POST
/api/map/shifts/public/:id/signup
Public signup (creates TEMP user if needed; rate limited: 10/hour)
Volunteer
Auth: Any authenticated user
Method
Path
Description
GET
/api/map/shifts/volunteer/upcoming
Upcoming shifts with signup status
GET
/api/map/shifts/volunteer/my-signups
Own confirmed signups
POST
/api/map/shifts/volunteer/:id/signup
Sign up for shift
DELETE
/api/map/shifts/volunteer/:id/signup
Cancel signup
Admin
Auth: Map admins
Method
Path
Description
GET
/api/map/shifts
Paginated shifts with filters
GET
/api/map/shifts/stats
Statistics
GET
/api/map/shifts/calendar
Calendar data (?startDate=&endDate=)
GET
/api/map/shifts/:id
Single shift with signups
POST
/api/map/shifts
Create shift
PUT
/api/map/shifts/:id
Update shift
DELETE
/api/map/shifts/:id
Delete shift
POST
/api/map/shifts/:id/signups
Admin-add volunteer
DELETE
/api/map/shifts/:id/signups/:signupId
Remove volunteer
POST
/api/map/shifts/:id/email-details
Email details to all volunteers
Shift Series
Auth: Map admins
Method
Path
Description
POST
/api/map/shifts/series
Create recurring shift series
GET
/api/map/shifts/series/:id
Get series
PUT
/api/map/shifts/series/:id
Update series
DELETE
/api/map/shifts/series/:id
Delete series
Canvassing
Prefix: /api/map/canvass
Volunteer
Auth: Any authenticated user
Method
Path
Description
GET
/api/map/canvass/my/assignments
Shift assignments
GET
/api/map/canvass/my/stats
Personal canvass statistics
GET
/api/map/canvass/my/visits
Visit history
GET
/api/map/canvass/my/session
Active canvass session
POST
/api/map/canvass/sessions
Start canvass session
POST
/api/map/canvass/sessions/:id/end
End session
GET
/api/map/canvass/cuts/:cutId/locations
Locations in cut with visit annotations
GET
/api/map/canvass/cuts/:cutId/route
Walking route algorithm for cut
GET
/api/map/canvass/locations
All locations with visit annotations
PUT
/api/map/canvass/locations/:id
Edit address (role-gated fields)
POST
/api/map/canvass/locations
Create location
POST
/api/map/canvass/reverse-geocode
Reverse geocode lat/lng
POST
/api/map/canvass/geocode-search
Geocode address for map (rate limited: 10/min)
POST
/api/map/canvass/visits
Record door knock (rate limited: 30/min)
POST
/api/map/canvass/visits/bulk
Record visit for all unvisited units (rate limited: 5/min)
Admin
Auth: SUPER_ADMIN or MAP_ADMIN
Method
Path
Description
GET
/api/map/canvass/stats
Platform-wide canvass statistics
GET
/api/map/canvass/stats/cuts/:cutId
Statistics for specific cut
GET
/api/map/canvass/activity
Recent activity feed
GET
/api/map/canvass/volunteers
All volunteers with canvass activity
GET
/api/map/canvass/volunteers/:userId
Individual volunteer statistics
GET
/api/map/canvass/visits
All visits with filters
GPS Tracking
Prefix: /api/map/tracking
Volunteer
Auth: Any authenticated user
Method
Path
Description
POST
/api/map/tracking/sessions
Start GPS tracking session
POST
/api/map/tracking/sessions/:id/end
End tracking session
POST
/api/map/tracking/sessions/:id/points
Submit GPS point batch (rate limited: 6/min)
POST
/api/map/tracking/sessions/:id/link-canvass
Link to canvass session
GET
/api/map/tracking/my/session
Active tracking session
GET
/api/map/tracking/my/sessions
Own historical sessions
GET
/api/map/tracking/my/sessions/:id/route
Full route for own session
Admin
Auth: Map admins
Method
Path
Description
GET
/api/map/tracking/live
Live volunteer positions + trails
GET
/api/map/tracking/sessions
All historical tracking sessions
GET
/api/map/tracking/sessions/:id/route
Full route for any session
Map Settings
Prefix: /api/map/settings
Method
Path
Auth
Description
GET
/api/map/settings
:material-close:
Public map settings (center, zoom, walk sheet config)
PUT
/api/map/settings
Map admin
Update map settings
Geocoding
Prefix: /api/map/geocoding · Auth: Map admins
Method
Path
Description
GET
/api/map/geocoding/search
Geocode address search (?q=&limit=1-10)
Landing Pages
Prefix: /api/pages and /api/page-blocks
Public
Method
Path
Auth
Description
GET
/api/pages/:slug/view
:material-close:
Get published page by slug
Admin
Auth: Admin roles
Method
Path
Description
GET
/api/pages
Paginated landing pages
GET
/api/pages/:id
Single page
POST
/api/pages
Create page
PUT
/api/pages/:id
Update page
DELETE
/api/pages/:id
Delete page
POST
/api/pages/sync
Sync MkDocs overrides from filesystem
POST
/api/pages/validate
Validate and repair MkDocs exports
Block Library
Auth: Admin roles
Method
Path
Description
GET
/api/page-blocks
List blocks
GET
/api/page-blocks/:id
Single block
POST
/api/page-blocks
Create block
PUT
/api/page-blocks/:id
Update block
DELETE
/api/page-blocks/:id
Delete block
Email Templates
Prefix: /api/email-templates · Auth: Admin roles (seed/cache require SUPER_ADMIN)
Method
Path
Description
GET
/api/email-templates
List templates
GET
/api/email-templates/:id
Single template
POST
/api/email-templates
Create template
PUT
/api/email-templates/:id
Update template
DELETE
/api/email-templates/:id
Delete template
GET
/api/email-templates/:id/versions
Version history
GET
/api/email-templates/:id/versions/:versionNumber
Specific version
POST
/api/email-templates/:id/rollback
Rollback to prior version
POST
/api/email-templates/validate
Validate Handlebars syntax
POST
/api/email-templates/:id/test
Send test email (rate limited: 10/15min)
GET
/api/email-templates/:id/test-logs
Test send logs
POST
/api/email-templates/seed
Seed templates from filesystem
POST
/api/email-templates/clear-cache
Clear template cache
QR Codes
Method
Path
Auth
Description
GET
/api/qr
:material-close:
Generate QR code PNG (?text=&size=50-500)
Cached for 1 hour. Returns image/png.
Site Settings
Prefix: /api/settings
Method
Path
Auth
Description
GET
/api/settings
:material-close:
Public site settings (SMTP credentials stripped)
GET
/api/settings/admin
SUPER_ADMIN
Full settings including SMTP credentials
PUT
/api/settings
SUPER_ADMIN
Update settings
POST
/api/settings/email/test-connection
SUPER_ADMIN
Test SMTP connection
POST
/api/settings/email/test-send
SUPER_ADMIN
Send test email
Listmonk (Newsletter Sync)
Prefix: /api/listmonk · Auth: SUPER_ADMIN
Method
Path
Description
GET
/api/listmonk
Sync status + connection check
GET
/api/listmonk/stats
Subscriber counts from Listmonk
POST
/api/listmonk/test-connection
Health check
POST
/api/listmonk/sync/participants
Sync campaign participants
POST
/api/listmonk/sync/locations
Sync locations
POST
/api/listmonk/sync/users
Sync users
POST
/api/listmonk/sync/all
Run all sync operations
POST
/api/listmonk/reinitialize
Reinitialize Listmonk lists
GET
/api/listmonk/proxy-url
Proxy port + JWT for iframe
Documentation Management
Prefix: /api/docs · Auth: Authenticated, non-TEMP (write operations require SUPER_ADMIN)
Method
Path
Description
GET
/api/docs/status
MkDocs + Code Server availability
GET
/api/docs/config
Port numbers for iframe URLs
GET
/api/docs/mkdocs-config
Read raw mkdocs.yml
PUT
/api/docs/mkdocs-config
Write mkdocs.yml
POST
/api/docs/build
Trigger MkDocs build
POST
/api/docs/upload
Upload asset (20 MB, whitelisted extensions)
GET
/api/docs/files
File tree (?force=true bypasses cache)
POST
/api/docs/files/rename
Rename or move file
GET
/api/docs/files/*
Read file content
PUT
/api/docs/files/*
Write file content
POST
/api/docs/files/*
Create file or folder
DELETE
/api/docs/files/*
Delete file or empty folder
Services
Prefix: /api/services · Auth: SUPER_ADMIN
Method
Path
Description
GET
/api/services/status
Health check all managed services (NocoDB, n8n, Gitea, MailHog, Mini QR, Excalidraw, Homepage)
GET
/api/services/config
Port numbers + subdomain info
Pangolin (Tunnel Management)
Prefix: /api/pangolin · Auth: SUPER_ADMIN
Method
Path
Description
GET
/api/pangolin/status
Tunnel health + connection info
GET
/api/pangolin/config
Current env configuration
GET
/api/pangolin/newt-status
Newt container status
POST
/api/pangolin/newt-restart
Restart Newt container
GET
/api/pangolin/sites
List Pangolin sites
GET
/api/pangolin/exit-nodes
Available exit nodes
GET
/api/pangolin/resource-definitions
Resource definitions from YAML
GET
/api/pangolin/resources
List resources
POST
/api/pangolin/setup
Create site + all resources (rate limited: 3/5min)
POST
/api/pangolin/sync
Sync resources (create missing, update changed)
PUT
/api/pangolin/resource/:id
Update resource
DELETE
/api/pangolin/resource/:id
Delete resource
GET
/api/pangolin/resource/:id/clients
Connected clients
GET
/api/pangolin/certificate/:domainId/:domain
Certificate info
POST
/api/pangolin/certificate/:certId
Update certificate
Observability
Prefix: /api/observability · Auth: SUPER_ADMIN · Rate limited: 20/min
Method
Path
Description
GET
/api/observability/status
Check 7 monitoring services
GET
/api/observability/metrics-summary
Key metrics from Prometheus
GET
/api/observability/alerts
Active alerts from Alertmanager
Payments
Prefix: /api/payments
Public
Method
Path
Auth
Description
GET
/api/payments/config
:material-close:
Stripe publishable key + donation settings
GET
/api/payments/plans
:material-close:
Active subscription plans
GET
/api/payments/products
:material-close:
Active products (?type=)
POST
/api/payments/subscribe
:material-check:
Create subscription checkout
POST
/api/payments/purchase
Optional
Product checkout (guest or logged-in)
POST
/api/payments/donate
:material-close:
Donation checkout
GET
/api/payments/my-subscription
:material-check:
Current subscription
POST
/api/payments/my-subscription/cancel
:material-check:
Cancel subscription
POST
/api/payments/webhook
:material-close:
Stripe webhook (raw body)
Admin
Auth: SUPER_ADMIN
Method
Path
Description
GET
/api/payments/admin/settings
Payment settings (secrets masked)
PUT
/api/payments/admin/settings
Update payment settings
POST
/api/payments/admin/settings/test-connection
Test Stripe connection
GET
/api/payments/admin/dashboard
Subscription + donation statistics
GET
/api/payments/admin/plans
All subscription plans
POST
/api/payments/admin/plans
Create plan
PUT
/api/payments/admin/plans/:id
Update plan
DELETE
/api/payments/admin/plans/:id
Delete plan
POST
/api/payments/admin/plans/:id/sync-stripe
Sync plan to Stripe
GET
/api/payments/admin/subscriptions
All subscriptions with filters
POST
/api/payments/admin/subscriptions/:id/cancel
Cancel subscription
GET
/api/payments/admin/products
All products
POST
/api/payments/admin/products
Create product
PUT
/api/payments/admin/products/:id
Update product
DELETE
/api/payments/admin/products/:id
Delete product
POST
/api/payments/admin/products/:id/sync-stripe
Sync product to Stripe
GET
/api/payments/admin/orders
List orders
POST
/api/payments/admin/orders/:id/refund
Refund order
GET
/api/payments/admin/donations
List donations
GET
/api/payments/admin/export
CSV export of completed orders
Media API (Fastify — Port 4100)
The Media API is a separate Fastify server sharing the same PostgreSQL database. It handles all video-related functionality.
Health
Method
Path
Auth
Description
GET
/health
:material-close:
Media API health check
Videos (Admin)
Prefix: /api/videos · Auth: Admin roles
CRUD & Publishing
Method
Path
Description
GET
/api/videos
List videos (?limit=&offset=&search=&orientation=&producers=&isShort=)
GET
/api/videos/producers
Distinct producer list
GET
/api/videos/health
Video count health check
GET
/api/videos/:id
Single video detail
PATCH
/api/videos/:id
Update metadata (title, producer, tags, quality, etc.)
POST
/api/videos/:id/publish
Publish to category
POST
/api/videos/:id/unpublish
Unpublish
POST
/api/videos/bulk-publish
Bulk publish
POST
/api/videos/bulk-unpublish
Bulk unpublish
POST
/api/videos/:id/lock
Lock published video
POST
/api/videos/:id/unlock
Unlock video
POST
/api/videos/:id/generate-thumbnail
Generate thumbnail via FFmpeg
POST
/api/videos/bulk-generate-thumbnails
Bulk thumbnail generation
Upload
Method
Path
Description
POST
/api/videos/upload
Single video upload (multipart, 10 GB limit, streams to disk)
POST
/api/videos/upload/batch
Batch upload (returns 207 multi-status)
Actions
Method
Path
Description
POST
/api/videos/:id/duplicate
Duplicate video record
POST
/api/videos/:id/replace
Replace video file, keep metadata
GET
/api/videos/:id/analytics
Detailed analytics (?startDate=&endDate=)
POST
/api/videos/:id/reset-analytics
Reset all analytics
GET
/api/videos/:id/preview-link
Generate 24-hour JWT preview link
GET
/api/videos/analytics/top
Top videos (`?metric=views
GET
/api/videos/analytics/overview
Global analytics overview
Scheduling
Method
Path
Description
POST
/api/videos/:id/schedule-publish
Schedule future publish ({publishAt, timezone?})
POST
/api/videos/:id/schedule-unpublish
Schedule future unpublish
DELETE
/api/videos/:id/schedule/:action
Cancel scheduled operation
GET
/api/videos/schedules/upcoming
Upcoming scheduled operations
GET
/api/videos/:id/schedule-history
Schedule history for video
GET
/api/videos/schedules/stats
Schedule queue statistics
POST
/api/videos/schedules/pause
Pause schedule queue
POST
/api/videos/schedules/resume
Resume schedule queue
POST
/api/videos/schedules/cleanup
Clean old completed jobs
Video Fetch
Method
Path
Description
POST
/api/videos/fetch
Submit fetch job ({urls: string[]}, 1– 20 URLs)
GET
/api/videos/fetch/jobs
List recent fetch jobs
GET
/api/videos/fetch/jobs/:jobId
Job detail + log
GET
/api/videos/fetch/jobs/:jobId/log
SSE log stream (Redis pub/sub)
DELETE
/api/videos/fetch/jobs/:jobId
Cancel fetch job
Streaming (Public)
Prefix: /api/videos
Method
Path
Auth
Description
GET
/api/videos/stream/health
:material-close:
Streaming health check
GET
/api/videos/:id/stream
Optional
HTTP range-supporting video stream
GET
/api/videos/:id/thumbnail
Optional
Serve thumbnail image
GET
/api/videos/:id/metadata
:material-close:
Public video metadata for embedding
!!! note
Admins can stream unpublished videos by providing a valid JWT.
Public Gallery
Prefix: /api/public
Method
Path
Auth
Description
GET
/api/public
Optional
Published videos (`?limit=&offset=&search=&sort=recent
GET
/api/public/categories
Optional
Categories with video counts
GET
/api/public/producers
Optional
Published producers
GET
/api/public/:id
Optional
Single published video
GET
/api/public/:id/thumbnail
Optional
Published thumbnail
GET
/api/public/:id/stream
Optional
Published video stream
Tracking
Prefix: /api/track · Auth: None required
Method
Path
Description
GET
/api/track/health
Tracking health check
POST
/api/track/view
Record video view (returns {viewId})
POST
/api/track/event
Record play/pause/seek/complete event
POST
/api/track/heartbeat
Update watch time (10s interval, sendBeacon)
POST
/api/track/batch
Batch up to 50 tracking events
??? info "Tracking is GDPR-compliant"
IP addresses are hashed with a daily-rotating salt. Raw IPs are never stored. Tracking data is retained for 90 days.
Reactions
Prefix: /api/reactions
Method
Path
Auth
Description
GET
/api/reactions/config
:material-close:
Available reaction types + emoji mappings
GET
/api/reactions
:material-close:
List reactions (?mediaId=&userId=&limit=)
GET
/api/reactions/:mediaId/chat
:material-close:
Reactions in chat timeline format
POST
/api/reactions
:material-check:
Add reaction (30s cooldown per type)
Available types: like, love, laugh, wow, sad, angry
Method
Path
Auth
Description
GET
/api/public/:id/comments
:material-close:
List comments (?limit=&offset=)
POST
/api/public/:id/comments
Optional
Create comment (word-filtered; rate limited: 5/min)
GET
/api/public/:id/chat-stream
:material-close:
SSE stream for real-time chat (30s keepalive)
Prefix: /api/media/admin/comments · Auth: Admin roles
Method
Path
Description
GET
/api/media/admin/comments/stats
Counts by status
GET
/api/media/admin/comments
All comments with filters
PATCH
/api/media/admin/comments/:id/approve
Approve comment
PATCH
/api/media/admin/comments/:id/hide
Hide comment
PATCH
/api/media/admin/comments/:id/unhide
Unhide comment
PUT
/api/media/admin/comments/:id/notes
Update moderation notes
DELETE
/api/media/admin/comments/:id
Delete comment
Word Filters
Prefix: /api/media/admin/word-filters · Auth: Admin roles
Method
Path
Description
GET
/api/media/admin/word-filters
List filter entries grouped by level
POST
/api/media/admin/word-filters
Add word (`{word, level: low
DELETE
/api/media/admin/word-filters/:id
Remove word
Chat Threads & Notifications
Auth: Authenticated
Method
Path
Description
GET
/api/media/chat/threads
Videos with user's comments + unread counts
POST
/api/media/chat/threads/:mediaId/read
Mark thread as read
GET
/api/media/notifications/stream
Per-user SSE notification stream (?token=)
Shorts
Method
Path
Auth
Description
GET
/api/shorts
Optional
Shorts feed (`?sort=recent
POST
/api/shorts/scan
Admin
Auto-classify short videos by duration
Upvotes
Method
Path
Auth
Description
POST
/api/public/:id/upvote
:material-close:
Toggle upvote (session-based via X-Session-ID header)
GET
/api/public/:id/upvote-status
:material-close:
Check upvote status for current session
Playlists
Public
Prefix: /api/playlists
Method
Path
Auth
Description
GET
/api/playlists/featured
Optional
Featured playlists
GET
/api/playlists/popular
Optional
Popular public playlists (?search=)
GET
/api/playlists/share/:token
Optional
Playlist by share token
GET
/api/playlists/:id
Optional
Playlist detail (public, owner, or share token)
POST
/api/playlists/:id/view
Optional
Record playlist view
User Playlists
Auth: Authenticated
Method
Path
Description
GET
/api/playlists/my
Own playlists
POST
/api/playlists
Create playlist
PUT
/api/playlists/:id
Update playlist (ownership check)
DELETE
/api/playlists/:id
Delete playlist
POST
/api/playlists/:id/videos
Add video ({mediaId})
DELETE
/api/playlists/:id/videos/:mediaId
Remove video
PUT
/api/playlists/:id/videos/reorder
Reorder videos
POST
/api/playlists/:id/share
Generate share token
DELETE
/api/playlists/:id/share
Revoke share token
Playlist Admin
Prefix: /api/media/playlists · Auth: Admin roles
Method
Path
Description
GET
/api/media/playlists
All playlists
GET
/api/media/playlists/featured
Featured playlists with admin info
POST
/api/media/playlists/:id/feature
Feature a playlist
DELETE
/api/media/playlists/:id/feature
Unfeature a playlist
PUT
/api/media/playlists/featured/reorder
Reorder featured playlists
PUT
/api/media/playlists/:id
Admin update any playlist
POST
/api/media/playlists/:id/duplicate
Duplicate playlist
DELETE
/api/media/playlists/:id
Admin delete any playlist
User Profile
Prefix: /api/media/me · Auth: Authenticated
Method
Path
Description
GET
/api/media/me/stats
User stats + 30-day activity + achievements
GET
/api/media/me/watch-history
Paginated watch history
POST
/api/media/me/stats/recalculate
Recompute stats from raw data
GET
/api/media/me/settings
Privacy settings
PUT
/api/media/me/settings
Update privacy settings
PUT
/api/media/me/profile
Update display name
PUT
/api/media/me/password
Change password
Route Summary
API
Module
Endpoint Count
Express
Auth
9
Users
7
Dashboard
7
Campaigns (CRUD + public + user + moderation + emails)
16
Responses
10
Email Queue
4
Representatives
7
Locations (CRUD + geocode + import)
21
Cuts
11
Shifts (CRUD + series)
19
Canvassing
20
GPS Tracking
10
Map Settings + Geocoding
3
Pages + Blocks
12
Email Templates
13
QR Codes
1
Site Settings
5
Listmonk
9
Docs Management
11
Services
2
Pangolin
16
Observability
3
Payments (public + admin)
29
Health + Metrics
3
Express Total
~248
Fastify
Videos (CRUD + upload + actions + schedule + fetch)
39
Streaming
4
Public Gallery
6
Tracking
5
Reactions
4
Comments + Chat
13
Shorts + Upvotes
4
Playlists (public + user + admin)
18
User Profile
7
Health
1
Fastify Total
~101
Grand Total
~349