{"openapi":"3.0.0","paths":{"/v1/health":{"get":{"operationId":"HealthController_check","summary":"Health check endpoint with dependency status","parameters":[],"responses":{"200":{"description":""}},"tags":["Health"]}},"/v1/auth/google":{"get":{"operationId":"AuthController_getGoogleAuthUrl","summary":"Get Google OAuth URL","parameters":[{"name":"callback_url","required":true,"in":"query","description":"Frontend callback URL after auth","schema":{"example":"http://localhost:3000/auth/callback","type":"string"}}],"responses":{"200":{"description":"Google OAuth URL returned"}},"tags":["Authentication"]}},"/v1/auth/google/callback":{"get":{"operationId":"AuthController_googleCallback","summary":"Google OAuth callback (browser redirect)","parameters":[{"name":"code","required":true,"in":"query","schema":{"type":"string"}},{"name":"state","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to frontend"}},"tags":["Authentication"]},"post":{"operationId":"AuthController_googleCallbackPost","summary":"Google OAuth callback (SPA POST — code + state in body)","parameters":[],"responses":{"200":{"description":"Auth successful — cookie set, user returned"},"400":{"description":"Missing code or state"},"401":{"description":"Invalid or expired state nonce"}},"tags":["Authentication"]}},"/v1/auth/logout":{"post":{"operationId":"AuthController_logout","summary":"Logout — invalidate current session and clear cookie","parameters":[],"responses":{"200":{"description":"Logged out successfully"}},"tags":["Authentication"]}},"/v1/auth/me":{"get":{"operationId":"AuthController_me","summary":"Get current authenticated user","parameters":[],"responses":{"200":{"description":"User profile returned"}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/v1/auth/token":{"get":{"operationId":"AuthController_getToken","summary":"Get current session token (for API clients)","parameters":[],"responses":{"200":{"description":""}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/v1/api-keys":{"post":{"operationId":"ApiKeysController_create","summary":"Create a new API key","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreatedResponseDto"}}}}},"tags":["API Keys"],"security":[{"bearer":[]}]},"get":{"operationId":"ApiKeysController_list","summary":"List all API keys (prefix only)","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyResponseDto"}}}}}},"tags":["API Keys"],"security":[{"bearer":[]}]}},"/v1/api-keys/{id}":{"delete":{"operationId":"ApiKeysController_revoke","summary":"Revoke an API key","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["API Keys"],"security":[{"bearer":[]}]}},"/v1/analytics/track":{"post":{"operationId":"AnalyticsController_track","summary":"Track an analytics event (authenticated)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackEventDto"}}}},"responses":{"200":{"description":""}},"tags":["Analytics"],"security":[{"bearer":[]}]}},"/v1/analytics/track-anonymous":{"post":{"operationId":"AnalyticsController_trackAnonymous","summary":"Track an anonymous analytics event (pre-signup funnel: ad_landing_view, etc.)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackAnonymousEventDto"}}}},"responses":{"200":{"description":""}},"tags":["Analytics"]}},"/v1/users/me":{"get":{"operationId":"UsersController_getProfile","summary":"Get current user profile","parameters":[],"responses":{"200":{"description":"User profile returned"}},"tags":["Users"],"security":[{"bearer":[]}]},"patch":{"operationId":"UsersController_updateProfile","summary":"Update current user profile","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"Profile updated"}},"tags":["Users"],"security":[{"bearer":[]}]}},"/v1/users/me/subscription":{"get":{"operationId":"UsersController_getSubscription","summary":"Get current user subscription status","parameters":[],"responses":{"200":{"description":"Subscription status returned"}},"tags":["Users"],"security":[{"bearer":[]}]}},"/v1/users/newsletter-unsubscribe":{"post":{"operationId":"UsersController_newsletterUnsubscribe","summary":"Unsubscribe from newsletter","parameters":[],"responses":{"200":{"description":"Newsletter unsubscribed"}},"tags":["Users"],"security":[{"bearer":[]}]}},"/v1/users/newsletter-subscribe":{"post":{"operationId":"UsersController_newsletterSubscribe","summary":"Subscribe to newsletter","parameters":[],"responses":{"200":{"description":"Newsletter subscribed"}},"tags":["Users"],"security":[{"bearer":[]}]}},"/v1/projects":{"post":{"operationId":"ProjectsController_create","summary":"Create a new project","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProjectDto"}}}},"responses":{"201":{"description":"Project created"}},"tags":["Projects"],"security":[{"bearer":[]}]},"get":{"operationId":"ProjectsController_findAll","summary":"List current user's projects","parameters":[],"responses":{"200":{"description":"Projects list returned"}},"tags":["Projects"],"security":[{"bearer":[]}]}},"/v1/projects/{id}":{"get":{"operationId":"ProjectsController_findOne","summary":"Get project by ID with sprites","parameters":[{"name":"id","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Project returned"},"403":{"description":"Access denied"},"404":{"description":"Project not found"}},"tags":["Projects"],"security":[{"bearer":[]}]},"patch":{"operationId":"ProjectsController_update","summary":"Update project","parameters":[{"name":"id","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProjectDto"}}}},"responses":{"200":{"description":"Project updated"},"403":{"description":"Access denied"}},"tags":["Projects"],"security":[{"bearer":[]}]},"delete":{"operationId":"ProjectsController_remove","summary":"Delete project (soft delete)","parameters":[{"name":"id","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Project deleted"},"403":{"description":"Access denied"}},"tags":["Projects"],"security":[{"bearer":[]}]}},"/v1/projects/{projectId}/sprites":{"get":{"operationId":"SpritesController_findByProject","summary":"List sprites in a project","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Sprites returned"},"403":{"description":"Access denied"}},"tags":["Sprites"],"security":[{"bearer":[]}]},"post":{"operationId":"SpritesController_createBlank","summary":"Create a blank transparent sprite in a project","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBlankSpriteDto"}}}},"responses":{"201":{"description":"Blank sprite created"},"403":{"description":"Access denied"},"404":{"description":"Project not found"}},"tags":["Sprites"],"security":[{"bearer":[]}]}},"/v1/sprites/{id}":{"get":{"operationId":"SpritesController_findOne","summary":"Get sprite by ID with animations","parameters":[{"name":"id","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Sprite returned"},"403":{"description":"Access denied"}},"tags":["Sprites"],"security":[{"bearer":[]}]},"patch":{"operationId":"SpritesController_update","summary":"Update sprite (e.g. rename)","parameters":[{"name":"id","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSpriteDto"}}}},"responses":{"200":{"description":"Sprite updated"}},"tags":["Sprites"],"security":[{"bearer":[]}]},"delete":{"operationId":"SpritesController_remove","summary":"Delete sprite","parameters":[{"name":"id","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Sprite deleted"}},"tags":["Sprites"],"security":[{"bearer":[]}]}},"/v1/sprites/{id}/package":{"get":{"operationId":"SpritesController_downloadPackage","summary":"Download an engine-ready character package (ZIP)","parameters":[{"name":"id","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}},{"name":"engine","required":false,"in":"query","description":"Target game engine. Determines which engine-specific project files are included.","schema":{"default":"generic","enum":["godot","phaser","unity","gamemaker","rpgmaker-mz","generic"],"type":"string"}},{"name":"animations","required":false,"in":"query","description":"Comma-separated animation types to include (e.g. \"idle,walk,attack\"). Defaults to every completed animation, latest version per type+direction.","schema":{"example":"idle,walk,attack","type":"string"}}],"responses":{"200":{"description":"ZIP file download"},"400":{"description":"No completed animations to package"},"403":{"description":"Access denied"}},"tags":["Sprites"],"security":[{"bearer":[]}]}},"/v1/sprites/{id}/package/manifest":{"get":{"operationId":"SpritesController_getPackageManifest","summary":"Get the character package manifest without building the ZIP","parameters":[{"name":"id","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}},{"name":"engine","required":false,"in":"query","description":"Target game engine. Determines which engine-specific project files are included.","schema":{"default":"generic","enum":["godot","phaser","unity","gamemaker","rpgmaker-mz","generic"],"type":"string"}},{"name":"animations","required":false,"in":"query","description":"Comma-separated animation types to include (e.g. \"idle,walk,attack\"). Defaults to every completed animation, latest version per type+direction.","schema":{"example":"idle,walk,attack","type":"string"}}],"responses":{"200":{"description":"Manifest returned"},"400":{"description":"No completed animations to package"},"403":{"description":"Access denied"}},"tags":["Sprites"],"security":[{"bearer":[]}]}},"/v1/sprites/{id}/upload":{"post":{"operationId":"SpritesController_uploadEdited","summary":"Upload edited sprite image from pixel editor","parameters":[{"name":"id","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Sprite image updated"}},"tags":["Sprites"],"security":[{"bearer":[]}]}},"/v1/generations":{"post":{"operationId":"GenerationController_create","summary":"Create new generation (sprite/variation)","description":"Dispatches async job to Gemini. Poll GET /generations/:id for status.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGenerationDto"}}}},"responses":{"201":{"description":"Generation job created (pending)"},"402":{"description":"Credit limit reached"}},"tags":["Generation"],"security":[{"bearer":[]}]},"get":{"operationId":"GenerationController_findAll","summary":"List current user's generations","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}}],"responses":{"200":{"description":"Paginated generations list"}},"tags":["Generation"],"security":[{"bearer":[]}]}},"/v1/projects/{projectId}/generations":{"get":{"operationId":"GenerationController_findByProject","summary":"List generations for a specific project","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"Paginated generations list for project"},"403":{"description":"Access denied"}},"tags":["Generation"],"security":[{"bearer":[]}]}},"/v1/generations/select":{"post":{"operationId":"GenerationController_selectVariation","summary":"Select a generation variation to save as a Sprite","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectVariationDto"}}}},"responses":{"201":{"description":"Sprite created from selected variation"},"403":{"description":"Access denied"},"404":{"description":"Generation not found"}},"tags":["Generation"],"security":[{"bearer":[]}]}},"/v1/generations/{id}":{"get":{"operationId":"GenerationController_findOne","summary":"Get generation by ID (poll for status/results)","parameters":[{"name":"id","required":true,"in":"path","description":"Generation ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Generation status and result"},"403":{"description":"Access denied"},"404":{"description":"Generation not found"}},"tags":["Generation"],"security":[{"bearer":[]}]}},"/v1/generations/{id}/image":{"get":{"operationId":"GenerationController_getImage","summary":"Stable public redirect to a result image of a generation","description":"Returns 302 redirect when the generation is completed, 202 while still pending. Use ?index=N for multi-output generations. No auth required.","parameters":[{"name":"id","required":true,"in":"path","description":"Generation ID","schema":{"type":"string"}},{"name":"index","required":false,"in":"query","description":"Result image index, defaults to 0","schema":{"type":"number"}}],"responses":{"202":{"description":"Generation still pending"},"302":{"description":"Redirect to the image URL"},"404":{"description":"Generation not found"}},"tags":["Generation"],"security":[{"bearer":[]}]}},"/v1/generations/{id}/animation":{"get":{"operationId":"GenerationController_getAnimation","summary":"Returns animation spritesheet data for a character generation","description":"Returns 202 while the animation is still pending, 200 with spritesheet data when ready. No auth required.","parameters":[{"name":"id","required":true,"in":"path","description":"Generation ID","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","description":"Animation type filter (e.g. walk, idle)","schema":{"type":"string"}}],"responses":{"200":{"description":"Animation ready with spritesheet data"},"202":{"description":"Animation still pending"},"404":{"description":"Generation not found"}},"tags":["Generation"],"security":[{"bearer":[]}]}},"/v1/sprites/{spriteId}/animations":{"post":{"operationId":"AnimationController_createAnimation","summary":"Generate animation for a sprite (async)","description":"Queues an async animation generation job. Poll GET /sprites/:spriteId/animations for results.","parameters":[{"name":"spriteId","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAnimationDto"}}}},"responses":{"202":{"description":"Animation generation queued"},"403":{"description":"Access denied"},"404":{"description":"Sprite not found"}},"tags":["Animations"],"security":[{"bearer":[]}]},"get":{"operationId":"AnimationController_findBySprite","summary":"List animations for a sprite","parameters":[{"name":"spriteId","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Animations list returned"},"403":{"description":"Access denied"}},"tags":["Animations"],"security":[{"bearer":[]}]}},"/v1/sprites/{spriteId}/animations/batch":{"post":{"operationId":"AnimationController_createAnimationBatch","summary":"Generate multiple animation takes for a sprite (async)","description":"Queues 1-3 independent animation generation jobs for the same sprite. Poll GET /sprites/:spriteId/animations for results.","parameters":[{"name":"spriteId","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAnimationBatchDto"}}}},"responses":{"202":{"description":"Animation batch queued"},"403":{"description":"Access denied"},"404":{"description":"Sprite not found"}},"tags":["Animations"],"security":[{"bearer":[]}]}},"/v1/animations":{"post":{"operationId":"AnimationController_createAnimationFromBody","summary":"Generate animation for a sprite (spriteId in body)","description":"Queues an async animation generation job. spriteId is passed in the request body.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAnimationWithSpriteDto"}}}},"responses":{"202":{"description":"Animation generation queued"},"403":{"description":"Access denied"},"404":{"description":"Sprite not found"}},"tags":["Animations"],"security":[{"bearer":[]}]}},"/v1/animations/{id}":{"get":{"operationId":"AnimationController_findOne","summary":"Get animation by ID","parameters":[{"name":"id","required":true,"in":"path","description":"Animation ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Animation returned"},"403":{"description":"Access denied"}},"tags":["Animations"],"security":[{"bearer":[]}]},"delete":{"operationId":"AnimationController_remove","summary":"Delete animation","parameters":[{"name":"id","required":true,"in":"path","description":"Animation ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Animation deleted"}},"tags":["Animations"],"security":[{"bearer":[]}]}},"/v1/animations/{id}/gif":{"get":{"operationId":"AnimationController_downloadGif","summary":"Download animation as animated GIF","parameters":[{"name":"id","required":true,"in":"path","description":"Animation ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Animated GIF file"},"403":{"description":"Access denied"},"404":{"description":"Animation not found"}},"tags":["Animations"],"security":[{"bearer":[]}]}},"/v1/animations/{id}/frames/{frameIndex}/upload":{"post":{"operationId":"AnimationController_replaceFrame","summary":"Replace a single animation frame","parameters":[{"name":"id","required":true,"in":"path","description":"Animation ID","schema":{"type":"string"}},{"name":"frameIndex","required":true,"in":"path","description":"Frame index (0-based)","schema":{"type":"number"}}],"responses":{"200":{"description":"Frame replaced and spritesheet re-stitched"}},"tags":["Animations"],"security":[{"bearer":[]}]}},"/v1/animations/{id}/process":{"post":{"operationId":"AnimationController_processAnimation","summary":"Create a processed copy of a 2D animation","parameters":[{"name":"id","required":true,"in":"path","description":"Animation ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessAnimationDto"}}}},"responses":{"201":{"description":"Processed animation created"}},"tags":["Animations"],"security":[{"bearer":[]}]}},"/v1/projects/{projectId}/tilesets":{"post":{"operationId":"TilesetController_createTileset","summary":"Generate a tileset for a project (async)","description":"Queues an async tileset generation job. Poll GET /projects/:projectId/tilesets for results.","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTilesetDto"}}}},"responses":{"202":{"description":"Tileset generation queued"},"403":{"description":"Access denied"},"404":{"description":"Project not found"}},"tags":["Tilesets"],"security":[{"bearer":[]}]},"get":{"operationId":"TilesetController_findByProject","summary":"List tilesets for a project","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Tilesets list returned"},"403":{"description":"Access denied"}},"tags":["Tilesets"],"security":[{"bearer":[]}]}},"/v1/tilesets/{id}":{"get":{"operationId":"TilesetController_findOne","summary":"Get tileset by ID","parameters":[{"name":"id","required":true,"in":"path","description":"Tileset ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Tileset returned"},"403":{"description":"Access denied"},"404":{"description":"Tileset not found"}},"tags":["Tilesets"],"security":[{"bearer":[]}]},"delete":{"operationId":"TilesetController_remove","summary":"Delete tileset","parameters":[{"name":"id","required":true,"in":"path","description":"Tileset ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Tileset deleted"},"403":{"description":"Access denied"}},"tags":["Tilesets"],"security":[{"bearer":[]}]}},"/v1/sprites/{spriteId}/isometric":{"post":{"operationId":"IsometricController_create","summary":"Generate isometric 8-direction turnaround for a sprite (async)","description":"Queues isometric generation. Poll GET /isometric/:id for results.","parameters":[{"name":"spriteId","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIsometricDto"}}}},"responses":{"202":{"description":"Isometric generation queued"},"403":{"description":"Access denied"},"404":{"description":"Sprite not found"}},"tags":["Isometric"],"security":[{"bearer":[]}]},"get":{"operationId":"IsometricController_findBySprite","summary":"List isometric sets for a sprite","parameters":[{"name":"spriteId","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Isometric sets list"}},"tags":["Isometric"],"security":[{"bearer":[]}]}},"/v1/isometric/{id}":{"get":{"operationId":"IsometricController_findOne","summary":"Get isometric set by ID","parameters":[{"name":"id","required":true,"in":"path","description":"IsometricSet ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Isometric set returned"}},"tags":["Isometric"],"security":[{"bearer":[]}]},"delete":{"operationId":"IsometricController_remove","summary":"Delete isometric set","parameters":[{"name":"id","required":true,"in":"path","description":"IsometricSet ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Isometric set deleted"}},"tags":["Isometric"],"security":[{"bearer":[]}]}},"/v1/billing/status":{"get":{"operationId":"BillingController_getStatus","summary":"Get subscription status and credit balance","parameters":[],"responses":{"200":{"description":"Subscription status returned"}},"tags":["Billing"],"security":[{"bearer":[]}]}},"/v1/billing/create-checkout":{"post":{"operationId":"BillingController_createCheckout","summary":"Create Stripe Checkout session for Pro upgrade","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutDto"}}}},"responses":{"201":{"description":"Checkout URL returned"}},"tags":["Billing"],"security":[{"bearer":[]}]}},"/v1/billing/checkout":{"post":{"operationId":"BillingController_createCheckoutAlias","summary":"Create Stripe Checkout session (alias for /billing/create-checkout)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutDto"}}}},"responses":{"201":{"description":"Checkout URL returned"}},"tags":["Billing"],"security":[{"bearer":[]}]}},"/v1/billing/portal":{"post":{"operationId":"BillingController_createPortal","summary":"Create Stripe Billing Portal session","parameters":[],"responses":{"201":{"description":"Portal URL returned"}},"tags":["Billing"],"security":[{"bearer":[]}]}},"/v1/billing/buy-credits":{"post":{"operationId":"BillingController_buyCredits","summary":"Create Stripe Checkout session for credit pack purchase","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BuyCreditsDto"}}}},"responses":{"201":{"description":"Checkout URL returned"}},"tags":["Billing"],"security":[{"bearer":[]}]}},"/v1/billing/credit-packs":{"get":{"operationId":"BillingController_getCreditPacks","summary":"Get available credit pack options with pricing","parameters":[],"responses":{"200":{"description":"Credit packs returned"}},"tags":["Billing"]}},"/v1/billing/webhook":{"post":{"operationId":"BillingController_handleWebhook","summary":"Stripe webhook handler","description":"Do not call this directly — it is called by Stripe","parameters":[{"name":"stripe-signature","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook processed"}},"tags":["Billing"]}},"/v1/tile-packs":{"get":{"operationId":"TilePacksController_findAll","summary":"List all tile packs (built-in + user custom)","parameters":[{"name":"projectionType","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Tile packs list returned"}},"tags":["Tile Packs"],"security":[{"bearer":[]}]}},"/v1/tile-packs/{slug}":{"get":{"operationId":"TilePacksController_findBySlug","summary":"Get a tile pack by slug with all tiles","parameters":[{"name":"slug","required":true,"in":"path","description":"Tile pack slug (e.g. fantasy-terrain)","schema":{"type":"string"}}],"responses":{"200":{"description":"Tile pack returned"},"404":{"description":"Tile pack not found"}},"tags":["Tile Packs"],"security":[{"bearer":[]}]}},"/v1/tile-packs/generate":{"post":{"operationId":"TileGenerationController_generate","summary":"Generate a custom AI tile","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomTileDto"}}}},"responses":{"201":{"description":"Generation job queued"},"402":{"description":"Credit limit reached or insufficient credits"}},"tags":["Tile Packs — Custom Generation"],"security":[{"bearer":[]}]}},"/v1/tile-packs/custom":{"get":{"operationId":"TileGenerationController_getCustomPack","summary":"Get the current user's custom tile pack","parameters":[],"responses":{"200":{"description":"Custom tile pack returned"}},"tags":["Tile Packs — Custom Generation"],"security":[{"bearer":[]}]}},"/v1/tile-packs/custom/{tileId}":{"delete":{"operationId":"TileGenerationController_deleteCustomTile","summary":"Delete a custom tile","parameters":[{"name":"tileId","required":true,"in":"path","description":"ID of the TilePackTile to delete","schema":{"type":"string"}}],"responses":{"200":{"description":"Tile deleted"},"403":{"description":"Access denied"},"404":{"description":"Tile not found"}},"tags":["Tile Packs — Custom Generation"],"security":[{"bearer":[]}]}},"/v1/maps":{"post":{"operationId":"MapsController_create","summary":"Create a new map","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMapDto"}}}},"responses":{"201":{"description":"Map created"}},"tags":["Maps"],"security":[{"bearer":[]}]},"get":{"operationId":"MapsController_findAll","summary":"List current user's maps","parameters":[],"responses":{"200":{"description":"Maps list returned"}},"tags":["Maps"],"security":[{"bearer":[]}]}},"/v1/maps/{id}":{"get":{"operationId":"MapsController_findOne","summary":"Get map by ID with layers and tile packs","parameters":[{"name":"id","required":true,"in":"path","description":"Map ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Map returned"},"403":{"description":"Access denied"},"404":{"description":"Map not found"}},"tags":["Maps"],"security":[{"bearer":[]}]},"patch":{"operationId":"MapsController_update","summary":"Update map (layers, name, metadata, tile packs)","parameters":[{"name":"id","required":true,"in":"path","description":"Map ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMapDto"}}}},"responses":{"200":{"description":"Map updated"},"403":{"description":"Access denied"},"404":{"description":"Map not found"}},"tags":["Maps"],"security":[{"bearer":[]}]},"delete":{"operationId":"MapsController_remove","summary":"Delete map (soft delete)","parameters":[{"name":"id","required":true,"in":"path","description":"Map ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Map deleted"},"403":{"description":"Access denied"},"404":{"description":"Map not found"}},"tags":["Maps"],"security":[{"bearer":[]}]}},"/v1/maps/{id}/thumbnail":{"post":{"operationId":"MapsController_uploadThumbnail","summary":"Upload rendered thumbnail for a map","parameters":[{"name":"id","required":true,"in":"path","description":"Map ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Thumbnail uploaded"}},"tags":["Maps"],"security":[{"bearer":[]}]}},"/v1/maps/{id}/ai-edit":{"post":{"operationId":"MapsController_aiEdit","summary":"AI edit map with natural language command","parameters":[{"name":"id","required":true,"in":"path","description":"Map ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiEditMapDto"}}}},"responses":{"201":{"description":"AI edit job created"}},"tags":["Maps"],"security":[{"bearer":[]}]}},"/v1/maps/{id}/ai-edit/{generationId}":{"get":{"operationId":"MapsController_getAiEditStatus","summary":"Get AI edit status (poll)","parameters":[{"name":"id","required":true,"in":"path","description":"Map ID","schema":{"type":"string"}},{"name":"generationId","required":true,"in":"path","description":"Generation ID","schema":{"type":"string"}}],"responses":{"200":{"description":"AI edit status returned"}},"tags":["Maps"],"security":[{"bearer":[]}]}},"/v1/maps/{id}/export/{format}":{"get":{"operationId":"MapsController_exportMap","summary":"Export map (tmx, json, godot, unity, gamemaker)","parameters":[{"name":"id","required":true,"in":"path","description":"Map ID","schema":{"type":"string"}},{"name":"format","required":true,"in":"path","description":"Export format","schema":{"type":"string"}}],"responses":{"200":{"description":"File download"}},"tags":["Maps"],"security":[{"bearer":[]}]}},"/v1/feedback":{"post":{"operationId":"FeedbackController_create","summary":"Submit feedback","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFeedbackDto"}}}},"responses":{"201":{"description":"Feedback submitted"},"400":{"description":"Rate limit exceeded"}},"tags":["Feedback"],"security":[{"bearer":[]}]}},"/v1/parallax":{"post":{"operationId":"ParallaxController_create","summary":"Generate a new parallax background (costs 5 credits)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateParallaxDto"}}}},"responses":{"201":{"description":"Generation job created and queued"},"400":{"description":"Insufficient credits or too many active generations"},"401":{"description":"Unauthorized"}},"tags":["Parallax Backgrounds"],"security":[{"bearer":[]}]},"get":{"operationId":"ParallaxController_findAll","summary":"List all parallax backgrounds for the authenticated user","parameters":[],"responses":{"200":{"description":"List of parallax backgrounds"}},"tags":["Parallax Backgrounds"],"security":[{"bearer":[]}]}},"/v1/parallax/{id}":{"get":{"operationId":"ParallaxController_findOne","summary":"Get a parallax background by ID","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Parallax background details"},"403":{"description":"Access denied"},"404":{"description":"Not found"}},"tags":["Parallax Backgrounds"],"security":[{"bearer":[]}]},"delete":{"operationId":"ParallaxController_delete","summary":"Soft-delete a parallax background","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Background deleted"},"403":{"description":"Access denied"},"404":{"description":"Not found"}},"tags":["Parallax Backgrounds"],"security":[{"bearer":[]}]}},"/v1/models-3d":{"post":{"operationId":"Models3dController_create","summary":"Create a 3D model from image upload, sprite ID, or text prompt","description":"Dispatches async job to the TRELLIS GPU server. Cost: 5 credits. Poll GET /models-3d/:id for status. When status=completed, modelUrl holds the GLB URL.","parameters":[],"requestBody":{"required":true,"description":"Multipart form: image file + optional JSON fields, OR JSON body with spriteId/prompt","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"binary","description":"Source image (PNG/JPG/WebP, max 10 MB)"},"projectId":{"type":"string"},"prompt":{"type":"string"},"spriteId":{"type":"string"},"name":{"type":"string"}}}},"application/json":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"binary","description":"Source image (PNG/JPG/WebP, max 10 MB)"},"projectId":{"type":"string"},"prompt":{"type":"string"},"spriteId":{"type":"string"},"name":{"type":"string"}}}}}},"responses":{"201":{"description":"3D model job created (pending)"},"400":{"description":"Invalid input or rate limit exceeded"},"402":{"description":"Insufficient credits"},"403":{"description":"Access denied to project or sprite"},"404":{"description":"Project or sprite not found"}},"tags":["3D Studio"],"security":[{"bearer":[]}]},"get":{"operationId":"Models3dController_findAll","summary":"List current user's 3D models","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}}],"responses":{"200":{"description":"Paginated 3D models list"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/gallery":{"get":{"operationId":"Models3dController_gallery","summary":"List gallery models (public, no auth required)","parameters":[{"name":"category","required":false,"in":"query","schema":{"type":"string"}},{"name":"theme","required":false,"in":"query","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Gallery models list"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/admin/import":{"post":{"operationId":"Models3dController_adminImport","summary":"Admin: import an existing GLB file as a built-in 3D model","description":"Saves the uploaded GLB to storage and creates a Model3D record with sourceType=builtin, userId=null, status=completed. No credits deducted.","parameters":[],"requestBody":{"required":true,"description":"GLB file + model metadata","content":{"multipart/form-data":{"schema":{"type":"object","required":["glb","name"],"properties":{"glb":{"type":"string","format":"binary","description":"GLB file (max 100 MB)"},"name":{"type":"string","description":"Display name for the model"},"category":{"type":"string","description":"Category tag, e.g. dungeon, medieval, nature"},"source":{"type":"string","description":"Attribution source, e.g. KayKit, Quaternius"}}}}}},"responses":{"201":{"description":"Built-in model created"},"400":{"description":"Missing file or invalid input"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/admin/thumbnail/{id}":{"post":{"operationId":"Models3dController_adminUploadThumbnail","summary":"Admin: upload thumbnail for any model (including builtin)","parameters":[{"name":"id","required":true,"in":"path","description":"Model3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Thumbnail uploaded"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/{id}":{"get":{"operationId":"Models3dController_findOne","summary":"Get 3D model by ID (poll for status/results)","parameters":[{"name":"id","required":true,"in":"path","description":"Model3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"3D model details"},"403":{"description":"Access denied"},"404":{"description":"3D model not found"}},"tags":["3D Studio"],"security":[{"bearer":[]}]},"delete":{"operationId":"Models3dController_remove","summary":"Soft-delete a 3D model","parameters":[{"name":"id","required":true,"in":"path","description":"Model3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Model deleted"},"403":{"description":"Access denied"},"404":{"description":"3D model not found"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/{id}/thumbnail":{"post":{"operationId":"Models3dController_uploadThumbnail","summary":"Upload rendered thumbnail for a 3D model","parameters":[{"name":"id","required":true,"in":"path","description":"Model3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Thumbnail uploaded"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/{id}/download":{"get":{"operationId":"Models3dController_download","summary":"Get GLB download URL for a completed 3D model","parameters":[{"name":"id","required":true,"in":"path","description":"Model3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"GLB download URL"},"400":{"description":"Model not yet completed"},"403":{"description":"Access denied"},"404":{"description":"3D model not found"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/health/trellis":{"get":{"operationId":"Models3dController_trellisHealth","summary":"Check TRELLIS GPU server health","parameters":[],"responses":{"200":{"description":"TRELLIS server health status"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/models-3d/{id}/animations":{"post":{"operationId":"Models3dController_createAnimation","summary":"Create an animation for a 3D model","description":"Dispatches async job: auto-rig (UniRig) → motion generation (HY-Motion) → retarget (Blender). Cost: 3 credits. Poll GET /animations-3d/:animId for status.","parameters":[{"name":"id","required":true,"in":"path","description":"Model3D ID (must be completed)","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAnimation3dDto"}}}},"responses":{"201":{"description":"Animation job created (pending)"},"400":{"description":"Model not completed or rate limit"},"402":{"description":"Insufficient credits"}},"tags":["3D Studio"],"security":[{"bearer":[]}]},"get":{"operationId":"Models3dController_listAnimations","summary":"List animations for a 3D model","parameters":[{"name":"id","required":true,"in":"path","description":"Model3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of animations"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/animations-3d/{animId}":{"get":{"operationId":"Models3dController_findAnimation","summary":"Get animation by ID (poll for status)","parameters":[{"name":"animId","required":true,"in":"path","description":"Animation3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Animation details"}},"tags":["3D Studio"],"security":[{"bearer":[]}]},"delete":{"operationId":"Models3dController_removeAnimation","summary":"Soft-delete an animation","parameters":[{"name":"animId","required":true,"in":"path","description":"Animation3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Animation deleted"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/animations-3d/{animId}/download":{"get":{"operationId":"Models3dController_downloadAnimation","summary":"Get animated GLB download URL","parameters":[{"name":"animId","required":true,"in":"path","description":"Animation3D ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Animated GLB download URL"}},"tags":["3D Studio"],"security":[{"bearer":[]}]}},"/v1/character-studio/library":{"get":{"operationId":"CharacterStudioController_getLibrary","summary":"List the CC0 base characters and accessories","description":"Base characters are rigged low-poly humanoids (KayKit Adventurers, CC0). `actions` lists the Sprixen animation types each character can be rendered as. Accessories (weapons, shields) can be attached by the customization prompt.","parameters":[],"responses":{"200":{"description":"Characters and accessories"},"503":{"description":"Character Studio is not enabled"}},"tags":["Character Studio 3D"],"security":[{"bearer":[]}]}},"/v1/character-studio/characters":{"post":{"operationId":"CharacterStudioController_create","summary":"Customize a library character from a text request","description":"Cost: 2 credits (refunded on technical failure). Creates a rigged GLB with all animations plus 4 preview views. Poll GET /character-studio/characters/:id until status=completed. Pass fromModelId to iterate on a previous customization.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCharacterDto"}}}},"responses":{"201":{"description":"Customization queued (pending)"},"402":{"description":"Insufficient credits"},"404":{"description":"Base character, project or previous model not found"}},"tags":["Character Studio 3D"],"security":[{"bearer":[]}]},"get":{"operationId":"CharacterStudioController_list","summary":"List the caller's customized characters, newest first","parameters":[{"name":"page","required":false,"in":"query","description":"Page number, starting at 1. Default 1.","schema":{"example":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Items per page, 1 to 50. Default 24.","schema":{"example":24,"type":"number"}}],"responses":{"200":{"description":"{ data, total, page, limit }"}},"tags":["Character Studio 3D"],"security":[{"bearer":[]}]}},"/v1/character-studio/characters/{id}/sprite-renders":{"get":{"operationId":"CharacterStudioController_listSpriteRenders","summary":"List the sprite sheet renders of a character, newest first (up to 50)","parameters":[{"name":"id","required":true,"in":"path","description":"Character (3D model) ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Renders with their animations when completed"},"404":{"description":"Character not found"}},"tags":["Character Studio 3D"],"security":[{"bearer":[]}]}},"/v1/character-studio/characters/{id}":{"get":{"operationId":"CharacterStudioController_get","summary":"Get a customized character (status, GLB, previews, applied ops)","parameters":[{"name":"id","required":true,"in":"path","description":"Character (3D model) ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Character"},"404":{"description":"Character not found"}},"tags":["Character Studio 3D"],"security":[{"bearer":[]}]}},"/v1/character-studio/characters/{id}/sprite-sheet":{"post":{"operationId":"CharacterStudioController_createSpriteSheet","summary":"Render an 8-direction sprite sheet of a completed character","description":"Cost: 4 credits per animation type (refunded on technical failure). Creates a Sprite (frame 0 of \"down\") with 8 Animation rows, one per direction. Poll GET /character-studio/sprite-renders/:id.","parameters":[{"name":"id","required":true,"in":"path","description":"Completed character (3D model) ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSpriteSheetDto"}}}},"responses":{"201":{"description":"Render queued (pending)"},"402":{"description":"Insufficient credits"},"404":{"description":"Completed character or project not found"}},"tags":["Character Studio 3D"],"security":[{"bearer":[]}]}},"/v1/character-studio/sprite-renders/{id}":{"get":{"operationId":"CharacterStudioController_getSpriteRender","summary":"Get a sprite sheet render (status, sprite and animations)","parameters":[{"name":"id","required":true,"in":"path","description":"Sprite render ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Render status with spriteId and animations when completed"},"404":{"description":"Render not found"}},"tags":["Character Studio 3D"],"security":[{"bearer":[]}]}},"/v1/music":{"post":{"operationId":"MusicGenerationController_create","summary":"Create a music track from text prompt","description":"Dispatches async job to ACE-Step GPU server. Cost: 3 credits. Optionally accepts a reference audio file for style/timbre guidance. Poll GET /music/:id for status. When status=completed, audioUrl holds the audio URL.","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateMusicDto"}},"application/json":{"schema":{"$ref":"#/components/schemas/CreateMusicDto"}}}},"responses":{"201":{"description":"Music job created (pending)"},"400":{"description":"Invalid input or rate limit exceeded"},"402":{"description":"Insufficient credits"}},"tags":["Music Studio"],"security":[{"bearer":[]}]},"get":{"operationId":"MusicGenerationController_findAll","summary":"List current user's music tracks","parameters":[{"name":"limit","required":false,"in":"query","schema":{"example":20,"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}}],"responses":{"200":{"description":"Paginated music tracks list"}},"tags":["Music Studio"],"security":[{"bearer":[]}]}},"/v1/music/health/ace-step":{"get":{"operationId":"MusicGenerationController_aceStepHealth","summary":"Check ACE-Step music server health","parameters":[],"responses":{"200":{"description":"ACE-Step server health status"}},"tags":["Music Studio"],"security":[{"bearer":[]}]}},"/v1/music/gallery":{"get":{"operationId":"MusicGenerationController_gallery","summary":"List gallery music tracks (public, no auth required)","parameters":[{"name":"category","required":false,"in":"query","description":"Filter by gallery category (e.g. dungeon, adventure, boss, ambient)","schema":{"type":"string"}},{"name":"theme","required":false,"in":"query","description":"Filter by gallery theme (e.g. fantasy, scifi, horror, retro)","schema":{"type":"string"}}],"responses":{"200":{"description":"Gallery music tracks list"}},"tags":["Music Studio"],"security":[{"bearer":[]}]}},"/v1/music/{id}":{"get":{"operationId":"MusicGenerationController_findOne","summary":"Get music track by ID (poll for status)","parameters":[{"name":"id","required":true,"in":"path","description":"MusicTrack ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Music track details"},"403":{"description":"Access denied"},"404":{"description":"Music track not found"}},"tags":["Music Studio"],"security":[{"bearer":[]}]},"delete":{"operationId":"MusicGenerationController_remove","summary":"Soft-delete a music track","parameters":[{"name":"id","required":true,"in":"path","description":"MusicTrack ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Track deleted"},"403":{"description":"Access denied"},"404":{"description":"Music track not found"}},"tags":["Music Studio"],"security":[{"bearer":[]}]}},"/v1/music/{id}/download":{"get":{"operationId":"MusicGenerationController_download","summary":"Get audio download URL for a completed track","parameters":[{"name":"id","required":true,"in":"path","description":"MusicTrack ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Audio download URL"},"400":{"description":"Track not yet completed"},"403":{"description":"Access denied"},"404":{"description":"Music track not found"}},"tags":["Music Studio"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects":{"post":{"operationId":"GameCreatorController_createProject","summary":"Create a new game project","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGameProjectDto"}}}},"responses":{"201":{"description":"Game project created"}},"tags":["Game Creator"],"security":[{"bearer":[]}]},"get":{"operationId":"GameCreatorController_listProjects","summary":"List current user's game projects","parameters":[],"responses":{"200":{"description":"List of game projects"}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}":{"get":{"operationId":"GameCreatorController_getProject","summary":"Get game project by ID","parameters":[{"name":"id","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Game project returned"},"403":{"description":"Access denied"},"404":{"description":"Not found"}},"tags":["Game Creator"],"security":[{"bearer":[]}]},"patch":{"operationId":"GameCreatorController_updateProject","summary":"Update game project metadata","parameters":[{"name":"id","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGameProjectDto"}}}},"responses":{"200":{"description":"Game project updated"},"403":{"description":"Access denied"},"404":{"description":"Not found"}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/conversations":{"get":{"operationId":"GameCreatorController_getProjectConversations","summary":"List conversations linked to a game project","parameters":[{"name":"id","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of conversations for the project"},"403":{"description":"Access denied"},"404":{"description":"Not found"}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/versions":{"get":{"operationId":"GameCreatorController_getVersions","summary":"List code versions for a game project","parameters":[{"name":"id","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of versions"},"403":{"description":"Access denied"},"404":{"description":"Not found"}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/assets":{"get":{"operationId":"GameCreatorController_getGameAssets","summary":"List assets for a game project","parameters":[{"name":"id","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of assets"}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/assets/{assetId}":{"get":{"operationId":"GameCreatorController_getGameAssetDetail","summary":"Get asset detail with sprite + animations","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/versions/{versionId}/restore":{"post":{"operationId":"GameCreatorController_restoreVersion","summary":"Restore a specific code version","parameters":[{"name":"id","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}},{"name":"versionId","required":true,"in":"path","description":"Version ID to restore","schema":{"type":"string"}}],"responses":{"200":{"description":"Version restored"},"403":{"description":"Access denied"},"404":{"description":"Not found"}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/publish":{"post":{"operationId":"GameCreatorController_publish","summary":"Publish a game project","parameters":[{"name":"id","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Project published"},"403":{"description":"Access denied or project has no code"},"404":{"description":"Not found"}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/slugs/check":{"get":{"operationId":"GameCreatorController_checkSlug","summary":"Check if a slug is available","parameters":[{"name":"slug","required":true,"in":"query","schema":{"type":"string"}},{"name":"projectId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Game Creator"]}},"/v1/game-creator/projects/{id}/unpublish":{"post":{"operationId":"GameCreatorController_unpublish","summary":"Unpublish a game","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Game Creator"],"security":[{"bearer":[]}]}},"/v1/game-creator/play/{slug}":{"get":{"operationId":"GameCreatorController_playGame","summary":"Serve published game (public)","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Game Creator"]}},"/v1/game-creator/play/{slug}/*":{"get":{"operationId":"GameCreatorController_playGameFile","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Game Creator"]}},"/v1/game-creator/preview/{projectId}":{"get":{"operationId":"GameCreatorController_previewEntry","summary":"Serve game preview entry file","parameters":[{"name":"projectId","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Entry HTML file"},"404":{"description":"Project not found or has no files"}},"tags":["Game Creator"]}},"/v1/game-creator/preview/{projectId}/*":{"get":{"operationId":"GameCreatorController_previewFile","summary":"Serve a specific file from game project","parameters":[{"name":"projectId","required":true,"in":"path","description":"Game project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"File content"},"404":{"description":"File not found"}},"tags":["Game Creator"]}},"/v1/game-creator/templates":{"get":{"operationId":"GameCreatorController_getTemplates","summary":"List available game templates","parameters":[],"responses":{"200":{"description":"List of template metadata"}},"tags":["Game Creator"]}},"/v1/game-creator/templates/{type}":{"get":{"operationId":"GameCreatorController_getTemplate","summary":"Get a template with full starter code","parameters":[{"name":"type","required":true,"in":"path","description":"Template type (platformer, topdown, isometric)","schema":{"type":"string"}}],"responses":{"200":{"description":"Template with code"},"404":{"description":"Template type not found"}},"tags":["Game Creator"]}},"/v1/mod-builder/projects":{"post":{"operationId":"ModBuilderController_createProject","summary":"Create a new mod project","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateModProjectDto"}}}},"responses":{"201":{"description":"Mod project created"}},"tags":["Mod Builder"],"security":[{"bearer":[]}]},"get":{"operationId":"ModBuilderController_listProjects","summary":"List current user's mod projects","parameters":[{"name":"platform","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"List of mod projects"}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}":{"get":{"operationId":"ModBuilderController_getProject","summary":"Get mod project by ID","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]},"patch":{"operationId":"ModBuilderController_updateProject","summary":"Update mod project metadata","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGameProjectDto"}}}},"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]},"delete":{"operationId":"ModBuilderController_deleteProject","summary":"Soft-delete a mod project","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/conversations":{"get":{"operationId":"ModBuilderController_getProjectConversations","summary":"List conversations for a mod project","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/versions":{"get":{"operationId":"ModBuilderController_getVersions","summary":"List code versions for a mod project","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/versions/{versionId}":{"get":{"operationId":"ModBuilderController_getVersion","summary":"Get a single version with its full file snapshot","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}},{"name":"versionId","required":true,"in":"path","description":"Version ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/versions/{versionId}/restore":{"post":{"operationId":"ModBuilderController_restoreVersion","summary":"Restore a specific code version","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"versionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/assets":{"get":{"operationId":"ModBuilderController_getAssets","summary":"List assets for a mod project","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/graph":{"get":{"operationId":"ModBuilderController_getGraph","summary":"Resource graph (files + event/NUI/export edges)","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/gui-preview":{"get":{"operationId":"ModBuilderController_getGuiPreview","summary":"Roblox GUI preview - parsed ScreenGui/Frame tree","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/projects/{id}/export":{"get":{"operationId":"ModBuilderController_exportProject","summary":"Download the mod project as a .zip archive","parameters":[{"name":"id","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Zip file with the full mod"}},"tags":["Mod Builder"],"security":[{"bearer":[]}]}},"/v1/mod-builder/preview/{projectId}":{"get":{"operationId":"ModBuilderController_previewIndex","summary":"Serve mod NUI preview (index)","parameters":[{"name":"projectId","required":true,"in":"path","description":"Mod project ID","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"]}},"/v1/mod-builder/preview/{projectId}/*":{"get":{"operationId":"ModBuilderController_previewFile","summary":"Serve mod NUI preview file","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Mod Builder"]}},"/v1/users/me/onboarding":{"get":{"operationId":"OnboardingController_getOnboarding","summary":"Get current user onboarding state","parameters":[],"responses":{"200":{"description":"Onboarding state returned"}},"tags":["Onboarding"],"security":[{"bearer":[]}]},"patch":{"operationId":"OnboardingController_updateOnboarding","summary":"Update current user onboarding state","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOnboardingDto"}}}},"responses":{"200":{"description":"Onboarding state updated"}},"tags":["Onboarding"],"security":[{"bearer":[]}]}},"/v1/chat/debug/code-agent":{"post":{"operationId":"ChatController_debugCodeAgent","summary":"[DEBUG] Test code agent directly","parameters":[],"responses":{"201":{"description":""}},"tags":["chat"],"security":[{"bearer":[]}]}},"/v1/chat/conversations":{"post":{"operationId":"ChatController_createConversation","summary":"Create a new conversation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationDto"}}}},"responses":{"201":{"description":"Conversation created"}},"tags":["chat"],"security":[{"bearer":[]}]},"get":{"operationId":"ChatController_findAllConversations","summary":"List current user's conversations","parameters":[],"responses":{"200":{"description":"List of conversations"}},"tags":["chat"],"security":[{"bearer":[]}]}},"/v1/chat/conversations/{id}":{"get":{"operationId":"ChatController_findConversation","summary":"Get a conversation with messages","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Conversation with messages"},"403":{"description":"Access denied"},"404":{"description":"Conversation not found"}},"tags":["chat"],"security":[{"bearer":[]}]},"delete":{"operationId":"ChatController_deleteConversation","summary":"Delete a conversation (soft delete)","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Conversation deleted"},"403":{"description":"Access denied"},"404":{"description":"Conversation not found"}},"tags":["chat"],"security":[{"bearer":[]}]}},"/v1/chat/conversations/{id}/messages":{"post":{"operationId":"ChatController_sendMessage","summary":"Send a message to the chat assistant","description":"Sends a user message (text and/or image) to Gemini, executes any tool calls, and returns the assistant response. Accepts multipart/form-data or JSON (text-only). May take several seconds.","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"content":{"type":"string","description":"Message text (optional when image is provided)"},"image":{"type":"string","format":"binary","description":"Optional image attachment (PNG/JPEG/WebP/GIF, max 5 MB)"}}}},"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","description":"Message text (optional when image is provided)"},"image":{"type":"string","format":"binary","description":"Optional image attachment (PNG/JPEG/WebP/GIF, max 5 MB)"}}}}}},"responses":{"201":{"description":"Assistant message returned"},"400":{"description":"No text or image provided"},"403":{"description":"Access denied"},"404":{"description":"Conversation not found"}},"tags":["chat"],"security":[{"bearer":[]}]},"get":{"operationId":"ChatController_getMessages","summary":"Get paginated message history","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"example":1,"type":"number"}}],"responses":{"200":{"description":"Paginated messages"},"403":{"description":"Access denied"},"404":{"description":"Conversation not found"}},"tags":["chat"],"security":[{"bearer":[]}]}},"/v1/chat/conversations/{id}/messages/stream":{"post":{"operationId":"ChatController_sendMessageStream","summary":"Stream assistant response via SSE","description":"Sends a user message and streams SSE events for each step of the agentic loop. Events: message_saved, thinking, tool_start, tool_complete, text, done, error, ping. Does not support image uploads.","parameters":[{"name":"id","required":true,"in":"path","description":"Conversation ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","description":"Message text"}},"required":["content"]}}}},"responses":{"200":{"description":"SSE stream of assistant events"},"400":{"description":"No text provided"},"403":{"description":"Access denied"},"404":{"description":"Conversation not found"}},"tags":["chat"],"security":[{"bearer":[]}]}},"/v1/game-creator/live/overview":{"get":{"operationId":"SprixenLiveController_globalOverview","summary":"Get global Sprixen Live overview across owned game projects","parameters":[],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/live/apps":{"get":{"operationId":"SprixenLiveController_listLiveApps","summary":"List Game Servers / Sprixen Live apps owned by the current user","parameters":[],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]},"post":{"operationId":"SprixenLiveController_createLiveApp","summary":"Create a standalone Game Server / Sprixen Live app","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLiveGameDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/live/apps/{liveGameId}":{"get":{"operationId":"SprixenLiveController_getLiveApp","summary":"Get a standalone Game Server / Sprixen Live app","parameters":[{"name":"liveGameId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]},"patch":{"operationId":"SprixenLiveController_updateLiveApp","summary":"Update a standalone Game Server / Sprixen Live app","parameters":[{"name":"liveGameId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLiveGameDto"}}}},"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/config":{"patch":{"operationId":"SprixenLiveController_updateConfig","summary":"Enable/configure Sprixen Live for a game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLiveConfigDto"}}}},"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/overview":{"get":{"operationId":"SprixenLiveController_overview","summary":"Get Sprixen Live dashboard overview for a game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/players":{"get":{"operationId":"SprixenLiveController_listPlayers","summary":"List players for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/players/{playerId}":{"get":{"operationId":"SprixenLiveController_playerDetail","summary":"Get detailed player activity for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"playerId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/saves":{"get":{"operationId":"SprixenLiveController_listSaves","summary":"List cloud saves for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/matches":{"get":{"operationId":"SprixenLiveController_listMatches","summary":"List multiplayer matches for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/lobbies":{"get":{"operationId":"SprixenLiveController_listCreatorLobbies","summary":"List multiplayer lobbies for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/leaderboards":{"get":{"operationId":"SprixenLiveController_listCreatorLeaderboards","summary":"List leaderboards and top scores for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]},"post":{"operationId":"SprixenLiveController_createLeaderboard","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLeaderboardDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/achievements":{"get":{"operationId":"SprixenLiveController_listCreatorAchievements","summary":"List achievements and unlock rates for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]},"post":{"operationId":"SprixenLiveController_createAchievement","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAchievementDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/inventory-items":{"get":{"operationId":"SprixenLiveController_listCreatorInventory","summary":"List inventory items and ownership stats for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]},"post":{"operationId":"SprixenLiveController_createInventoryItem","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInventoryItemDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/analytics":{"get":{"operationId":"SprixenLiveController_analytics","summary":"Get analytics dashboard data for a Sprixen Live game project","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/live/games/{slug}/bootstrap":{"post":{"operationId":"SprixenLiveController_bootstrap","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BootstrapLiveDto"}}}},"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/saves/{slot}":{"get":{"operationId":"SprixenLiveController_getSave","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"slot","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]},"put":{"operationId":"SprixenLiveController_putSave","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"slot","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveGameDto"}}}},"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/leaderboards/{key}":{"get":{"operationId":"SprixenLiveController_getLeaderboard","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"key","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/leaderboards/{key}/score":{"post":{"operationId":"SprixenLiveController_submitScore","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"key","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitScoreDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/achievements/{key}/unlock":{"post":{"operationId":"SprixenLiveController_unlockAchievement","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"key","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/inventory":{"get":{"operationId":"SprixenLiveController_listInventory","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/inventory/{itemKey}":{"post":{"operationId":"SprixenLiveController_grantInventory","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"itemKey","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantInventoryDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/analytics":{"post":{"operationId":"SprixenLiveController_track","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackLiveEventDto"}}}},"responses":{"202":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/lobbies":{"get":{"operationId":"SprixenLiveController_listLobbies","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]},"post":{"operationId":"SprixenLiveController_createLobby","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLobbyDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/sdk.js":{"get":{"operationId":"SprixenLiveController_serveSdk","summary":"Sprixen Live client SDK (browser). Stable URL so SDK updates propagate to existing games without rebuilding template snapshots.","parameters":[],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/sdk/version":{"get":{"operationId":"SprixenLiveController_sdkVersion","summary":"Current Sprixen Live SDK version","parameters":[],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/queue/join":{"post":{"operationId":"SprixenLiveController_joinQueue","summary":"Join the matchmaking queue (FIFO, server-paired)","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinQueueDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/queue/leave":{"post":{"operationId":"SprixenLiveController_leaveQueue","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/queue/status":{"get":{"operationId":"SprixenLiveController_queueStatus","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/friends/request":{"post":{"operationId":"SprixenLiveController_friendRequest","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FriendRequestDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/friends/{id}/accept":{"post":{"operationId":"SprixenLiveController_friendAccept","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/friends/{id}/decline":{"post":{"operationId":"SprixenLiveController_friendDecline","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/friends/{otherPlayerId}":{"delete":{"operationId":"SprixenLiveController_friendRemove","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"otherPlayerId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/friends":{"get":{"operationId":"SprixenLiveController_friendsList","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/live/games/{slug}/friends/pending":{"get":{"operationId":"SprixenLiveController_friendsPending","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Sprixen Live"]}},"/v1/game-creator/projects/{id}/live/players/{playerId}/ban":{"post":{"operationId":"SprixenLiveController_banPlayer","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"playerId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BanPlayerDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/players/{playerId}/unban":{"post":{"operationId":"SprixenLiveController_unbanPlayer","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"playerId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/players/{playerId}/mute":{"post":{"operationId":"SprixenLiveController_mutePlayer","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"playerId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutePlayerDto"}}}},"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/game-creator/projects/{id}/live/players/{playerId}/unmute":{"post":{"operationId":"SprixenLiveController_unmutePlayer","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"playerId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Sprixen Live"],"security":[{"bearer":[]}]}},"/v1/sprites/{spriteId}/sequences":{"post":{"operationId":"SequencesController_create","summary":"Create a sequence for a sprite","description":"Creates a composed animation sequence by ordering existing animations into clips. No credits are charged — manual composition is free. Only animations belonging to the same sprite may be referenced.","parameters":[{"name":"spriteId","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSequenceDto"}}}},"responses":{"201":{"description":"Sequence created"},"400":{"description":"Invalid input or clip animation not found/mismatched"},"403":{"description":"Access denied to this sprite"},"404":{"description":"Sprite not found"}},"tags":["Sequences"],"security":[{"bearer":[]}]},"get":{"operationId":"SequencesController_findBySprite","summary":"List sequences for a sprite","parameters":[{"name":"spriteId","required":true,"in":"path","description":"Sprite ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Sequences list returned"},"403":{"description":"Access denied to this sprite"},"404":{"description":"Sprite not found"}},"tags":["Sequences"],"security":[{"bearer":[]}]}},"/v1/sequences/{id}":{"get":{"operationId":"SequencesController_findOne","summary":"Get a sequence by ID (clips expanded)","description":"Returns the sequence with each clip expanded to include its referenced Animation record. If an animation was deleted, that clip surfaces with animation: null and error: \"animation_deleted\".","parameters":[{"name":"id","required":true,"in":"path","description":"Sequence ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Sequence returned with expanded clips"},"403":{"description":"Access denied"},"404":{"description":"Sequence not found"}},"tags":["Sequences"],"security":[{"bearer":[]}]},"patch":{"operationId":"SequencesController_update","summary":"Update a sequence","description":"All fields are optional. Providing clips triggers full replacement of clipsJson and recomputes durationMs. Each clip's animationId is re-validated against the sprite.","parameters":[{"name":"id","required":true,"in":"path","description":"Sequence ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSequenceDto"}}}},"responses":{"200":{"description":"Sequence updated"},"400":{"description":"Invalid input"},"403":{"description":"Access denied"},"404":{"description":"Sequence not found"}},"tags":["Sequences"],"security":[{"bearer":[]}]},"delete":{"operationId":"SequencesController_remove","summary":"Soft-delete a sequence","description":"Sets deletedAt on the sequence record. The sequence disappears from listings but data is preserved for potential recovery.","parameters":[{"name":"id","required":true,"in":"path","description":"Sequence ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Sequence soft-deleted"},"403":{"description":"Access denied"},"404":{"description":"Sequence not found"}},"tags":["Sequences"],"security":[{"bearer":[]}]}},"/v1/ttrpg":{"post":{"operationId":"TtrpgController_create","summary":"Create an AI tabletop adventure generation job","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTtrpgAdventureDto"}}}},"responses":{"201":{"description":"Adventure queued for generation"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"get":{"operationId":"TtrpgController_findAll","summary":"List current user's AI tabletop adventures","parameters":[],"responses":{"200":{"description":"Adventure list"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/sessions/{sessionId}":{"get":{"operationId":"TtrpgController_getSession","summary":"Get an active AI-DM play session","parameters":[{"name":"sessionId","required":true,"in":"path","description":"TTRPG session ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Session with transcript"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/sessions/{sessionId}/turns":{"post":{"operationId":"TtrpgController_sendTurn","summary":"Send a player action to the AI-DM","parameters":[{"name":"sessionId","required":true,"in":"path","description":"TTRPG session ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTtrpgTurnDto"}}}},"responses":{"201":{"description":"Player and AI-DM messages created"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/characters":{"post":{"operationId":"TtrpgController_createCharacter","summary":"Create a reusable player character (AI sheet + portrait, async)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTtrpgCharacterDto"}}}},"responses":{"201":{"description":"Character queued for generation"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"get":{"operationId":"TtrpgController_listCharacters","summary":"List the current user's player characters","parameters":[],"responses":{"200":{"description":"Character list"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/characters/{characterId}":{"get":{"operationId":"TtrpgController_getCharacter","summary":"Get a player character by ID","parameters":[{"name":"characterId","required":true,"in":"path","description":"Character ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Character details"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"patch":{"operationId":"TtrpgController_updateCharacter","summary":"Update a character (name / sheet text, free)","parameters":[{"name":"characterId","required":true,"in":"path","description":"Character ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTtrpgCharacterDto"}}}},"responses":{"200":{"description":"Updated character"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"delete":{"operationId":"TtrpgController_removeCharacter","summary":"Soft-delete a player character","parameters":[{"name":"characterId","required":true,"in":"path","description":"Character ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Character deleted"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/characters/{characterId}/regenerate-portrait":{"post":{"operationId":"TtrpgController_regenerateCharacterPortrait","summary":"Regenerate a character portrait + token (1 credit)","parameters":[{"name":"characterId","required":true,"in":"path","description":"Character ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated character with new art"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/scenes/{index}":{"patch":{"operationId":"TtrpgController_updateScene","summary":"Update scene text fields (free, no image regeneration)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"index","required":true,"in":"path","description":"Zero-based scene index","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTtrpgSceneDto"}}}},"responses":{"200":{"description":"Updated adventure"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"delete":{"operationId":"TtrpgController_deleteScene","summary":"Delete a scene (free; minimum 1 scene enforced)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"index","required":true,"in":"path","description":"Zero-based scene index","schema":{"type":"number"}}],"responses":{"200":{"description":"Updated adventure"},"400":{"description":"Cannot delete the last scene"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/scenes/{index}/regenerate-map":{"post":{"operationId":"TtrpgController_regenerateSceneMap","summary":"Regenerate the battlemap for a scene (costs 1 credit)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"index","required":true,"in":"path","description":"Zero-based scene index","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateTtrpgSceneMapDto"}}}},"responses":{"200":{"description":"Updated adventure with new map"},"400":{"description":"Adventure not completed yet"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/scenes":{"post":{"operationId":"TtrpgController_addScene","summary":"Add a new scene with a generated battlemap (costs 1 credit)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTtrpgSceneDto"}}}},"responses":{"201":{"description":"Updated adventure with new scene"},"400":{"description":"Adventure not completed yet"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/npcs/{index}":{"patch":{"operationId":"TtrpgController_updateNpc","summary":"Update NPC text fields (free, no image regeneration)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"index","required":true,"in":"path","description":"Zero-based NPC index","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTtrpgNpcDto"}}}},"responses":{"200":{"description":"Updated adventure"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"delete":{"operationId":"TtrpgController_deleteNpc","summary":"Delete an NPC (free)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"index","required":true,"in":"path","description":"Zero-based NPC index","schema":{"type":"number"}}],"responses":{"200":{"description":"Updated adventure"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/npcs/{index}/regenerate-art":{"post":{"operationId":"TtrpgController_regenerateNpcArt","summary":"Regenerate portrait and token for an NPC (costs 1 credit)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"index","required":true,"in":"path","description":"Zero-based NPC index","schema":{"type":"number"}}],"responses":{"200":{"description":"Updated adventure with new NPC art"},"400":{"description":"Adventure not completed yet"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/npcs":{"post":{"operationId":"TtrpgController_addNpc","summary":"Add a new NPC with generated portrait and token (costs 1 credit)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTtrpgNpcDto"}}}},"responses":{"201":{"description":"Updated adventure with new NPC"},"400":{"description":"Adventure not completed yet"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/regenerate-cover":{"post":{"operationId":"TtrpgController_regenerateCover","summary":"Regenerate the cinematic cover / key art (1 credit)","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated adventure with new cover"},"402":{"description":"Insufficient credits"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/party":{"get":{"operationId":"TtrpgController_getParty","summary":"List the characters in an adventure's party","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Party characters"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/party/{characterId}":{"post":{"operationId":"TtrpgController_addPartyMember","summary":"Add a character to an adventure party","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"characterId","required":true,"in":"path","description":"Character ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated party"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"delete":{"operationId":"TtrpgController_removePartyMember","summary":"Remove a character from an adventure party","parameters":[{"name":"id","required":true,"in":"path","description":"Adventure ID","schema":{"type":"string"}},{"name":"characterId","required":true,"in":"path","description":"Character ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated party"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/sessions":{"post":{"operationId":"TtrpgController_startSession","summary":"Start or resume an AI-DM play session for an adventure","parameters":[{"name":"id","required":true,"in":"path","description":"TTRPG adventure ID","schema":{"type":"string"}}],"responses":{"201":{"description":"Active session with opening message"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}/export":{"get":{"operationId":"TtrpgController_exportAdventure","summary":"Export an adventure as JSON or plain text","parameters":[{"name":"id","required":true,"in":"path","description":"TTRPG adventure ID","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"enum":["json","text"],"type":"string"}}],"responses":{"200":{"description":"Adventure export file"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/ttrpg/{id}":{"get":{"operationId":"TtrpgController_findOne","summary":"Get an AI tabletop adventure by ID","parameters":[{"name":"id","required":true,"in":"path","description":"TTRPG adventure ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Adventure details"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]},"delete":{"operationId":"TtrpgController_remove","summary":"Soft-delete an AI tabletop adventure","parameters":[{"name":"id","required":true,"in":"path","description":"TTRPG adventure ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Adventure deleted"}},"tags":["AI Tabletop"],"security":[{"bearer":[]}]}},"/v1/vfx/capabilities":{"get":{"operationId":"VfxController_capabilities","summary":"Get available VFX generation modes and pricing","parameters":[],"responses":{"200":{"description":""}},"tags":["VFX Studio"],"security":[{"bearer":[]}]}},"/v1/vfx/ai":{"post":{"operationId":"VfxController_createAi","summary":"Create an AI visual effect (6 credits, eight frames, idempotent request ID)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAiVfxDto"}}}},"responses":{"201":{"description":""}},"tags":["VFX Studio"],"security":[{"bearer":[]}]}},"/v1/vfx":{"post":{"operationId":"VfxController_create","summary":"Export a transparent VFX pack (2 credits, idempotent request ID)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVfxDto"}}}},"responses":{"201":{"description":""}},"tags":["VFX Studio"],"security":[{"bearer":[]}]},"get":{"operationId":"VfxController_list","summary":"List your VFX exports","parameters":[],"responses":{"200":{"description":""}},"tags":["VFX Studio"],"security":[{"bearer":[]}]}},"/v1/vfx/suggest":{"post":{"operationId":"VfxController_suggest","summary":"Match a description to effect settings","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VfxPromptDto"}}}},"responses":{"201":{"description":""}},"tags":["VFX Studio"],"security":[{"bearer":[]}]}},"/v1/vfx/style-profile":{"get":{"operationId":"VfxController_getStyleProfile","summary":"Get a reference sprite's style profile (palette/outline/pixelSize) for the style lock preview","parameters":[{"name":"spriteId","required":true,"in":"query","description":"Reference sprite to extract a style profile from. Must belong to the caller.","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["VFX Studio"],"security":[{"bearer":[]}]}},"/v1/vfx/{id}":{"get":{"operationId":"VfxController_get","summary":"Get your VFX export status and files","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["VFX Studio"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/token":{"post":{"operationId":"DndToolsController_createToken","summary":"Create a VTT token from an image upload or an AI prompt","description":"Multipart upload is free (no AI); a prompt-generated token costs 1 credit.","parameters":[],"requestBody":{"required":true,"description":"Multipart form: image file + optional fields, OR JSON body with prompt/style","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"binary","description":"Source image (PNG/JPG/WebP, max 8 MB)"},"prompt":{"type":"string"},"style":{"type":"string"},"border":{"type":"string"},"size":{"type":"number"},"shape":{"type":"string"}}}},"application/json":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"binary","description":"Source image (PNG/JPG/WebP, max 8 MB)"},"prompt":{"type":"string"},"style":{"type":"string"},"border":{"type":"string"},"size":{"type":"number"},"shape":{"type":"string"}}}}}},"responses":{"202":{"description":"Token job queued"},"402":{"description":"Insufficient credits"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/portrait":{"post":{"operationId":"DndToolsController_createPortrait","summary":"Generate a character portrait by prompt (includes a token)","description":"Costs 1 credit. Result includes portraitUrl and tokenUrl.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDndPortraitDto"}}}},"responses":{"202":{"description":"Portrait job queued"},"402":{"description":"Insufficient credits"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/battle-map":{"post":{"operationId":"DndToolsController_createBattleMap","summary":"Generate a top-down battle map with an optional grid and .dd2vtt export","description":"Costs 2 credits.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDndBattleMapDto"}}}},"responses":{"202":{"description":"Battle map job queued"},"402":{"description":"Insufficient credits"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/npc":{"post":{"operationId":"DndToolsController_createNpc","summary":"Generate an NPC (free text, optional +2-credit art)","description":"Text-only requests answer 200 synchronously and count against the daily free-text limit. With includeArt=true they return a 202 job like the image tools and are charged credits instead.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDndNpcDto"}}}},"responses":{"200":{"description":"NPC text result (no art requested)"},"202":{"description":"NPC job queued (art requested)"},"402":{"description":"Insufficient credits (art requested)"},"429":{"description":"Daily free-text limit reached (no art requested)"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/encounter":{"post":{"operationId":"DndToolsController_createEncounter","summary":"Generate a combat encounter (free, SRD monsters only)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDndEncounterDto"}}}},"responses":{"200":{"description":"Encounter result"},"429":{"description":"Daily free-text limit reached"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/monster":{"post":{"operationId":"DndToolsController_createMonster","summary":"Generate a homebrew monster stat block (free, SRD-safe)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDndMonsterDto"}}}},"responses":{"200":{"description":"Monster result"},"429":{"description":"Daily free-text limit reached"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/jobs/{jobId}":{"get":{"operationId":"DndToolsController_getJob","summary":"Poll a DnD Tools job by ID (caller must own the job)","parameters":[{"name":"jobId","required":true,"in":"path","description":"Job ID returned at creation","schema":{"type":"string"}}],"responses":{"200":{"description":"Job status/result"},"403":{"description":"Job exists but is owned by another user"},"404":{"description":"Job not found"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/quota":{"get":{"operationId":"DndToolsController_getQuota","summary":"Get the caller tier, size limits, credit costs, and remaining daily free-text quota","parameters":[],"responses":{"200":{"description":"Tier, limits, credits and quota status"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/history":{"get":{"operationId":"DndToolsController_getHistory","summary":"List the current user's last 50 completed DnD Tools jobs","parameters":[],"responses":{"200":{"description":"Job history"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/tools/dnd/foundry-package":{"post":{"operationId":"DndToolsController_foundryPackage","summary":"Bundle completed jobs into a Foundry VTT import ZIP","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoundryPackageDto"}}}},"responses":{"200":{"description":"ZIP archive"},"400":{"description":"No completed jobs found for the given IDs"}},"tags":["DnD Tools"],"security":[{"bearer":[]}]}},"/v1/roblox-packs":{"post":{"operationId":"RobloxPackController_create","summary":"Create a Roblox Creator Pack or standalone classic avatar outfit (idempotent request ID)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRobloxPackDto"}}}},"responses":{"201":{"description":""}},"tags":["Roblox Creator Pack"],"security":[{"bearer":[]}]},"get":{"operationId":"RobloxPackController_list","summary":"List your Roblox Creator Packs","parameters":[],"responses":{"200":{"description":""}},"tags":["Roblox Creator Pack"],"security":[{"bearer":[]}]}},"/v1/roblox-packs/{id}":{"get":{"operationId":"RobloxPackController_get","summary":"Get a Roblox Creator Pack status and item assets","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Roblox Creator Pack"],"security":[{"bearer":[]}]}},"/v1/roblox-packs/{id}/items/{item}/regenerate":{"post":{"operationId":"RobloxPackController_regenerateItem","summary":"Regenerate a single item (icon, thumbnail, uiKit or vfx) for the cost of that item alone (idempotent request ID)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"item","required":true,"in":"path","schema":{"enum":["icon","thumbnail","uiKit","clothing","vfx"],"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateRobloxPackItemDto"}}}},"responses":{"201":{"description":""}},"tags":["Roblox Creator Pack"],"security":[{"bearer":[]}]}},"/v1/roblox-packs/{id}/items/uiKit/fix-hud-extraction":{"post":{"operationId":"RobloxPackController_fixHudExtraction","summary":"One-off, no-charge fix for HUD icon crop bleed: reprocesses the 8 already-stored HUD icons in place with connected-component isolation and patches the ZIP. Does not call Gemini.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Roblox Creator Pack"],"security":[{"bearer":[]}]}},"/v1/roblox-packs/{id}/download":{"get":{"operationId":"RobloxPackController_download","summary":"Download the pack ZIP (authenticated, owner only)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"ZIP file download"}},"tags":["Roblox Creator Pack"],"security":[{"bearer":[]}]}},"/v1/roblox-packs/{id}/starter-project":{"get":{"operationId":"RobloxPackController_starterProject","summary":"Download a playable Roblox Studio Rojo starter from your completed Creator Pack.","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Roblox Creator Pack"],"security":[{"bearer":[]}]}},"/v1/roblox-ready-packs":{"get":{"operationId":"RobloxReadyPacksController_list","parameters":[{"name":"genre","required":true,"in":"query","schema":{"type":"string"}},{"name":"featured","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Roblox Ready Packs"]}},"/v1/roblox-ready-packs/{slug}":{"get":{"operationId":"RobloxReadyPacksController_detail","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Roblox Ready Packs"]}},"/v1/roblox-ready-packs/{slug}/download":{"get":{"operationId":"RobloxReadyPacksController_download","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Roblox Ready Packs"],"security":[{"bearer":[]}]}},"/v1/roblox-ready-packs/{slug}/starter-project":{"get":{"operationId":"RobloxReadyPacksController_starterProject","summary":"Download a playable Rojo v7 Roblox Studio starter project for a published Ready Pack.","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Roblox Ready Packs"],"security":[{"bearer":[]}]}},"/v1/roblox-ready-packs/{slug}/prototype":{"post":{"operationId":"RobloxReadyPacksController_prototype","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReadyPackPrototypeDto"}}}},"responses":{"201":{"description":""}},"tags":["Roblox Ready Packs"],"security":[{"bearer":[]}]}},"/v1/admin/roblox-ready-packs":{"post":{"operationId":"RobloxReadyPacksAdminController_create","summary":"Owner-only: curate a completed source Roblox pack into a Ready Pack draft.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRobloxReadyPackDto"}}}},"responses":{"201":{"description":""}},"tags":["Admin: Roblox Ready Packs"],"security":[{"bearer":[]}]}},"/v1/admin/roblox-ready-packs/{id}":{"patch":{"operationId":"RobloxReadyPacksAdminController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRobloxReadyPackDto"}}}},"responses":{"200":{"description":""}},"tags":["Admin: Roblox Ready Packs"],"security":[{"bearer":[]}]}},"/v1/admin/roblox-ready-packs/{id}/publish":{"post":{"operationId":"RobloxReadyPacksAdminController_publish","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Admin: Roblox Ready Packs"],"security":[{"bearer":[]}]}},"/v1/admin/drops":{"post":{"operationId":"DropsAdminController_create","summary":"Owner-only: queue generation of a new drop (idempotent via requestId)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDropDto"}}}},"responses":{"201":{"description":"Drop created (or resumed) and generation queued"}},"tags":["Admin: Drops"],"security":[{"bearer":[]}]},"get":{"operationId":"DropsAdminController_list","summary":"Owner-only: list every drop (draft + published)","parameters":[],"responses":{"200":{"description":""}},"tags":["Admin: Drops"],"security":[{"bearer":[]}]}},"/v1/admin/drops/{id}":{"get":{"operationId":"DropsAdminController_get","summary":"Owner-only: drop generation status, item checkpoints, cost tracking","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Admin: Drops"],"security":[{"bearer":[]}]}},"/v1/admin/drops/{id}/publish":{"post":{"operationId":"DropsAdminController_publish","summary":"Owner-only: publish a drop (requires generation to be complete)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["Admin: Drops"],"security":[{"bearer":[]}]}},"/v1/admin/drops/{id}/regenerate":{"post":{"operationId":"DropsAdminController_regenerate","summary":"Owner-only: reset and regenerate a subset of item checkpoints (e.g. just the 6 VFX), then reassemble the ZIPs","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateDropItemsDto"}}}},"responses":{"201":{"description":""}},"tags":["Admin: Drops"],"security":[{"bearer":[]}]}},"/v1/drops":{"get":{"operationId":"DropsController_list","summary":"List published drops (public)","parameters":[],"responses":{"200":{"description":""}},"tags":["Drops"]}},"/v1/drops/{slug}":{"get":{"operationId":"DropsController_detail","summary":"Drop detail (public), includes hasAccess when authenticated","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Drops"]}},"/v1/drops/{slug}/manifest-url":{"get":{"operationId":"DropsController_manifestUrl","summary":"Foundry-installable manifest URL, gated by entitlement","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Drops"],"security":[{"bearer":[]}]}},"/v1/drops/{slug}/download/module":{"get":{"operationId":"DropsController_downloadModule","summary":"Download the full Foundry module ZIP (gated by entitlement)","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Drops"],"security":[{"bearer":[]}]}},"/v1/drops/{slug}/download/loose":{"get":{"operationId":"DropsController_downloadLoose","summary":"Download the loose assets ZIP (Roll20/Owlbear), gated by entitlement","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Drops"],"security":[{"bearer":[]}]}},"/v1/drops/{slug}/download/sample":{"get":{"operationId":"DropsController_downloadSample","summary":"Download the free sample map ZIP, no login required","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Drops"]}},"/v1/drops/dl/{token}/module.json":{"get":{"operationId":"DropsManifestController_manifest","summary":"Foundry manifest JSON for a drop module, resolved by manifestToken","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Drops (Foundry manifest)"]}},"/v1/drops/dl/{token}/module.zip":{"get":{"operationId":"DropsManifestController_zip","summary":"Foundry module ZIP for a drop, resolved by manifestToken","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Drops (Foundry manifest)"]}},"/v1/jobs":{"get":{"operationId":"JobsController_list","summary":"List the authenticated user generation inbox","parameters":[],"responses":{"200":{"description":""}},"tags":["Generation jobs"],"security":[{"bearer":[]}]}},"/v1/jobs/sync":{"post":{"operationId":"JobsController_sync","summary":"Reconcile the durable inbox and return its latest state","parameters":[],"responses":{"201":{"description":""}},"tags":["Generation jobs"],"security":[{"bearer":[]}]}},"/v1/jobs/{id}/dismiss":{"patch":{"operationId":"JobsController_dismiss","summary":"Dismiss a completed or failed inbox item","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Generation jobs"],"security":[{"bearer":[]}]}},"/v1/jobs/terminal/dismiss-all":{"patch":{"operationId":"JobsController_dismissAllTerminal","summary":"Dismiss all completed and failed inbox items","parameters":[],"responses":{"200":{"description":""}},"tags":["Generation jobs"],"security":[{"bearer":[]}]}}},"info":{"title":"Sprixen API","description":"Sprixen AI Game Asset Generator, REST API & MCP Server","version":"1.0","contact":{}},"tags":[],"servers":[{"url":"https://api.sprixen.com","description":"Production"},{"url":"http://localhost:8080","description":"Local Development"}],"components":{"securitySchemes":{"api-key":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"Sprixen API key (spx_live_...)","name":"Authorization","in":"header"},"cookie":{"type":"apiKey","in":"cookie","name":"sprixen_session"}},"schemas":{"CreateApiKeyDto":{"type":"object","properties":{"name":{"type":"string","example":"My Production Key","description":"A label to identify this API key"}},"required":["name"]},"ApiKeyCreatedResponseDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","example":"My Production Key"},"keyPrefix":{"type":"string","example":"spx_live_a1b2"},"lastUsedAt":{"type":"string","nullable":true},"revokedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"},"key":{"type":"string","example":"spx_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8","description":"The full API key. This will only be shown once."}},"required":["id","name","keyPrefix","lastUsedAt","revokedAt","createdAt","key"]},"ApiKeyResponseDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","example":"My Production Key"},"keyPrefix":{"type":"string","example":"spx_live_a1b2"},"lastUsedAt":{"type":"string","nullable":true},"revokedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}},"required":["id","name","keyPrefix","lastUsedAt","revokedAt","createdAt"]},"TrackEventDto":{"type":"object","properties":{"event":{"type":"string","example":"playground_game_started"},"properties":{"type":"object","example":{"map":"super-mario","sprite_id":"abc123"}}},"required":["event"]},"TrackAnonymousEventDto":{"type":"object","properties":{"anonymousId":{"type":"string","example":"anon_01H4Z9"},"event":{"type":"string","example":"ad_landing_view"},"properties":{"type":"object"},"attribution":{"type":"object","example":{"utm_source":"google","utm_medium":"cpc","gclid":"CjwKCAi..."}}},"required":["anonymousId","event"]},"UpdateUserDto":{"type":"object","properties":{"name":{"type":"string","example":"John Doe"},"avatarUrl":{"type":"string","example":"https://example.com/avatar.png"}}},"CreateProjectDto":{"type":"object","properties":{"name":{"type":"string","example":"My Fantasy RPG"},"description":{"type":"string","example":"Assets for my fantasy RPG game"},"resolution":{"type":"string","example":"128x128","enum":["16x16","32x32","64x64","128x128","256x256","512x512"],"default":"128x128"},"artStyle":{"type":"string","example":"pixel_art","enum":["pixel_art","cartoon","anime","hand_drawn","chibi"],"default":"pixel_art"},"palette":{"type":"object","description":"Custom color palette — array of hex color strings (e.g. [\"#FF5733\",\"#2ECC71\"]) or a preset name string (e.g. \"nes\")","example":["#FF5733","#2ECC71","#3498DB"]},"viewAngle":{"type":"string","example":"side_scroll","enum":["side_scroll","top_down","isometric","front_facing"],"default":"side_scroll"},"pixelPerfect":{"type":"string","example":"auto","enum":["auto","on","off"],"default":"auto","description":"Pixel Perfect resize mode. \"auto\" (recommended) picks the right algorithm based on artStyle + resolution. \"on\" forces crispy nearest-neighbor (retro pixel art, Gungeon/NES style). \"off\" forces smooth Lanczos (cartoon, anime, higher-res art). Nearest-neighbor is NOT universally better - it destroys fine detail at higher resolutions."},"referenceImageUrl":{"type":"string","example":"https://example.com/reference.png"}},"required":["name"]},"UpdateProjectDto":{"type":"object","properties":{}},"CreateBlankSpriteDto":{"type":"object","properties":{"name":{"type":"string","example":"New Sprite"}}},"UpdateSpriteDto":{"type":"object","properties":{"name":{"type":"string","example":"Knight Hero"}}},"ChainAnimationDto":{"type":"object","properties":{"type":{"type":"string","example":"idle","enum":["idle","walk","run","attack","jump","death","hurt","custom"],"description":"Animation type to generate automatically after sprite completes"},"frameCount":{"type":"number","example":8,"description":"Number of frames (defaults per type if omitted)"},"fps":{"type":"number","example":12,"description":"Frames per second (default 12)"},"direction":{"type":"string","example":"down","enum":["right","left","up","down","up-right","up-left","down-right","down-left"],"description":"Facing direction for top-down/isometric games. When omitted Gemini picks a default (typically right side view). Use this with `chainAnimations: []` to chain the same animation type in multiple directions."},"resolution":{"type":"string","example":"28x32","description":"Override resolution for this chained animation (e.g. \"28x32\"). If omitted, falls back to project resolution. Match this with the sprite's resolution (when CreateGenerationDto.resolution was set) so the animation preserves the same chunky pixel-art style as the base sprite instead of being upscaled."},"idlePreset":{"type":"string","example":"lively","enum":["breath","lively","looking","item"],"description":"Pre-baked idle style. Only honored when type=\"idle\". Translates to a customDescription internally."},"quality":{"type":"string","example":"standard","enum":["standard","premium"],"description":"Animation quality. \"standard\" is a Gemini sprite sheet. \"premium\" produces a richer, higher-frame animation (costs more credits). Defaults to \"standard\"."}},"required":["type"]},"CreateGenerationDto":{"type":"object","properties":{"prompt":{"type":"string","example":"knight warrior with sword and shield","description":"Describe the sprite to generate. Max 6000 characters."},"projectId":{"type":"string","example":"proj_abc123","description":"Project ID to use style config from (optional)"},"type":{"type":"string","example":"sprite","enum":["sprite","variation"],"default":"sprite"},"referenceImageUrl":{"type":"string","example":"/api/public/uploads/generations/user123/gen456_0.png","description":"Optional reference image URL or path to guide style (passed to Gemini)"},"webhookUrl":{"type":"string","example":"https://your-server.com/webhooks/sprixen","description":"Optional webhook URL — receives a POST when generation completes or fails"},"variantCount":{"type":"number","example":1,"description":"Number of sprite variants to generate (1-3). Each variant costs 2 credits. Locked to 1 when chainAnimation is used.","default":1,"minimum":1,"maximum":3},"chainAnimation":{"description":"When provided, the sprite generation automatically chains into an animation job. Requires projectId to be set. Costs 6 credits instead of 2.","allOf":[{"$ref":"#/components/schemas/ChainAnimationDto"}]},"chainAnimations":{"description":"Multiple animation types to chain after sprite completes. Use instead of chainAnimation when you want multiple animations. Requires projectId. Costs 4 credits per animation.","type":"array","items":{"$ref":"#/components/schemas/ChainAnimationDto"}},"skipBgRemoval":{"type":"boolean","description":"Skip background removal (RMBG-2.0). Use for full-scene images like game backgrounds where bg removal would damage the image.","default":false},"resolution":{"type":"string","description":"Override resolution for this generation (e.g. \"256x256\"). If not provided, uses the project resolution or the default 128x128.","example":"256x256"},"noBase":{"type":"boolean","description":"When true, adds \"NO base, NO pedestal, NO ground plane, NO ground shadow\" to the prompt. Useful for character sprites that should float freely without any surface underneath.","default":false},"packItems":{"description":"For pack generation: list of items to generate in a single sheet. Each item becomes a separate sprite. Max 8 items.","example":["wall torch","vine roots","stone crack","moss patch"],"type":"array","items":{"type":"string"}},"packType":{"type":"string","description":"For pack generation: prompt contract preset for generic items/props, ability icons, or HUD/UI pieces.","enum":["items_props","ability_icons","hud_ui"],"example":"hud_ui"},"pixelPerfect":{"type":"string","description":"Pixel Perfect resize mode for this generation (overrides project setting). \"auto\" (recommended): system picks the right algorithm based on artStyle + resolution. \"on\": forces crispy nearest-neighbor kernel (retro pixel art, Gungeon/NES-style). \"off\": forces smooth Lanczos kernel (cartoon, anime, higher-res art). IMPORTANT: nearest-neighbor is NOT universally \"better\" - it destroys fine detail at higher resolutions. Use \"auto\" unless you have a specific reason.","enum":["auto","on","off"],"example":"auto"}},"required":["prompt"]},"SelectVariationDto":{"type":"object","properties":{"generationId":{"type":"string","example":"gen_abc123","description":"ID of the Generation to select a variation from"},"variationId":{"type":"string","example":"var_xyz456","description":"ID of the specific variation (resultUrl index) to select"},"spriteName":{"type":"string","example":"Knight Hero","description":"Name to give the new Sprite record"}},"required":["generationId","variationId","spriteName"]},"CreateAnimationDto":{"type":"object","properties":{"type":{"type":"string","example":"idle","enum":["idle","walk","run","attack","jump","death","hurt","custom"],"description":"Animation type"},"frameCount":{"type":"number","example":4,"description":"Number of frames (defaults per type: idle=4, walk=6, run=6, attack=4)"},"quality":{"type":"string","example":"standard","enum":["standard","premium"],"description":"Generation quality mode. standard creates a compact sprite sheet with Gemini image generation. premium uses Veo image-to-video and converts all video frames into a transparent sprite animation."},"fps":{"type":"number","example":12,"description":"Frames per second (default 12)"},"customDescription":{"type":"string","example":"A warrior swinging a sword in a wide arc","description":"Optional motion guidance for the animation. Max 500 characters."},"archetype":{"type":"string","example":"humanoid_biped","enum":["humanoid_biped","quadruped","winged","amorphous","serpentine"],"description":"Character body archetype. If provided, the backend uses it directly (no inference). If omitted, the backend uses sprite metadata if cached, otherwise runs a vision-based classifier. Values: humanoid_biped (two-legged upright), quadruped (four-legged), winged (flying), amorphous (slime/blob), serpentine (snake-like)."},"weaponHand":{"type":"string","example":"right","enum":["left","right","both"],"description":"Which hand holds the weapon. If provided, the prompt enforces weapon-hand consistency across frames. Omit if the character has no weapon."},"weaponType":{"type":"string","example":"axe","description":"Optional weapon type label (sword/axe/bow/staff/etc). Included in the prompt for better consistency."},"hasShield":{"type":"boolean","description":"True if the character carries a shield. The prompt enforces shield position consistency."},"locomotionType":{"type":"string","example":"auto","enum":["auto","biped","quadruped"],"deprecated":true,"description":"DEPRECATED: use `archetype` instead. Legacy field — only maps to humanoid_biped or quadruped."},"direction":{"type":"string","example":"right","enum":["right","left","up","down","up-right","up-left","down-right","down-left"],"description":"Facing direction of the character. Cardinal: \"right\", \"left\", \"up\", \"down\". Diagonal: \"up-right\", \"up-left\", \"down-right\", \"down-left\" (isometric 45-degree angles)."},"normalizeSize":{"type":"boolean","default":false,"description":"If true, normalizes the animation frame sizes to match the base sprite dimensions. Ensures consistent character size across all animation directions."},"resolution":{"type":"string","example":"28x32","description":"Override resolution for this animation (e.g. \"28x32\"). Each frame in the spritesheet will be sized to this. If omitted, falls back to the project resolution. Use this when the base sprite was generated with a resolution override (CreateGenerationDto.resolution) and the animation needs to match that style/scale instead of the broader project default."},"idlePreset":{"type":"string","example":"lively","enum":["breath","lively","looking","item"],"description":"Pre-baked idle style. Only honored when type=\"idle\". The backend translates the chosen preset into a `customDescription` internally — pick a preset for one-click flavor, or pass `customDescription` for full control. If both are set, customDescription wins."}},"required":["type"]},"CreateAnimationBatchDto":{"type":"object","properties":{"type":{"type":"string","example":"idle","enum":["idle","walk","run","attack","jump","death","hurt","custom"],"description":"Animation type"},"frameCount":{"type":"number","example":4,"description":"Number of frames (defaults per type: idle=4, walk=6, run=6, attack=4)"},"quality":{"type":"string","example":"standard","enum":["standard","premium"],"description":"Generation quality mode. standard creates a compact sprite sheet with Gemini image generation. premium uses Veo image-to-video and converts all video frames into a transparent sprite animation."},"fps":{"type":"number","example":12,"description":"Frames per second (default 12)"},"customDescription":{"type":"string","example":"A warrior swinging a sword in a wide arc","description":"Optional motion guidance for the animation. Max 500 characters."},"archetype":{"type":"string","example":"humanoid_biped","enum":["humanoid_biped","quadruped","winged","amorphous","serpentine"],"description":"Character body archetype. If provided, the backend uses it directly (no inference). If omitted, the backend uses sprite metadata if cached, otherwise runs a vision-based classifier. Values: humanoid_biped (two-legged upright), quadruped (four-legged), winged (flying), amorphous (slime/blob), serpentine (snake-like)."},"weaponHand":{"type":"string","example":"right","enum":["left","right","both"],"description":"Which hand holds the weapon. If provided, the prompt enforces weapon-hand consistency across frames. Omit if the character has no weapon."},"weaponType":{"type":"string","example":"axe","description":"Optional weapon type label (sword/axe/bow/staff/etc). Included in the prompt for better consistency."},"hasShield":{"type":"boolean","description":"True if the character carries a shield. The prompt enforces shield position consistency."},"locomotionType":{"type":"string","example":"auto","enum":["auto","biped","quadruped"],"deprecated":true,"description":"DEPRECATED: use `archetype` instead. Legacy field — only maps to humanoid_biped or quadruped."},"direction":{"type":"string","example":"right","enum":["right","left","up","down","up-right","up-left","down-right","down-left"],"description":"Facing direction of the character. Cardinal: \"right\", \"left\", \"up\", \"down\". Diagonal: \"up-right\", \"up-left\", \"down-right\", \"down-left\" (isometric 45-degree angles)."},"normalizeSize":{"type":"boolean","default":false,"description":"If true, normalizes the animation frame sizes to match the base sprite dimensions. Ensures consistent character size across all animation directions."},"resolution":{"type":"string","example":"28x32","description":"Override resolution for this animation (e.g. \"28x32\"). Each frame in the spritesheet will be sized to this. If omitted, falls back to the project resolution. Use this when the base sprite was generated with a resolution override (CreateGenerationDto.resolution) and the animation needs to match that style/scale instead of the broader project default."},"idlePreset":{"type":"string","example":"lively","enum":["breath","lively","looking","item"],"description":"Pre-baked idle style. Only honored when type=\"idle\". The backend translates the chosen preset into a `customDescription` internally — pick a preset for one-click flavor, or pass `customDescription` for full control. If both are set, customDescription wins."},"outputCount":{"type":"number","example":3,"description":"Number of independent animation takes to generate for the same sprite (1-3). Each standard output costs 4 credits and is saved as a separate Animation record. Premium animation is limited to 1 output.","minimum":1,"maximum":3,"default":1}},"required":["type"]},"CreateAnimationWithSpriteDto":{"type":"object","properties":{"type":{"type":"string","example":"idle","enum":["idle","walk","run","attack","jump","death","hurt","custom"],"description":"Animation type"},"frameCount":{"type":"number","example":4,"description":"Number of frames (defaults per type: idle=4, walk=6, run=6, attack=4)"},"quality":{"type":"string","example":"standard","enum":["standard","premium"],"description":"Generation quality mode. standard creates a compact sprite sheet with Gemini image generation. premium uses Veo image-to-video and converts all video frames into a transparent sprite animation."},"fps":{"type":"number","example":12,"description":"Frames per second (default 12)"},"customDescription":{"type":"string","example":"A warrior swinging a sword in a wide arc","description":"Optional motion guidance for the animation. Max 500 characters."},"archetype":{"type":"string","example":"humanoid_biped","enum":["humanoid_biped","quadruped","winged","amorphous","serpentine"],"description":"Character body archetype. If provided, the backend uses it directly (no inference). If omitted, the backend uses sprite metadata if cached, otherwise runs a vision-based classifier. Values: humanoid_biped (two-legged upright), quadruped (four-legged), winged (flying), amorphous (slime/blob), serpentine (snake-like)."},"weaponHand":{"type":"string","example":"right","enum":["left","right","both"],"description":"Which hand holds the weapon. If provided, the prompt enforces weapon-hand consistency across frames. Omit if the character has no weapon."},"weaponType":{"type":"string","example":"axe","description":"Optional weapon type label (sword/axe/bow/staff/etc). Included in the prompt for better consistency."},"hasShield":{"type":"boolean","description":"True if the character carries a shield. The prompt enforces shield position consistency."},"locomotionType":{"type":"string","example":"auto","enum":["auto","biped","quadruped"],"deprecated":true,"description":"DEPRECATED: use `archetype` instead. Legacy field — only maps to humanoid_biped or quadruped."},"direction":{"type":"string","example":"right","enum":["right","left","up","down","up-right","up-left","down-right","down-left"],"description":"Facing direction of the character. Cardinal: \"right\", \"left\", \"up\", \"down\". Diagonal: \"up-right\", \"up-left\", \"down-right\", \"down-left\" (isometric 45-degree angles)."},"normalizeSize":{"type":"boolean","default":false,"description":"If true, normalizes the animation frame sizes to match the base sprite dimensions. Ensures consistent character size across all animation directions."},"resolution":{"type":"string","example":"28x32","description":"Override resolution for this animation (e.g. \"28x32\"). Each frame in the spritesheet will be sized to this. If omitted, falls back to the project resolution. Use this when the base sprite was generated with a resolution override (CreateGenerationDto.resolution) and the animation needs to match that style/scale instead of the broader project default."},"idlePreset":{"type":"string","example":"lively","enum":["breath","lively","looking","item"],"description":"Pre-baked idle style. Only honored when type=\"idle\". The backend translates the chosen preset into a `customDescription` internally — pick a preset for one-click flavor, or pass `customDescription` for full control. If both are set, customDescription wins."},"spriteId":{"type":"string","example":"sprite_abc123","description":"ID of the sprite to animate"}},"required":["type","spriteId"]},"ProcessAnimationDto":{"type":"object","properties":{"mode":{"type":"string","default":"aggressive","enum":["stabilize","tight","aggressive"],"description":"Non-destructive post-process mode. stabilize recenters/bottom-aligns frames without scaling. tight and aggressive crop alpha content and scale it back onto the same frame canvas."}}},"CreateTilesetDto":{"type":"object","properties":{"prompt":{"type":"string","example":"grassy meadow with wildflowers","description":"Description of the tileset content and style. Max 6000 characters.","maxLength":6000},"type":{"type":"string","example":"ground","enum":["ground","wall","decoration","transition"],"description":"Tileset category — controls top-down vs side perspective hints"},"tileSize":{"type":"number","example":32,"description":"Pixel size of each individual tile (default 32)"}},"required":["prompt","type"]},"CreateIsometricDto":{"type":"object","properties":{}},"CreateCheckoutDto":{"type":"object","properties":{"successUrl":{"type":"string","example":"http://localhost:3000/billing/success","description":"URL to redirect after successful payment"},"cancelUrl":{"type":"string","example":"http://localhost:3000/billing/cancel","description":"URL to redirect if checkout is cancelled"}}},"BuyCreditsDto":{"type":"object","properties":{"pack":{"type":"string","example":"200","description":"Credit pack size: 50, 150, 200, or 500","enum":["50","150","200","500"]},"successUrl":{"type":"string","example":"http://localhost:3000/billing/success","description":"URL to redirect after successful payment"},"cancelUrl":{"type":"string","example":"http://localhost:3000/billing/cancel","description":"URL to redirect if checkout is cancelled"}},"required":["pack"]},"CreateCustomTileDto":{"type":"object","properties":{"prompt":{"type":"string","description":"Prompt describing the tile texture. Max 6000 characters.","maxLength":6000},"tileLabel":{"type":"string","description":"Display name for the tile","maxLength":100},"collision":{"type":"string","enum":["walkable","solid","water"],"default":"walkable"},"projectionType":{"type":"string","enum":["isometric","top_down","side_view"],"default":"isometric"},"category":{"type":"string","enum":["terrain","decoration","entity"],"default":"terrain","description":"Tile category — affects positioning rules for side-view tiles"},"footprintW":{"type":"number","description":"Number of grid columns this tile occupies (multi-tile footprint width)","minimum":1,"maximum":10,"default":1},"footprintH":{"type":"number","description":"Number of grid rows this tile occupies (multi-tile footprint height)","minimum":1,"maximum":10,"default":1},"webhookUrl":{"type":"string","description":"Optional webhook URL — receives a POST when tile generation completes or fails","example":"https://your-server.com/webhooks/sprixen"},"flatOnly":{"type":"boolean","description":"If true, returns only the flat diamond top face without 3D side faces (isometric only)","default":false}},"required":["prompt"]},"CreateMapDto":{"type":"object","properties":{"name":{"type":"string","example":"My Fantasy Map"},"description":{"type":"string"},"gridWidth":{"type":"number","default":16,"minimum":1,"maximum":64},"gridHeight":{"type":"number","default":16,"minimum":1,"maximum":64},"layers":{"type":"object","description":"Initial layer data"},"tilePackSlugs":{"description":"Tile pack slugs used in this map","type":"array","items":{"type":"string"}},"projectionType":{"type":"string","enum":["isometric","top_down","side_view","world"],"default":"isometric","description":"Map projection type (locked after creation)"}},"required":["name","gridWidth","gridHeight"]},"UpdateMapDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"gridWidth":{"type":"number"},"gridHeight":{"type":"number"},"layers":{"type":"object"},"terrainWeights":{"type":"object","description":"Terrain weight map. Structure: { cells: [[{grass:1.0, dirt:0.0, ...}, ...], ...], materials: [\"grass\",\"dirt\",\"sand\",\"water\",\"stone\"] }"},"metadata":{"type":"object"},"thumbnailUrl":{"type":"string"},"tilePackSlugs":{"type":"array","items":{"type":"string"}}}},"AiEditMapDto":{"type":"object","properties":{"command":{"type":"string","example":"Fill the terrain with grass and add trees at the borders","description":"Natural language command for AI map editing","maxLength":6000}},"required":["command"]},"CreateFeedbackDto":{"type":"object","properties":{"category":{"type":"string","enum":["bug","feature_request","suggestion","help","other"],"example":"suggestion"},"message":{"type":"string","example":"It would be great to have sprite animation preview"}},"required":["category","message"]},"CreateParallaxDto":{"type":"object","properties":{"prompt":{"type":"string","description":"Scene description for the parallax background. Max 6000 characters.","example":"Enchanted forest at dusk with fireflies","maxLength":6000},"name":{"type":"string","description":"Display name for the background","example":"Enchanted Forest"},"artStyle":{"type":"string","description":"Art style to use for generation","default":"Fantasy pixel art / 16-bit era game aesthetic","example":"Fantasy pixel art / 16-bit era game aesthetic"},"layerCount":{"type":"number","description":"Number of parallax layers to generate (3-5)","default":4,"minimum":3,"maximum":5}},"required":["prompt"]},"CreateAnimation3dDto":{"type":"object","properties":{"prompt":{"type":"string","description":"Text prompt describing the desired animation motion","example":"A person walking forward at a normal pace with natural arm swing"},"presetSlug":{"type":"string","description":"Preset slug (walk, run, idle, attack, etc.). When set, the prompt is auto-filled from the preset library.","example":"walk"},"name":{"type":"string","description":"Human-readable name for the animation. Defaults to the preset label or a truncated prompt.","example":"Walk Cycle"},"animationType":{"type":"string","description":"Animation pipeline to use. \"auto\" (default) lets the system detect via rigging. \"humanoid\" forces skeletal animation (HY-Motion). \"non_humanoid\" forces vertex deformation (AnimateAnyMesh).","example":"auto","enum":["auto","humanoid","non_humanoid"]}},"required":["prompt"]},"CreateCharacterDto":{"type":"object","properties":{"baseSlug":{"type":"string","description":"Slug of the base library character (see GET /v1/character-studio/library), e.g. \"knight\". When fromModelId is set it must match the base of that model.","example":"knight"},"prompt":{"type":"string","description":"Free-text request describing the customization (colors, weapons, accessories, glow, hat...). The planner turns it into structured edit ops. With fromModelId it is applied on top of that customization.","example":"Make it a crimson fire knight: red armor with glowing orange accents, hold the one-handed axe and the round shield","minLength":3,"maxLength":6000},"name":{"type":"string","description":"Human-readable name. Defaults to the base character name plus the start of the prompt.","example":"Fire Knight"},"projectId":{"type":"string","description":"Project to associate the character with (must belong to you).","example":"cld1234..."},"fromModelId":{"type":"string","description":"Iterate on a previous customization: ID of one of your completed Character Studio models. Its edit ops are used as the starting point and the new prompt is applied on top.","example":"cld5678..."}},"required":["baseSlug","prompt"]},"CreateSpriteSheetDto":{"type":"object","properties":{"animationType":{"type":"string","description":"Sprixen animation type to render. Mapped to the base character action. Produces 8 directions (down, down-left, left, up-left, up, up-right, right, down-right).","enum":["idle","walk","run","attack","jump","death","hurt"],"example":"walk"},"frames":{"type":"number","description":"Frames per direction, 1 to 16. Default 8.","minimum":1,"maximum":16,"default":8},"size":{"type":"number","description":"Frame size in pixels (square), 64 to 256. Default 128. Downscale or pixelate on your side for smaller sprites.","minimum":64,"maximum":512,"default":128},"elevation":{"type":"number","description":"Camera elevation in degrees, 0 (side) to 60 (top-down tilt). Default 30.","minimum":0,"maximum":60,"default":30},"projectId":{"type":"string","description":"Project that receives the generated Sprite. Defaults to the model project, then to your first project.","example":"cld1234..."}},"required":["animationType"]},"CreateMusicDto":{"type":"object","properties":{"prompt":{"type":"string","description":"Text prompt describing the desired music","example":"Epic orchestral battle theme with dramatic strings and brass","maxLength":6000},"lyrics":{"type":"string","description":"Lyrics for vocal generation. Use [Verse], [Chorus], [Bridge], [Instrumental Break] tags. Leave empty for instrumental.","example":"[Verse]\nWalking through the neon lights\n[Chorus]\nWe are the future now"},"vocalLanguage":{"type":"string","description":"Vocal language ISO code (e.g. \"en\", \"ja\", \"ko\", \"zh\"). Only used when lyrics are provided.","example":"en"},"presetSlug":{"type":"string","description":"Preset slug (epic_orchestral, chiptune_adventure, etc.)","example":"epic_orchestral"},"name":{"type":"string","description":"Human-readable name for the track","example":"Battle Theme"},"genre":{"type":"string","description":"Desired genre tag","example":"orchestral"},"bpm":{"type":"number","description":"Beats per minute (60-200)","example":120},"key":{"type":"string","description":"Musical key (e.g. \"C minor\", \"F major\")","example":"C minor"},"duration":{"type":"number","description":"Desired duration in seconds (15-300)","example":30},"quality":{"type":"string","description":"Generation quality mode. Both run XL turbo (4B): \"fast\" = 8 steps (3 credits), \"quality\" = 16 steps (5 credits)","example":"fast"}},"required":["prompt"]},"CreateGameProjectDto":{"type":"object","properties":{"title":{"type":"string","example":"My Platformer","description":"Title for the game project"},"templateType":{"type":"string","enum":["platformer","metroidvania","topdown","isometric","tower-defense","dungeon-crawler","card-battle","rpg-adventure","planet-defense","infinite-jumper","horde-survivors","micro-factory","isometric-strategy","flappy-clone","isometric-rpg","enter-the-gungeon","platform-brawler"],"example":"platformer","description":"Starting template type"}}},"UpdateGameProjectDto":{"type":"object","properties":{"title":{"type":"string","example":"My Awesome Platformer","description":"Updated title"},"description":{"type":"string","example":"A fun platformer with custom sprites","description":"Short description of the game"}}},"CreateModProjectDto":{"type":"object","properties":{"title":{"type":"string","example":"My Police MDT","description":"Title for the mod project"},"platform":{"type":"string","enum":["fivem","roblox"],"example":"fivem","description":"Target platform"},"framework":{"type":"string","enum":["qbcore","esx","standalone","luau"],"example":"qbcore","description":"Framework (qbcore/esx/standalone for FiveM, luau for Roblox)"},"templateType":{"type":"string","example":"job-system","description":"Template type to initialize from"}},"required":["platform"]},"UpdateOnboardingDto":{"type":"object","properties":{"globalCompleted":{"type":"boolean","description":"Mark global onboarding as completed","example":true},"globalSkipped":{"type":"boolean","description":"Skip global onboarding (also marks it as completed)","example":false},"globalLastStepSeen":{"type":"number","description":"Index of the last onboarding step the user has seen","example":2},"completeFeatureTour":{"type":"string","description":"Slug of a feature tour to mark as completed (merged into featureToursCompleted map)","example":"map-builder"},"reset":{"type":"boolean","description":"Reset onboarding to initial state (step 0, not completed)","example":true}}},"CreateConversationDto":{"type":"object","properties":{"title":{"type":"string","example":"Fantasy RPG assets","description":"Optional title for the conversation"},"context":{"type":"string","example":"game-creator","description":"Conversation context (null for regular chat, \"game-creator\" for game creation)"},"gameProjectId":{"type":"string","example":"clx1234abcd","description":"Optional game project ID to link this conversation to"}}},"CreateLiveGameDto":{"type":"object","properties":{}},"UpdateLiveGameDto":{"type":"object","properties":{}},"UpdateLiveConfigDto":{"type":"object","properties":{}},"CreateLeaderboardDto":{"type":"object","properties":{}},"CreateAchievementDto":{"type":"object","properties":{}},"CreateInventoryItemDto":{"type":"object","properties":{}},"BootstrapLiveDto":{"type":"object","properties":{}},"SaveGameDto":{"type":"object","properties":{}},"SubmitScoreDto":{"type":"object","properties":{}},"GrantInventoryDto":{"type":"object","properties":{}},"TrackLiveEventDto":{"type":"object","properties":{}},"CreateLobbyDto":{"type":"object","properties":{}},"JoinQueueDto":{"type":"object","properties":{}},"FriendRequestDto":{"type":"object","properties":{}},"BanPlayerDto":{"type":"object","properties":{}},"MutePlayerDto":{"type":"object","properties":{}},"TransitionOutDto":{"type":"object","properties":{"type":{"type":"string","enum":["cut","hold","crossfade"],"example":"cut","description":"Transition type: cut (instant), hold (freeze a frame), crossfade (opacity blend)"},"durationMs":{"type":"number","example":200,"description":"Duration in ms — required for hold and crossfade, ignored for cut"},"holdFrameIndex":{"type":"number","example":7,"description":"For hold: which frame index to freeze (default: last frame)"}},"required":["type"]},"SequenceClipDto":{"type":"object","properties":{"id":{"type":"string","example":"c1","description":"Local clip ID — stable across reorders (drag-drop key)"},"animationId":{"type":"string","example":"clx_abc123","description":"ID of the Animation record this clip references"},"repeat":{"type":"number","example":2,"description":"How many times to repeat this clip. Use -1 for terminal infinite loop."},"speedMultiplier":{"type":"number","example":1,"description":"Speed multiplier for this clip. Default 1.0."},"transitionOut":{"description":"Transition to play after this clip ends","allOf":[{"$ref":"#/components/schemas/TransitionOutDto"}]}},"required":["id","animationId","repeat"]},"CreateSequenceDto":{"type":"object","properties":{"name":{"type":"string","example":"intro-to-attack","description":"Human-readable name for the sequence"},"loopMode":{"type":"string","enum":["forever","once","pingpong"],"default":"forever","description":"How the sequence loops: forever, once, or pingpong"},"clips":{"description":"Ordered list of clips that make up the sequence","type":"array","items":{"$ref":"#/components/schemas/SequenceClipDto"}},"metadata":{"type":"object","description":"Free-form metadata (tags, notes, etc.)","example":{"tags":["combat","boss"]}}},"required":["name","clips"]},"UpdateSequenceDto":{"type":"object","properties":{"name":{"type":"string","example":"renamed-sequence","description":"New name for the sequence"},"loopMode":{"type":"string","enum":["forever","once","pingpong"],"description":"How the sequence loops: forever, once, or pingpong"},"clips":{"description":"Full replacement of clips array. If provided, all existing clips are replaced.","type":"array","items":{"$ref":"#/components/schemas/SequenceClipDto"}},"metadata":{"type":"object","description":"Free-form metadata (tags, notes, etc.)","example":{"tags":["combat","boss"]}}}},"CreateTtrpgAdventureDto":{"type":"object","properties":{"prompt":{"type":"string","example":"A ruined moon temple where a lost knight asks for help before dawn","minLength":10,"maxLength":6000},"templateSlug":{"type":"string","example":"moonlit-ruins"},"setting":{"type":"string","example":"dark-fantasy"},"tone":{"type":"string","enum":["heroic","dark","cozy","weird","mystery"],"default":"heroic"},"length":{"type":"string","enum":["micro","short","one_shot","medium","long"],"default":"one_shot","description":"micro=1 scene, short=2, one_shot=3, medium=4, long=6. Credit cost scales with length."},"artStyle":{"type":"string","example":"Fantasy painted game art with clean tabletop tokens"},"railsLevel":{"type":"number","minimum":0,"maximum":100,"default":50},"partyFantasy":{"type":"string","example":"A clever rogue and a haunted cleric"}},"required":["prompt"]},"SendTtrpgTurnDto":{"type":"object","properties":{"content":{"type":"string","example":"I sneak behind the statue and inspect the silver keyhole.","minLength":1,"maxLength":6000},"rollResult":{"type":"number","minimum":1,"maximum":20,"example":14}},"required":["content"]},"CreateTtrpgCharacterDto":{"type":"object","properties":{"concept":{"type":"string","description":"Free-text concept for the character (up to 6000 characters). The AI turns this into a full light stat sheet, portrait, and token. Example: \"a grizzled dwarven storm-priest who lost her faith\".","example":"A nimble street thief with a haunted past and a silver tongue"},"name":{"type":"string","description":"Optional character name. If omitted, the AI invents a fitting one."},"artStyle":{"type":"string","description":"Optional art-style direction for the portrait. Defaults to a clean painted fantasy character style."}},"required":["concept"]},"UpdateTtrpgCharacterDto":{"type":"object","properties":{"name":{"type":"string","description":"Rename the character."},"sheet":{"type":"object","description":"Full replacement of the character sheet (light stat block + narrative fields). Text edits are free."}}},"UpdateTtrpgSceneDto":{"type":"object","properties":{"title":{"type":"string","example":"The Crumbling Bridge","maxLength":120},"description":{"type":"string","maxLength":6000},"readAloud":{"type":"string","maxLength":6000},"clue":{"type":"string","maxLength":6000},"encounter":{"type":"string","maxLength":6000},"difficultyNote":{"type":"string","example":"Medium: social choices can avoid danger.","maxLength":200},"mapPrompt":{"type":"string","example":"top-down view of a crumbling stone bridge over a raging river","maxLength":6000}}},"RegenerateTtrpgSceneMapDto":{"type":"object","properties":{"mapPrompt":{"type":"string","example":"top-down dark dungeon chamber with a central altar and shadowed corridors","description":"Optional override for the battlemap prompt. If provided, the scene mapPrompt is also updated.","maxLength":6000}}},"CreateTtrpgSceneDto":{"type":"object","properties":{"title":{"type":"string","example":"The Amber Vault","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":6000},"readAloud":{"type":"string","maxLength":6000},"clue":{"type":"string","maxLength":6000},"encounter":{"type":"string","maxLength":6000},"difficultyNote":{"type":"string","example":"Medium: choices matter more than combat.","maxLength":200},"mapPrompt":{"type":"string","example":"top-down view of a treasure vault with golden columns and trapped floors","maxLength":6000}},"required":["title"]},"UpdateTtrpgNpcDto":{"type":"object","properties":{"name":{"type":"string","example":"Kael Dran","maxLength":80},"role":{"type":"string","example":"Innkeeper turned spy","maxLength":120},"personality":{"type":"string","maxLength":6000},"motivation":{"type":"string","maxLength":6000},"secret":{"type":"string","maxLength":6000},"sampleLine":{"type":"string","example":"You should have turned back at the old stones.","maxLength":300},"visualPrompt":{"type":"string","example":"weathered middle-aged woman with silver-streaked hair and watchful green eyes","maxLength":6000}}},"CreateTtrpgNpcDto":{"type":"object","properties":{"name":{"type":"string","example":"Kael Dran","minLength":1,"maxLength":80},"role":{"type":"string","example":"Innkeeper turned spy","maxLength":120},"personality":{"type":"string","maxLength":6000},"motivation":{"type":"string","maxLength":6000},"secret":{"type":"string","maxLength":6000},"sampleLine":{"type":"string","example":"You should have turned back at the old stones.","maxLength":300},"visualPrompt":{"type":"string","example":"weathered middle-aged woman with silver-streaked hair and watchful green eyes","maxLength":6000}},"required":["name"]},"CreateAiVfxDto":{"type":"object","properties":{"requestId":{"type":"string","format":"uuid","description":"Keep this UUID for identical retries to avoid duplicate charges."},"name":{"type":"string","maxLength":80},"prompt":{"type":"string","minLength":10,"maxLength":6000}},"required":["requestId","name","prompt"]},"VfxPaletteDto":{"type":"object","properties":{"main":{"type":"string","example":"#ff6a1f"},"accent":{"type":"string","example":"#ffd23f"},"glow":{"type":"string","example":"#fff2c2"}},"required":["main","accent","glow"]},"VfxVariantDto":{"type":"object","properties":{"colorName":{"type":"string","description":"One of the family's 4 named palettes."},"sizeName":{"type":"string"}}},"CreateVfxDto":{"type":"object","properties":{"requestId":{"type":"string","format":"uuid","description":"Keep this UUID for identical retries to avoid duplicate charges."},"name":{"type":"string","maxLength":80},"preset":{"type":"string","enum":["portal","aura","shield","burst","beam","hazard","flame","fireball","firewall","frost","icestorm","snow","lightning","chainlightning","thunder","waterwave","whirlpool","rain","smoke","fog","poison","acid","healing","blessing","leaves","vines","runes","summoning","blackhole","stars","meteor","slash","claw","shockwave","shatter","confetti"],"description":"v1 legacy preset. Omit and use `family` for v2."},"color":{"type":"string","example":"#a78bfa"},"accent":{"type":"string","example":"#67e8f9"},"family":{"type":"string","enum":["fireball","fire-bolt","burning-hands","flame-wall","meteor","ray-of-frost","ice-storm","frost-nova","lightning-bolt","chain-lightning","shock","magic-missile","shield-ward","arcane-portal","eldritch-blast","dispel-magic","cure-wounds","bless-aura","spirit-guardians","inflict-wounds","hunger-of-hadar","necrotic-burst","entangle","thorn-whip","poison-spray","slash","thrust","hit-spark","block","critical-hit","rain","snow","fog","embers","dust","level-up","coin-burst","checkpoint","heal-pop","shield-break"],"description":"v2 effect family slug. Presence of this field selects the v2 recipe."},"palette":{"description":"Explicit main/accent/glow hex. Omit to use `variant.colorName` or the family default.","allOf":[{"$ref":"#/components/schemas/VfxPaletteDto"}]},"fps":{"type":"number","enum":[12,24],"default":24},"loop":{"type":"boolean","description":"Overrides the family's default loop/one-shot behavior."},"variant":{"$ref":"#/components/schemas/VfxVariantDto"},"projectId":{"type":"string","description":"Project that owns the reference sprite. Enables style lock."},"spriteId":{"type":"string","description":"Reference sprite in that project to match the effect's look to."},"aiStyleLock":{"type":"boolean","description":"+2 credits. Requires projectId/spriteId. Generates AI particle textures matched to the sprite; falls back to no-AI style lock and refunds on failure."},"sfx":{"type":"boolean","description":"+1 credit. Pairs a short procedural sound effect with the export."},"intensity":{"type":"number","minimum":0.1,"maximum":1},"seed":{"type":"number","minimum":0,"maximum":999999},"duration":{"type":"number","enum":[1,2,4],"description":"v1 only accepts 2 or 4."},"size":{"type":"number","enum":[256,512,1024],"description":"v1 only accepts 256 or 512."}},"required":["requestId","name","intensity","seed","duration","size"]},"VfxPromptDto":{"type":"object","properties":{"prompt":{"type":"string","maxLength":6000}},"required":["prompt"]},"CreateDndPortraitDto":{"type":"object","properties":{"prompt":{"type":"string","example":"A one-eyed half-orc mercenary captain with a cracked tusk and a wolf-fur cloak","minLength":3,"maxLength":6000},"style":{"type":"string","enum":["painted","ink","pixel","anime","watercolor"]},"border":{"type":"string","enum":["gold","silver","iron","wood","arcane","none"],"default":"gold"},"size":{"type":"number","enum":[512,1024],"description":"Clamped down to the caller tier max (free 512, pro 1024)."}},"required":["prompt","style"]},"DndGridCellsDto":{"type":"object","properties":{"cols":{"type":"number","example":30,"minimum":4,"maximum":60},"rows":{"type":"number","example":22,"minimum":4,"maximum":45}},"required":["cols","rows"]},"CreateDndBattleMapDto":{"type":"object","properties":{"prompt":{"type":"string","example":"A collapsed watchtower overgrown with vines beside a shallow river ford","minLength":3,"maxLength":6000},"setting":{"type":"string","enum":["fantasy","dungeon","scifi","cyberpunk","modern"],"default":"fantasy"},"gridCells":{"description":"Default 30x22, capped at 60x45.","allOf":[{"$ref":"#/components/schemas/DndGridCellsDto"}]},"gridVisible":{"type":"boolean","default":true},"size":{"type":"number","enum":[1024,1536],"description":"Width in px; height is always 3:4 of width. Clamped down to the caller tier max."}},"required":["prompt"]},"CreateDndNpcDto":{"type":"object","properties":{"prompt":{"type":"string","example":"A retired pirate quartermaster who now runs the harbor tavern and still owes debts to old crewmates","minLength":3,"maxLength":6000},"includeArt":{"type":"boolean","default":false},"style":{"type":"string","enum":["painted","ink","pixel","anime","watercolor"],"default":"painted"}},"required":["prompt"]},"CreateDndEncounterDto":{"type":"object","properties":{"partyLevel":{"type":"number","example":4,"minimum":1,"maximum":20},"partySize":{"type":"number","example":4,"minimum":1,"maximum":10},"difficulty":{"type":"string","enum":["easy","medium","hard","deadly"],"default":"medium"},"environment":{"type":"string","example":"ruined temple","maxLength":80},"theme":{"type":"string","example":"cultists guarding a sealed vault","maxLength":200}},"required":["partyLevel","partySize"]},"CreateDndMonsterDto":{"type":"object","properties":{"concept":{"type":"string","example":"A crystalline elemental that feeds on sound and shatters when it screams","minLength":3,"maxLength":6000},"cr":{"type":"string","example":"5","description":"Challenge rating, e.g. \"1/4\", \"5\", \"12\"."},"type":{"type":"string","example":"elemental","maxLength":40},"size":{"type":"string","enum":["tiny","small","medium","large","huge","gargantuan"]}},"required":["concept"]},"FoundryPackageDto":{"type":"object","properties":{"jobIds":{"example":["cljob1","cljob2"],"description":"Completed DnD Tools job IDs owned by the caller (max 50).","type":"array","items":{"type":"string"}}},"required":["jobIds"]},"CreateRobloxPackDto":{"type":"object","properties":{"mode":{"type":"string","enum":["pack","clothing"],"description":"Create the full game kit or a standalone classic avatar outfit."},"sourcePost":{"type":"string","description":"Internal blog post slug that led to this creation."},"requestId":{"type":"string","format":"uuid","description":"Keep this UUID for identical retries to avoid duplicate charges."},"projectId":{"type":"string","description":"Project to pull artStyle, palette and (optionally) a reference sprite from. Omit for a style-neutral pack."},"name":{"type":"string","maxLength":80,"description":"Game name, used on the icon/thumbnail preview and the README."},"description":{"type":"string","minLength":4,"maxLength":6000,"description":"Game (or outfit) description used to steer every prompt. One or two sentences work best."},"genre":{"type":"string","enum":["obby","simulator","tycoon","rpg","horror","roleplay","shooter","other"]},"referenceSpriteId":{"type":"string","description":"A sprite ID from the project to lock the art style across every item. The icon is generated first and then becomes the reference for the rest regardless of this field."},"readyPackId":{"type":"string","description":"Published Ready Pack used as visual direction. The generated pack remains private and uses the selected mode price."}},"required":["requestId","name","description","genre"]},"RegenerateRobloxPackItemDto":{"type":"object","properties":{"requestId":{"type":"string","format":"uuid","description":"Keep this UUID for identical retries of this exact regenerate click to avoid duplicate charges."}},"required":["requestId"]},"CreateReadyPackPrototypeDto":{"type":"object","properties":{"requestId":{"type":"string"}},"required":["requestId"]},"ReadyPackGameAssetDto":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"category":{"type":"string","enum":["branding","ui","vfx"]},"role":{"type":"string"},"url":{"type":"string"},"mimeType":{"type":"string"},"metadata":{"type":"object"}},"required":["key","label","category","role","url","mimeType"]},"ReadyPackGameBuilderManifestDto":{"type":"object","properties":{"version":{"type":"number","example":1},"compatibleTemplates":{"type":"array","items":{"type":"string"}},"defaultTemplate":{"type":"string"},"palette":{"type":"array","items":{"type":"string"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/ReadyPackGameAssetDto"}}},"required":["version","compatibleTemplates","defaultTemplate","palette","assets"]},"CreateRobloxReadyPackDto":{"type":"object","properties":{"sourcePackId":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"tagline":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"featured":{"type":"boolean"},"sortOrder":{"type":"number"},"previewUrls":{"type":"object","description":"Public-only preview URLs, never source pack internals."},"publicManifest":{"type":"object","description":"Safe item metadata and URLs only."},"gameBuilderManifest":{"$ref":"#/components/schemas/ReadyPackGameBuilderManifestDto"}},"required":["sourcePackId","slug","title","tagline","description","genre","previewUrls","publicManifest"]},"UpdateRobloxReadyPackDto":{"type":"object","properties":{"title":{"type":"string"},"tagline":{"type":"string"},"description":{"type":"string"},"genre":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"featured":{"type":"boolean"},"sortOrder":{"type":"number"},"previewUrls":{"type":"object"},"publicManifest":{"type":"object"},"gameBuilderManifest":{"$ref":"#/components/schemas/ReadyPackGameBuilderManifestDto"}}},"DropPaletteDto":{"type":"object","properties":{"main":{"type":"string","example":"#1c3b3a"},"accent":{"type":"string","example":"#c98a3a"},"glow":{"type":"string","example":"#7fd8c9"}},"required":["main","accent","glow"]},"DropWallSegmentDto":{"type":"object","properties":{"x1":{"type":"number","example":2},"y1":{"type":"number","example":4},"x2":{"type":"number","example":12},"y2":{"type":"number","example":4},"kind":{"type":"string","enum":["wall","door","secret"],"example":"wall"},"doorState":{"type":"string","enum":["closed","locked","secret"],"example":"locked"}},"required":["x1","y1","x2","y2","kind"]},"DropLightDto":{"type":"object","properties":{"x":{"type":"number","example":17},"y":{"type":"number","example":12},"radius":{"type":"number","example":6},"color":{"type":"string","example":"#9ee7ff"},"intensity":{"type":"number","example":0.7},"dim":{"type":"number","example":0.2}},"required":["x","y","radius","color","intensity"]},"DropMapSceneDto":{"type":"object","properties":{"name":{"type":"string","example":"Tide-Flooded Market Square"},"description":{"type":"string","example":"A collapsed market plaza half-submerged by the tide, stalls tipped over, a beached rowboat."},"doors":{"type":"array","example":["south"],"items":{"type":"string","enum":["north","south","east","west"]}},"walls":{"description":"Authoritative internal map geometry in grid units.","type":"array","items":{"$ref":"#/components/schemas/DropWallSegmentDto"}},"lights":{"description":"Foundry AmbientLight definitions in grid units.","type":"array","items":{"$ref":"#/components/schemas/DropLightDto"}}},"required":["name","description","doors"]},"DropTokenSubjectDto":{"type":"object","properties":{"name":{"type":"string","example":"Drowned Sailor"},"description":{"type":"string","example":"A bloated, barnacle-crusted undead sailor in tattered oilskins, seaweed tangled in its hair."}},"required":["name","description"]},"DropAudioTrackDto":{"type":"object","properties":{"name":{"type":"string","example":"Storm Surf Ambience"},"prompt":{"type":"string","example":"Seamless 60-second ambient loop: crashing storm surf, distant thunder, creaking dock timbers."},"bpm":{"type":"number","example":70}},"required":["name","prompt"]},"CreateDropDto":{"type":"object","properties":{"slug":{"type":"string","example":"sunken-harbor","description":"URL slug, lowercase kebab-case, unique."},"title":{"type":"string","example":"Sunken Harbor"},"theme":{"type":"string","example":"A storm-drowned pirate port half-swallowed by the tide."},"month":{"type":"string","example":"2026-09"},"palette":{"description":"Style-lock palette in hex, injected into every generation prompt for cross-item consistency.","allOf":[{"$ref":"#/components/schemas/DropPaletteDto"}]},"mood":{"type":"string","example":"dusk, heavy fog, storm-lashed"},"material":{"type":"string","example":"weathered wood and barnacle-crusted stone"},"requestId":{"type":"string","example":"req-sunken-harbor-2026-09-19","description":"Idempotency key. Re-posting the same requestId resumes/returns the existing Drop instead of creating a duplicate."},"mapScenes":{"description":"Explicit list of battle map scenes (5 expected). Omit to have the pipeline brainstorm them from theme/mood via Gemini text.","type":"array","items":{"$ref":"#/components/schemas/DropMapSceneDto"}},"tokenSubjects":{"description":"Explicit list of token subjects (20 expected). Omit to have the pipeline brainstorm them from theme/mood via Gemini text.","type":"array","items":{"$ref":"#/components/schemas/DropTokenSubjectDto"}},"audioTracks":{"description":"Explicit list of ambient audio tracks (3 expected). Omit to have the pipeline brainstorm them from theme/mood via Gemini text.","type":"array","items":{"$ref":"#/components/schemas/DropAudioTrackDto"}}},"required":["slug","title","theme","month","palette","requestId"]},"RegenerateDropItemsDto":{"type":"object","properties":{"items":{"example":["vfx-1","vfx-2","vfx-3","vfx-4","vfx-5","vfx-6"],"description":"Item checkpoint keys to reset and regenerate (e.g. vfx-1..vfx-6, token-3, map-2-night). Every other item is left untouched.","type":"array","items":{"type":"string"}}},"required":["items"]}}}}