import { ContentCard } from "@/components/public/ui"; // Public API documentation. Static, hand-maintained from the routes that // actually exist under src/app/api — keep this in sync when endpoints change. export const metadata = { title: "API" }; type Method = "GET" | "POST" | "DELETE"; type Endpoint = { method: Method; path: string; description: string; /** Requires `Authorization: Bearer `. */ bearer?: boolean; /** Requires a signed-in web session (NextAuth), not a Bearer token. */ session?: boolean; }; type Group = { icon: string; title: string; subtitle: string; endpoints: Endpoint[]; }; // Mirrors the route.ts files under src/app/api. Only documents endpoints that // really exist; auth flags reflect bearerUserId() / auth() usage in each route. const GROUPS: Group[] = [ { icon: "👤", title: "Users", subtitle: "Profiles and the signed-in account.", endpoints: [ { method: "GET", path: "/api/users/{username}", description: "Public profile for a user by username (look, motto, rank, badges).", }, { method: "GET", path: "/api/me", description: "The currently signed-in user, or { user: null } when not authenticated.", }, ], }, { icon: "📰", title: "Content", subtitle: "News articles, comments and photos.", endpoints: [ { method: "GET", path: "/api/articles", description: "List published news articles (paginated via query params).", }, { method: "GET", path: "/api/articles/{slug}", description: "A single article by slug, with its comments.", }, { method: "POST", path: "/api/articles/{slug}/comment", description: "Post a comment on an article.", bearer: true, }, { method: "GET", path: "/api/photos", description: "Recent in-game camera photos.", }, ], }, { icon: "🏙️", title: "Community", subtitle: "Hotel population, guilds, staff and teams.", endpoints: [ { method: "GET", path: "/api/home", description: "Aggregated home-page payload (settings, news, online stats).", }, { method: "GET", path: "/api/online", description: "Users currently online.", }, { method: "GET", path: "/api/online/count", description: "Just the online-user count.", }, { method: "GET", path: "/api/leaderboard", description: "Player leaderboard (ranked by the requested metric).", }, { method: "GET", path: "/api/guilds", description: "List guilds.", }, { method: "GET", path: "/api/guilds/{id}", description: "A single guild with its members.", }, { method: "GET", path: "/api/staff", description: "Staff members above the configured minimum rank.", }, { method: "GET", path: "/api/teams", description: "Public staff teams / rank groups.", }, ], }, { icon: "💰", title: "Economy", subtitle: "Shop catalogue and rare-furniture values.", endpoints: [ { method: "GET", path: "/api/shop", description: "Shop products (filter by category via query params).", }, { method: "GET", path: "/api/shop/categories", description: "Shop categories.", }, { method: "GET", path: "/api/values", description: "Rare-value catalogue.", }, { method: "GET", path: "/api/values/{id}", description: "A single rare value entry.", }, { method: "GET", path: "/api/values/categories", description: "Rare-value categories.", }, ], }, { icon: "📻", title: "Radio", subtitle: "Now-playing, listeners, DJ points and shouts.", endpoints: [ { method: "GET", path: "/api/radio/now-playing", description: "The track currently on air.", }, { method: "GET", path: "/api/radio/current-dj", description: "The DJ currently live.", }, { method: "GET", path: "/api/radio/listeners", description: "Current listener count.", }, { method: "GET", path: "/api/radio/config", description: "Public radio configuration.", }, { method: "GET", path: "/api/radio/embed-config", description: "Configuration for the embeddable radio player.", }, { method: "GET", path: "/api/radio/auto-play", description: "AutoDJ playback state.", }, { method: "GET", path: "/api/radio/stream", description: "Stream metadata / proxy details.", }, { method: "GET", path: "/api/radio/points/leaderboard", description: "DJ points leaderboard.", }, { method: "GET", path: "/api/radio/points", description: "The signed-in user's own DJ points.", bearer: true, }, { method: "GET", path: "/api/radio/shouts", description: "Recent radio shout-outs.", }, { method: "POST", path: "/api/radio/shouts", description: "Submit a shout-out to the current DJ.", bearer: true, }, ], }, { icon: "⚙️", title: "Settings", subtitle: "Public site configuration.", endpoints: [ { method: "GET", path: "/api/settings", description: "Public, non-sensitive site settings (name, theme, links).", }, ], }, { icon: "🔑", title: "Tokens", subtitle: "Issue and manage personal access tokens.", endpoints: [ { method: "POST", path: "/api/tokens", description: "Mint a new personal access token (the plaintext is shown once).", session: true, }, { method: "GET", path: "/api/me/tokens", description: "List your personal access tokens (id, name, last used).", session: true, }, { method: "DELETE", path: "/api/me/tokens?id={id}", description: "Revoke one of your tokens by id.", session: true, }, ], }, { icon: "🎫", title: "Tickets", subtitle: "Help-center tickets and replies.", endpoints: [ { method: "GET", path: "/api/tickets", description: "List your own help-center tickets.", bearer: true, }, { method: "POST", path: "/api/tickets", description: "Open a new help-center ticket.", bearer: true, }, { method: "GET", path: "/api/tickets/{id}", description: "A single ticket you own, with its replies.", bearer: true, }, { method: "POST", path: "/api/tickets/{id}/reply", description: "Reply to one of your tickets.", bearer: true, }, ], }, { icon: "❤️", title: "Health", subtitle: "Service status.", endpoints: [ { method: "GET", path: "/api/health", description: "Liveness probe — reports app and database status.", }, ], }, ]; const METHOD_CLASS: Record = { GET: "bg-green-600/20 text-[var(--color-text)]", POST: "bg-[var(--color-primary)]/18 text-[var(--color-text)]", DELETE: "bg-red-500/22 text-[var(--color-text)]", }; function AuthTag({ endpoint }: { endpoint: Endpoint }) { if (endpoint.bearer) { return ( 🔒 Bearer ); } if (endpoint.session) { return ( 🔒 Session ); } return ( Public ); } function EndpointRow({ endpoint }: { endpoint: Endpoint }) { return (
{endpoint.method} {endpoint.path} {endpoint.description}
); } export default function DevelopersPage() { const totalEndpoints = GROUPS.reduce((n, g) => n + g.endpoints.length, 0); return (

All endpoints live under /api and return JSON. Most read endpoints are open; a handful that touch your account need a token. Browse the groups below — each row shows the method, path, what it does and whether it needs authentication.

Most reads are open and need no credentials. Endpoints marked{" "} 🔒 Bearer {" "} require a personal access token sent in the request header:

Authorization: Bearer <your-token>

To get a token, sign in to the website and POST to{" "} /api/tokens — the plaintext token is returned{" "} once and never shown again, so store it safely. You can list and revoke your tokens at /api/me/tokens. These token-management endpoints are marked{" "} 🔒 Session {" "} because they use your signed-in web session rather than a Bearer token.

Tokens are tied to your account: Bearer endpoints only ever return or modify your own data (your tickets, your shouts, your DJ points).

{GROUPS.map((group) => (
{group.endpoints.map((endpoint) => ( ))}
))}
); }