{"components":{"schemas":{"ChatMessage":{"properties":{"filtered_body":{"type":"string"},"id":{"format":"uuid","type":"string"},"sender_id":{"format":"uuid","type":"string"},"sender_username":{"type":["string","null"]},"sent_at":{"format":"date-time","type":"string"}},"required":["id","sender_id","filtered_body","sent_at"],"type":"object"},"RegisterRequest":{"properties":{"email":{"format":"email","type":"string"},"password":{"minLength":8,"type":"string"},"referral_code":{"description":"Optional referral code","type":"string"},"username":{"type":"string"}},"required":["email","password","username"],"type":"object"},"TOTPSetupResponse":{"properties":{"secret":{"type":"string"},"uri":{"type":"string"}},"required":["secret","uri"],"type":"object"},"AuthResponse":{"properties":{"token":{"type":"string"},"user":{"properties":{"email":{"format":"email","type":"string"},"email_verified":{"type":"boolean"},"id":{"format":"uuid","type":"string"},"is_admin":{"type":"boolean"},"tier":{"type":"string"},"totp_enabled":{"type":"boolean"},"username":{"type":"string"}},"required":["id","email","username","tier","is_admin","email_verified","totp_enabled"],"type":"object"}},"required":["token","user"],"type":"object"},"OAuthAppPublic":{"description":"Simplified public view of an OAuth app — returned by the public lookup endpoint.","properties":{"client_id":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"}},"required":["name","client_id"],"type":"object"},"OAuthTokenResponse":{"properties":{"access_token":{"type":"string"},"expires_in":{"type":"integer"},"refresh_token":{"type":"string"},"session_token":{"description":"Short-lived session token — only present on authorization_code grants","type":"string"},"session_token_expires_at":{"description":"Expiry of the session token — only present on authorization_code grants","format":"date-time","type":"string"},"token_type":{"enum":["Bearer"],"type":"string"}},"required":["access_token","token_type","expires_in","refresh_token"],"type":"object"},"TOTPEnableResponse":{"properties":{"message":{"type":"string"},"recovery_codes":{"items":{"type":"string"},"type":"array"}},"required":["message","recovery_codes"],"type":"object"},"Friend":{"properties":{"email":{"format":"email","type":"string"},"id":{"format":"uuid","type":"string"},"username":{"type":"string"}},"required":["id","username","email"],"type":"object"},"TablePresenceEntry":{"properties":{"last_seen":{"format":"date-time","type":"string"},"status":{"enum":["online","offline"],"type":"string"},"user_id":{"format":"uuid","type":"string"}},"required":["user_id","status","last_seen"],"type":"object"},"AuthorizationCodeResponse":{"properties":{"code":{"type":"string"},"expires_in":{"description":"Always 600 seconds","type":"integer"},"redirect_uri":{"type":"string"}},"required":["code","redirect_uri","expires_in"],"type":"object"},"LoginRequest":{"properties":{"email":{"format":"email","type":"string"},"password":{"type":"string"},"totp_code":{"description":"Required if TOTP is enabled on the account","type":"string"}},"required":["email","password"],"type":"object"},"MeResponse":{"properties":{"user":{"properties":{"email":{"format":"email","type":"string"},"email_verified":{"type":"boolean"},"id":{"format":"uuid","type":"string"},"points":{"type":"integer"},"referral_code":{"type":"string"},"signup_multiplier":{"type":"integer"},"stats":{"properties":{"connected_apps":{"type":"integer"},"direct_referrals":{"type":"integer"},"total_earned":{"type":"integer"},"total_spent":{"type":"integer"}},"required":["direct_referrals","connected_apps","total_earned","total_spent"],"type":"object"},"tier":{"type":"string"},"tier_emoji":{"type":"string"},"totp_enabled":{"type":"boolean"},"username":{"type":"string"}},"required":["id","email","username","tier","tier_emoji","points","referral_code","signup_multiplier","email_verified","totp_enabled","stats"],"type":"object"}},"required":["user"],"type":"object"},"Error":{"properties":{"error":{"type":"string"}},"required":["error"],"type":"object"},"DeployTier":{"properties":{"ad_reach_multiplier":{"description":"Decimal value as string","type":"string"},"cpu_limit_millicores":{"type":"integer"},"custom_domain_allowed":{"type":"boolean"},"id":{"format":"uuid","type":"string"},"memory_limit_mb":{"type":"integer"},"monthly_cost_points":{"type":["integer","null"]},"monthly_cost_sats":{"type":["integer","null"]},"monthly_stipend_points":{"type":"integer"},"name":{"type":"string"},"payment_method":{"enum":["buddo_points","bitcoin"],"type":"string"},"slug":{"type":"string"}},"required":["id","name","slug","payment_method","memory_limit_mb","cpu_limit_millicores","custom_domain_allowed","monthly_stipend_points","ad_reach_multiplier"],"type":"object"},"FriendshipResponse":{"properties":{"friendship_id":{"format":"uuid","type":"string"},"status":{"enum":["pending","accepted","rejected","removed"],"type":"string"}},"required":["friendship_id","status"],"type":"object"},"OperatorAnalytics":{"properties":{"total_awards":{"type":"integer"},"total_spends":{"type":"integer"},"total_users":{"type":"integer"},"window_days":{"type":"integer"}},"required":["total_users","total_spends","total_awards","window_days"],"type":"object"},"Deployment":{"properties":{"env":{"type":["object","null"]},"id":{"format":"uuid","type":"string"},"image":{"type":"string"},"inserted_at":{"format":"date-time","type":"string"},"name":{"type":"string"},"port":{"type":["integer","null"]},"status":{"enum":["pending","running","stopped","failed","error"],"type":"string"},"tier":{"type":"string"},"updated_at":{"format":"date-time","type":"string"},"url":{"type":["string","null"]}},"required":["id","name","image","status","tier","inserted_at","updated_at"],"type":"object"},"PublicOperator":{"properties":{"app_description":{"type":["string","null"]},"app_name":{"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"logo_url":{"type":["string","null"]},"website_url":{"type":["string","null"]}},"required":["id","app_name","inserted_at"],"type":"object"},"UserSearchResult":{"properties":{"id":{"format":"uuid","type":"string"},"username":{"type":"string"}},"required":["id","username"],"type":"object"},"TokenRequest":{"properties":{"token":{"type":"string"}},"required":["token"],"type":"object"},"UserProfile":{"properties":{"user":{"description":"User profile object","type":"object"}},"required":["user"],"type":"object"},"TransferPointsResult":{"properties":{"amount_transferred":{"type":"integer"},"points":{"type":"integer"},"success":{"type":"boolean"}},"required":["success","points","amount_transferred"],"type":"object"},"ChatChannel":{"properties":{"id":{"format":"uuid","type":"string"},"ref_id":{"type":["string","null"]},"type":{"enum":["lobby","table"],"type":"string"}},"required":["id","type"],"type":"object"},"OAuthAppWithSecret":{"allOf":[{"$ref":"#/components/schemas/OAuthApp"},{"properties":{"client_secret":{"type":"string"}},"required":["client_secret"],"type":"object"}],"description":"OAuth app including client_secret — only returned on creation.","type":"object"},"DeployLog":{"properties":{"action":{"enum":["create","deploy","stop","restart","destroy","suspend","reactivate"],"type":"string"},"details":{"type":["object","null"]},"id":{"format":"uuid","type":"string"},"initiated_by":{"format":"uuid","type":["string","null"]},"inserted_at":{"format":"date-time","type":"string"},"status":{"type":"string"}},"required":["id","action","status","inserted_at"],"type":"object"},"TokenIntrospection":{"properties":{"active":{"type":"boolean"},"app_id":{"format":"uuid","type":"string"},"expires_at":{"format":"date-time","type":"string"},"reason":{"description":"Present when active is false (e.g. 'expired')","type":"string"},"scopes":{"items":{"type":"string"},"type":"array"},"user_id":{"format":"uuid","type":"string"}},"required":["active"],"type":"object"},"ConnectedApp":{"properties":{"app_description":{"type":["string","null"]},"app_id":{"format":"uuid","type":"string"},"app_name":{"type":"string"},"connected_at":{"format":"date-time","type":"string"},"last_used":{"format":"date-time","type":["string","null"]},"logo_url":{"type":["string","null"]},"scopes":{"items":{"type":"string"},"type":"array"}},"required":["app_id","app_name","scopes","connected_at"],"type":"object"},"ModuleStatus":{"properties":{"completed":{"type":["integer","null"]},"module_number":{"type":"integer"},"repeat_eligible_at":{"format":"date-time","type":["string","null"]},"status":{"enum":["incomplete","completed"],"type":"string"},"total":{"type":["integer","null"]}},"required":["status","module_number"],"type":"object"},"HealthResponse":{"properties":{"status":{"type":"string"},"version":{"type":"string"}},"required":["status","version"],"type":"object"},"OperatorApp":{"description":"Full operator app details including user count.","properties":{"allowed_scopes":{"items":{"type":"string"},"type":"array"},"app_description":{"type":["string","null"]},"app_name":{"type":"string"},"client_id":{"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"is_approved":{"type":"boolean"},"listed":{"type":"boolean"},"logo_url":{"type":["string","null"]},"redirect_uris":{"items":{"type":"string"},"type":"array"},"total_users":{"type":"integer"},"updated_at":{"format":"date-time","type":"string"},"website_url":{"type":["string","null"]}},"required":["id","app_name","client_id","redirect_uris","allowed_scopes","is_approved","listed","total_users","inserted_at","updated_at"],"type":"object"},"ErrorWithMessage":{"properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"type":"object"},"CreateDeploymentRequest":{"properties":{"custom_domain":{"description":"Optional custom domain for the deployment","type":"string"},"env_vars":{"additionalProperties":{"type":"string"},"description":"Environment variables (max 50 keys)","maxProperties":50,"type":"object"},"image":{"description":"Container image — must start with harbor.buddo.xyz:4443/, docker.io/library/, or ghcr.io/","type":"string"},"name":{"description":"Human-readable name for this deployment","maxLength":63,"pattern":"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$","type":"string"},"port":{"default":3000,"description":"Container port to expose (default 3000)","maximum":65535,"minimum":1,"type":"integer"},"subdomain":{"description":"Subdomain for the deployment (1-32 chars, lowercase alphanumeric and hyphens)","maxLength":32,"minLength":1,"pattern":"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$","type":"string"},"tier":{"description":"Deploy tier slug","type":"string"}},"required":["name","subdomain","image","tier"],"type":"object"},"PresenceEntry":{"properties":{"current_game":{"type":["string","null"]},"last_seen":{"format":"date-time","type":"string"},"lobby":{"type":["string","null"]},"status":{"enum":["online","offline"],"type":"string"}},"required":["status","last_seen"],"type":"object"},"OAuthError":{"description":"OAuth 2.0 error response per RFC 6749","properties":{"error":{"type":"string"},"error_description":{"type":"string"}},"required":["error"],"type":"object"},"PhoenixError":{"description":"Phoenix default error format — used by some endpoints.","properties":{"errors":{"properties":{"detail":{"type":"string"}},"required":["detail"],"type":"object"}},"required":["errors"],"type":"object"},"SpendPointsResult":{"properties":{"amount_spent":{"type":"integer"},"points":{"type":"integer"},"success":{"type":"boolean"}},"required":["success","points","amount_spent"],"type":"object"},"PointsBalance":{"properties":{"points":{"type":"integer"}},"required":["points"],"type":"object"},"AdEvent":{"properties":{"event":{"properties":{"campaign_id":{"format":"uuid","type":"string"},"event_type":{"enum":["impression","click"],"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"payout_rate_snapshot":{"type":"number"},"surface_type":{"type":"string"}},"required":["id","campaign_id","event_type","payout_rate_snapshot","surface_type","inserted_at"],"type":"object"}},"required":["event"],"type":"object"},"ScopeRequest":{"properties":{"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"requested_scopes":{"items":{"type":"string"},"type":"array"},"status":{"type":"string"}},"required":["id","status","requested_scopes","inserted_at"],"type":"object"},"RegisterResponse":{"properties":{"user":{"properties":{"email":{"format":"email","type":"string"},"email_verified":{"type":"boolean"},"id":{"format":"uuid","type":"string"},"points":{"type":"integer"},"referral_code":{"type":"string"},"registration_number":{"type":"integer"},"tier":{"type":"string"},"username":{"type":"string"}},"required":["id","email","username","tier","points","referral_code","email_verified","registration_number"],"type":"object"}},"required":["user"],"type":"object"},"OperatorAppUpdate":{"description":"Operator app details returned after update — excludes total_users.","properties":{"allowed_scopes":{"items":{"type":"string"},"type":"array"},"app_description":{"type":["string","null"]},"app_name":{"type":"string"},"client_id":{"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"is_approved":{"type":"boolean"},"listed":{"type":"boolean"},"logo_url":{"type":["string","null"]},"redirect_uris":{"items":{"type":"string"},"type":"array"},"updated_at":{"format":"date-time","type":"string"},"website_url":{"type":["string","null"]}},"required":["id","app_name","client_id","redirect_uris","allowed_scopes","is_approved","listed","inserted_at","updated_at"],"type":"object"},"PublicOperatorList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PublicOperator"},"type":"array"},"page":{"type":"integer"},"page_size":{"type":"integer"},"total":{"type":"integer"}},"required":["data","page","page_size","total"],"type":"object"},"SessionStatus":{"properties":{"is_alive":{"type":"boolean"},"last_heartbeat":{"format":"date-time","type":["string","null"]},"points_earned":{"type":["integer","null"]},"session_active":{"type":"boolean"},"session_id":{"type":["string","null"]}},"required":["session_active","session_id","points_earned","last_heartbeat","is_alive"],"type":"object"},"DeploymentStatus":{"description":"Lightweight deployment status — subset of full Deployment object.","properties":{"id":{"format":"uuid","type":"string"},"status":{"enum":["pending","running","stopped","failed","error"],"type":"string"},"url":{"type":["string","null"]}},"required":["id","status"],"type":"object"},"SessionRefreshResponse":{"properties":{"expires_at":{"format":"date-time","type":"string"},"session_token":{"type":"string"}},"required":["session_token","expires_at"],"type":"object"},"SessionEndResult":{"properties":{"message":{"type":"string"},"session_id":{"type":"string"}},"required":["message","session_id"],"type":"object"},"MessageResponse":{"properties":{"message":{"type":"string"}},"required":["message"],"type":"object"},"OAuthApp":{"properties":{"allowed_scopes":{"items":{"type":"string"},"type":"array"},"app_description":{"type":"string"},"app_name":{"type":"string"},"client_id":{"type":"string"},"id":{"format":"uuid","type":"string"},"inserted_at":{"format":"date-time","type":"string"},"is_approved":{"type":"boolean"},"logo_url":{"type":["string","null"]},"redirect_uris":{"items":{"type":"string"},"type":"array"},"website_url":{"type":["string","null"]}},"required":["id","app_name","client_id","redirect_uris","allowed_scopes","is_approved"],"type":"object"},"PendingFriendRequest":{"properties":{"friendship_id":{"format":"uuid","type":"string"},"id":{"format":"uuid","type":"string"},"sent_at":{"format":"date-time","type":"string"},"username":{"type":"string"}},"required":["id","friendship_id","username","sent_at"],"type":"object"},"LearningPathResponse":{"properties":{"learning_path":{"type":"string"},"status":{"type":"string"}},"required":["status","learning_path"],"type":"object"},"Campaign":{"properties":{"campaign":{"properties":{"bitcoin_tier":{"type":["string","null"]},"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"payout_rate":{"type":"number"},"surface_type":{"type":"string"},"targeting_metadata":{"type":["object","null"]}},"required":["id","name","surface_type","payout_rate"],"type":"object"}},"required":["campaign"],"type":"object"}},"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","description":"JWT token obtained from POST /api/auth/login","scheme":"bearer","type":"http"},"oauth2":{"flows":{"authorizationCode":{"authorizationUrl":"/api/oauth/authorize","scopes":{"app:balance:read":"Read the operator app's point balance","deploy:manage":"Deploy and manage hosted applications","points:award":"Deprecated — award is admin-only. Retained for existing token compatibility.","points:read":"Read user point balance","points:spend":"Spend user points (credits operator account)","points:transfer":"Transfer points between users","profile:read":"Read user profile and session information"},"tokenUrl":"/api/oauth/token","x-pkce-required":true}},"type":"oauth2"}}},"externalDocs":{"description":"Agent onboarding guide","url":"https://docs.buddocloud.com/AGENTS.md"},"info":{"description":"OAuth2-protected API for operator applications to manage user profiles, points, sessions, and ads on the Buddo platform.","title":"Buddo External API","version":"2.0.0"},"openapi":"3.1.0","paths":{"/api/social/users/search":{"get":{"description":"Returns up to 20 matching users. Excludes the current user and blocked users.","operationId":"searchUsers","parameters":[{"description":"Search query (minimum 2 characters)","in":"query","name":"q","required":true,"schema":{"minLength":2,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"users":{"items":{"$ref":"#/components/schemas/UserSearchResult"},"maxItems":20,"type":"array"}},"required":["users"],"type":"object"}}},"description":"Search results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Query too short (minimum 2 characters)"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Search users by username","tags":["Social"]}},"/api/external/points/spend":{"post":{"operationId":"spendPoints","requestBody":{"content":{"application/json":{"schema":{"properties":{"amount":{"minimum":1,"type":"integer"},"description":{"type":"string"}},"required":["amount"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpendPointsResult"}}},"description":"Points spent"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Insufficient balance or app account not found"}},"security":[{"oauth2":["points:spend"]}],"summary":"Spend user points, crediting the operator account","tags":["Points"]}},"/api/deploy/apps":{"get":{"description":"Returns all deployments owned by the authenticated operator. Rate limited to 30 requests per minute.","operationId":"listDeployments","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deployments":{"items":{"$ref":"#/components/schemas/Deployment"},"type":"array"}},"required":["deployments"],"type":"object"}}},"description":"List of deployments"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"List operator's deployments","tags":["Deploy"]},"post":{"description":"Creates a new container deployment on the specified tier. Rate limited to 10 requests per minute.","operationId":"createDeployment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeploymentRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"deployment":{"$ref":"#/components/schemas/Deployment"},"message":{"type":"string"}},"required":["deployment","message"],"type":"object"}}},"description":"Deployment created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Tier not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation failed (invalid subdomain, image, or tier)"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deployment limit reached for this tier/account"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"Create a new deployment","tags":["Deploy"]}},"/health":{"get":{"description":"Returns server health status and API version.","operationId":"healthCheck","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}},"description":"Server is healthy"}},"summary":"Health check","tags":["Infrastructure"]}},"/api/oauth/apps/{client_id}":{"get":{"description":"Returns public app information. No authentication required.","operationId":"getOAuthApp","parameters":[{"description":"The OAuth app's client_id","in":"path","name":"client_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthAppPublic"}}},"description":"Public app info"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App not found"}},"summary":"Get public app metadata by client_id","tags":["OAuth"]}},"/api/btc-progress/learning-path":{"post":{"description":"Sets the user's learning path. Cannot be changed after starting modules.","operationId":"setLearningPath","requestBody":{"content":{"application/json":{"schema":{"properties":{"path":{"description":"Learning path identifier","type":"string"}},"required":["path"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningPathResponse"}}},"description":"Learning path set"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid or missing path"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Cannot change learning path after starting modules"}},"security":[{"bearerAuth":[]}],"summary":"Set learning path","tags":["Education"]}},"/api/auth/reset-password":{"post":{"operationId":"resetPassword","requestBody":{"content":{"application/json":{"schema":{"properties":{"password":{"minLength":8,"type":"string"},"token":{"type":"string"}},"required":["token","password"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}},"description":"Password reset successful"},"400":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PhoenixError"}]}}},"description":"Invalid or expired token, or missing parameters"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Password too short (minimum 8 characters)"}},"summary":"Reset password using a reset token","tags":["Auth"]}},"/api/social/friends/pending":{"get":{"operationId":"listPendingFriendRequests","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PendingFriendRequest"},"type":"array"}}},"description":"List of pending friend requests"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"List pending friend requests to the current user","tags":["Social"]}},"/api/auth/totp/enable":{"post":{"description":"Requires a valid TOTP code from the authenticator app. Returns recovery codes on success — these should be stored securely by the user.","operationId":"enableTotp","requestBody":{"content":{"application/json":{"schema":{"properties":{"code":{"description":"6-digit TOTP code from authenticator app","type":"string"}},"required":["code"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TOTPEnableResponse"}}},"description":"TOTP enabled, recovery codes returned"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"TOTP setup not initiated or missing code parameter"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid TOTP code or unauthorized"}},"security":[{"bearerAuth":[]}],"summary":"Enable TOTP by confirming a valid code","tags":["Auth"]}},"/api/session/refresh":{"post":{"description":"Validates the current session token, sends a heartbeat, and returns a rotated token. The old token is invalidated atomically. Rate limited to 20 requests per minute per IP. Does not use JWT — authenticates via the session_token in the request body.","operationId":"refreshSession","requestBody":{"content":{"application/json":{"schema":{"properties":{"session_token":{"description":"The current session token to refresh","type":"string"}},"required":["session_token"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionRefreshResponse"}}},"description":"New session token issued"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing session_token parameter"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session expired or token invalid/already consumed"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Session not found (GenServer no longer running)"}},"summary":"Refresh a session token","tags":["Sessions"]}},"/api/external/ads/serve":{"get":{"operationId":"serveAd","parameters":[{"description":"The ad surface type to serve","in":"query","name":"surface_type","required":true,"schema":{"enum":["banner","video","interstitial","native","rewarded"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}},"description":"Campaign served"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorWithMessage"}}},"description":"Bad request — missing or invalid surface_type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorWithMessage"}}},"description":"No campaign available"}},"security":[{"oauth2":["profile:read"]}],"summary":"Serve an ad campaign for a given surface type","tags":["Ads"]}},"/api/external/points/transfer":{"post":{"operationId":"transferPoints","requestBody":{"content":{"application/json":{"schema":{"properties":{"amount":{"minimum":1,"type":"integer"},"description":{"type":"string"},"to_user_id":{"format":"uuid","type":"string"}},"required":["amount","to_user_id"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferPointsResult"}}},"description":"Points transferred"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Insufficient balance"}},"security":[{"oauth2":["points:transfer"]}],"summary":"Transfer points from the authenticated user to another user","tags":["Points"]}},"/api/auth/login":{"post":{"description":"Returns a JWT token on success. If the account has TOTP enabled and no totp_code is provided, returns 401 with totp_required: true. Rate limited to 10 requests per minute per IP.","operationId":"loginUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}},"description":"Login successful"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing email or password"},"401":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"},"totp_required":{"description":"Present and true when TOTP code is needed","type":"boolean"}},"required":["error"],"type":"object"}}},"description":"Invalid credentials or TOTP required"}},"summary":"Authenticate and receive a JWT token","tags":["Auth"]}},"/api/social/friends/{id}":{"delete":{"operationId":"removeFriend","parameters":[{"description":"Friendship ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"status":{"enum":["removed"],"type":"string"}},"required":["status"],"type":"object"}}},"description":"Friendship removed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not authorized to remove this friendship"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Friendship not found"}},"security":[{"bearerAuth":[]}],"summary":"Remove a friendship","tags":["Social"]},"put":{"operationId":"respondToFriendRequest","parameters":[{"description":"Friendship ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"action":{"enum":["accept","reject"],"type":"string"}},"required":["action"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"status":{"enum":["accepted","rejected"],"type":"string"}},"required":["status"],"type":"object"}}},"description":"Friend request accepted or rejected"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing or invalid action"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not authorized to respond to this request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Friend request not found"}},"security":[{"bearerAuth":[]}],"summary":"Accept or reject a friend request","tags":["Social"]}},"/api/btc-progress/{step_id}/complete":{"post":{"description":"Marks a learning step as completed. Returns reward amount if applicable, or indicates if the step was already completed.","operationId":"completeLearningStep","parameters":[{"description":"The learning step identifier","in":"path","name":"step_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"reward":{"type":["integer","null"]},"status":{"enum":["completed","already_completed"],"type":"string"},"step_id":{"type":"string"}},"required":["status","step_id"],"type":"object"}}},"description":"Step completion result"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Learning path not set or step not available"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Step not found"}},"security":[{"bearerAuth":[]}],"summary":"Complete a learning step","tags":["Education"]}},"/api/social/block":{"post":{"operationId":"blockUser","requestBody":{"content":{"application/json":{"schema":{"properties":{"user_id":{"format":"uuid","type":"string"}},"required":["user_id"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"status":{"enum":["blocked"],"type":"string"}},"required":["status"],"type":"object"}}},"description":"User blocked"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing user_id"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"User not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"User already blocked"}},"security":[{"bearerAuth":[]}],"summary":"Block a user","tags":["Social"]}},"/api/btc-progress/module/{module_number}/status":{"get":{"description":"Returns completion status for a specific learning module.","operationId":"getModuleStatus","parameters":[{"description":"The module number","in":"path","name":"module_number","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleStatus"}}},"description":"Module progress status"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Module not found"}},"security":[{"bearerAuth":[]}],"summary":"Get module progress","tags":["Education"]}},"/api/auth/register":{"post":{"description":"Creates a new user. Rate limited to 5 requests per hour per IP.","operationId":"registerUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"}}},"description":"User created"},"422":{"content":{"application/json":{"schema":{"properties":{"errors":{"description":"Field-level validation errors","type":"object"}},"required":["errors"],"type":"object"}}},"description":"Validation errors (duplicate email, weak password, etc.)"}},"summary":"Register a new user account","tags":["Auth"]}},"/api/deploy/apps/{id}/status":{"get":{"description":"Returns only status and health fields — optimized for polling. Rate limited to 30 requests per minute.","operationId":"getDeploymentStatus","parameters":[{"description":"Deployment ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deployment":{"$ref":"#/components/schemas/Deployment"}},"required":["deployment"],"type":"object"}}},"description":"Deployment status"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — deployment belongs to another operator"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deployment not found"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"Lightweight deployment status check","tags":["Deploy"]}},"/api/external/user":{"get":{"operationId":"getUser","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}},"description":"User profile"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"}},"security":[{"oauth2":["profile:read"]}],"summary":"Get authenticated user profile","tags":["User"]}},"/api/external/ads/event":{"post":{"operationId":"recordAdEvent","requestBody":{"content":{"application/json":{"schema":{"properties":{"campaign_id":{"format":"uuid","type":"string"},"event_type":{"enum":["impression","click"],"type":"string"},"surface_type":{"default":"banner","description":"Ad surface type (defaults to banner)","type":"string"}},"required":["campaign_id","event_type"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdEvent"}}},"description":"Event recorded"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Campaign not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Duplicate event"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Campaign exhausted or validation failed"}},"security":[{"oauth2":["profile:read"]}],"summary":"Record an ad impression or click event","tags":["Ads"]}},"/api/public/operators":{"get":{"description":"Returns a paginated list of approved, publicly listed operator apps. No authentication required.","operationId":"listPublicOperators","parameters":[{"description":"Page number (default 1)","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}},{"description":"Items per page (default 20, max 100)","in":"query","name":"page_size","required":false,"schema":{"default":20,"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOperatorList"}}},"description":"Paginated list of public operators"}},"summary":"List approved public operators","tags":["Public"]}},"/api/btc-progress/available-modules":{"get":{"description":"Returns the user's learning path and all available modules.","operationId":"listAvailableModules","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"learning_path":{"type":["string","null"]},"modules":{"items":{"type":"object"},"type":"array"}},"required":["modules"],"type":"object"}}},"description":"Available modules"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"List available modules","tags":["Education"]}},"/api/oauth/authorize":{"get":{"description":"Issues an authorization code for the given client_id. Requires PKCE with S256 method. The code is valid for 600 seconds.","operationId":"authorizeApp","parameters":[{"description":"The OAuth app's client_id","in":"query","name":"client_id","required":true,"schema":{"type":"string"}},{"description":"Must be 'code'","in":"query","name":"response_type","required":true,"schema":{"enum":["code"],"type":"string"}},{"description":"Must match one of the app's registered redirect_uris","in":"query","name":"redirect_uri","required":false,"schema":{"type":"string"}},{"description":"Opaque value for CSRF protection — returned unchanged","in":"query","name":"state","required":false,"schema":{"type":"string"}},{"description":"Space-separated scopes (must be subset of app's allowed_scopes)","in":"query","name":"scope","required":false,"schema":{"type":"string"}},{"description":"PKCE code challenge (BASE64URL(SHA256(code_verifier)))","in":"query","name":"code_challenge","required":true,"schema":{"type":"string"}},{"description":"Must be 'S256' — plain is not supported","in":"query","name":"code_challenge_method","required":true,"schema":{"enum":["S256"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizationCodeResponse"}}},"description":"Authorization code issued"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid request — wrong response_type, invalid client_id, invalid redirect_uri, invalid scopes, or code_challenge_method not S256"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App is not approved"},"422":{"content":{"application/json":{"schema":{"properties":{"errors":{"type":"object"}},"required":["errors"],"type":"object"}}},"description":"Validation errors"}},"security":[{"bearerAuth":[]}],"summary":"Request an authorization code","tags":["OAuth"]}},"/api/btc-progress/module/{module_number}/reset":{"post":{"description":"Resets a completed module so it can be repeated. Only available after a cooldown period.","operationId":"resetModule","parameters":[{"description":"The module number","in":"path","name":"module_number","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"module_number":{"type":"integer"},"status":{"enum":["reset"],"type":"string"}},"required":["status","module_number"],"type":"object"}}},"description":"Module reset"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Module not completed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not eligible for repeat yet"}},"security":[{"bearerAuth":[]}],"summary":"Reset a completed module","tags":["Education"]}},"/api/auth/me":{"get":{"operationId":"getCurrentUser","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}},"description":"User profile with stats"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Get the authenticated user's full profile","tags":["Auth"]}},"/api/oauth/token":{"post":{"description":"Supports two grant types: 'authorization_code' (with PKCE code_verifier for public clients or client_secret for confidential clients) and 'refresh_token'. Authorization code grants also return a session_token for lightweight session maintenance.","operationId":"exchangeToken","requestBody":{"content":{"application/json":{"schema":{"discriminator":{"propertyName":"grant_type"},"oneOf":[{"properties":{"client_id":{"type":"string"},"client_secret":{"description":"Required for confidential clients (alternative to code_verifier)","type":"string"},"code":{"description":"The authorization code received from /api/oauth/authorize","type":"string"},"code_verifier":{"description":"PKCE code verifier — required for public clients (no client_secret)","type":"string"},"grant_type":{"enum":["authorization_code"],"type":"string"},"redirect_uri":{"description":"Must match the redirect_uri used in the authorize request","type":"string"}},"required":["grant_type","code","redirect_uri","client_id"],"title":"Authorization Code Grant","type":"object"},{"properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"grant_type":{"enum":["refresh_token"],"type":"string"},"refresh_token":{"type":"string"}},"required":["grant_type","refresh_token","client_id","client_secret"],"title":"Refresh Token Grant","type":"object"}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}},"description":"Token exchange successful"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}},"description":"Invalid request — see error and error_description fields for details. Possible error codes: invalid_client, invalid_grant (code expired, already used, invalid redirect_uri, invalid code_verifier), invalid_request (PKCE required)"}},"summary":"Exchange an authorization code or refresh token for access tokens","tags":["OAuth"]}},"/api/oauth/my-apps/{id}/scope-request":{"post":{"description":"Submits a request to expand the app's allowed scopes. Requires admin approval.","operationId":"requestAdditionalScopes","parameters":[{"description":"The app's internal ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"requested_scopes":{"description":"Scopes to add to the app's allowed_scopes","items":{"type":"string"},"type":"array"}},"required":["requested_scopes"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScopeRequest"}}},"description":"Scope request created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"You do not own this app"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid scopes, already granted, or validation error"}},"security":[{"bearerAuth":[]}],"summary":"Request additional scopes for an owned app","tags":["OAuth"]}},"/api/oauth/apps":{"post":{"description":"Creates a new OAuth app. Returns the client_secret — this is the only time it is shown. Rate limited to 3 requests per hour.","operationId":"createOAuthApp","requestBody":{"content":{"application/json":{"schema":{"properties":{"allowed_scopes":{"items":{"type":"string"},"type":"array"},"app_description":{"type":"string"},"app_name":{"type":"string"},"logo_url":{"type":"string"},"redirect_uris":{"items":{"type":"string"},"type":"array"},"website_url":{"type":"string"}},"required":["app_name","redirect_uris"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"app":{"$ref":"#/components/schemas/OAuthAppWithSecret"}},"required":["app"],"type":"object"}}},"description":"App created — client_secret included in response (shown only once)"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"422":{"content":{"application/json":{"schema":{"properties":{"errors":{"type":"object"}},"required":["errors"],"type":"object"}}},"description":"Validation errors"}},"security":[{"bearerAuth":[]}],"summary":"Register a new OAuth app","tags":["OAuth"]}},"/api/auth/totp/verify":{"post":{"operationId":"verifyTotp","requestBody":{"content":{"application/json":{"schema":{"properties":{"code":{"description":"6-digit TOTP code or recovery code","type":"string"}},"required":["code"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}},"description":"TOTP verified"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"TOTP not enabled or missing code parameter"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid TOTP or recovery code"}},"security":[{"bearerAuth":[]}],"summary":"Verify a TOTP or recovery code","tags":["Auth"]}},"/api/oauth/my-apps/{id}":{"put":{"operationId":"updateMyApp","parameters":[{"description":"The app's internal ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"allowed_scopes":{"items":{"type":"string"},"type":"array"},"app_description":{"type":"string"},"app_name":{"type":"string"},"logo_url":{"type":"string"},"redirect_uris":{"items":{"type":"string"},"type":"array"},"website_url":{"type":"string"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthApp"}}},"description":"App updated"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"You do not own this app"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App not found"},"422":{"content":{"application/json":{"schema":{"properties":{"errors":{"type":"object"}},"required":["errors"],"type":"object"}}},"description":"Validation errors"}},"security":[{"bearerAuth":[]}],"summary":"Update an OAuth app owned by the authenticated user","tags":["OAuth"]}},"/.well-known/oauth-protected-resource":{"get":{"description":"Returns a JSON document describing this resource server's capabilities, supported scopes, and associated authorization server endpoints.","operationId":"oauthProtectedResourceDiscovery","responses":{"200":{"content":{"application/json":{"schema":{"description":"RFC 9728 protected resource metadata document","type":"object"}}},"description":"OAuth Protected Resource metadata"}},"summary":"RFC 9728 OAuth Protected Resource discovery","tags":["Infrastructure"]}},"/api/social/presence/table":{"get":{"description":"Returns presence status for all users at a specific game table.","operationId":"getTablePresence","parameters":[{"description":"Game identifier","in":"query","name":"game","required":true,"schema":{"type":"string"}},{"description":"Table/room reference ID","in":"query","name":"ref_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"users":{"items":{"$ref":"#/components/schemas/TablePresenceEntry"},"type":"array"}},"required":["users"],"type":"object"}}},"description":"Table presence list"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing game or ref_id parameter"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Get presence for a game table","tags":["Social"]}},"/api/social/block/{user_id}":{"delete":{"operationId":"unblockUser","parameters":[{"description":"ID of the user to unblock","in":"path","name":"user_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"status":{"enum":["unblocked"],"type":"string"}},"required":["status"],"type":"object"}}},"description":"User unblocked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Block record not found"}},"security":[{"bearerAuth":[]}],"summary":"Unblock a user","tags":["Social"]}},"/api/deploy/apps/{id}/restart":{"post":{"description":"Restarts the deployment's container. Rate limited to 10 requests per minute.","operationId":"restartDeployment","parameters":[{"description":"Deployment ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deployment":{"$ref":"#/components/schemas/Deployment"}},"required":["deployment"],"type":"object"}}},"description":"Deployment restarted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — deployment belongs to another operator"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deployment not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Restart failed (e.g. deployment is destroyed)"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"Restart a deployment","tags":["Deploy"]}},"/api/auth/self-verify-email":{"post":{"description":"Agents cannot check email inboxes. This endpoint allows any authenticated user to mark their email as verified by presenting a valid JWT. JWT possession proves account ownership. The regular token-based flow (POST /api/auth/verify-email) remains available for human users. Agent flow: register → login → self-verify-email.","operationId":"selfVerifyEmail","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}},"description":"Email verified successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Email already verified"}},"security":[{"bearerAuth":[]}],"summary":"Mark email as verified using a valid JWT (agent-friendly path)","tags":["Auth"]}},"/api/external/session/status":{"get":{"operationId":"getSessionStatus","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStatus"}}},"description":"Session status"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"}},"security":[{"oauth2":["profile:read"]}],"summary":"Check whether the user has an active session with this app","tags":["Sessions"]}},"/api/operator/analytics":{"get":{"description":"Returns aggregate analytics for the operator's app over the default 30-day window.","operationId":"getOperatorAnalytics","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"analytics":{"$ref":"#/components/schemas/OperatorAnalytics"}},"required":["analytics"],"type":"object"}}},"description":"Operator analytics"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — not the app owner"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App not found"}},"security":[{"oauth2":["app:balance:read"]}],"summary":"Get operator analytics","tags":["Operator"]}},"/api/auth/totp/setup":{"post":{"operationId":"setupTotp","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TOTPSetupResponse"}}},"description":"TOTP secret and URI for QR code"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"TOTP is already enabled"}},"security":[{"bearerAuth":[]}],"summary":"Generate a TOTP secret and provisioning URI","tags":["Auth"]}},"/api/social/chat/channels":{"post":{"description":"Creates a chat channel of the given type, or returns the existing one if it already exists.","operationId":"createChatChannel","requestBody":{"content":{"application/json":{"schema":{"properties":{"ref_id":{"description":"Optional reference ID for the channel","type":"string"},"type":{"enum":["lobby","table"],"type":"string"}},"required":["type"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatChannel"}}},"description":"Chat channel created or found"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing type parameter"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Create or ensure a chat channel","tags":["Social"]}},"/api/deploy/apps/{id}":{"delete":{"description":"Permanently destroys a deployment and its container. Rate limited to 10 requests per minute.","operationId":"destroyDeployment","parameters":[{"description":"Deployment ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"id":{"format":"uuid","type":"string"},"message":{"type":"string"}},"required":["message","id"],"type":"object"}}},"description":"Deployment destroyed"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — deployment belongs to another operator"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deployment not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Destroy failed"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"Destroy a deployment","tags":["Deploy"]},"get":{"description":"Returns full deployment details. Rate limited to 30 requests per minute.","operationId":"getDeployment","parameters":[{"description":"Deployment ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deployment":{"$ref":"#/components/schemas/Deployment"}},"required":["deployment"],"type":"object"}}},"description":"Deployment details"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — deployment belongs to another operator"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deployment not found"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"Get a single deployment","tags":["Deploy"]}},"/api/auth/forgot-password":{"post":{"description":"Always returns 200 regardless of whether the email exists, to prevent enumeration. Rate limited to 5 requests per hour per IP.","operationId":"forgotPassword","requestBody":{"content":{"application/json":{"schema":{"properties":{"email":{"format":"email","type":"string"}},"required":["email"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}},"description":"Reset link sent (if email exists)"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing email parameter"}},"summary":"Request a password reset link","tags":["Auth"]}},"/api/external/session/end":{"post":{"operationId":"endSession","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionEndResult"}}},"description":"Session ended"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No active session"}},"security":[{"oauth2":["profile:read"]}],"summary":"End the user's active session with this app","tags":["Sessions"]}},"/api/external/points":{"get":{"operationId":"getPoints","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PointsBalance"}}},"description":"Point balance"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"}},"security":[{"oauth2":["points:read"]}],"summary":"Get user point balance","tags":["Points"]}},"/api/oauth/my-apps":{"get":{"operationId":"listMyApps","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/OAuthApp"},"type":"array"}}},"description":"List of owned apps"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"List OAuth apps owned by the authenticated user","tags":["OAuth"]}},"/api/oauth/token/verify":{"post":{"description":"Returns token validity and metadata. Always returns 200 — check the 'active' field. No authentication required.","operationId":"verifyToken","requestBody":{"content":{"application/json":{"schema":{"properties":{"token":{"description":"The access token to verify","type":"string"}},"required":["token"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenIntrospection"}}},"description":"Token introspection result (active: true/false)"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing token parameter"}},"summary":"Introspect an access token","tags":["OAuth"]}},"/api/deploy/apps/{id}/stop":{"post":{"description":"Stops the deployment's container. Rate limited to 10 requests per minute.","operationId":"stopDeployment","parameters":[{"description":"Deployment ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deployment":{"$ref":"#/components/schemas/Deployment"}},"required":["deployment"],"type":"object"}}},"description":"Deployment stopped"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — deployment belongs to another operator"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deployment not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Stop failed (e.g. already stopped or destroyed)"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"Stop a running deployment","tags":["Deploy"]}},"/api/social/friends":{"get":{"operationId":"listFriends","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Friend"},"type":"array"}}},"description":"List of friends"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"List current user's friends","tags":["Social"]},"post":{"operationId":"sendFriendRequest","requestBody":{"content":{"application/json":{"schema":{"properties":{"friend_id":{"format":"uuid","type":"string"}},"required":["friend_id"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FriendshipResponse"}}},"description":"Friend request sent"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing friend_id"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Blocked by target user"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"User not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Already friends or request already sent"}},"security":[{"bearerAuth":[]}],"summary":"Send a friend request","tags":["Social"]}},"/api/auth/verify-email":{"post":{"operationId":"verifyEmail","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}},"description":"Email verified"},"400":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PhoenixError"}]}}},"description":"Invalid or expired token"}},"summary":"Verify a user's email address with a token","tags":["Auth"]}},"/api/operator/app":{"get":{"description":"Returns full app details including total user count. Requires the requesting OAuth token to belong to the app owner.","operationId":"getOperatorApp","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"app":{"$ref":"#/components/schemas/OperatorApp"}},"required":["app"],"type":"object"}}},"description":"Operator app details"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — not the app owner"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App not found"}},"security":[{"oauth2":["app:balance:read"]}],"summary":"Get operator's app details","tags":["Operator"]},"put":{"description":"Updates app metadata. All fields are optional. Returns the updated app without total_users.","operationId":"updateOperatorApp","requestBody":{"content":{"application/json":{"schema":{"properties":{"app_description":{"type":"string"},"app_name":{"type":"string"},"redirect_uris":{"items":{"type":"string"},"type":"array"},"website_url":{"type":"string"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"app":{"$ref":"#/components/schemas/OperatorAppUpdate"}},"required":["app"],"type":"object"}}},"description":"App updated"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — not the app owner"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App not found"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation failed"}},"security":[{"oauth2":["app:balance:read"]}],"summary":"Update operator's app","tags":["Operator"]}},"/api/deploy/apps/{id}/logs":{"get":{"description":"Returns the audit log of actions performed on a deployment. Rate limited to 30 requests per minute.","operationId":"getDeploymentLogs","parameters":[{"description":"Deployment ID","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"logs":{"items":{"$ref":"#/components/schemas/DeployLog"},"type":"array"}},"required":["logs"],"type":"object"}}},"description":"Deployment action logs"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Forbidden — deployment belongs to another operator"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Deployment not found"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"Get deployment action logs","tags":["Deploy"]}},"/api/user/connected-apps":{"get":{"description":"Returns all OAuth apps the authenticated user has authorized.","operationId":"listConnectedApps","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"connected_apps":{"items":{"$ref":"#/components/schemas/ConnectedApp"},"type":"array"}},"required":["connected_apps"],"type":"object"}}},"description":"List of connected apps"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"List user's connected OAuth apps","tags":["User"]}},"/api/deploy/tiers":{"get":{"description":"Returns all deployment tiers with pricing and resource limits. Rate limited to 30 requests per minute.","operationId":"listDeployTiers","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"tiers":{"items":{"$ref":"#/components/schemas/DeployTier"},"type":"array"}},"required":["tiers"],"type":"object"}}},"description":"List of deployment tiers"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid OAuth token"}},"security":[{"oauth2":["deploy:manage"]}],"summary":"List available deployment tiers","tags":["Deploy"]}},"/api/auth/send-verification":{"post":{"operationId":"sendVerificationEmail","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}},"description":"Verification email sent"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Email already verified"}},"security":[{"bearerAuth":[]}],"summary":"Resend the email verification link","tags":["Auth"]}},"/api/user/connected-apps/{app_id}":{"delete":{"description":"Revokes all OAuth tokens for the specified app and removes the connection.","operationId":"revokeConnectedApp","parameters":[{"description":"The app's ID to revoke","in":"path","name":"app_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}},"description":"App access revoked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Connected app not found"}},"security":[{"bearerAuth":[]}],"summary":"Revoke app access","tags":["User"]}},"/api/external/app/balance":{"get":{"operationId":"getAppBalance","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PointsBalance"}}},"description":"App point balance"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"App account not found"}},"security":[{"oauth2":["app:balance:read"]}],"summary":"Get the operator app's earned-point balance","tags":["App"]}},"/api/social/chat/{channel_id}/messages":{"get":{"description":"Returns messages in reverse chronological order. Supports cursor-based pagination via the 'before' parameter.","operationId":"getChatMessages","parameters":[{"description":"Chat channel ID","in":"path","name":"channel_id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Number of messages to return (default 50)","in":"query","name":"limit","required":false,"schema":{"default":50,"type":"integer"}},{"description":"Return messages before this timestamp (ISO 8601)","in":"query","name":"before","required":false,"schema":{"format":"date-time","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ChatMessage"},"type":"array"}}},"description":"List of chat messages"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Get messages from a chat channel","tags":["Social"]},"post":{"operationId":"sendChatMessage","parameters":[{"description":"Chat channel ID","in":"path","name":"channel_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"body":{"maxLength":1000,"type":"string"}},"required":["body"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatMessage"}}},"description":"Message sent"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing body"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid message (too long, profanity filter, etc.)"}},"security":[{"bearerAuth":[]}],"summary":"Send a message to a chat channel","tags":["Social"]}},"/api/social/presence":{"delete":{"description":"Removes the current user's presence record.","operationId":"untrackPresence","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"status":{"enum":["ok"],"type":"string"}},"required":["status"],"type":"object"}}},"description":"Presence untracked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Untrack user presence","tags":["Social"]},"get":{"description":"Returns presence status for the current user's friends. Optionally filter by specific user IDs.","operationId":"getFriendsPresence","parameters":[{"description":"Comma-separated UUIDs to filter by specific users","in":"query","name":"user_ids","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"$ref":"#/components/schemas/PresenceEntry"},"type":"object"}}},"description":"Map of user_id to presence entry"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Get friends' online presence","tags":["Social"]},"post":{"description":"Reports the current user as online, optionally in a specific game or lobby.","operationId":"trackPresence","requestBody":{"content":{"application/json":{"schema":{"properties":{"game":{"description":"Current game identifier","type":"string"},"lobby":{"description":"Current lobby identifier","type":"string"}},"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"status":{"enum":["ok"],"type":"string"}},"required":["status"],"type":"object"}}},"description":"Presence tracked"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unauthorized — missing or invalid JWT"}},"security":[{"bearerAuth":[]}],"summary":"Track user presence","tags":["Social"]}}},"servers":[{"description":"Production","url":"https://api.buddo.xyz"},{"description":"Development","url":"http://localhost:4000"}]}