API Routes Map & Audit

Last Updated: August 20, 2026 Scope: All route handlers under src/app/api/83 endpoints.

Overview

theunparty.app grew organically as a solo build, and the API surface is now large enough that it needs a single, shared picture before the user‑journey "game logic" gets wired on top of it. This document is that picture: what each route does, who calls it, how it's protected, and which routes are not wired to anything yet.

Every folder under src/app/api/<em></em> that contains a route.ts/route.tsx becomes a live HTTP endpoint (Next.js App Router). A [param] folder is a dynamic segment (e.g. book/[bookId]). Two files in the tree are not endpoints: book/[bookId]/route-old.ts (legacy, dead) and book/types.ts (shared types).

This file is a hand‑maintained audit. When you add, remove, or re‑wire a route,

update the matching table and the Audit findings section.

---

Visual map

How traffic flows: who calls the API → which domain handles it → where the data lives.

diagram

Rendering diagram…

---

Legend

Wiring — is the route actually reached, and by whom?

Mark🟢
MeaningCalled from the app UI (pages / components)
Mark🧭
MeaningReached via navigation or a <meta> / <img> URL (not fetch)
Mark
MeaningTriggered by Vercel Cron (vercel.json)
Mark🪝
MeaningExternal signed POST (webhook or Vercel drain)
Mark📤
MeaningExternal client using an API key
Mark🛠
MeaningOps / manual tool (run by hand, not wired to UI)
Mark🔴
MeaningNo caller found in the repo (orphaned — wire it or delete it)

Auth — how the route is protected.

Mark🌐
MeaningPublic — no auth
Mark👤
MeaningSigned‑in user (Clerk auth())
Mark🛡️
MeaningAdmin role (requireAdmin())
Mark🔑
MeaningAPI key (STORY_API_KEY header)
Mark
MeaningCron secret (CRON_SECRET bearer)
Mark🔏
MeaningHMAC signature (webhook / drain)
Mark⚠️
MeaningGap — admin route that checks sign‑in only, not the admin role
Mark
MeaningGap — mutating route with no auth at all

---

At a glance

Count13
Primary storePostgres (+ 1 in‑memory)
Count6
Primary storePostgres
Count20
Primary storePostgres
DomainContent
Count8
Primary storePostgres / disk
DomainCommerce
Count6
Primary storePolar / Stripe + Postgres
Count6
Primary storePostgres / GitHub / static
Count14
Primary storeFilesystem / Blob / Apple
Count3
Primary storeSlack / Loops / Postgres
Count7
Primary storePostgres / GitHub
DomainTotal
Count83
Primary store

---

1. Journey engine (game logic)

The core loop the user journey is built on: steps (tasks), the questions and challenges attached to them, and founder onboarding. This is the surface the new game logic will wire into, so its gaps matter most.

Endpoint/api/steps
MethodsGET POST PUT DELETE
Auth👤
PurposeList / manage the caller's steps (filter, paginate)
Wiring🟢
Endpoint/api/steps/check-completion
MethodsGET POST
Auth👤
PurposeRe‑evaluate a step against its requirements and complete it
Wiring🟢
Endpoint/api/steps/recommendations
MethodsGET
Auth👤
PurposeSuggest next steps from the user's responses
Wiring🟢
Endpoint/api/steps/[stepId]/complete
MethodsPOST DELETE
Auth👤
PurposeManually mark a step complete / incomplete
Wiring🔴
Endpoint/api/steps/record-action
MethodsPOST
Auth👤
PurposeRecord a named action and auto‑complete matching action‑steps
Wiring🔴
Endpoint/api/questions
MethodsGET POST
Auth👤
PurposeGet a category's questions; save a response
Wiring🟢
Endpoint/api/questions/categories
MethodsGET
Auth👤
PurposeList question categories
Wiring🟢
Endpoint/api/questions/[questionId]/response
MethodsGET
Auth👤
PurposeThe caller's saved response to one question
Wiring🟢
Endpoint/api/challenges/[challengeId]/vote
MethodsGET POST
Auth👤
PurposeSave / read the caller's poll vote
Wiring🟢
Endpoint/api/challenges/[challengeId]/results
MethodsGET
Auth🌐
PurposeAggregated poll results (counts, percentages)
Wiring🟢
Endpoint/api/founders/foundersquestions
MethodsGET
Auth👤
PurposeFounder questions from the DB
Wiring🔴
Endpoint/api/founders/responses
MethodsGET
Auth👤¹
PurposeThe founder's progress on founder questions
Wiring🔴
Endpoint/api/score
MethodsGET POST
Auth🌐
PurposeUpdate / reset a score kept in memory only
Wiring🔴

