API Reference
All HTTP endpoints for the Project Syrup backend.
âšī¸ Authentication
Admin endpoints require a valid JWT in the Authorization: Bearer <token> header,
or in the token cookie set by a successful login. Public endpoints require no auth.
Public Pages (HTML)
Server-rendered HTML pages returned to browsers.
GET /
Home page. Lists active waffles with live spot counts.
GET /waffles
Full public waffle list.
GET /waffle/:slug
Waffle detail page with live spot grid. This is the URL you share with buyers.
GET /buyer/:handle
Public buyer stats page showing win/loss history for an Instagram handle.
GET /about
Public about page. Authenticated admins see additional system information.
Public API â Waffles
GET /api/waffles
List all active (non-archived) waffles. Returns an array of waffle summary objects.
GET /api/waffles/:slug
Get full waffle details by slug, including title, price, total spots, claimed/paid counts, and media links.
GET /api/waffles/:slug/export
Download waffle spot list as CSV. Columns: spot number, status, claimed_by_handle. No auth required.
Public API â Spots & Claims
GET /api/waffles/:slug/spots
Get the full spot grid for a waffle. Returns all spots with number, status, and (if claimed) the Instagram handle.
POST /api/claims
Claim one or more spots. Rate-limited.
{
"waffle_slug": "blue-wig-3",
"spot_numbers": [4, 7, 12],
"instagram_handle": "dani_boo_glass"
}
Returns the updated spot objects or an error if any spot is already taken.
Transactionally safe â no double-claims possible.
Public API â Buyers
GET /api/buyers/:handle/stats
Win/loss statistics for an Instagram handle. Returns total claims, wins, and losses.
GET /api/buyers/:handle/history
Full claim history for an Instagram handle across all waffles.
Admin API â Auth
POST /api/admin/login
Authenticate and receive a JWT.
{ "username": "admin", "password": "syrup" }
Returns { "token": "..." } on success. Sets a secure token cookie.
Returns 401 on bad credentials; 429 after lockout threshold is exceeded.
POST /api/admin/logout
Clears the session cookie. No body required.
POST /api/admin/forgot-password
Request a password reset token by username.
{ "username": "admin" }
Always returns 200 (does not reveal whether the username exists). A super_admin must retrieve the token and share it with the user out-of-band.
POST /api/admin/reset-password
Reset password using a token.
{ "token": "...", "new_password": "..." }
Token is single-use and expires. Password must meet the password policy.
Admin API â Current Admin
GET /api/admin/me
Returns the authenticated admin's profile: id, username, role, timezone, profile fields.
PATCH /api/admin/me/timezone
Update timezone preference.
{ "timezone": "America/New_York" }
POST /api/admin/change-password
Change your own password. Requires current password.
{ "current_password": "...", "new_password": "..." }
Admin API â Waffles
GET /api/admin/waffles
List waffles. Query param:
?archived=true for archived waffles, ?archived=false (default) for active.
POST /api/admin/waffles
Create a new waffle.
{
"title": "Blue Wig #3",
"price_per_spot": "25.00",
"total_spots": 50,
"media_links": ["https://www.instagram.com/p/..."]
}
PATCH /api/admin/waffles/:id
Update waffle title, price, or media links. Spot count cannot be changed after creation.
POST /api/admin/waffles/:id/archive
Archive a waffle. Requires admin or higher.
POST /api/admin/waffles/:id/unarchive
Restore an archived waffle to active status.
DELETE /api/admin/waffles/:id
Permanently delete a waffle and all its spots. Requires typing
DELETE and providing current password as confirmation.
{ "confirmation": "DELETE", "password": "..." }
Requires admin or higher. Irreversible.
POST /api/admin/waffles/:id/winner
Enter the winning spot number.
{ "spot_number": 17 }
Marks the spot as winner, updates buyer stats for all paid spots.
POST /api/admin/waffles/:id/clear-winner
Clear the current winner. Resets the winning spot to active; recalculates buyer stats.
POST /api/admin/waffles/:id/change-winner
Reassign the winner to a different spot.
{ "spot_number": 23 }
Recalculates buyer stats for all affected spots.
Admin API â Spots
POST /api/admin/spots/:id/pay
Mark a pending spot as paid. Broadcasts a WebSocket update to all connected clients.
POST /api/admin/spots/:id/release
Release a pending or paid spot back to available. Broadcasts a WebSocket update.
Admin API â Admin Management
All endpoints in this section require super_admin role.
GET /api/admin/admins
List all admin accounts with username, role, and active status.
POST /api/admin/admins
Create a new admin account.
{ "username": "jane", "password": "...", "role": "admin" }
Valid roles: super_admin, admin, waffle_manager.
PATCH /api/admin/admins/:id
Update role. Demotions require current password confirmation.
{ "role": "waffle_manager", "current_password": "..." }
PATCH /api/admin/admins/:id/password
Reset another admin's password. The admin should change it on next login.
{ "new_password": "..." }
DELETE /api/admin/admins/:id
Deactivate an admin account. Requires current password.
{ "current_password": "..." }
Deactivated admins cannot log in. Their audit history is preserved. They can be reactivated.
Admin API â Reports
Available to all roles.
GET /api/admin/reports/drought
Buyers with the most losses and no recent win, sorted by loss streak.
GET /api/admin/reports/power-buyers
Buyers ranked by total paid spots across all waffles.
GET /api/admin/reports/monthly-activity
Waffles run, spots claimed, and payments received grouped by calendar month.
GET /api/admin/reports/spot-velocity
Time from waffle creation to last spot claimed, per waffle. Shows how quickly your drops sell out.
Admin API â Audit Log
Requires admin or super_admin.
GET /api/admin/audit
List audit log entries with pagination. Query params:
?page=1&per_page=50&admin_id=&action=&from=&to=
GET /api/admin/audit/:id
Get a single audit log entry by ID.
GET /api/admin/audit/export
Export filtered audit log entries as CSV. Same filter params as the list endpoint.
Admin API â Login History
GET /api/admin/login-history
List login history. Visibility is role-scoped:
- waffle_manager â own entries only
- admin â own + waffle_manager entries
- super_admin â all entries
?admin_id= (super_admin only).
Admin API â Users
GET /api/admin/users
List all registered buyer handles (the users registry). Returns handle and first-seen timestamp.
Admin API â Settings
Requires super_admin.
GET /api/admin/settings
Retrieve current system settings (WHOIS server, JWT expiry, retention periods, lockout config).
PATCH /api/admin/settings
Update one or more system settings.
{
"whois_server": "whois.pwhois.org",
"jwt_expiry_hours": 24,
"audit_log_retention_days": 90,
"login_history_retention_days": 90
}
WebSocket
WS /ws/:slug
Connect to the real-time hub for a waffle. Replace
:slug with the waffle slug.
The client receives JSON messages whenever a spot changes state.
Message format
{
"type": "spot_update",
"spot": {
"id": 42,
"number": 7,
"status": "paid",
"claimed_by_handle": "dani_boo_glass"
}
}
Message types
| type | Trigger |
|---|---|
spot_update | Any spot status change (claim, pay, release, winner) |
winner | Winner entered â includes winning spot and handle |
ping | Server heartbeat (every ~30s) â client should reply pong |
Reconnection
The client-side WebSocket implementation (websocket-client.js) uses exponential backoff with jitter and a maximum retry cap. Stale connections are detected via the server-side ping/pong heartbeat.
Health & Readiness
GET /health
Returns
200 with {"status":"ok","db":"connected"} when healthy.
Returns 503 if the database is unreachable.
GET /ready
Readiness probe for container orchestrators. Same response format as
/health.