API Documentation
Base URL:
https://pfedl.com/api. Endpoints marked requires login or a role need an
Authorization: Bearer <token> header, using the token returned from POST /auth/login.
Roles rank user < helper < moderator < admin < developer < owner — an endpoint
marked e.g. "moderator+" needs that role or higher. Endpoints tagged bot secret are for the
PFEDL Discord bot only and require an X-Bot-Secret header. Everything else is public and needs no auth.
Auth
POST/auth/register
Create an account. The very first account on the site becomes owner.
Body: username, password, email
POST/auth/login
Log in and receive a session token.
Body: username, password
POST/auth/logout
Invalidate the current session token.
GET/auth/me
Return the currently logged-in user.
POST/auth/avatar
Upload a profile picture (raw image body, max 5MB) to R2.
DELETE/auth/avatar
Remove your profile picture.
PATCH/auth/flag
Set your country flag.
Body: country_code
POST/auth/change-password
Change your account password.
Body: oldPassword, newPassword
GET/auth/discord/start
Get the Discord OAuth URL to redirect the user to.
Query: mode=login|link
GET/auth/discord/callback
OAuth redirect target — exchanges the code and redirects back to the site.
Query: code, state
POST/auth/discord/unlink
Unlink your Discord account.
Levels
GET/levels/top1
The current #1 classic level, its first victor, and how many days it has held #1.
GET/levels
List all levels with their victors.
Query: archived=1
GET/levels/:id
Single level detail.
POST/levels
Create a level.
Body: name, creators, publisher, verifier, gd_level_id, aredl_position, gd_version, length, gddl_tier, nlw_rank, video_url, tags, position
PATCH/levels/:id
Edit a level's fields.
Body: any of name, creators, publisher, verifier, gd_level_id, gd_version, length, gddl_tier, nlw_rank, video_url, tags
PATCH/levels/:id/first-victor
Override who's shown as the first victor.
Body: username
PUT/levels/:id/position
Move a level to a new list position.
Body: newPosition
DELETE/levels/:id
Delete a level.
Records
POST/records
Submit a full-completion record, for a listed level or an unlisted AREDL level.
Body: level_id, aredl_level_id, aredl_level_name, video_url
GET/records
List records.
Query: status=pending|approved|rejected
PATCH/records/:id/approve or /reject
Approve or reject a submission. Approving an unlisted level's record auto-adds the level to the list, shifts boundary levels, and logs a changelog entry.
Body: reason
DELETE/records/:id
Delete a record.
Leaderboard
GET/leaderboard
Player rankings (cached).
Query: mode=t150|allextreme|platformer|combined, sort=points|victories|first_victories
Spotlight
Backs the homepage "Record of the Week" and "Reigns" panels. All fields are derived from approved records only, cached 15 minutes.
GET/spotlight
Most recent completion, most recent placement (with victor), hardest completion in the last week/month, most recent platformer completion, and the #1 classic/platformer reign length (with victor).
Stats
Backs the homepage "Stats" strip. Cached 15 minutes.
GET/stats
List age (months/days since 4/5/26), total registered users, total approved completions (classic + platformer), and total placed levels (classic + platformer).
Players
GET/players/:id_or_username
Full player profile — records, points, ranks, progress, claims, game scores.
Level Page
GET/level/:identifier
Level page by position or name — victors, first victor, points value, weekly/monthly/hitlist claimers, average rating.
Query: type=platformer
GET/level/:identifier/history
Reconstructed position history for a level.
Query: type=platformer
Changelog
GET/changelog
List changelog entries.
Query: tab=classic|platformer|submissions
POST/changelog
Add a manual changelog entry.
Body: type, action, level_id, old_position, new_position, above_level_id, below_level_id, source, occurred_at
PATCH/changelog
Edit a changelog entry.
Body: id, type, action, level_id, old_position, new_position, above_level_id, below_level_id, occurred_at
DELETE/changelog
Delete a changelog entry.
Body: id, type
POST/changelog/import
One-time import from the internal action log.
Body: type
Roadmap
GET/roadmap
List all roadmap cards.
POST/roadmap
Create a roadmap card.
Body: title, description, category, status, priority
PATCH/roadmap/:id
Edit a card (used by both the edit modal and drag-and-drop).
Body: any of title, description, category, status, priority, sort_order
DELETE/roadmap/:id
Delete a card.
Reviews
GET/reviews/mine
Level IDs you've already reviewed.
GET/reviews/level/:level_id
All reviews for a level plus the average rating.
POST/reviews
Leave or update a review — requires an approved completion of the level first.
Body: level_id, rating (1-10), note (≤150 chars)
Progress
Percentage completions on listed PFEDL levels.
GET/progress/level/:level_id
All tracked percentage progress for a level.
POST/progress
Submit a percentage completion.
Body: level_id, percent (15-99), video_url
Uncompleted
Unlisted AREDL levels with tracked percentage progress from the community.
GET/uncompleted
Ranked list of uncompleted levels.
GET/uncompleted/eligible
AREDL levels not yet on PFEDL.
GET/uncompleted/:aredl_level_id
Full progress breakdown for one level.
POST/uncompleted
Submit percentage progress on an unlisted level.
Body: aredl_level_id, percent (15-99), video_url
Hitlist
GET/hitlist
List hitlist picks by tier.
POST/hitlist
Add an unbeaten level to the hitlist (each admin gets up to 5 picks per tier).
Body: aredl_level_id, tier (Easy/Medium/Hard/Insane/Extreme/Impossible)
DELETE/hitlist/:id
Remove a pick.
Weekly / Monthly
GET/weeklymonthly
Current weekly and monthly challenge levels (auto-generates if missing).
POST/weeklymonthly/override
Manually set the current weekly/monthly level.
Body: type, tier, aredl_level_id
POST/weeklymonthly/reroll
Re-randomize the current weekly/monthly level.
Body: type, tier
POST/claims
Claim a weekly or monthly completion.
Body: aredl_level_id, claim_type (weekly|monthly), tier, video_url
GET/claims
List claims.
Query: status=
PATCH/claims/:id/approve or /reject
Approve or reject a claim.
Body: reason
Packs
GET/packs
List all packs.
GET/packs/:id
Single pack with its levels.
POST/packs
Create a pack.
Body: name, tier (bronze/silver/gold/sapphire/ruby/emerald/diamond), description
PATCH/packs/:id
Edit a pack.
Body: name, tier, description
DELETE/packs/:id
Delete a pack.
POST/packs/:id/levels
Add a level to a pack.
Body: level_id
DELETE/packs/:id/levels/:levelId
Remove a level from a pack.
Platformer
Mirrors the classic Levels/Records/Leaderboard endpoints for the platformer list.
GET/platformer/levels/top1
The current #1 platformer level, its first victor, and how many days it has held #1.
GET/platformer/levels
List platformer levels.
POST/platformer/levels
Create a platformer level.
Body: same fields as classic levels
PATCH/platformer/levels/:id
Edit a platformer level.
DELETE/platformer/levels/:id
Delete a platformer level.
POST/platformer/records
Submit a platformer record.
Body: level_id, aredl_level_id, aredl_level_name, video_url
GET/platformer/records
List platformer records.
Query: status=
PATCH/platformer/records/:id/approve or /reject
Approve or reject a platformer submission.
Body: reason
GET/platformer/leaderboard
Platformer rankings.
Query: sort=points|victories|first_victories
Games
Scores for the mini-games (Higher or Lower, Roulette, Wheel).
GET/games/scores
Your saved scores.
Query: game=
POST/games/scores
Save a best score (only stored if it beats your existing one).
Body: game, score, metadata
GET/games/leaderboard
Game high-score leaderboard.
Query: game=
Roulette
GET/roulette/runs
Your roulette run history.
POST/roulette/runs
Save a run and update your best score.
Body: seed, score, mode
Search
GET/search
Global search across players and levels (classic + platformer).
Query: q= (min 2 chars)
AREDL / AREPL Proxy
Mirrors the upstream AREDL (classic) and AREPL (platformer) list APIs, cached locally.
POST/aredl/sync /arepl/sync
Pull the full upstream list into the local cache.
GET/aredl/level/:level_id /arepl/level/:level_id
Live detail + creators fetch from the upstream API.
GET/aredl/all /arepl/all
Full cached upstream list.
GET/aredl/search
Search the cached AREDL list.
Query: q= (min 2 chars)
GET/aredl/status /arepl/status
Cached entry count and last-synced time.
Settings
GET/settings
Site-wide settings (e.g. scoring constants).
PUT/settings/:key
Update a setting.
Body: value
Admin
POST/admin/resync-positions
Reorder levels to match AREDL ranks.
POST/admin/bust-cache
Clear all KV caches.
POST/admin/resequence
Fix duplicate or gapped level positions.
GET/admin/users
List all users.
PATCH/admin/users/:id/role
Change a user's role.
Body: role
PATCH/admin/users/:id/reset-password
Reset a user's password.
Body: newPassword
DELETE/admin/users/:id
Delete a user.
Bot
Reserved for the PFEDL Discord bot's authenticated lookups.
GET/bot/discord/:discord_id
Look up a PFEDL player by their linked Discord ID.