API Reference
Overview
The platform exposes two independent APIs:
| API | Base Path | Auth | Consumers |
|---|---|---|---|
| Public API | /api/v1 | Anonymous (fingerprint/session) | Player-facing frontend |
| Admin API | /api/admin | JWT / session token | Admin dashboard |
All endpoints return JSON. Errors use standard HTTP status codes with a { "error": "message" } body.
Public API
Games
GET /api/v1/games
List games for the Home page, sorted by rank.
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 40 | Items per page (max 100) |
platform | string | auto-detected | desktop or mobile |
Response
{ "games": [ { "id": "abc123", "title": "Game Title", "slug": "game-title", "thumbnailUrl": "https://...", "authorName": "Studio Name", "rank": 1, "tags": ["new", "trendy"], "isExploration": false } ], "pagination": { "page": 1, "limit": 40, "total": 1200, "hasMore": true }}Notes:
- Response includes both Ranked and Exploration games (90/10 split handled server-side)
- Exploration games are interspersed at fixed intervals
GET /api/v1/games/:slug
Get full game details and player page data.
Response
{ "game": { "id": "abc123", "title": "Game Title", "slug": "game-title", "description": "Full description...", "instructions": "How to play...", "thumbnailUrl": "https://...", "iframeUrl": "https://...", "authorId": "author456", "authorName": "Studio Name", "authorSlug": "studio-name", "categories": [ { "id": "cat1", "name": "Action", "slug": "action" } ], "tags": ["new"], "rating": { "score": 42, "likes": 58, "dislikes": 16 }, "rank": 15 }, "recommendations": { "side": [ { "id": "...", "title": "...", "slug": "...", "thumbnailUrl": "..." } ], "bottom": [ { "id": "...", "title": "...", "slug": "...", "thumbnailUrl": "..." } ], "categories": [ { "id": "...", "name": "Puzzle", "slug": "puzzle", "iconUrl": "..." } ] }, "authorGames": [ { "id": "...", "title": "...", "slug": "...", "thumbnailUrl": "..." } ]}Events triggered: game_page_view (server-side)
Categories
GET /api/v1/categories
List all categories.
Response
{ "categories": [ { "id": "cat1", "name": "Action", "slug": "action", "iconUrl": "https://...", "gameCount": 245 } ]}GET /api/v1/categories/:slug/games
List games in a category, sorted by rank.
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 40 | Items per page |
platform | string | auto | desktop or mobile |
Response: Same shape as GET /api/v1/games with category context.
Authors
GET /api/v1/authors/:slug/games
List all games by an author, sorted by rank.
Query Parameters: Same as games list (page, limit, platform).
Response
{ "author": { "id": "author456", "name": "Studio Name", "slug": "studio-name" }, "games": [...], "pagination": {...}}Note: No exploration slots on author pages.
Search
GET /api/v1/search
Search across games, categories, and authors.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Query (min 2 characters) |
type | string | No | Filter: games, categories, authors. Default: all |
limit | number | No | Max results per type (default 5) |
Response
{ "games": [ { "id": "...", "title": "...", "slug": "...", "thumbnailUrl": "..." } ], "categories": [ { "id": "...", "name": "...", "slug": "...", "iconUrl": "..." } ], "authors": [ { "id": "...", "name": "..." } ]}Events
POST /api/v1/events
Submit one or more tracking events.
Request
{ "events": [ { "type": "game_click", "gameId": "abc123", "timestamp": "2026-02-10T14:30:00.000Z", "context": { "surface": "home", "position": 5, "platform": "desktop" } }, { "type": "gameplay_start", "gameId": "abc123", "timestamp": "2026-02-10T14:30:05.000Z" } ], "sessionId": "sess_xyz"}Supported Event Types
See Data Model → Event Catalog for the full event list with descriptions. The following types are accepted by this endpoint:
game_click, game_loading_start, game_loading_end, game_focused_start, game_focused_stop, gameplay_start, gameplay_stop, category_click, show_ad
Note:
game_like/game_dislikeandgame_impression/game_page_vieware not sent through this endpoint. Likes and dislikes use the Feedback endpoints. Impressions and page views are generated server-side.
Response
{ "accepted": 2 }Notes:
- Events can be batched (recommended: flush every 5s or on page unload)
game_impressionandgame_page_vieware generated server-side, not submitted by the client- Invalid events are silently dropped;
acceptedcount reflects valid events only
Feedback
POST /api/v1/games/:id/like
Like a game. Replaces a previous dislike if any.
Response
{ "rating": { "score": 43, "likes": 59, "dislikes": 16 } }POST /api/v1/games/:id/dislike
Dislike a game. Replaces a previous like if any.
DELETE /api/v1/games/:id/like
Remove a like or dislike.
Admin API
All admin endpoints require authentication via Authorization: Bearer <token> header.
Catalog
GET /api/admin/games
List games with full admin context. Supports all dashboard filters.
Query Parameters
| Param | Type | Description |
|---|---|---|
state | string | exploration, ranked |
visibility | string | draft, hidden, visible |
tags | string | Comma-separated: new,trendy |
surface | string | home, category |
category | string | Category slug |
platform | string | desktop, mobile |
timeRange | string | 24h, 7d, 30d, 6m, all, or custom |
from | ISO date | Start date (when timeRange=custom) |
to | ISO date | End date (when timeRange=custom) |
search | string | Search by title or author |
page | number | Page number |
limit | number | Items per page |
Response
{ "games": [ { "id": "abc123", "title": "Game Title", "thumbnailUrl": "https://...", "authorName": "Studio", "visibility": "visible", "state": "ranked", "tags": ["trendy"], "rank": 15, "delta": { "metric": "dau", "value": "+15%", "direction": "up" }, "quickMetrics": { "dau": 1240, "playtime": "4m32s", "ctr": 0.12, "retentionD1": 0.35 } } ], "pagination": {...}}POST /api/admin/games
Create a new game (Draft state).
Request
{ "title": "Game Title", "description": "...", "categories": ["cat1", "cat2"], "authorName": "Studio", "iframeUrl": "https://...", "iconSource": "https://..."}Response
{ "game": { "id": "abc123", "visibility": "draft", ... }, "requirementsChecklist": { "title": true, "description": true, "categories": true, "icon": true, "iframeSource": true, "allMet": true }}PATCH /api/admin/games/:id
Update game content, visibility, or settings.
Request (partial update)
{ "title": "New Title", "visibility": "visible", "categories": ["cat1", "cat3"]}Errors
| Status | Message |
|---|---|
| 400 | Cannot set visible: requirements not met |
| 404 | Game not found |
DELETE /api/admin/games/:id
Permanently delete a game and its data.
Bulk Operations
POST /api/admin/games/import
Bulk import games from a broker.
Request
{ "broker": "gamedistribution", "options": { "initialVisibility": "draft", "autoMapCategories": true }}Response
{ "imported": 342, "skipped": 12, "duplicates": 8, "errors": []}POST /api/admin/games/bulk
Apply actions to multiple games.
Request
{ "gameIds": ["abc123", "def456", "ghi789"], "action": "setVisibility", "value": "visible"}Supported Actions: setVisibility, disable, delete
Metrics
GET /api/admin/games/:id/metrics
Get full metrics for a single game.
Query Parameters
| Param | Type | Description |
|---|---|---|
timeRange | string | 24h, 7d, 30d, 6m, all, custom |
from / to | ISO date | Custom range |
platform | string | desktop, mobile, all |
surface | string | home, category, all |
Response
{ "engagement": { "plays": { "total": 15420, "dailyAvg": 2203 }, "dau": { "total": 8930, "dailyAvg": 1276 }, "impressions": { "total": 125000, "dailyAvg": 17857 }, "ctr": 0.123 }, "performance": { "playtime": { "avgPerPlayer": "4m32s" }, "engagedPlayers": 0.68, "conversion": 0.82, "retentionD1": 0.35, "retentionD7": 0.18, "returningUsers": 0.42, "rating": { "score": 42, "likes": 58, "dislikes": 16 }, "loadingTime": { "avgMs": 2340 } }, "monetization": { "adsShown": { "total": 4200, "perSession": 0.27 } }}Data Types
Game (Public)
interface GameCard { id: string title: string slug: string thumbnailUrl: string authorName: string rank: number | null tags: ('new' | 'trendy' | 'updated')[] isExploration: boolean}Game (Admin)
interface AdminGame extends GameCard { visibility: 'draft' | 'hidden' | 'visible' state: 'exploration' | 'ranked' iframeUrl: string description: string categories: Category[] broker: string | null createdAt: string updatedAt: string}TrackingEvent
interface TrackingEvent { type: EventType gameId?: string categoryId?: string timestamp: string context?: { surface?: 'home' | 'category' | 'game_page' position?: number platform?: 'desktop' | 'mobile' categorySlug?: string }}CORS Policy
| Origin | Allowed |
|---|---|
https://playupi.com | Full access (all methods) |
https://admin.playupi.com | Admin API only |
https://*.vercel.app | Preview deploys (dev/staging only) |
| All others | Blocked |
Access-Control-Allow-Origin: https://playupi.comAccess-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONSAccess-Control-Allow-Headers: Content-Type, AuthorizationAccess-Control-Max-Age: 86400The Public API (/api/v1) allows GET and POST from the main domain. The Admin API (/api/admin) restricts to the admin subdomain. Preview deploy origins are allowed only in non-production environments.
Error Format
All errors follow a consistent format:
{ "error": "Human-readable error message", "code": "GAME_NOT_FOUND", "details": {}}| Status | When |
|---|---|
| 400 | Invalid request (missing fields, bad format) |
| 401 | Missing or invalid auth token (admin API) |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 409 | Conflict (duplicate, state violation) |
| 429 | Rate limited |
| 500 | Internal server error |