¹ founders/responses is restricted to a single identity via FOUNDER_USER_ID, not a Clerk role.

---

2. Analytics & tracking

Records the journey so admins can see progress and retention. analytics/vercel is a drain endpoint and is listed again under Integrations.

Endpoint/api/analytics/session
MethodsPOST
Auth👤
PurposeStart / track a user session
Wiring🟢
Endpoint/api/analytics/user-progress
MethodsGET POST
Auth👤
PurposeGet or update the user's profile progress
Wiring🟢
Endpoint/api/analytics/idea-progress
MethodsPOST
Auth👤
PurposeUpsert idea‑progression completion rate
Wiring🟢
Endpoint/api/analytics/metrics
MethodsGET
Auth👤
PurposePlatform‑level metrics
Wiring🟢
Endpoint/api/analytics/retention
MethodsGET
Auth👤
PurposeRetention metrics (active users by window)
Wiring🔴
Endpoint/api/analytics/vercel
MethodsPOST
Auth🔏
PurposeIngest the Vercel analytics drain
Wiring🪝

---

3. Admin console

Backs the /admin UI. Authorization here is inconsistent — see Audit finding B. 🛡️ = proper admin‑role check; ⚠️ = sign‑in only (any logged‑in user passes); = no check at all.

Endpoint/api/admin/articles
MethodsGET POST
Auth🛡️
PurposeList / create editorial articles
Wiring🟢
Endpoint/api/admin/articles/[id]
MethodsGET PUT DELETE
Auth🛡️
PurposeRead / update / delete an article
Wiring🟢
Endpoint/api/admin/categories
MethodsGET POST
Auth🛡️
PurposeList / create step categories
Wiring🟢
Endpoint/api/admin/steps
MethodsGET POST
Auth🛡️
PurposeList / create steps
Wiring🟢
Endpoint/api/admin/steps/[id]
MethodsGET PUT DELETE
Auth🛡️
PurposeRead / update / delete a step
Wiring🟢
Endpoint/api/admin/steps/[id]/visibility
MethodsPATCH POST
Auth
PurposeSet / toggle step visibility
Wiring🟢
Endpoint/api/admin/steps/bulk-delete
MethodsPOST
Auth🛡️
PurposeBulk delete steps
Wiring🟢
Endpoint/api/admin/steps/sync-questions
MethodsPOST
Auth🛡️
PurposeSync founder questions into the DB
Wiring🛠
Endpoint/api/admin/questions
MethodsGET POST
Auth⚠️
PurposeList / create questions
Wiring🟢
Endpoint/api/admin/questions/[id]
MethodsGET PUT DELETE
Auth⚠️
PurposeRead / update / delete a question
Wiring🟢
Endpoint/api/admin/questions/upload
MethodsGET POST
Auth🛡️
PurposeBulk upload questions with Zod validation
Wiring🛠
Endpoint/api/admin/question-responses
MethodsGET
Auth⚠️
PurposeEvery user's question responses (Clerk‑enriched)
Wiring🟢
Endpoint/api/admin/submissions
MethodsGET POST
Auth🛡️
PurposeList / create user submissions
Wiring🟢
Endpoint/api/admin/submissions/[id]
MethodsGET PUT DELETE
Auth🛡️
PurposeReview a submission
Wiring🟢
Endpoint/api/admin/problem-worksheets
MethodsGET
Auth🛡️
PurposeList submitted problem worksheets
Wiring🟢
Endpoint/api/admin/repositories
MethodsGET POST PUT DELETE
Auth⚠️
PurposeManage tracked GitHub repositories
Wiring🟢
Endpoint/api/admin/user-progress
MethodsGET
Auth🛡️
PurposePer‑user progress overview
Wiring🟢
Endpoint/api/admin/dashboard-stats
MethodsGET
Auth⚠️
PurposeAggregate dashboard stats
Wiring🟢
Endpoint/api/admin/db-stats
MethodsGET
Auth⚠️
PurposeRow counts per table
Wiring🟢
Endpoint/api/admin/observability
MethodsGET
Auth⚠️
PurposeTrace / analytics observability data
Wiring🟢

