API Reference
All HTTP endpoints for the Project Syrup backend.
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.
?format= query parameter:
story(default) â 1080Ã1920 vertical, ideal for Instagram Storiessquareâ 1080Ã1080, ideal for feed posts
Content-Type: image/png with Cache-Control: public, max-age=3600. Returns 404 for archived waffles. No auth required.
Public API â Waffles
Public API â Spots & Claims
{
"waffle_id": "<uuid>",
"spots": [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. Instagram handle is normalized server-side (lowercased, @ stripped).
count random available spots in a single transaction. Rate-limited via the same limiter as /api/claims.
{
"waffle_id": "<uuid>",
"count": 5,
"instagram_handle": "dani_boo_glass"
}
Server selects count random spots under a row-level lock and claims them. Returns:
{
"claimed_count": 5,
"requested_count": 5,
"spot_numbers": [3, 8, 14, 22, 29],
"message": "Claimed 5 spots"
}
Partial fulfillment: if fewer spots are available than requested, the server claims as many as it can and returns claimed_count < requested_count with a message like "Claimed 3 of 5 requested spots". No client-side maximum cap is enforced â the server is authoritative.
Each claimed spot fires a separate WebSocket spot_update broadcast.
Public API â Buyers
{
"instagram_handle": "dani_boo_glass",
"stats": { "total_wins": 3, "total_losses": 12, "total_spots_claimed": 47, ... },
"win_rate": 0.0638,
"expected_win_rate": 0.0421,
"luck_rating": 0.0217,
"trophies": ["Stanley Cup", "Mystery Box"],
"waffle_history": [...]
}
luck_rating = actual win rate minus statistically expected win rate over completed waffles. Positive = luckier than expected; negative = unluckier.
trophies are item names extracted from double-quoted text in waffle titles (e.g. a title of Waffle â "Stanley Cup" yields "Stanley Cup").
Public API â Misc
{ "version": "v0.1.23-beta.3" }
Admin API â Auth
{ "username": "admin", "password": "syrup" }
Returns { "token": "..." } on success. Sets a secure token cookie.
Returns 401 on bad credentials; 429 after lockout threshold is exceeded.
{ "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.
{ "token": "...", "new_password": "..." }
Token is single-use and expires. Password must meet the password policy.
Admin API â Current Admin
{ "timezone": "America/New_York" }
{ "current_password": "...", "new_password": "..." }
Admin API â Waffles
?archived=true for archived waffles, ?archived=false (default) for active.
{
"title": "Blue Wig #3",
"description": "Limited drop",
"total_spots": 50,
"spot_price": 5,
"payment_methods": ["<payment-method-uuid>", "<payment-method-uuid>"],
"item_count": 1,
"image_url": "https://...",
"instagram_media_links": ["https://www.instagram.com/p/..."]
}
item_count defaults to 1 if omitted. Set to 2â10 for multi-item waffles where you'll draw that many winners. Returns the created waffle with its generated slug.
DELETE and providing current password as confirmation.
{ "confirmation": "DELETE", "password": "..." }
Requires admin or higher. Irreversible.
{ "winning_spot_numbers": [17] }
// or for a multi-item waffle (item_count > 1):
{ "winning_spot_numbers": [17, 23, 42] }
Marks each listed spot as winner, marks all other paid spots as loser, populates the waffle's winning_instagram_handles array, and updates buyer stats. Spots must be distinct and currently paid. Fires a waffle_completed WebSocket broadcast.
winner_cleared WebSocket broadcast./winner.
{ "winning_spot_numbers": [23] }
// or for multi-item:
{ "winning_spot_numbers": [23, 31, 44] }
Old winning spots not in the new selection revert to paid; new selections become winner; loser status is recomputed across all paid spots. Recalculates buyer stats. Fires a winner_changed WebSocket broadcast.
{ "template_id": "<uuid>", "message": "Grab your spots! đ§\n{url}" }
/waffle/:slug/card.png will regenerate it.Admin API â Share Templates v0.1.23-beta
Share message templates are reusable caption bodies. Requires waffle_manager or higher. Supports placeholders: {title}, {price}, {spots}, {url}.
{"templates": [...]}.{ "name": "Standard Drop", "body": "Spots open! đ§\nLink: {url}" }
Admin API â Spots
Admin API â Payment Methods v0.1.22
Stored payment method pool (Venmo, PayPal, CashApp, Zelle). Admin and super_admin can create, list, update, and soft-delete entries. Waffles reference payment methods by UUID via the payment_methods array on create/edit.
PaymentMethod objects with id, type, display_name, handle_or_url, and is_active.{
"type": "venmo",
"display_name": "Venmo â @notfixingit",
"handle_or_url": "@notfixingit"
}
Valid type values: venmo, paypal, cashapp, zelle. handle_or_url can be a raw handle (the public page will format the link based on type) or a full URL. New methods are is_active: true by default.
{ "display_name": "Venmo â Primary", "handle_or_url": "@notfixingit", "is_active": true }
All fields are optional â only the ones present are updated. Changes propagate to every waffle that references this method.
is_active = false). The record is preserved so historical waffles still resolve. The method no longer appears in the multi-select on new or edited waffles. To reactivate, PATCH with {"is_active": true}.
Admin API â Admin Management
All endpoints in this section require super_admin role.
{ "username": "jane", "password": "...", "role": "admin" }
Valid roles: super_admin, admin, waffle_manager.
{ "role": "waffle_manager", "current_password": "..." }
{ "new_password": "..." }
{ "current_password": "..." }
Deactivated admins cannot log in. Their audit history is preserved. They can be reactivated.
Admin API â Reports
Available to all roles.
Admin API â Audit Log
Requires admin or super_admin.
?page=1&per_page=50&admin_id=&action=&from=&to=
Admin API â Login History
- waffle_manager â own entries only
- admin â own + waffle_manager entries
- super_admin â all entries
?admin_id= (super_admin only).
Admin API â Users
Admin API â Settings
Requires super_admin.
{
"whois_server": "whois.pwhois.org",
"jwt_expiry_hours": 24,
"audit_log_retention_days": 90,
"login_history_retention_days": 90
}
WebSocket
: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
200 with {"status":"ok","db":"connected"} when healthy.
Returns 503 if the database is unreachable.
/health.