API · v1

Gaffr API

REST under https://api.gaffr.app, realtime over WebSocket. Auth via signed JWT in the Authorization header.

Public base: api.gaffr.app
Bearer JWT
Rate limit: 60 req/min
Authentication

Every authenticated request must include:

Authorization: Bearer <supabase_access_token>
Content-Type: application/json

Auth

POST/auth/oauth/google
Public
Sign in with Google

Initiates Google OAuth via Supabase.

Response
{ "url": "https://accounts.google.com/o/oauth2/v2/auth?..." }
POST/auth/oauth/apple
Public
Sign in with Apple

Initiates Apple OAuth via Supabase.

Response
{ "url": "https://appleid.apple.com/auth/authorize?..." }
POST/auth/signout
Auth
Sign out

Revokes the current session.

Response
{ "ok": true }

Roster

GET/api/roster/season
Auth
Get season roster

Returns the locked 5-player season roster.

Response
{
  "locked_at": "2025-08-14T18:22:00Z",
  "players": [
    { "id": "p11", "name": "Bukayo Saka", "position": "MID", "team": "ARS", "points": 32 },
    { "id": "p14", "name": "William Saliba", "position": "DEF", "team": "ARS", "points": 8 }
  ],
  "total": 78,
  "state": "alive"
}
POST/api/roster/season
Auth
Submit season roster

Locks 5 players for the season. Rejects after lock.

Request body
{ "players": ["p11", "p14", "p25", "p28", "p15"] }
Response
{ "locked_at": "2025-08-14T18:22:00Z", "ok": true }
POST/api/roster/january-switch
Auth
Use January switch

Swap exactly one player. Only valid Jan 1–31.

Request body
{ "out": "p1", "in": "p38" }
Response
{ "ok": true, "new_total": 76, "state": "alive", "switch_used": true }
GET/api/roster/monthly
Auth
Get monthly roster

Returns the current month's 3-player roster.

Response
{ "month": "2025-11", "players": ["p22","p36","p30"], "total": 9, "target": 15 }
POST/api/roster/monthly
Auth
Submit monthly roster

Locks 3 outfielders for the current month.

Request body
{ "players": ["p22","p36","p30"] }
Response
{ "ok": true, "month": "2025-11" }

Players

GET/api/players
Auth
List players

Filter by team, position, search. Paginated.

Response
{
  "data": [
    { "id": "p1", "name": "Mohamed Salah", "team": "liv", "position": "FWD", "price": 13.2, "points": 31 }
  ],
  "page": 1,
  "total": 50
}
GET/api/players/:id/stats
Auth
Player stats by GW

Granular per-gameweek breakdown including all FPL live metrics.

Response
{
  "id": "p1",
  "stats": [
    { "gw": 8, "goals": 1, "assists": 0, "yellow": 0, "red": 0, "tackles": 1, "shots": 4, "fouls": 1, "minutes": 90 }
  ]
}

Leaderboards

GET/api/leaderboard/season
Auth
Season leaderboard

Sorted by total points ascending from 100. Busted users separated.

Response
{
  "alive": [
    { "user_id": "u-101", "name": "Sam Whitaker", "total": 98 }
  ],
  "busted": [
    { "user_id": "u-110", "name": "Yuki Tanaka", "total": 103, "over_by": 3 }
  ]
}
GET/api/leaderboard/monthly?month=2025-11
Auth
Monthly leaderboard

Filtered to gameweeks within the calendar month.

Response
{ "month": "2025-11", "rows": [{ "user_id": "u-101", "total": 14 }] }

Admin

PATCH/api/admin/scoring-rules/:id
Auth
Update rule

Change points or active flag. Admin only.

Request body
{ "points": 2, "active": true }
Response
{ "ok": true }
POST/api/admin/sync/bootstrap
Auth
Trigger bootstrap sync

Re-pulls players/teams from FPL.

Response
{ "queued": true, "job_id": "sync_4821" }
POST/api/admin/sync/live
Auth
Trigger live sync

Re-pulls live events for a gameweek.

Request body
{ "gw": 9 }
Response
{ "queued": true }
DELETE/api/admin/users/:id
Auth
Delete user

Soft-deletes a user and detaches their roster.

Response
{ "ok": true }

Realtime

GETwss://api.gaffr.app/realtime/leaderboard
Auth
Leaderboard channel

Pushes ranking updates as live events score.

Response
event: rank.update
data: { "user_id": "u-103", "delta": 1, "new_total": 87 }