---

4. Content

Editorial articles, stories, and the dynamic social image. articles/stories are public to read but require STORY_API_KEY to write or to pull the full external feed.

Endpoint/api/articles
MethodsGET POST
Auth🌐 / 🔑
PurposeList published articles (GET); create (POST, key)
Wiring🟢
Endpoint/api/articles/[slug]
MethodsGET PUT
Auth🌐 / 🔑
PurposeRead / update an article by slug
Wiring🟢
Endpoint/api/articles/[slug]/thoughts
MethodsGET POST
Auth👤
PurposeReader "thoughts" on an article
Wiring🟢
Endpoint/api/articles/unppp/[unpppId]
MethodsGET
Auth🌐
PurposeMarkdown articles for a UNPPP
Wiring🟢
Endpoint/api/stories
MethodsGET
Auth🔑
PurposeFull story feed for external sync
Wiring📤
Endpoint/api/stories/all
MethodsGET
Auth🌐
PurposeBatched fetch of all stories (client bootstrap)
Wiring🟢
Endpoint/api/stories/[storyId]
MethodsGET PUT
Auth🌐 / 🔑
PurposeRead / update one story
Wiring🟢
Endpoint/api/og
MethodsGET
Auth🌐
PurposeDynamic Open Graph image (ImageResponse)
Wiring🧭

---

5. Commerce

Legacy book sales (Stripe) plus the current Polar checkout / pricing path. webhook/polar also appears under Integrations.

Endpoint/api/book
MethodsGET POST
Auth🌐
PurposeList books; create a book (Zod)
Wiring🟢
Endpoint/api/book/[bookId]
MethodsPOST
Auth🌐
PurposeClaim / purchase a book
Wiring🟢
Endpoint/api/book/payment-intent
MethodsPOST
Auth🌐
PurposeCreate a Stripe payment intent
Wiring🟢
Endpoint/api/checkout
MethodsGET
Auth🌐
PurposeRedirect into a Polar checkout session
Wiring🧭
Endpoint/api/unppp/price
MethodsGET
Auth🌐
PurposeLive Polar product price (cached 1h)
Wiring🟢
Endpoint/api/webhook/polar
MethodsPOST
Auth🔏
PurposePolar payment webhook (order → fulfillment)
Wiring🪝

---

6. UNPPP & build data

Read‑only data feeding the status dashboard and priority views. Three overlapping priority/effort endpoints exist — only priority-score is wired; see Audit finding A.

Endpoint/api/unppp/priority-score
MethodsGET
Auth🌐
PurposeLive UNPPP priority scores (Prisma + percentiles, cached)
Wiring🟢
Endpoint/api/unppp/priority
MethodsGET
Auth🌐
PurposeStatic UNPPP priority data (force-static)
Wiring🔴
Endpoint/api/unppp/effort
MethodsGET
Auth🌐
PurposeUNPPP effort / timeline data
Wiring🔴
Endpoint/api/theunpartybuilder/scores
MethodsGET
Auth🌐
PurposeBuilder assumption / similarity scores
Wiring🔴
Endpoint/api/status
MethodsGET
Auth🌐
PurposeBuild‑status summary from priority scores
Wiring🔴
Endpoint/api/repo-data
MethodsGET HEAD
Auth🌐²
PurposeGitHub repo metadata (GitHub App creds)
Wiring🟢

² repo-data is unauthenticated but reads through server‑side GitHub App credentials.

---

7. Media & assets

SVG library management, generic uploads, Apple Music tokens, and cached brand assets. The SVG routes read/write the local filesystem, which is ephemeral on Vercel — see Audit finding C.

