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}
Headers¶
All authenticated requests require:
The Media API also accepts tokens via query parameter for SSE streams:
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 |
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 |
Health check — PostgreSQL + Redis ping | |
| GET | /api/metrics |
Prometheus metrics (text/plain) |
Auth¶
Prefix: /api/auth
| Method | Path | Auth | Rate Limited | Description |
|---|---|---|---|---|
| POST | /api/auth/login |
Email + password login | ||
| POST | /api/auth/register |
Create account (always USER role) |
||
| POST | /api/auth/verify-email |
Verify email with token | ||
| POST | /api/auth/resend-verification |
Resend verification email | ||
| POST | /api/auth/forgot-password |
Send password reset email | ||
| POST | /api/auth/reset-password |
Set new password with reset token | ||
| POST | /api/auth/refresh |
Rotate refresh token → new token pair | ||
| POST | /api/auth/logout |
Invalidate refresh token | ||
| GET | /api/auth/me |
Current user profile |
Login request & response
Request:
Response: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 |
List all active campaigns | |
| GET | /api/campaigns/:slug/details |
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 |
Send advocacy email to representatives (rate limited: 30/hour) | |
| POST | /api/campaigns/:slug/track-mailto |
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 |
List approved public responses | |
| GET | /api/campaigns/:slug/response-stats |
Response statistics | |
| POST | /api/campaigns/:slug/responses |
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 |
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 |
Lookup representatives by postal code (cache-first) | |
| GET | /api/representatives/test-connection |
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 |
NAR Import body
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 |
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 |
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 |
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 |
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 |
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: ⅗min) |
| 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 |
Stripe publishable key + donation settings | |
| GET | /api/payments/plans |
Active subscription plans | |
| GET | /api/payments/products |
Active products (?type=) |
|
| POST | /api/payments/subscribe |
Create subscription checkout | |
| POST | /api/payments/purchase |
Optional | Product checkout (guest or logged-in) |
| POST | /api/payments/donate |
Donation checkout | |
| GET | /api/payments/my-subscription |
Current subscription | |
| POST | /api/payments/my-subscription/cancel |
Cancel subscription | |
| POST | /api/payments/webhook |
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 |
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|watchTime&limit=) |
| 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 |
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 |
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|popular|oldest&category=) |
| 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 |
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 |
Available reaction types + emoji mappings | |
| GET | /api/reactions |
List reactions (?mediaId=&userId=&limit=) |
|
| GET | /api/reactions/:mediaId/chat |
Reactions in chat timeline format | |
| POST | /api/reactions |
Add reaction (30s cooldown per type) |
Available types: like, love, laugh, wow, sad, angry
Comments & Chat¶
Public Comments¶
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/public/:id/comments |
List comments (?limit=&offset=) |
|
| POST | /api/public/:id/comments |
Optional | Create comment (word-filtered; rate limited: 5/min) |
| GET | /api/public/:id/chat-stream |
SSE stream for real-time chat (30s keepalive) |
Comment Admin¶
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|medium|high|custom}) |
| 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|popular|random) |
| POST | /api/shorts/scan |
Admin | Auto-classify short videos by duration |
Upvotes¶
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/public/:id/upvote |
Toggle upvote (session-based via X-Session-ID header) |
|
| GET | /api/public/:id/upvote-status |
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 |