{"openapi":"3.0.3","info":{"title":"ArenaForge API","version":"0.1.0","description":"REST API for ArenaForge — an independent gaming creator & community platform. Not affiliated with any game publisher. All endpoints return { success, data, meta } or { success: false, error: { code, message } }. Authentication uses HTTP-only cookies (short-lived JWT access token + rotating opaque refresh token).","contact":{"name":"ArenaForge","url":"https://arenaforge.local"},"license":{"name":"Proprietary"}},"servers":[{"url":"/api/v1","description":"API v1"}],"tags":[{"name":"Health","description":"Service health and readiness"},{"name":"Auth","description":"Registration, verification, login, refresh rotation, password reset"},{"name":"Users","description":"Own profile management"},{"name":"Search","description":"Cross-entity search"},{"name":"Posts","description":"Post discovery (creation lands with the content phase)"},{"name":"Videos","description":"Video discovery"},{"name":"Maps","description":"Community map discovery + trending"},{"name":"Creators","description":"Creator directory"},{"name":"Featured","description":"Admin-curated featured content"},{"name":"Academy","description":"Published courses"},{"name":"Challenges","description":"Active challenges"},{"name":"Events","description":"Upcoming events"},{"name":"News","description":"Published news articles"},{"name":"Notifications","description":"User notifications"}],"components":{"securitySchemes":{"cookieAuth":{"type":"apiKey","in":"cookie","name":"af_at"}},"schemas":{"Error":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string","example":"Invalid request"},"details":{}}}}},"PaginationMeta":{"type":"object","properties":{"limit":{"type":"integer"},"nextCursor":{"type":"string","nullable":true},"hasMore":{"type":"boolean"}}}}},"paths":{"/health":{"get":{"tags":["Health"],"summary":"Liveness probe","security":[],"responses":{"200":{"description":"Service is up"}}}},"/ready":{"get":{"tags":["Health"],"summary":"Readiness probe (database + redis checks)","security":[],"responses":{"200":{"description":"Ready"},"503":{"description":"Degraded"}}}},"/auth/register":{"post":{"tags":["Auth"],"summary":"Create an account (email + username uniqueness, PUBG UID optional)","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password","confirmPassword","username","displayName","acceptTerms","acceptGuidelines"],"properties":{"email":{"type":"string","format":"email","description":"OPTIONAL. Accounts work without email; providing one requires verification before login."},"password":{"type":"string","minLength":12},"confirmPassword":{"type":"string"},"username":{"type":"string","pattern":"^[a-z0-9_]{3,20}$"},"displayName":{"type":"string"},"pubgUid":{"type":"string","pattern":"^\\d{5,12}$","description":"Identifier only — the platform never asks for game passwords"},"pubgUsername":{"type":"string"},"country":{"type":"string","minLength":2,"maxLength":2},"language":{"type":"string","default":"en"},"acceptTerms":{"type":"boolean","enum":[true]},"acceptGuidelines":{"type":"boolean","enum":[true]}}}}}},"responses":{"201":{"description":"Account created; verification email queued"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"EMAIL_TAKEN / USERNAME_TAKEN"},"429":{"description":"RATE_LIMITED"}}}},"/auth/login":{"post":{"tags":["Auth"],"summary":"Login with email or username; sets access + refresh cookies","security":[],"responses":{"200":{"description":"Logged in"},"401":{"description":"INVALID_CREDENTIALS"},"403":{"description":"EMAIL_NOT_VERIFIED / ACCOUNT_BANNED / ACCOUNT_SUSPENDED"}}}},"/auth/refresh":{"post":{"tags":["Auth"],"summary":"Rotate refresh token (reuse detection revokes the whole session family)","security":[],"responses":{"200":{"description":"New tokens set"},"401":{"description":"Invalid/expired/reused token"}}}},"/auth/logout":{"post":{"tags":["Auth"],"summary":"Revoke current session family and clear cookies","security":[],"responses":{"200":{"description":"Logged out"}}}},"/auth/verify-email":{"post":{"tags":["Auth"],"summary":"Consume an email verification token","security":[],"responses":{"200":{"description":"Verified"},"401":{"description":"INVALID_TOKEN"}}}},"/auth/resend-verification":{"post":{"tags":["Auth"],"summary":"Re-send verification email (uniform response, no enumeration)","security":[],"responses":{"200":{"description":"Queued"}}}},"/auth/forgot-password":{"post":{"tags":["Auth"],"summary":"Request a password reset link","security":[],"responses":{"200":{"description":"Always 200 (no enumeration)"}}}},"/auth/reset-password":{"post":{"tags":["Auth"],"summary":"Consume reset token, set new password, revoke all sessions","security":[],"responses":{"200":{"description":"Password reset"},"401":{"description":"INVALID_TOKEN"}}}},"/auth/me":{"get":{"tags":["Auth"],"summary":"Current user with profile + real counters","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"User"},"401":{"description":"AUTH_REQUIRED"}}}},"/users/me":{"patch":{"tags":["Users"],"summary":"Update own profile (displayName, bio, country, interests, socials, PUBG identifiers)","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Updated user"},"400":{"description":"VALIDATION_ERROR"},"401":{"description":"AUTH_REQUIRED"}}}},"/users/me/email":{"post":{"tags":["Users"],"summary":"Set or change account email (resets verification and emails a fresh token)","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"202":{"description":"Verification email sent; account unverified until the link is used"},"401":{"description":"AUTH_REQUIRED"},"409":{"description":"EMAIL_TAKEN"}}}},"/users/me/completeness":{"get":{"tags":["Users"],"summary":"Profile completeness from real fields","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Percent + checks"}}}},"/search":{"get":{"tags":["Search"],"summary":"Search users, creators, posts, videos, maps, courses, hashtags","security":[],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2}},{"name":"type","in":"query","schema":{"type":"string","enum":["all","users","creators","posts","videos","maps","courses","hashtags"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":5,"maximum":20}}],"responses":{"200":{"description":"Grouped results (real DB ILIKE search; OpenSearch-ready contract)"}}}},"/posts":{"get":{"tags":["Posts"],"summary":"Published posts, cursor-paginated (latest first)","security":[],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Posts page + PaginationMeta (empty array when none)"}}},"post":{"tags":["Posts"],"summary":"Create a post (text + up to 4 uploaded images + #hashtags)","security":[{"cookieAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":120},"content":{"type":"string","maxLength":5000},"visibility":{"type":"string","enum":["PUBLIC","FOLLOWERS","PRIVATE"]},"media":{"type":"array","maxItems":4,"items":{"type":"object"}}},"required":["content"]}}}},"responses":{"201":{"description":"Post created"},"401":{"description":"AUTH_REQUIRED"}}}},"/videos":{"get":{"tags":["Videos"],"summary":"Published videos, cursor-paginated","security":[],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Videos page"}}}},"/maps":{"get":{"tags":["Maps"],"summary":"Published maps with sort=latest|popular|trending (time-decay score)","security":[],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"sort","in":"query","schema":{"type":"string","enum":["latest","popular","trending"]}},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"difficulty","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Maps page; trending is computed from real counters with recency decay"}}}},"/creators":{"get":{"tags":["Creators"],"summary":"Approved creator directory (real users only)","security":[],"responses":{"200":{"description":"Creators page"}}}},"/featured":{"get":{"tags":["Featured"],"summary":"Active admin-curated featured content","security":[],"responses":{"200":{"description":"Featured items"}}}},"/courses":{"get":{"tags":["Academy"],"summary":"Published academy courses","security":[],"responses":{"200":{"description":"Courses"}}}},"/challenges":{"get":{"tags":["Challenges"],"summary":"Active/upcoming challenges","security":[],"responses":{"200":{"description":"Challenges"}}}},"/events":{"get":{"tags":["Events"],"summary":"Upcoming/active events","security":[],"responses":{"200":{"description":"Events"}}}},"/news":{"get":{"tags":["News"],"summary":"Published news articles","security":[],"responses":{"200":{"description":"Articles"}}}},"/notifications":{"get":{"tags":["Notifications"],"summary":"Own notifications (cursor paginated, unread count)","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Notifications"},"401":{"description":"AUTH_REQUIRED"}}}},"/uploads/images":{"post":{"tags":["Media"],"summary":"Upload an image (jpg/png/webp/gif, 8 MB max, magic-byte validated)","security":[{"cookieAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Stored media metadata (storageKey, url, dimensions)"},"401":{"description":"AUTH_REQUIRED"},"413":{"description":"FILE_TOO_LARGE"}}}},"/uploads/videos":{"post":{"tags":["Media"],"summary":"Upload a video (mp4/mov/webm/mkv/avi, 512 MB max) — creates a Video and runs the FFmpeg pipeline","security":[{"cookieAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"title":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","enum":["GAMEPLAY","HIGHLIGHTS","TUTORIAL","MONTAGE","REVIEW","GUIDE","PODCAST","ENTERTAINMENT","OTHER"]}},"required":["file","title"]}}}},"responses":{"201":{"description":"Video created (PROCESSING -> PUBLISHED)"},"401":{"description":"AUTH_REQUIRED"},"413":{"description":"FILE_TOO_LARGE"}}}},"/posts/{publicId}":{"get":{"tags":["Posts"],"summary":"Post detail with media, hashtags and viewer like/save state","security":[],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Post"},"404":{"description":"POST_NOT_FOUND"}}}},"/posts/{publicId}/like":{"post":{"tags":["Posts"],"summary":"Like a post (unique per user, transactional counter)","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{liked, likeCount}"},"409":{"description":"CONFLICT already liked"}}},"delete":{"tags":["Posts"],"summary":"Remove like","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{liked:false, likeCount}"}}}},"/posts/{publicId}/save":{"post":{"tags":["Posts"],"summary":"Save/bookmark a post","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{saved, saveCount}"},"409":{"description":"CONFLICT already saved"}}},"delete":{"tags":["Posts"],"summary":"Remove save","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{saved:false}"}}}},"/posts/{publicId}/share":{"post":{"tags":["Posts"],"summary":"Record a share (channel: copy/whatsapp/telegram/discord/facebook/x/native)","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{shared, shareCount}"}}}},"/posts/{publicId}/comments":{"get":{"tags":["Comments"],"summary":"Comment tree (2 levels) with viewer like state","security":[],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Comments"}}},"post":{"tags":["Comments"],"summary":"Comment or reply (parentId)","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Comment created"}}}},"/comments/{publicId}/like":{"post":{"tags":["Comments"],"summary":"Like a comment","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{liked, likeCount}"},"409":{"description":"CONFLICT"}}}},"/comments/{publicId}":{"delete":{"tags":["Comments"],"summary":"Soft-delete own comment (staff can delete any)","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{deleted:true}"},"403":{"description":"FORBIDDEN"}}}},"/videos/{publicId}":{"get":{"tags":["Videos"],"summary":"Video detail: playable URL, thumbnail, duration, related, viewer state","security":[],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Video"},"404":{"description":"VIDEO_NOT_FOUND"}}}},"/videos/{publicId}/view":{"post":{"tags":["Videos"],"summary":"Record a view (one per logged-in user per video; anonymous views counted)","security":[],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{counted, viewCount}"}}}},"/videos/{publicId}/like":{"post":{"tags":["Videos"],"summary":"Like a video","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{liked, likeCount}"},"409":{"description":"CONFLICT"}}},"delete":{"tags":["Videos"],"summary":"Remove like","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/videos/{publicId}/save":{"post":{"tags":["Videos"],"summary":"Save a video","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{saved, saveCount}"},"409":{"description":"CONFLICT"}}},"delete":{"tags":["Videos"],"summary":"Remove save","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/videos/{publicId}/share":{"post":{"tags":["Videos"],"summary":"Record a share","security":[{"cookieAuth":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{shared, shareCount}"}}}},"/notifications/{id}/read":{"patch":{"tags":["Notifications"],"summary":"Mark a notification read (ownership enforced)","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated count"},"401":{"description":"AUTH_REQUIRED"}}}}}}