Endpoint/api/get-svgs
MethodsGET
Auth🌐
PurposeList SVG files in public/svgs
Wiring🟢
Endpoint/api/get-trashed-svgs
MethodsGET
Auth🌐
PurposeList trashed SVGs
Wiring🟢
Endpoint/api/upload-svg
MethodsPOST
Auth🌐
PurposeUpload an SVG to public/svgs
Wiring🟢
Endpoint/api/delete-svg
MethodsDELETE
Auth🌐
PurposeMove an SVG to trash
Wiring🟢
Endpoint/api/rename-svg
MethodsPOST
Auth🌐
PurposeRename an SVG file
Wiring🟢
Endpoint/api/restore-svg
MethodsPOST
Auth🌐
PurposeRestore an SVG from trash
Wiring🟢
Endpoint/api/add-tag
MethodsPOST
Auth🌐
PurposeTag an SVG (writes tags.json)
Wiring🟢
Endpoint/api/get-icon
MethodsGET
Auth🌐
PurposeIcon lookup — mock/stub
Wiring🔴
Endpoint/api/upload
MethodsPOST
Auth🌐
PurposeGeneric file upload (Vercel Blob)
Wiring🟢
Endpoint/api/brand-colors
MethodsGET
Auth🌐
PurposeBrand palette from theunpartybrand (cached 1h)
Wiring🟢
Endpoint/api/html/[filename]
MethodsGET
Auth🌐
PurposeServe a public/*.html file
Wiring🟢
Endpoint/api/musickit-token
MethodsGET
Auth🌐
PurposeApple MusicKit developer token
Wiring🟢
Endpoint/api/music/metadata
MethodsGET
Auth🌐
PurposeApple Music dev token — duplicates musickit-token
Wiring🟢
Endpoint/api/music/mood-enrich
MethodsGET
Auth🌐
PurposeMatch MOOD commits to Apple Music (ops helper)
Wiring🛠

---

8. Messaging & search

Endpoint/api/search
MethodsGET POST
Auth🌐
PurposeHybrid search (local + theunpartyapi)
Wiring🟢
Endpoint/api/chat/send
MethodsPOST
Auth🌐
PurposeSend a chat message to Slack
Wiring🟢
Endpoint/api/problem/send
MethodsGET POST
Auth👤
PurposeSubmit a problem worksheet (Loops email + DB)
Wiring🟢

---

9. Integrations (cron, webhooks, drains)

Machine‑to‑machine endpoints. These are supposed to have no UI caller — they are driven by Vercel Cron, external webhooks, or Vercel drains, and each is signed/secret‑gated.

Endpoint/api/cron/db-cleanup
MethodsGET
Auth
PurposePrune analytics rows older than 30 days
Wiring
Endpoint/api/cron/snapshot-unppp-pageviews
MethodsGET
Auth
PurposeSnapshot pageviews JSON via auto‑merged PR
Wiring
Endpoint/api/cron/snapshot-build-priority
MethodsGET
Auth
PurposeSnapshot build‑priority JSON via PR
Wiring
Endpoint/api/cron/snapshot-connect
MethodsGET
Auth
PurposeSnapshot connect JSON via PR
Wiring
Endpoint/api/cron/snapshot-about-cost
MethodsGET
Auth
PurposeSnapshot about‑cost JSON via PR
Wiring
Endpoint/api/webhook/polar
MethodsPOST
Auth🔏
PurposePolar payment webhook
Wiring🪝
Endpoint/api/github/webhook
MethodsPOST
Auth🔏
PurposeGitHub App webhook (repo events → Loops)
Wiring🪝
Endpoint/api/analytics/vercel
MethodsPOST
Auth🔏
PurposeVercel analytics drain ingest
Wiring🪝
Endpoint/api/drains/traces
MethodsGET POST
Auth🔏
PurposeVercel OTLP trace drain ingest
Wiring🪝

_Cron schedules live in vercel.json._

_Count note: webhook/polar and analytics/vercel are tallied under their primary domains (Commerce and Analytics) in At a glance and repeated here only to show the full machine‑to‑machine surface — so this table lists 9 rows for 7 unique Integrations‑owned endpoints._

---

Audit findings

A. Routes with no caller (orphans)

No fetch, navigation, or external trigger references these anywhere in the repo. Each is a decision: wire it into the journey, or delete it.

Endpoint/api/steps/[stepId]/complete
Likely reasonSuperseded by steps/check-completion (auto‑complete)
Suggested actionConfirm, then remove or wire as the manual override
Endpoint/api/steps/record-action
Likely reasonIntended hook — its own doc says "call this wherever an action happens"
Suggested actionWire into real actions (e.g. connected_github)
Endpoint/api/analytics/retention
Likely reasonDashboard never added
Suggested actionWire into the admin dashboard or remove
Endpoint/api/founders/foundersquestions
Likely reasonApp reads static founders/foundersquestions.ts instead
Suggested actionRemove or repoint to the DB source
Endpoint/api/founders/responses
Likely reasonNo consumer
Suggested actionRemove or wire into the founder view
Endpoint/api/score
Likely reasonIn‑memory only — resets every cold start, never persisted
Suggested actionRemove, or back with Postgres if scores are real
Endpoint/api/unppp/priority
Likely reasonSuperseded by unppp/priority-score
Suggested actionRemove the static duplicate
Endpoint/api/unppp/effort
Likely reasonNo consumer
Suggested actionWire into the status/effort view or remove
Endpoint/api/theunpartybuilder/scores
Likely reasonNo consumer
Suggested actionWire into the builder view or remove
Endpoint/api/status
Likely reasonPage renders from data directly, not this route
Suggested actionRemove or repoint the page to it
Endpoint/api/get-icon
Likely reasonMock/stub (// Mock database or condition)
Suggested actionRemove

_Not listed as orphans (intentionally UI‑less): cron/<em>, webhook/</em>, analytics/vercel, drains/traces (machine‑triggered); admin/questions/upload, admin/steps/sync-questions, music/mood-enrich (ops tools); stories (external API‑key feed)._

B. Authorization gaps & inconsistencies

The /api/admin/* namespace mixes three different protection levels:

⛔ No auth at alladmin/steps/[id]/visibility (PATCH & POST) mutates step

visibility with zero authentication. Any anonymous caller can hide/show steps.

⚠️ Sign‑in only, no role checkadmin/dashboard-stats, admin/db-stats,

admin/observability, admin/questions, admin/questions/[id], admin/question-responses, admin/repositories verify auth() has a userId but never call requireAdmin(). Any signed‑in user can read admin data (DB stats, observability, every user's responses) or mutate repositories.

🛡️ Correct — the remaining /api/admin/* routes use requireAdmin()

(src/lib/admin.tscheckRole('admin')).

Recommendation: make requireAdmin() the single gate for the whole /api/admin/* namespace (ideally enforced in middleware.ts as well as per‑route), so protection can't drift route‑by‑route.

Note: the app's middleware.ts requires sign‑in for

/admin/<em> pages, but that only forces authentication, not the admin role*, and

API routes still need their own server‑side checks.

C. Legacy, duplicate & stub code

ItemDead route file
Wheresrc/app/api/book/[bookId]/route-old.ts
NoteSuperseded by route.ts; delete
ItemDuplicate token logic
Wheremusic/metadata vs musickit-token
Notemetadata's own comment says it "reuses the same logic"; collapse to one
ItemStub endpoint
Whereget-icon
NoteComment admits it's a mock; no real store
ItemEphemeral filesystem writes
WhereSVG routes + add-tag
Noteupload-svg, delete-svg, rename-svg, restore-svg, add-tag write under public/, which does not persist on Vercel — moves are lost on redeploy. Migrate to Blob/DB if these must survive
ItemIn‑memory state
Wherescore
NoteSee finding A

D. Structural observations

Unauthenticated writes. The SVG/asset and book/score/chat routes accept

writes with no auth. Acceptable for a solo build, but each is a public write path — gate them (or at least rate‑limit) before launch.

Naming is mostly consistent — verb‑style top‑level SVG routes

(get-svgs, delete-svg, …) sit alongside RESTful nested resources (steps/[id], admin/…). Fine to leave, but new routes should prefer the nested RESTful form.

Priority/effort duplication. unppp/priority, unppp/priority-score, and

unppp/effort overlap; consolidating removes a class of "which one is live?" bugs.

---

Maintaining this map

There is no test runner in this repo (see CLAUDE.md); this audit was produced by static inspection. To refresh it after changing routes:

List endpoints & methods — every exported GET/POST/PUT/PATCH/DELETE

handler (as export async function, export function, or export const) under src/app/api/<em></em>/route.ts(x).

Check wiring — search the repo (outside src/app/api/) for the route path in

fetch(...), href/window.location, <Image>/<img> src, vercel.json crons, and provider webhook configs. No hit → likely an orphan (finding A).

Check auth — look for requireAdmin(), auth(), STORY_API_KEY, CRON_SECRET,

or a signature verifier at the top of each handler.

#API#ARCHITECTURE#AUDIT

🧗🏾‍♂️ in progress

THOUGHTS.