{"swagger":"2.0","info":{"version":"0.9.10","title":"Ludo.ai API"},"basePath":"/api","tags":[{"name":"Authentication"},{"name":"Games"},{"name":"Explore"},{"name":"Gamestorming"},{"name":"Misc"},{"name":"Guide"},{"name":"Projects"},{"name":"Payments"},{"name":"Team"},{"name":"Assets"}],"schemes":["https"],"securityDefinitions":{"ApiKey":{"type":"apiKey","name":"Authorization","in":"header","description":"For accessing the API a valid API Key token must be passed in all the queries in\nthe 'Authorization' header.\nThe following syntax must be used in the 'Authorization' header: ApiKey xxxxxx.yyyyyyy.zzzzzz\n"},"Bearer":{"type":"apiKey","name":"Authorization","in":"header","description":"For accessing the API a valid JWT token must be passed in all the queries in\nthe 'Authorization' header.\nA valid JWT token is generated by the API and retourned as answer of a call\nto the route /login giving a valid user & password.\nThe following syntax must be used in the 'Authorization' header: Bearer xxxxxx.yyyyyyy.zzzzzz\n"}},"consumes":["application/json"],"produces":["application/json"],"paths":{"/register/{email}":{"get":{"summary":"Validates the email for the registration process. (Roles: user, none)","tags":["Authentication"],"operationId":"startRegistration","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"email","in":"path","required":true,"type":"string"},{"name":"captcha_token","in":"query","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/register":{"post":{"summary":"Registers a user. (Roles: user, none)","tags":["Authentication"],"operationId":"register","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"auth","in":"body","required":true,"schema":{"$ref":"#/definitions/RegistrationPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/check-email":{"post":{"summary":"Checks if an email is valid for registration. (Roles: user, none)","tags":["Authentication"],"operationId":"checkEmailValid","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"email","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/register-paywall/team":{"post":{"summary":"Registers a user and joins a team. (Roles: user, none)","tags":["Authentication"],"operationId":"registerPaywallTeam","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"auth","in":"body","required":true,"schema":{"$ref":"#/definitions/RegistrationPaywallTeamPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/login":{"post":{"summary":"Allow a user to log in and receive a JWT Token. (Roles: user, none)","tags":["Authentication"],"operationId":"login","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"auth","in":"body","description":"Username and password data","required":true,"schema":{"$ref":"#/definitions/Authentication"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/verify-magic-link":{"get":{"summary":"Verify magic link code and authenticate user","tags":["Authentication"],"operationId":"verifyMagicLink","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"code","in":"query","description":"Magic link verification code","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/discord/link-start":{"post":{"summary":"Starts the Discord account link flow and returns the OAuth url","tags":["Authentication"],"operationId":"startDiscordLink","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/DiscordLinkStartResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Discord linking is not configured on this environment","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/google":{"post":{"summary":"Authenticates with google (Roles: user, none)","tags":["Authentication"],"operationId":"authenticateGoogle","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"parameters":[{"name":"token","in":"body","description":"Google access token","required":true,"schema":{"$ref":"#/definitions/StringValue"}},{"name":"register","in":"query","type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/refresh-token":{"get":{"summary":"Refreshes auth token (Roles: user)","tags":["Authentication"],"operationId":"refreshToken","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/referral":{"get":{"summary":"Get referral info for the authenticated user (Roles: user)","tags":["Authentication"],"operationId":"getReferralInfo","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/forgot-password":{"post":{"summary":"Send password recovery email (Roles: none)","tags":["Authentication"],"operationId":"forgotPassword","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"email","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/reset-password":{"post":{"summary":"Sets a new password (Roles: none)","tags":["Authentication"],"operationId":"resetPassword","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ResetPasswordPayload"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/verify":{"post":{"summary":"Exchange a 2FA challenge ticket plus one proof for a full login payload. (Roles: none)","tags":["Authentication"],"operationId":"twoFactorVerify","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/TwoFactorVerifyPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"410":{"description":"The challenge ticket has expired or was already used","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/webauthn/login-options":{"post":{"summary":"WebAuthn request options for a login challenge (ticket) or a step-up (Bearer). (Roles: user, none)","tags":["Authentication"],"operationId":"twoFactorWebauthnLoginOptions","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"data","in":"body","required":false,"schema":{"$ref":"#/definitions/TwoFactorLoginOptionsPayload"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"410":{"description":"The challenge ticket has expired or was already used","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/totp/setup":{"post":{"summary":"Begin authenticator-app enrolment; returns the secret and a QR code. (Roles: user)","tags":["Authentication"],"operationId":"twoFactorTotpSetup","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":false,"schema":{"$ref":"#/definitions/TwoFactorStepUpPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TwoFactorSetupResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/totp/confirm":{"post":{"summary":"Confirm authenticator enrolment with a code; returns a fresh token and recovery codes. (Roles: user)","tags":["Authentication"],"operationId":"twoFactorTotpConfirm","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/TwoFactorTotpConfirmPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TwoFactorEnrollResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/webauthn/register-options":{"post":{"summary":"WebAuthn creation options for enrolling a passkey. (Roles: user)","tags":["Authentication"],"description":"Requires NO step-up proof, even on an account that already has 2FA: this call mutates nothing, and the only thing it exposes that the session cannot already read is excludeCredentials, whose ids GET /auth/2fa/status returns to the same session. The single proof for adding a passkey accompanies POST /auth/2fa/webauthn/register. Asking at both would cost two distinct proofs per passkey, since every proof type is single-use.","operationId":"twoFactorWebauthnRegisterOptions","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":false,"schema":{"$ref":"#/definitions/TwoFactorStepUpPayload"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/webauthn/register":{"post":{"summary":"Finish passkey enrolment with the authenticator's attestation. (Roles: user)","tags":["Authentication"],"description":"The mutating half of the ceremony, and the ONLY place a step-up proof is required when adding a passkey to an account that already has 2FA: send exactly one of totp_code, recovery_code or webauthn_assertion alongside the attestation. First-time enrolment on an unprotected account needs no proof.","operationId":"twoFactorWebauthnRegister","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/TwoFactorRegisterPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TwoFactorRegisterResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/disable":{"post":{"summary":"Turn two-factor authentication off. Requires a step-up proof. (Roles: user)","tags":["Authentication"],"operationId":"twoFactorDisable","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":false,"schema":{"$ref":"#/definitions/TwoFactorStepUpPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TwoFactorDisableResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/webauthn/{credentialId}":{"delete":{"summary":"Remove a registered passkey. Requires a step-up proof. (Roles: user)","tags":["Authentication"],"operationId":"twoFactorRemoveCredential","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"credentialId","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":false,"schema":{"$ref":"#/definitions/TwoFactorStepUpPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TwoFactorRemoveCredentialResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No such passkey on this account","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/recovery-codes":{"post":{"summary":"Regenerate the ten recovery codes. Requires a step-up proof. (Roles: user)","tags":["Authentication"],"operationId":"twoFactorRegenerateRecoveryCodes","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":false,"schema":{"$ref":"#/definitions/TwoFactorStepUpPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TwoFactorRecoveryCodesResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"429":{"description":"Too many failed second-factor attempts","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/2fa/status":{"get":{"summary":"Current two-factor state for the authenticated user. (Roles: user)","tags":["Authentication"],"operationId":"twoFactorStatus","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TwoFactorStatusResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/user/info":{"post":{"summary":"Updates the user info (Roles: user)","tags":["Authentication"],"operationId":"updateUserInfo","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/UserInfoPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"summary":"Deletes the user (Roles: user)","tags":["Authentication"],"operationId":"deleteUser","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/user/review-prompt":{"post":{"summary":"Asks whether to show the review prompt, or records the answer to it (Roles: user)","tags":["Authentication"],"operationId":"reviewPrompt","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ReviewPromptPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ReviewPromptResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/user/api-key":{"get":{"summary":"Generates a new API key (Roles: user)","tags":["Authentication"],"operationId":"generateApiKey","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/auth/validate-api-key":{"get":{"description":"Validates an API key. Returns 200 if valid, 403 if invalid.","tags":["Authentication"],"operationId":"validateApiKeyEndpoint","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Valid API key"},"403":{"description":"Invalid API key","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/cli/auth/start":{"post":{"summary":"Starts a ludo CLI browser login (Roles: none, user)","tags":["CLI"],"operationId":"startCliAuth","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"data","in":"body","required":false,"schema":{"$ref":"#/definitions/CliAuthStartPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CliAuthStart"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/cli/events":{"post":{"summary":"Records ludo CLI usage events (Roles: none, user)","description":"Anonymous-by-default usage telemetry from the ludo CLI: one event per command, keyed by a random install id, plus the account when the CLI sends its API key. Never carries flag values, prompts, paths or file contents. Stored in UserTracking as `cli:<command>`.","tags":["CLI"],"operationId":"recordCliEvents","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/CliEventsPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OkResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/cli/auth/token":{"post":{"summary":"Polls a ludo CLI browser login; returns the CLI key once approved (Roles: none, user)","tags":["CLI"],"operationId":"pollCliAuth","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/CliAuthPollPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CliAuthPoll"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/oauth/requests/{request_id}":{"get":{"summary":"Returns a pending OAuth connection request for the consent page (Roles: user)","tags":["OAuth"],"operationId":"getOAuthRequest","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OAuthRequest"}},"404":{"description":"Unknown or expired request","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/oauth/requests/{request_id}/approve":{"post":{"summary":"Approves an OAuth connection request; returns where to send the browser (Roles: user)","tags":["OAuth"],"operationId":"approveOAuthRequest","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OAuthAnswer"}},"402":{"description":"The plan has no API access","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Unknown or expired request","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Already approved or denied","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/oauth/requests/{request_id}/deny":{"post":{"summary":"Denies an OAuth connection request; returns where to send the browser (Roles: user)","tags":["OAuth"],"operationId":"denyOAuthRequest","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OAuthAnswer"}},"404":{"description":"Unknown or expired request","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Already approved or denied","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/cli/auth/requests/{user_code}":{"get":{"summary":"Returns a pending ludo CLI login request for the approval page (Roles: user)","tags":["CLI"],"operationId":"getCliAuthRequest","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"user_code","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CliAuthRequest"}},"404":{"description":"Unknown or expired code","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/cli/auth/requests/{user_code}/approve":{"post":{"summary":"Approves a ludo CLI login request, minting a CLI key for it (Roles: user)","tags":["CLI"],"operationId":"approveCliAuthRequest","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"user_code","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CliAuthApproved"}},"402":{"description":"The plan has no API access","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Unknown or expired code","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Already approved or denied","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/cli/auth/requests/{user_code}/deny":{"post":{"summary":"Denies a ludo CLI login request (Roles: user)","tags":["CLI"],"operationId":"denyCliAuthRequest","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"user_code","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OkResponse"}},"404":{"description":"Unknown or expired code","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/user/api-keys":{"get":{"summary":"Lists the user's API keys (Roles: user)","tags":["API Keys"],"operationId":"listApiKeys","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ApiKeyInfo"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"post":{"summary":"Creates a named API key; the key is returned once (Roles: user)","tags":["API Keys"],"operationId":"createApiKey","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateApiKeyPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CreatedApiKey"}},"402":{"description":"The plan has no API access","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Too many keys","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/user/api-keys/{id}":{"delete":{"summary":"Revokes one of the user's API keys (Roles: user)","tags":["API Keys"],"operationId":"revokeApiKey","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OkResponse"}},"404":{"description":"Not found","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/teams":{"post":{"summary":"Adds a team. (Roles: user)","tags":["Team"],"operationId":"addTeam","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"team","in":"body","required":true,"schema":{"$ref":"#/definitions/Team"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Team"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"get":{"summary":"Gets a user's teams. (Roles: user)","tags":["Team"],"operationId":"getTeams","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Team"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/teams/{id}":{"patch":{"summary":"Renames a team. Only the team owner can do this. (Roles: user)","tags":["Team"],"operationId":"updateTeam","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/UpdateTeamPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Team"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"403":{"description":"Not the team owner","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Team not found","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/teams/{id}/join":{"get":{"summary":"Joins a team. (Roles: user)","tags":["Team"],"operationId":"joinTeam","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/LoginPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/teams/{id}/invite/{email}":{"get":{"summary":"Invite email to team. (Roles: user)","tags":["Team"],"operationId":"inviteToTeam","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"email","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Team"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"summary":"Remove invitation to team. (Roles: user)","tags":["Team"],"operationId":"removeInvite","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"email","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Team"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/teams/{id}/users":{"get":{"summary":"Get team users. (Roles: user)","tags":["Team"],"operationId":"getTeamUsers","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/TeamUserInfo"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/teams/{id}/users/{userId}":{"delete":{"summary":"Remove team user. (Roles: user)","tags":["Team"],"operationId":"removeTeamUser","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"userId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Team"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/{id}":{"get":{"summary":"Get a game's information (Roles: user)","tags":["Games"],"operationId":"getGameInformation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Game"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/":{"post":{"summary":"Get multiple games' information (Roles: user)","tags":["Games"],"operationId":"getGamesInformation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GameIds"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Game"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/search/games/title/{query}":{"get":{"summary":"Searches a game based on title (Roles: user)","tags":["Games"],"operationId":"searchGamesByTitle","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"query","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Game"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/utils/genres":{"get":{"summary":"Get all game genres (Roles: user, none)","tags":["Games"],"operationId":"getGenres","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"type":"string"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/utils/generation-styles":{"get":{"summary":"Get all generatiokn styles (Roles: user, none)","tags":["Games"],"operationId":"getGenerationStyles","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"type":"string"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/utils/platforms":{"get":{"summary":"Get all game platforms (Roles: user, none)","tags":["Games"],"operationId":"getPlatforms","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"type":"string"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/utils/sprite-poses":{"get":{"summary":"Get supported sprite poses (Roles: user, none)","tags":["Games"],"operationId":"getSpritePoses","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/SpritePose"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/utils/animation-presets":{"get":{"summary":"Get animation presets (Roles: user, none)","tags":["Games"],"operationId":"getAnimationPresets","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"parameters":[{"name":"id","in":"query","description":"Animation preset ID. If provided, returns the full preset with perspectives. If omitted, returns summaries without perspectives.","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/AnimationPreset"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Animation preset not found","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/utils/sprite3d-styles":{"get":{"summary":"The styles a 3D model can be rendered into a sprite with (Roles: user).","tags":["Games"],"operationId":"getSprite3DStyles","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Sprite3DStyle"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/sprite3d/preview":{"post":{"summary":"Preview a 3D model as a sprite in a style: a few frames of each view asked for, as inline mp4s, one per view in the order asked. Free; nothing is stored (Roles: user).","tags":["Games"],"operationId":"previewSprite3D","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Sprite3DPreviewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Sprite3DPreviewResult"}}},"204":{"description":"Cancelled (DELETE /games/generate/{request_id}) by a newer preview"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/sprite3d/look":{"post":{"summary":"Save the look a 3D model renders as a sprite with (style, its settings, camera) on the model, or clear it by leaving `look` out. Returns the look as saved (Roles: user).","tags":["Games"],"operationId":"saveSprite3DLook","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Sprite3DLookPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Sprite3DLook"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No such 3D model of the caller's","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/sprite3d/attachments":{"post":{"summary":"Save the items a 3D model holds in Directional Sprites (weapons and accessories, each a GLB fitted to one bone) on the model: the whole list, an empty one clears it. Returns the list as saved (Roles: user).","tags":["Games"],"operationId":"saveSprite3DAttachments","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Sprite3DAttachmentsPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Sprite3DAttachment"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No such 3D model of the caller's","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/assets":{"post":{"summary":"Suggests assets based on a project (Roles: user)","tags":["Gamestorming"],"operationId":"suggestProjectAssets","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SuggestPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ThreeDAsset"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/videos":{"post":{"summary":"Suggests videos based on a project (Roles: user)","tags":["Gamestorming"],"operationId":"suggestProjectVideos","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SuggestPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedVideo"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/audio":{"post":{"summary":"Suggests audio based on a project (Roles: user)","tags":["Gamestorming"],"operationId":"suggestProjectAudio","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SuggestPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedAudio"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/sections-to-add":{"get":{"summary":"Suggests sections to add (Roles: user)","tags":["Gamestorming"],"operationId":"suggestSectionsToAdd","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"selected_element","in":"query","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"type":"string"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/generated-images":{"post":{"summary":"Suggests generated images based on a project (Roles: user)","tags":["Gamestorming"],"operationId":"suggestProjectGeneratedImages","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SuggestPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GameImage"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/text/{type}":{"post":{"summary":"Suggests text based on a project (Roles: user)","tags":["Gamestorming"],"operationId":"suggestProjectText","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"type","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedText"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/followup":{"post":{"summary":"Suggests text based on a project (Roles: user)","tags":["Gamestorming"],"operationId":"suggestProjectFollowUp","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SuggestPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedText"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/suggest/project/{id}/titles":{"get":{"summary":"Suggests titles based on a project (Roles: user)","tags":["Gamestorming"],"operationId":"suggestProjectTitles","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedText"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/{generationId}":{"delete":{"summary":"Cancels a game generation (Roles: user)","tags":["Gamestorming"],"operationId":"cancelGeneration","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"generationId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/refined-3d-images":{"post":{"summary":"Generate 3D model images based on existing models (Roles: user)","tags":["Gamestorming"],"operationId":"generateRefined3DImages","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Refined3DPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ThreeDAsset"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/3d-images":{"post":{"summary":"Generates image suitable for 3d models (Roles: user)","tags":["Gamestorming"],"operationId":"generate3DImages","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}},{"name":"force3D","in":"query","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ThreeDAsset"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/image-to-3d":{"post":{"summary":"Generates 3D models from image (Roles: user)","tags":["Gamestorming"],"operationId":"imageTo3D","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ImageTo3DPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AssetGeneration"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/download-3d":{"post":{"summary":"Downloads a 3D model (Roles: user)","tags":["Gamestorming"],"operationId":"export3D","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Download3DPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AssetGeneration"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/retopology":{"post":{"summary":"Retopologizes a 3D model (Roles: user)","tags":["Gamestorming"],"operationId":"retopology","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/RetopologyPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AssetGeneration"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/animations":{"get":{"summary":"Lists accepted animation clips for a 3D model (Roles: user)","tags":["Gamestorming"],"operationId":"listAnimations","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"model_asset_id","in":"query","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Animation3DClip"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/animations/accept":{"post":{"summary":"Persists a chosen animation candidate against its 3D model (Roles: user)","tags":["Gamestorming"],"operationId":"acceptAnimation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AcceptAnimationPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Animation3DClip"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/animations/{id}":{"delete":{"summary":"Deletes an accepted animation clip (Roles: user)","tags":["Gamestorming"],"operationId":"deleteAnimation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/images":{"post":{"summary":"Generates images (Roles: user)","tags":["Gamestorming"],"operationId":"generateImages","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}},{"name":"skipGenerationHistory","in":"query","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GameImage"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/images-inpainting":{"post":{"summary":"Generates images inpainting (Roles: user)","tags":["Gamestorming"],"operationId":"generateImagesInpainting","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}},{"name":"skipGenerationHistory","in":"query","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GameImage"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/align-images":{"post":{"summary":"Aligns two images and returns alignment factors (Roles: user)","tags":["Gamestorming"],"operationId":"alignImages","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AlignImagesPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AlignImagesResult"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/segmentation":{"post":{"summary":"Segments an image into a mask (Roles: user)","tags":["Gamestorming"],"operationId":"segmentImage","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SegmentImagePayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/StringValue"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/videos":{"post":{"summary":"Generates videos (Roles: user)","tags":["Gamestorming"],"operationId":"generateVideo","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateVideoPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GeneratedVideo"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/videos/first-frame":{"post":{"summary":"Generates a video's first frame (Roles: user)","tags":["Gamestorming"],"operationId":"generateVideoFirstFrame","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GeneratedVideo"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/video-audio":{"post":{"summary":"Generates video audio (Roles: user)","tags":["Gamestorming"],"operationId":"generateVideoAudio","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateVideoAudioPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GeneratedVideo"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/audio":{"post":{"summary":"Generates audio assets (Roles: user)","tags":["Gamestorming"],"operationId":"generateAudio","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateAudioPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedAudio"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/sprite-image":{"post":{"summary":"Generates spritesheet from text (Roles: user)","tags":["Gamestorming"],"operationId":"generateSprite","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Spritesheet"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/image-to-spritesheet":{"post":{"summary":"Generates spritesheet from image (Roles: user)","tags":["Gamestorming"],"operationId":"generateImageToSpritesheet","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Spritesheet"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/image":{"post":{"summary":"Generate game-art images from a text prompt alone, selecting an image_type (e.g. sprite) and optionally art_style, perspective, and aspect_ratio. The job result is an array of image results, each with a url; request n (1-8) to control how many variations come back. Because it generates purely from text it takes no source image, so there is no upload size limit to trip. Credits are held when the job is accepted and refunded if it fails or is cancelled; the charge scales with the number of images produced. Use createImage to make new images from scratch; use generateWithStyle to match a reference image's art style, editImage to modify an existing image, and removeBackground to cut out a subject. Pass an optional request_id to tag the results so you can retrieve them later via listGenerations (type image). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Images"],"operationId":"createImage","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateImagePayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ImageResult"}}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/image/style":{"post":{"summary":"Generate new images that match the visual style of a reference image: supply a style_image (URL or base64) plus a text prompt describing what to create and an optional image_type (defaults to sprite). The job result is an array of image results, each with a url; request n (1-4) to control the number of variations. The style_image is uploaded and validated, and an image larger than 15MB is rejected with HTTP 400. Credits are held when the job is accepted and refunded if it fails or is cancelled; the charge scales with the number of images produced. Use this instead of createImage when style consistency with an existing asset matters; use editImage to alter the content of a specific image rather than borrow its style, and removeBackground to isolate a subject. Pass an optional request_id to tag the results so you can retrieve them later via listGenerations (type image). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Images"],"operationId":"generateWithStyle","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateWithStylePayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ImageResult"}}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/image/edit":{"post":{"summary":"Modify an existing image according to text instructions: supply a source image (URL or base64) and a prompt describing the changes (e.g. \"add clouds\", \"warmer color scheme\"), with an optional reference_image for extra style or content guidance. To change only one area, pass a mask (white = change, black = keep, the same size as the image): mask_mode \"replace\" (default) redraws that area from the prompt, \"erase\" removes what is there. Without a mask the whole image is re-rendered. Never ask for a transparent background in the prompt: it often comes back as a grey-and-white checkerboard painted into the pixels, which removeBackground cannot remove. A sprite keeps its transparency without being asked (see image_type). The job result is an array of image results, each with a url; request n (1-4) to control the number of edited variations. Provided images are uploaded and validated, and any image larger than 15MB is rejected with HTTP 400. Credits are held when the job is accepted and refunded if it fails or is cancelled; the charge scales with the number of images produced. Use editImage to transform a specific existing image; use createImage to generate from text alone, generateWithStyle to borrow a reference's art style, and removeBackground for the dedicated background-removal case. Pass an optional request_id to tag the results so you can retrieve them later via listGenerations (type image). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Images"],"operationId":"editImage","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/EditImagePayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ImageResult"}}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/animate":{"post":{"summary":"Animate a static sprite into a spritesheet driven by a motion text prompt (image-to-spritesheet): supply an initial_image (URL or base64) plus a motion_prompt like \"walking\" or \"attack slash\", and optionally a final_image to interpolate between a start and end frame. Describe the motion exactly and unambiguously, but do not over-describe it: an action the model already knows is one phrase, not a sequence of steps, and the character, art style, background and camera come from the image, not the prompt. For animations driven by up to three keyframes (including a middle frame), use animateSpriteKeyframes instead. The job result is a single sprite result: `spritesheet_url` (the sheet image), `video_url` (an mp4 of the animation - pass it as `video` to transferMotion or as `spritesheet_video_url` to createSpriteAudio; editSpritesheet takes `spritesheet_url`), `num_frames`/`num_cols`/`num_rows` (the grid layout), and, when requested, `gif_url`, `individual_frame_urls` and `spritesheet_with_background_url`. The hydra and hydra-plus models also return `audio_url`, a sound effect for the animation - there is no need to call createSpriteAudio afterwards; forge and forge-pixel produce no audio. The chosen model must support sprite animation and the duration must be valid for it; incompatible model/duration combinations return HTTP 400. Credits are held for the duration you requested when the job is accepted; the final charge is rate × seconds of video generated (legacy models keep their minimum charge), never more than for the duration you requested, and the difference (or everything, if the job fails or is cancelled) is refunded. The returned sheet is then trimmed to its best loop and can run slightly shorter than the video; that trim is not deducted. Choosing a tool: animateSprite (this one) when you can describe the motion in text; transferMotion when you want an exact motion copied from a reference video or one of the named presets from listAnimationPresets (e.g. a standard walk or attack cycle); generatePose first when the source image is not yet in the pose the animation should start from. Omit `model` to run on hydra (the default) - see the `model` field for the per-model rates. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"animateSprite","x-credit-action":"ANIMATE_SPRITE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AnimateSpritePayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SpriteResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/animate-keyframes":{"post":{"summary":"Animate a sprite through up to three fixed keyframes - initial_image, middle_image and final_image (each a URL or base64) - producing a spritesheet that interpolates through the provided frames in order. initial_image is required: the animation starts from it, and middle_image and final_image add keyframes after it (either may be omitted). The motion_prompt is optional here - when omitted, the motion is derived purely from the keyframes. Runs on hydra (default; also returns `audio_url`), hydra-plus (hydra with finer detail, also returns `audio_url`), forge, or forge-pixel for pixel-art sprites - the models that support a middle keyframe; any other model returns HTTP 400. For just a start and end frame, animateSprite with `final_image` does the same job. The job result is the same sprite result as animateSprite (`spritesheet_url`, `video_url`, grid fields, optional GIF and frame URLs, `audio_url` on hydra and hydra-plus). Credits are held for the duration you requested when the job is accepted; the final charge is rate × seconds of video generated, never more than for the duration you requested, and the difference (or everything, if the job fails or is cancelled) is refunded. The returned sheet is then trimmed to its best loop and can run slightly shorter than the video; that trim is not deducted. Use animateSprite instead for the classic single-image + text-prompt animation. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"animateSpriteKeyframes","x-credit-action":"ANIMATE_SPRITE","x-credit-models":["hydra","hydra-plus","forge","forge-pixel"],"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AnimateSpriteKeyframesPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SpriteResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/pose":{"post":{"summary":"Re-pose an existing sprite into a new target pose while preserving the character, taking a source image (URL or base64), a pose name (or \"Other\" with a free-text description), and an optional n (1-4) for how many variations to produce. Only works with sprite image types (not icons, screenshots, etc.). The job result is an array of pose results, each containing the generated image url, the pose and description used, and a suggested motion_prompt tuned for that pose. Credits are held when the job is accepted and refunded if it fails or is cancelled; the charge scales with the number of images generated. This is typically the first step before animating: call generatePose to set the character's pose, then feed the result (and its suggested motion_prompt) into animateSprite for the best animation quality; use rotateSprite instead when you want to change the camera angle rather than the pose. Pass an optional request_id to tag the results so you can locate them later via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"generatePose","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GeneratePosePayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/PoseResult"}}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/rotate":{"post":{"summary":"Re-render an existing sprite from a different camera viewpoint while keeping the same character and pose, taking a source image (URL or base64), a required camera_rotation azimuth (one of 0, 45, 90, 135, 180, -135, -90, -45 degrees), an optional camera_elevation (0, 30, or 60 degrees; omit to keep the current elevation), and an optional n (1-4) for the number of variations. Only works with sprite image types (not icons, screenshots, etc.). The job result is an array of rotate-sprite results, each with the generated image url and the camera_rotation and camera_elevation that were applied. Credits are held when the job is accepted and refunded if it fails or is cancelled; the charge scales with the number of images generated. Use this to produce alternate view angles of a character; use generatePose instead to change the character's pose rather than the camera, and animateSprite or transferMotion to bring a sprite to life. Pass an optional request_id to tag the results so you can retrieve them later via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"rotateSprite","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/RotateSpritePayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/RotateSpriteResult"}}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/transfer-motion":{"post":{"summary":"Transfer motion from a reference video or a named animation preset onto a static sprite image, producing an animated spritesheet that mimics the reference movement. Provide the sprite as image (URL or base64) plus either a video URL or a preset_id together with perspective and direction (all three from listAnimationPresets; if both video and preset_id are sent the video wins). The job result is the same sprite result as animateSprite: `spritesheet_url`, `video_url`, grid fields, optional GIF / frame / with-background URLs, and `audio_url` when the model is hydra. `duration` defaults to 1.5s where the chosen model offers it, otherwise to that model's shortest (hydra, the default, starts at 3s) - and a longer reference clip or preset is compressed to fit, so pass the preset's own `duration` (returned by listAnimationPresets) to keep its timing. It returns HTTP 400 if neither a video nor a complete preset_id/perspective/direction triple is supplied, if the named preset, perspective, or direction cannot be resolved, or if the model/duration combination is invalid. Credits are held for the duration you requested when the job is accepted; the final charge is rate × seconds of video generated (which follows the reference clip or preset; legacy models keep their minimum charge), never more than for the duration you requested, and the difference (or everything, if the job fails or is cancelled) is refunded. The returned sheet is then trimmed to its best loop and can run slightly shorter than the video; that trim is not deducted. Use this when you have an existing motion clip or preset to copy; prefer animateSprite to generate animation purely from a text prompt. Omit `model` to run on hydra (the default - most capable, and returns audio); pick forge for a cheaper run on simple motion, presets and matching poses; the `model` field lists rates. Pass an optional request_id to tag the result so you can retrieve it later via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"transferMotion","x-credit-action":"TRANSFER_SPRITE_MOTION","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/TransferMotionPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SpriteResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/docs":{"get":{"description":"Browse or read Ludo's own documentation in full - how to choose a sprite animation mode, when to use Generate Before / Generate After, how margins behave, which model suits a job, and each generator's known limitations. To answer a specific question, call searchDocs first: it returns just the sections that match. Use getDocs to see what documentation exists, or to read a whole document or named sections. This is the same documentation the Ludo web app shows its users, so it occasionally describes buttons rather than parameters; the substance applies to the API and MCP surfaces just the same. To browse, call it with NO parameters to get a table of contents - every document with its id, label and section titles, and no bodies - then call it again with `doc` (and optionally `sections`) to read only what you need. Fetching a whole document can return tens of thousands of characters, so prefer naming the sections, using titles copied from the table of contents. Section titles match ignoring case, spacing and punctuation: when only some requested titles exist you receive those sections plus `unmatched_sections` and `available_sections`, and when none exist (or `doc` is unknown) the call returns 400 listing the valid values so you can retry once. This is a free discovery endpoint: it does not charge credits and does not queue a job. Requires an API key (user scope).","tags":["Documentation"],"operationId":"getDocs","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"doc","in":"query","description":"Which document to read, by id (the table of contents returned by the no-parameter call lists them). Omit to receive the table of contents.","required":false,"type":"string","x-swagger-data":"documentationIndexes"},{"name":"sections","in":"query","description":"Section titles to return from `doc`, copied from the table of contents (an array of titles; over plain REST, a comma-separated string, where a title that itself contains a comma is still matched whole). Matching ignores case, spacing and punctuation; titles that match nothing are reported back in `unmatched_sections` rather than guessed at. Only valid together with `doc`. Omit to return every section of the document.","required":false,"type":"array","collectionFormat":"csv","items":{"type":"string"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/DocumentationResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Documentation temporarily unavailable","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/docs/search":{"get":{"description":"Search Ludo's own documentation with a plain-language question and get back only the few sections that answer it - the fastest way to learn how a feature is meant to be used before you generate with it (how to pick a sprite animation mode or model, how margins behave, what something costs, known limitations). Start here rather than reading whole documents. Each result carries `doc` and `section`, which you can pass straight to getDocs to re-read that section, and the section's full markdown `content`. Results are best first; weak matches are left out, so an empty `results` list means the documentation does not cover the question - rephrase it, or call getDocs with no parameters to browse the table of contents. This is the same documentation the Ludo web app shows its users, so it occasionally describes buttons rather than parameters; the substance applies to the API and MCP surfaces just the same. Returns up to `n` sections (default 3, max 10). If it answers 503 the search backend is briefly unavailable: call getDocs instead rather than retrying in a loop. This is a free discovery endpoint: it does not charge credits and does not queue a job. Requires an API key (user scope).","tags":["Documentation"],"operationId":"searchDocs","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"query","in":"query","description":"What you want to know, in plain language, e.g. \"how do I keep a sprite animation's colors consistent\" or \"what does a 3D model cost\".","required":true,"type":"string","minLength":1,"maxLength":500},{"name":"n","in":"query","description":"Maximum number of sections to return. Defaults to 3.","required":false,"type":"integer","format":"int32","minimum":1,"maximum":10}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/DocumentationSearchResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Documentation search temporarily unavailable","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/animation-presets":{"get":{"description":"List the available animation presets along with their perspectives and the eight supported compass directions (N, NE, E, SE, S, SW, W, NW). Synchronous GET with no request body: it returns an animations array (each with id, name, category, description, duration, and preview_url), a deduplicated perspectives array, and the directions list. This is a free discovery endpoint and does not charge credits. Use it to obtain the preset_id, perspective, and direction values that transferMotion needs, and to find motion preset names you can reference when animating; pair it with transferMotion (to apply a preset onto a sprite), animateSprite (text-prompt animation), or animate3DModelPreset (apply any preset by id to a rigged 3D model). Requires an API key (user scope).","tags":["Spritesheets"],"operationId":"listAnimationPresets","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AnimationPresetsResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/edit":{"post":{"summary":"Edit a previously generated spritesheet: re-prompt its underlying animation (edit_mode \"prompt\"), extend the frame beyond its borders (\"outpaint\"), repair a bad loop (\"fix_loop\"), or re-render it with finer detail (\"improve\": the extra pass hydra-plus runs when it generates, on a spritesheet generated with hydra only - any other returns HTTP 400; a sheet generated on hydra-plus already has it). Pass the `spritesheet_url` you received from `animateSprite`, `animateSpriteKeyframes`, `transferMotion`, or an earlier edit - it must be a spritesheet you generated within the last 7 days; arbitrary external images are not accepted. Omit `duration`, `frames`, `frame_size`, `crop` and `model` to keep the source spritesheet's values. A `prompt` is required for edit_mode \"prompt\", optional for \"outpaint\", and not accepted for \"fix_loop\" or \"improve\"; optionally add up to 5 reference `images` (URL or base64) to guide the edit. The job result is the same result shape as animateSprite (spritesheet URL, frame layout, optional GIF or individual frames). Credits are held when the job is accepted; the final charge is rate × seconds of video generated, never more than was held, and the difference (or everything, if the job fails or is cancelled) is refunded. The returned sheet is then trimmed to its best loop and can run slightly shorter than the video; that trim is not deducted. Prompt edits are priced per model (credits/second × duration) and held for the duration you requested; \"outpaint\", \"fix_loop\" and \"improve\" run a lighter pipeline in which `model` is ignored and are billed at a flat 1 credit per second of output, held for and capped at the length of the source spritesheet's video (`duration` does not change their price). Outpaint decides the extra margin itself - there is no amount parameter. The result carries the same fields as animateSprite (`spritesheet_url`, `video_url`, grid fields); new audio is generated only by prompt edits on hydra: \"fix_loop\", \"outpaint\" and \"improve\" do not regenerate it, and their result carries the source spritesheet's `audio_url` when it had one. Pass an optional `request_id` to tag the result for later retrieval via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"editSpritesheet","x-credit-action":"EDIT_SPRITESHEET","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/EditSpritesheetPayloadPublic"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SpriteResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/rotate-spritesheet":{"post":{"summary":"Re-render a previously generated sprite animation from a new view: the same motion and timing, seen from a different angle (e.g. a front-facing walk cycle turned into the same walk seen from the back). Pass the `spritesheet_url` you received from `animateSprite`, `animateSpriteKeyframes`, `transferMotion`, `editSpritesheet` or an earlier call - it must be a spritesheet you generated within the last 7 days; arbitrary external images are not accepted - and, as `image` (URL or base64), the character exactly as it must appear in the new view: it becomes the first frame of the animation. To produce that image, call rotateSprite on the animation's first frame (or any image of the character) and pick the view you want. An optional `final_image` fixes the last frame (it defaults to the first frame, for cyclic or returning animations), and an optional `prompt` steers the result: the character's facing in the new view (\"its back to the camera\"), where the action goes (\"the kick goes to the left of the frame\"), or anything else about the motion. Runs on hydra (default) or hydra-plus (finer detail). The job result is the same sprite result as animateSprite (`spritesheet_url`, `video_url`, grid fields, optional GIF and frame URLs). The animation covers the source's own length (up to 5 seconds; a longer source is sped up to fit), so omit `duration` unless you need to change that. Credits are held for that length when the job is accepted, or for 3 seconds when it is shorter (a new view is a new generation, and hydra generates at least 3 seconds); the final charge is the model's rate × seconds of video generated, never more than was held, and the difference (or everything, if the job fails or is cancelled) is refunded. Pass an optional request_id to tag the result for later retrieval via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"rotateSpritesheet","x-cli-name":"rotate-sheet","x-credit-action":"NEW_VIEW_SPRITESHEET","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/RotateSpritesheetPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SpriteResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/adjust":{"post":{"summary":"Adjust Sprite Sheet: re-export a previously generated sprite animation with different sheet settings - frame count, frame size, loop trimming or whitespace cropping - without generating the animation again. Free (no credits). Pass the `spritesheet_url` you received from `animateSprite`, `animateSpriteKeyframes`, `transferMotion`, `editSpritesheet`, `rotateSpritesheet` or an earlier adjust - it must be a spritesheet you generated through the API within the last 7 days; arbitrary external images are not accepted. Omit a setting to keep the source's value. The animation itself is unchanged: this only re-cuts the same video into a new sheet, so use it to fix framing (for example an effect clipped by True Size: re-export with a fixed `frame_size` or 0), to change the frame count or size for your engine, to upscale it 1.5x (`frame_size` -1), to turn loop trimming on or off, or to pack it as ordinary art when its detected pixel grid is wrong for the asset (`use_pixel_grid` false). It cannot change the motion, margins or content - use editSpritesheet for that. The source is left as it is and the result is a new spritesheet with its own `spritesheet_url`. The job result is the same sprite result as animateSprite (`spritesheet_url`, `video_url`, grid fields, optional GIF and frame URLs, and the source's `audio_url` when it has one). Limited to 20 calls per 5 minutes, 120 an hour and 1000 a day. Pass an optional request_id to tag the result for later retrieval via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"adjustSpritesheet","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AdjustSpritesheetPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SpriteResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/audio":{"post":{"summary":"Generate a sound effect for a previously generated spritesheet animation. Pass, as `spritesheet_video_url`, the `video_url` you received from `animateSprite`, `animateSpriteKeyframes`, `transferMotion`, or `editSpritesheet` - it must belong to a spritesheet you generated within the last 7 days; arbitrary external videos are not accepted. Spritesheets generated with the hydra model already come with `audio_url`, so only call this for forge / forge-pixel output or to replace hydra's sound. Optionally add a `prompt` describing the sound you want. The job result is only the URL of the generated audio file, an MP3 or WAV (the URL's extension, .mp3 or .wav, says which); no video is returned. The audio is also attached to the spritesheet, so it appears as `audio_url` in listGenerations (type spritesheet), where the spritesheet keeps the `request_id` it was generated with. Pass an optional `request_id` to make this call idempotent and to find its job again; it must not be one you already used for another generation. Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"createSpriteAudio","x-credit-action":"GENERATE_AUDIO_FOR_SPRITES","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateSpriteAudioPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/directional-styles":{"get":{"description":"List the styles Directional Sprites can render a 3D model in, with the perspectives (camera heights) and the eight directions they render from. Each style has an id (pass it as style_id), a label, a description, a family, whether it is pixel art, a thumbnail_url of the style at its defaults, its current version, and its settings: each setting has a name (the key in style_params), a kind (choice, bool or color - a color setting also takes any colour), its options ({label, value}) and its default. advanced_settings work the same way. Free discovery endpoint: it does not charge credits. Use it before previewDirectionalSprites, createDirectionalSprites and createDirectionalStills. Requires an API key (user scope).","tags":["Spritesheets"],"operationId":"listDirectionalSpriteStyles","x-cli-name":"directional-styles","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/DirectionalSpriteStylesResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/directional-preview":{"post":{"summary":"Preview Directional Sprites: a few frames of a 3D model in a style, from each direction asked for, to check a style before rendering. Takes the same model, animation, camera and style fields as createDirectionalSprites. The response (synchronous, not a job) has one preview per direction, in the order asked, each with `frame_urls` - the frames as images (4 for an animated model, 1 for a still one) - and `render_3d`, the style and settings as rendered, which createDirectionalSprites takes back as they are. Free (no credits), and nothing is saved to your generations; the frame images are kept for 7 days. A preview takes several seconds. You run one preview at a time: starting another while one is still running cancels the older one, which then answers 204 with no body. Preview one or two directions while trying styles and render only once the look is right. Requires an API key (user scope).","tags":["Spritesheets"],"operationId":"previewDirectionalSprites","x-cli-name":"directional-preview","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/DirectionalPreviewPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/DirectionalPreviewResult"}},"204":{"description":"Cancelled by a newer preview of yours"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Previews temporarily unavailable","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/directional":{"post":{"summary":"Directional Sprites: render a 3D model into one spritesheet per direction, in a style. Pass the GLB in `model` (URL or base64; rigged, to animate it - see create3DModel with rig, or rigModel) and at most one animation: `animation_url` (the `glb_url` of an animation from animate3DModel or animate3DModelPreset, made for this model), `preset_id` (a preset from listAnimationPresets, retargeted onto the model; any rig type can take one, though retargeting does not succeed on every model, and when it does not the call returns an error) or `model_animation` (the name of an animation the GLB itself carries). With no animation each sheet is a single frame. `directions` are where the camera stands around a character facing S (up to 8); every direction is rendered together, on the same frame size, scale and floor, so the sheets line up. Pick the style with listDirectionalSpriteStyles (style_id, and style_params for its settings) and check it with previewDirectionalSprites first. The job result is one sheet per direction, in the order asked, each with its `spritesheet_url`, grid fields, fps and `render_3d`. Credits are held for every direction when the job is accepted and charged for the sheets that come back; a failed or cancelled job is refunded. Pass an optional request_id to tag the sheets for later retrieval via listGenerations (type spritesheet). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"createDirectionalSprites","x-cli-name":"directional","x-credit-action":"RENDER_SPRITE_3D","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/DirectionalSpritesPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/DirectionalSpritesResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprite/directional-stills":{"post":{"summary":"Directional Sprites stills: render a 3D model as it is (no animation) into one large image per direction, in a style, on a transparent background. Pass the GLB in `model` (URL or base64), the `directions` (up to 8, rendered together so they line up) and the style (listDirectionalSpriteStyles). Each still is a sprite image you can animate with animateSprite or edit with editImage. The job result is one image per direction, in the order asked. Credits are held for every direction when the job is accepted and charged for the images that come back; a failed or cancelled job is refunded. Pass an optional request_id to tag the images for later retrieval via listGenerations (type image). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Spritesheets"],"operationId":"createDirectionalStills","x-cli-name":"directional-stills","x-credit-action":"RENDER_SPRITE_3D_STILL","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/DirectionalStillsPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/DirectionalStillsResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/video":{"post":{"summary":"Generate a short video clip from a source image and a motion text prompt (image-to-video); a source `image` is required - to make a video from text alone, first createImage and animate that, or use createVideoFromReferences with reference images. Griffin (default, 768p) generates a soundtrack; no separate audio step is needed. The older 480p model (`griffin`) has been retired from this endpoint. The job result is the video URL and its actual duration in seconds. Optionally pass `final_image` to interpolate between a start and end frame. The chosen `model` and `duration` must be compatible (incompatible combinations return HTTP 400); see the `model` and `duration` fields for the values each model accepts. Credits are held when the job is accepted; the final charge is rate × produced seconds, never more than for the duration you requested, and the difference (or everything, if the job fails or is cancelled) is refunded. Pass an optional `request_id` to tag the result so you can locate it later via listGenerations (type video). Related tools: use `createImage` for static images, `animateSprite` for sprite-sheet animation, and listGenerations (type video) to list videos you generated earlier. Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Videos"],"operationId":"createVideo","x-credit-action":"GENERATE_VIDEO","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateVideoPayloadPublic"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/VideoResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/video/references":{"post":{"summary":"Generate a video from 1-5 reference images and a text prompt (references-to-video). Unlike createVideo, which animates a single source image, this composes a new scene that borrows characters, objects, and style from the reference images. Each image can be a URL or base64. Griffin (default, 768p) generates a soundtrack, at the same per-second rate as createVideo. The older 480p model (`griffin`) has been retired from this endpoint. The job result is the video URL and its actual duration in seconds. Choose the output shape with `aspect_ratio` (\"default\" lets the model decide). The chosen `model` and `duration` must be compatible (incompatible combinations return HTTP 400). Credits are held when the job is accepted; the final charge is rate × produced seconds, never more than for the duration you requested, and the difference (or everything, if the job fails or is cancelled) is refunded. Pass an optional `request_id` to tag the result so you can locate it later via listGenerations (type video). Related tools: `createVideo` for image-to-video, `editVideo` to modify a generated video. Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Videos"],"operationId":"createVideoFromReferences","x-credit-action":"REFERENCES_TO_VIDEO","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ReferencesToVideoPayloadPublic"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/VideoResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/video/edit":{"post":{"summary":"Edit a previously generated video with a text prompt and optional reference images (video-to-video); runs on griffin-hd (768p, with soundtrack) and the output keeps the source video's duration unless you pass one. Pass the video `url` you received from `createVideo`, `createVideoFromReferences`, or an earlier edit - it must be a video you generated within the last 7 days; arbitrary external videos are not accepted. Optionally add up to 5 reference `images` (URL or base64) to guide the edit. The job result is the new video URL and its actual duration in seconds. Credits are held when the job is accepted; the final charge is rate × produced seconds, never more than for the duration you requested, and the difference (or everything, if the job fails or is cancelled) is refunded. Pass an optional `request_id` to tag the result so you can locate it later via listGenerations (type video). Related tools: `createVideo` to generate the source clip, `createVideoFromReferences` for reference-driven generation. Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Videos"],"operationId":"editVideo","x-credit-action":"VIDEO_EDIT","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/EditVideoPayloadPublic"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/VideoResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/video/upscale":{"post":{"summary":"Upscale a previously generated video to twice its resolution (2x). Pass the video `url` you received from `createVideo`, `createVideoFromReferences`, or `editVideo` - it must be a video you generated within the last 7 days; arbitrary external videos are not accepted. Both dimensions of the source must be under 960 pixels: griffin-hd (768p) output qualifies only when square (768x768), not landscape (1344x768) or portrait (768x1344); output from the retired griffin (480p) does. A too-large source fails the job and the held credits are refunded. The job result is the new video URL (2x width and height, same duration) and its duration in seconds. Billed per second of video, independent of model; held when the job is accepted and refunded if it fails or is cancelled. Pass an optional `request_id` to tag the result so you can locate it later via listGenerations (type video). Related tools: `createVideo` for image-to-video, `editVideo` to modify a generated video. Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Videos"],"operationId":"upscaleVideo","x-credit-action":"UPSCALE_VIDEO","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/UpscaleVideoPayloadPublic"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/VideoResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/image/remove-background":{"post":{"summary":"Remove the background from a single image, returning the subject isolated on a transparent background. Supply the source image (URL or base64); optionally set crop to trim the result to the content, and creative_edit (default true) for higher-quality output that may not match the input pixel-for-pixel - set it false when the subject must stay pixel-identical, e.g. an existing sprite you will animate. The job result is a single image result with a url (not an array). The image is uploaded and validated, and an image larger than 15MB is rejected with HTTP 400. Credits are held when the job is accepted and refunded if it fails or is cancelled. Use removeBackground for this dedicated cutout task; editImage can also remove backgrounds via a prompt but is better for broader edits, while createImage and generateWithStyle produce new images rather than process an existing one. Pass an optional request_id to tag the result so you can retrieve it later via listGenerations (type image). Requires an API key (user scope). The call returns 202 with a job id - poll `GET /assets/jobs/{id}`, honouring poll_after_ms or long-polling with wait=30, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Images"],"operationId":"removeBackground","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/RemoveBackgroundPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ImageResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/image/upscale":{"post":{"summary":"Upscale an image: the same picture at a higher resolution, up to 4x, with the result capped at 2048px on its longest side. The source image (URL or base64) must be below 2048px on its longest side; a larger one is rejected with HTTP 400. The job result is a single image result with a url (not an array). The image is uploaded and validated, and an image larger than 15MB is rejected with HTTP 400. Credits are held when the job is accepted and refunded if it fails or is cancelled. Use upscaleImage to raise the resolution of an image you already have; createImage and editImage make new or changed images instead. Pass an optional request_id to tag the result so you can retrieve it later via listGenerations (type image). Requires an API key (user scope). The call returns 202 with a job id - poll `GET /assets/jobs/{id}`, honouring poll_after_ms or long-polling with wait=30, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Images"],"operationId":"upscaleImage","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/UpscaleImagePayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ImageResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/image/pixel-art":{"post":{"summary":"Convert an image to pixel art: any art, broken AI pixel art or pixel art comes back as pixel art at about the input's own size. Free (no credits) and synchronous: the call answers with the result directly, with no job to poll. `pixel_size` is the size of one pixel-art pixel, in the input's pixels: larger values give coarser art. Omit it to use the image's own pixel size, which only works on an image that already has a pixel grid - when none can be detected the call fails with HTTP 400 asking for a pixel_size. On an input already on a pixel grid, a pixel_size you pass snaps to whole steps of that grid. `palette_size` (2-256) caps the number of colours; omit it to keep the colours as they are. The response has the image `url`, the `pixel_size` used (fractional when it was detected) and the art's size in pixel-art pixels as `columns` and `rows`; resize the image to columns x rows with nearest-neighbour to get one image pixel per art pixel. The source image (URL or base64) is uploaded and validated, and one larger than 15MB is rejected with HTTP 400. The source is left as it is and the result is a new image. Limited to 20 calls per 5 minutes, 120 an hour and 1000 a day. Pass an optional request_id to tag the result for later retrieval via listGenerations (type image). Requires an API key (user scope).","tags":["Images"],"operationId":"convertToPixelArt","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ConvertToPixelArtPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PixelArtResult"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/3d-model":{"post":{"summary":"Convert a source image into a textured 3D model (image-to-3D). The job result is a downloadable GLB model_url plus an array of snapshot image URLs rendered from different angles (handy for previews). model picks the generator: prop (default) is low-cost and suits everyday props and environment pieces; hero is far more detailed and accurate, for close-up assets, but slower and more expensive. image is the front view; with prop, pass an optional back_image (the same subject seen from behind) so the back of the model follows it instead of being invented - hero takes the front image only and rejects a back_image with HTTP 400. The image is converted as it is: to make a character image for it first, from a prompt or by redrawing your own image as a 3D render, use create3DModelImage. Accepts optional mesh controls: target_num_faces (max triangle count, 1000-200000, default 50000), texture_size (1024 or 2048, default 2048), and texture_type (\"pbr\", \"simple\", or \"none\", default \"pbr\"). Pass rig true to also rig the model in the same call, with the general rig (joint_naming as in rigModel), ready for animate3DModelPreset or createDirectionalSprites; that adds the rigModel price, charged only when the result comes back rigged. Credits are held when the job is accepted and refunded if it fails or is cancelled. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type 3d). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["3D Models"],"operationId":"create3DModel","x-credit-action":"GENERATE_3D_MODEL","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Create3DModelPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Model3DResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/3d-model/image":{"post":{"summary":"Make a character image ready to turn into a 3D model, the first step of Directional Sprites on the web. Pass a prompt describing the character, your own image (URL or base64), or both. With an image, it is redrawn as a 3D render, and the prompt becomes the changes to make (e.g. \"add a red cape and a sword\"); leave the prompt out to keep the character as it is. A redraw is a new picture each time, so call again for another try. The job result is a single image result with a url (not an array): pass that url as create3DModel's image. create3DModel itself converts the image it is given as it is. A call with neither prompt nor image is rejected with HTTP 400, and an image larger than 15MB too. Credits are held when the job is accepted and refunded if it fails or is cancelled. Pass an optional request_id to tag the result so you can retrieve it later via listGenerations (type image). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["3D Models"],"operationId":"create3DModelImage","x-credit-action":"GENERATE_IMAGE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Create3DModelImagePayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ImageResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/3d-model/rig":{"post":{"summary":"Rig a 3D model: generate a skeleton and skin weights for an existing GLB so it can be animated. Accepts a URL or base64-encoded GLB in `model` (here `model` is the 3D asset file, not an AI model name - rigging has no model choice). The job result is a downloadable `model_url` for the rigged GLB. rig_type selects the skeleton prior - general (default, any asset), humanoid (anime-style characters), game (classic game-character rigs), or the pinned humanoid templates for two-armed, two-legged characters: humanoid_template (standard 22-joint skeleton with named joints) and humanoid_template_hands (52 joints, five fingers per hand). Animation presets retarget onto any rig type, though not onto every model. joint_naming relabels the identified joints to a convention - smpl (default), mixamo, humanik, unreal, godot, rigify, or vroid - without changing the skeleton. Credits are held when the job is accepted and refunded if it fails or is cancelled. Rigging is non-destructive to geometry but replaces any prior skeleton, so animations made against an old rig no longer apply. Requires an API key (user scope). The call returns 202 with a job id - poll `GET /assets/jobs/{id}`, honouring poll_after_ms or long-polling with wait=30, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["3D Models"],"operationId":"rigModel","x-credit-action":"RIG_3D_MODEL","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Rig3DModelPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Rig3DModelResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/3d-model/animate":{"post":{"summary":"Generate text-driven skeletal animations for an already-rigged 3D model. Pass the rigged GLB in `model` (URL or base64; the 3D asset file, not an AI model name) and a motion `prompt` (e.g. \"walking\", \"swinging its axe\"). The model must already have a skeleton - rig it first with rigModel if not. The job result is num_variants candidate animations (default 4), each a standalone animation-only GLB (skeleton + one clip, no mesh) in `glb_url` plus an mp4 `preview_url`, so you can pick the best one and fuse it with your model in a game engine or three.js. mode selects the representation - rot_trans (default, rotation plus translation per bone, most faithful) or rot_only (rotation only, easier to retarget onto another skeleton in an engine). Animation quality is hit-or-miss, which is why multiple candidates are returned. Credits are held once per call regardless of variant count and refunded if the job fails or is cancelled. Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["3D Models"],"operationId":"animate3DModel","x-credit-action":"ANIMATE_3D_MODEL","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Animate3DModelPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AnimationCandidates"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/3d-model/animate-preset":{"post":{"summary":"Apply a curated animation preset to an already-rigged 3D model (retargeting). Pass the rigged GLB in `model` (URL or base64; the 3D asset file, not an AI model name) and a `preset_id` from the animation presets list (from listAnimationPresets; any preset id works). Any rig type can take a preset, though retargeting does not succeed on every model: when it does not, the job fails. The job result is one retargeted animation - a standalone animation-only GLB in `glb_url` plus an mp4 `preview_url` - in the same `animations` envelope as the animate endpoint. crop_loop trims the clip to its seamlessly-looping span (omit to follow the preset's own loop flag); in_place removes net travel so the character moves on the spot, as game-engine locomotion expects (omit to keep the preset's own travel). Credits are held when the job is accepted and refunded if it fails or is cancelled. Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["3D Models"],"operationId":"animate3DModelPreset","x-credit-action":"ANIMATE_3D_PRESET","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Animate3DPresetModelPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AnimationCandidates"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/audio/sound-effect":{"post":{"summary":"Produce a short sound effect (SFX) from a text description, such as \"laser gun firing\" or \"footsteps on gravel\". The job result is a single audio result containing the URL of an MP3 or WAV file (the URL's extension, .mp3 or .wav, says which). The description field is required, duration is capped at 10 seconds (0 means auto-pick based on the description), and you may set loop to true for a seamlessly looping effect. Credits are held when the job is accepted and refunded if it fails or is cancelled. Use this for short, discrete sounds; use createAmbiance for a continuous looping background soundscape, createMusic for musical pieces, and createAudioTransform to remix an existing audio sample. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type audio). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Audio"],"operationId":"createSoundEffect","x-credit-action":"GENERATE_AUDIO_SFX","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateSoundEffectPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/audio/ambiance":{"post":{"summary":"Produce a looping background ambiance soundscape from a text description, such as \"windy forest at dusk\" or \"busy tavern interior\". The job result is a single audio result containing the URL of an MP3 or WAV file (the URL's extension, .mp3 or .wav, says which). The description field is required and duration is capped at 10 seconds (0 means auto-pick based on the description). Credits are held when the job is accepted and refunded if it fails or is cancelled. Use this for continuous, atmospheric background loops; use createSoundEffect for short discrete sound effects, createMusic for musical pieces, and createAudioTransform to remix an existing audio sample. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type audio). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Audio"],"operationId":"createAmbiance","x-credit-action":"GENERATE_AUDIO_SFX","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateAmbiancePayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/audio/music":{"post":{"summary":"Produce a piece of music from a text description, such as \"epic orchestral battle theme\" or \"calm piano melody\", with optional lyrics. The job result is a single audio result containing the URL of an MP3 or WAV file (the URL's extension, .mp3 or .wav, says which). The description field is required; duration must be one of the allowed values (0 means auto, otherwise multiples of 10 up to 180 seconds) and out-of-range values return HTTP 400. Set loop to true for a seamlessly looping piece. Credits are held when the job is accepted and refunded if it fails or is cancelled. Use this for songs and musical scores; use createSoundEffect for short sound effects, createAmbiance for looping background soundscapes, and createAudioTransform to remix an existing audio sample. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type audio). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Audio"],"operationId":"createMusic","x-credit-action":"GENERATE_AUDIO_MUSIC","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateMusicPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/audio/transform":{"post":{"summary":"Remix an existing audio sample (a sound effect, ambiance, or music clip) into a variation guided by a text prompt, for example turning a track into an 80s synthwave or metal version. Both the sample and the prompt are required; the sample is uploaded as a URL or base64 audio and must be at most 15MB or the call returns HTTP 400, and duration must be one of the allowed values (0 means match the source, otherwise multiples of 10 up to 180 seconds). The job result is a single audio result containing the URL of an MP3 or WAV file (the URL's extension, .mp3 or .wav, says which). The optional modification_strength (0 to 1, default 0.6) controls how far the result departs from the original, and loop set to true makes the result loop seamlessly. Credits are held when the job is accepted and refunded if it fails or is cancelled. Use this to transform existing audio you already have; use createSoundEffect, createAmbiance, or createMusic to generate audio from scratch. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type audio). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Audio"],"operationId":"createAudioTransform","x-credit-action":"GENERATE_AUDIO_TRANSFORM","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateAudioTransformPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/audio/voice":{"post":{"summary":"Design a new voice from a character description (such as \"deep-voiced warrior\" or \"cheerful young girl\"), a character image, or both, and have it speak a short line of text, returning a sample of that newly created voice. text is required, plus voice_description or image (an image of the speaking character, given as a URL or base64, makes the description optional); the spoken text is limited to 100 characters or the call returns HTTP 400, and type selects \"human\" or \"non-human\" voices. The job result is a single audio result containing the URL of an MP3 or WAV file (the URL's extension, .mp3 or .wav, says which). Credits are held when the job is accepted and refunded if it fails or is cancelled. Use this to invent and audition a voice from a description; use createSpeech for text-to-speech that clones a specific voice from an audio sample, and createSpeechPreset for text-to-speech using a named preset voice. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type audio). Requires an API key (user scope). The call returns 202 with a job id - poll `GET /assets/jobs/{id}`, honouring poll_after_ms or long-polling with wait=30, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Audio"],"operationId":"createVoice","x-credit-action":"GENERATE_AUDIO_VOICE","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateVoicePayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/audio/speech":{"post":{"summary":"Convert text to speech by cloning the voice from an audio sample you provide (voice-cloning text-to-speech). Both text and sample are required; the text is limited to 250 characters and the sample is supplied as a URL or base64 audio that must be at most 15MB, with violations returning HTTP 400. An optional delivery note (at most 50 characters, e.g. \"angry, shouting\") says how the line is said. The job result is a single audio result containing the URL of an MP3 or WAV file (the URL's extension, .mp3 or .wav, says which). Credits are held when the job is accepted and refunded if it fails or is cancelled. Use this when you have a reference voice sample to clone; use createSpeechPreset to speak with a built-in named preset voice instead, and createVoice to design a brand-new voice from a text description rather than cloning one. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type audio). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Audio"],"operationId":"createSpeech","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"x-credit-action":"GENERATE_AUDIO_SPEECH_CLONE","parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateSpeechPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/audio/speech-preset":{"post":{"summary":"Convert text to speech using a named built-in preset voice, with optional language and either an emotion or a delivery note, depending on the voice. Both text and voice_preset_id are required and the text is limited to 250 characters. The voices named by a first name (e.g. \"Rachel\") take a delivery note (at most 50 characters, e.g. \"angry, shouting\") and no emotion; the descriptive voices (e.g. \"Serious woman\") take an emotion and no delivery note. Invalid input, including an emotion or delivery note the voice does not take, returns HTTP 400. The job result is a single audio result containing the URL of an MP3 or WAV file (the URL's extension, .mp3 or .wav, says which). Credits are held when the job is accepted and refunded if it fails or is cancelled. Use this when you want a ready-made catalog voice and do not need to supply your own sample; use createSpeech to clone a voice from an audio sample instead, and createVoice to design a new voice from a text description. Pass an optional request_id to tag the result so you can locate it later via listGenerations (type audio). Requires an API key (user scope). The call returns 202 with a job id - poll getApiJob (pass wait 30 to long-poll) honouring poll_after_ms, until status is succeeded; its result field is exactly the response documented for this operation. Synchronous responses are deprecated but still supported: pass `async: false` to block until the result is ready and receive it as the response body. Synchronous calls run on the same queue: the response carries an X-Ludo-Job-Id header, and a job still running after 15 minutes comes back as 202 with the job instead of an error. Each account may have up to 50 generations queued or running at once via the API; a request beyond that returns 429 (code PENDING_JOBS_LIMIT).","tags":["Audio"],"operationId":"createSpeechPreset","x-credit-action":"GENERATE_AUDIO_SPEECH","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateSpeechPresetPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AudioResult"}},"202":{"description":"Accepted - the generation was queued; poll GET /assets/jobs/{id}. A synchronous (async: false) call still running after 15 minutes also receives this.","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/generations":{"get":{"description":"List your generation history - everything you have generated, through the API or in the Ludo web studio - most recent first. This is a free read-only lookup (no credits, no generation); it does not create anything. Pick a media `type` (image, spritesheet, video, audio, or 3d) and optionally narrow by free-text `search` (matched against tags, prompts and the other text on the asset), `source`, a `date_from`/`date_to` range (unix seconds), or the `request_id` you passed when you generated. Results are paginated with `page_size` (default 20, max 100) and 1-based `page_number`, and come back as an object: `items` (this page), plus `page`, `page_size` and `has_more`. Page until `has_more` is false rather than until you get a short page. `has_more` is computed by reading one row beyond the page, so it costs no extra query; there is deliberately no total count.\n\n`source` selects the surface an item came from: `api` returns only your API/MCP generations, `web` only your web-studio ones, `all` (the default) both. API-originated items expire 7 days after creation - their files live under a lifecycle-pruned storage prefix - so `source=api` never returns anything older than 7 days, while web items are kept indefinitely and are not time-bounded. API-originated results expire after 7 days - download anything you want to retain. (Assets can be kept permanently from the Ludo web app.) Each item carries an `id`, its generation-history id, which identifies the item across these listings. Every item also carries `type`, echoing the media type you asked for; on audio items the kind of audio (sfx, music, speech) is reported as `audio_type` so it does not collide with it.\n\nThis lists work that has already FINISHED. To follow a generation that is still queued or running, poll getApiJob with the job id you were given instead. Requires an API key (user scope).","x-mcp-description":"List your generation history across the API and the web app - filter by type, source (api|web|all), text search, and date range. Returns {items, page, page_size, has_more}; keep paging while has_more is true. API-originated results expire after 7 days - download anything you want to retain. For an in-flight job's status use getApiJob instead.","tags":["Generations"],"operationId":"listGenerations","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"type","in":"query","description":"Which kind of generation to list","required":true,"type":"string","enum":["image","spritesheet","video","audio","3d"]},{"name":"search","in":"query","description":"Free-text search. Every whitespace-separated term must match (case-insensitive substring) the item's tags or one of its text fields (prompt, hints, style, label, ...), so \"dwarf axe\" narrows to items matching both.","required":false,"type":"string"},{"name":"source","in":"query","description":"Which surface the items were generated from: api (your API/MCP generations, last 7 days only), web (your Ludo web studio generations, no time limit), or all (default, both)","required":false,"type":"string","enum":["api","web","all"],"default":"all"},{"name":"date_from","in":"query","description":"Only return items generated at or after this time (unix seconds)","required":false,"type":"integer"},{"name":"date_to","in":"query","description":"Only return items generated at or before this time (unix seconds)","required":false,"type":"integer"},{"name":"page_size","in":"query","description":"Items per page (1-100)","required":false,"type":"integer","default":20},{"name":"page_number","in":"query","description":"1-based page number","required":false,"type":"integer","default":1},{"name":"request_id","in":"query","description":"Only return items tagged with this request_id when you generated them","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GenerationList"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/images/results":{"get":{"description":"**Deprecated:** prefer `GET /assets/generations` (all types, paginated, same source filter). This endpoint continues to work; no removal is scheduled.\nList images you previously generated through the API, most recent first. This is a free read-only history lookup (no credits, no generation); it does not create anything. Pass an optional request_id query parameter to return only the results tagged with that id when you originally called createImage, editImage, generateWithStyle, generatePose, rotateSprite, or removeBackground. Requires an API key (user scope).","tags":["Images"],"operationId":"getImageResults","deprecated":true,"x-mcp-exclude":true,"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"query","description":"Filter results by request_id","required":false,"type":"string"},{"name":"source","in":"query","description":"Which surface the results were generated from: api (default, your API/MCP generations from the last 7 days), web (your Ludo web studio generations, no time limit), or all","required":false,"type":"string","enum":["api","web","all"],"default":"api"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ImageResult"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/sprites/results":{"get":{"description":"**Deprecated:** prefer `GET /assets/generations` (all types, paginated, same source filter). This endpoint continues to work; no removal is scheduled.\nList spritesheets you previously generated through the API, most recent first. This is a free read-only history lookup (no credits, no generation); it does not create anything. Pass an optional request_id query parameter to return only the results tagged with that id when you originally called animateSprite or transferMotion. Requires an API key (user scope).","tags":["Spritesheets"],"operationId":"getSpriteResults","deprecated":true,"x-mcp-exclude":true,"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"query","description":"Filter results by request_id","required":false,"type":"string"},{"name":"source","in":"query","description":"Which surface the results were generated from: api (default, your API/MCP generations from the last 7 days), web (your Ludo web studio generations, no time limit), or all","required":false,"type":"string","enum":["api","web","all"],"default":"api"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/SpriteResult"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/videos/results":{"get":{"description":"**Deprecated:** prefer `GET /assets/generations` (all types, paginated, same source filter). This endpoint continues to work; no removal is scheduled.\nList videos you previously generated through the API, most recent first. This is a free read-only history lookup (no credits, no generation); it does not create anything. Pass an optional request_id query parameter to return only the results tagged with that id when you originally called createVideo. Requires an API key (user scope).","tags":["Videos"],"operationId":"getVideoResults","deprecated":true,"x-mcp-exclude":true,"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"query","description":"Filter results by request_id","required":false,"type":"string"},{"name":"source","in":"query","description":"Which surface the results were generated from: api (default, your API/MCP generations from the last 7 days), web (your Ludo web studio generations, no time limit), or all","required":false,"type":"string","enum":["api","web","all"],"default":"api"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/VideoResult"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/audio/results":{"get":{"description":"**Deprecated:** prefer `GET /assets/generations` (all types, paginated, same source filter). This endpoint continues to work; no removal is scheduled.\nList audio clips you previously generated through the API, most recent first. This is a free read-only history lookup (no credits, no generation); it does not create anything. Pass an optional request_id query parameter to return only the results tagged with that id when you originally called createSoundEffect, createAmbiance, createMusic, createAudioTransform, createVoice, createSpeech, or createSpeechPreset. Requires an API key (user scope).","tags":["Audio"],"operationId":"getAudioResults","deprecated":true,"x-mcp-exclude":true,"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"query","description":"Filter results by request_id","required":false,"type":"string"},{"name":"source","in":"query","description":"Which surface the results were generated from: api (default, your API/MCP generations from the last 7 days), web (your Ludo web studio generations, no time limit), or all","required":false,"type":"string","enum":["api","web","all"],"default":"api"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/AudioResult"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/3d-models/results":{"get":{"description":"**Deprecated:** prefer `GET /assets/generations` (all types, paginated, same source filter). This endpoint continues to work; no removal is scheduled.\nList 3D models you previously generated through the API, most recent first. This is a free read-only history lookup (no credits, no generation); it does not create anything. Pass an optional request_id query parameter to return only the results tagged with that id when you originally called create3DModel. Requires an API key (user scope).","tags":["3D Models"],"operationId":"get3DModelResults","deprecated":true,"x-mcp-exclude":true,"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"request_id","in":"query","description":"Filter results by request_id","required":false,"type":"string"},{"name":"source","in":"query","description":"Which surface the results were generated from: api (default, your API/MCP generations from the last 7 days), web (your Ludo web studio generations, no time limit), or all","required":false,"type":"string","enum":["api","web","all"],"default":"api"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ThreeDModelResult"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/jobs":{"get":{"description":"List the generation jobs you started through the API or MCP, most recent first. Web-app jobs are not included. This is a free read-only lookup (no credits, no generation). Filter with a comma-separated status list; limit defaults to 50 and is capped at 100. Requires an API key (user scope).","tags":["Jobs"],"operationId":"listApiJobs","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"status","in":"query","description":"Comma-separated statuses to include (queued, running, succeeded, failed, canceled). Defaults to all.","required":false,"type":"string"},{"name":"limit","in":"query","description":"Maximum number of jobs to return. Defaults to 50, capped at 100.","required":false,"type":"integer","format":"int32"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/PublicJob"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/jobs/{id}":{"get":{"description":"Fetch one job you started through the API or MCP. Statuses: queued, running (both non-terminal - the response carries poll_after_ms, wait at least that long before asking again), then succeeded (read `result`), failed (read `error`: {code, subcode, message, retriable}; the held credits are refunded) or canceled. Generations typically finish within a few minutes; a job still queued after 15 minutes can be cancelled and resubmitted. Better still, pass wait=<seconds> (up to 60) to long-poll: the request is held until the job reaches a terminal state, so one call replaces a polling loop. Once status is succeeded, `result` holds exactly what the synchronous form of the operation named by task_type would have returned. This is a free read-only lookup (no credits). Requires an API key (user scope).","tags":["Jobs"],"operationId":"getApiJob","x-mcp-description":"Poll the status of a generation job started by any tool. Every generation tool returns `{id, status}`; call getApiJob with that id until status is `succeeded` (then read `result`, shaped exactly like the tool's documented output) or `failed` (read `error`). Poll every few seconds, or pass `wait: 30` to long-poll - the call is held up to 30 seconds (max 60) and returns as soon as the job finishes.","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","description":"The job id returned by a generation call (its `id` in the 202 response).","required":true,"type":"string"},{"name":"wait","in":"query","description":"Seconds to long-poll for a terminal state (0-60). Defaults to 0 (return immediately).","required":false,"type":"integer","format":"int32"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No such job for this API key","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"description":"Cancel a queued job you started through the API or MCP; the credits held for it are refunded. Jobs that are already running cannot be canceled (409). Returns the canceled job. This is free (no credits). Requires an API key (user scope).","tags":["Jobs"],"operationId":"cancelApiJob","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","description":"The job id returned by a generation call (its `id` in the 202 response).","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PublicJob"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"No such job for this API key","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Job is not in a cancelable state (e.g. already running)","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/uploads":{"post":{"description":"Start an upload of a local file, so it can be passed to a generation endpoint by URL instead of inline as base64. Declare the file's MIME type and exact size in bytes (up to 15MB, the same limit the generation endpoints apply); the response carries an upload_url and a file_url. Send the file's bytes in a single PUT to upload_url (no Authorization header; the content type and size are fixed by this call, and a body of any other size is refused). Once the PUT succeeds, pass file_url in any image, audio, video or model field that accepts a URL. The file is public to anyone holding file_url and is deleted 7 days after upload. upload_url is single-use. Free (no credits). Requires an API key (user scope). Not exposed as an MCP tool.","tags":["Files"],"operationId":"createUpload","x-mcp-exclude":true,"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/CreateUploadPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/UploadSession"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/pricing":{"get":{"description":"What Ludo's paid service costs and includes, for an agent advising a user: every plan with its monthly and annual price, credits, seats, queue concurrency and what it includes or lacks; credit top-ups and their price brackets; what each API call costs; and the rules to know before telling a user to pay. The ones that matter most: the API, MCP and CLI need an active Pro or Studio plan (Free and Indie get 402 on every other call), and there every generation costs credits - \"unlimited\" on Pro and Studio is the web app only. Plans are bought in the web app (`links.billing`), not through the API. Plans, prices and rules here are the live ones, the same as https://ludo.ai/pricing. This is a free lookup (no credits, no job) and works with or without an API key, on any plan.","tags":["Pricing"],"operationId":"getPricing","security":[{"ApiKey":[]},{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/PricingGuide"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/account":{"get":{"description":"Show your account's credit balance, plan and auto top-up settings. `credits.available` is what generations can spend right now (plan_credits + extra - used); `resets_at` is when the plan's monthly credits refill (unix seconds). When `team_pool` is true the balance is your team's shared pool, and `auto_topup` is null because the team owner manages it. Check this before an expensive generation instead of waiting for a 402. Buying credits and changing auto top-up happen in the web app at `billing_url`. This is a free read-only lookup (no credits, no generation). Requires an API key (user scope).","tags":["Account"],"operationId":"getAccount","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AccountInfo"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/cli-key":{"delete":{"description":"Revoke the key making this call, when it was created by a browser login. Other API keys are managed in the web app. Free. Requires an API key (user scope).","tags":["Account"],"operationId":"revokeCurrentCliKey","x-mcp-exclude":true,"security":[{"ApiKey":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/OkResponse"}},"400":{"description":"Not a browser-login key","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/assets/feedback":{"post":{"description":"Report a problem with the Ludo API itself to the Ludo team. Use it ONLY for API bugs (an error, a failed or stuck job, a crash, a response that does not match this documentation) and API limitations (a parameter, operation or format you needed that the API does not support, and that you could not work around). NEVER use it to report model limitations or the quality of a result: an image, sprite, animation, 3D model, video or sound that looks wrong, ignores part of the prompt, has artifacts or is not what you wanted is not an API bug. Do not report those; retry with a different prompt, model or settings instead. Before reporting a limitation, call searchDocs with what you tried: if the docs already describe it as a known limitation, do not report it. Describe what you were trying to do and what went wrong, and name the operation it relates to. Always send the id of every job the report is about, in job_ids (up to 10): we look at those jobs' inputs and outputs, and a report about a result we cannot find cannot be investigated. Do not include personal data, secrets or file contents. Send one report per issue, and if you are an agent, tell the user what you sent. Free (no credits). Requires an API key (user scope).","tags":["Account"],"operationId":"submitApiFeedback","security":[{"ApiKey":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ApiFeedbackPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ApiFeedbackReceipt"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/video-to-spritesheet":{"post":{"summary":"Generates spritesheet from video (Roles: user)","tags":["Gamestorming"],"operationId":"generateVideoToSpritesheet","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Spritesheet"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/spritesheet-to-audio":{"post":{"summary":"Generates audio for spritesheet (Roles: user)","tags":["Gamestorming"],"operationId":"generateSpritesheetToAudio","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateSpritesheetAudioPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Spritesheet"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/spritesheet-motion-transfer":{"post":{"summary":"Generates spritesheet by transferring motion from video or spritesheet to image (Roles: user)","tags":["Gamestorming"],"operationId":"generateSpritesheetMotionTransfer","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateSpritesheetMotionTransferPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Spritesheet"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/render-model":{"post":{"summary":"Renders 3D model to video (Roles: user)","tags":["Gamestorming"],"operationId":"renderModel","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/StringValue"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/game-concept-header/images/{projectId}":{"delete":{"summary":"Cancels request to generate images for the game concept header (Roles: user)","tags":["Gamestorming"],"operationId":"cancelGameConceptHeaderImages","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/game-concept-header/images-new/{projectId}":{"post":{"summary":"Generates images for the game concept header (Roles: user)","tags":["Gamestorming"],"operationId":"generateGameConceptHeaderImagesNew","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"},{"name":"extract","in":"query","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/image-prompts":{"post":{"summary":"Image prompts based on current trends (Roles: user)","tags":["Gamestorming"],"operationId":"getImagePrompts","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"isVideo","in":"query","required":false,"type":"boolean"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/TrendKeywords"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/images/generations":{"post":{"summary":"Generates images for generations (Roles: user)","tags":["Gamestorming"],"operationId":"generateImagesForGenerations","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GameImage"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/step-by-step":{"post":{"summary":"Step by step game generator (Roles: user)","tags":["Gamestorming"],"operationId":"generateStepByStep","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StepByStepPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/StepValue"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/ideas-from-steps":{"post":{"summary":"Step by step game generator (Roles: user)","tags":["Gamestorming"],"operationId":"generateIdeasFromSteps","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StepByStepPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/games/generate/new":{"post":{"summary":"Generates game ideas (Roles: user)","tags":["Gamestorming"],"operationId":"generateNew","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generate/new-map":{"post":{"summary":"Generates new map (Roles: user)","tags":["Gamestorming"],"operationId":"generateNewMap","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewMapPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GeneratedMap"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generate/map-tiles":{"post":{"summary":"Generates map tiles (Roles: user)","tags":["Gamestorming"],"operationId":"generateMapTiles","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateMapTilesPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GeneratedMap"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generate/inpaint-map":{"post":{"summary":"Inpaints map elements (Roles: user)","tags":["Gamestorming"],"operationId":"generateInpaintMap","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"object"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generate/extract-map-element":{"post":{"summary":"Extracts map element (Roles: user)","tags":["Gamestorming"],"operationId":"generateExtractMapElement","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"object"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generation-history/{type}":{"get":{"summary":"Get user's generation history by type (Roles: user)","tags":["Misc"],"operationId":"getGenerationHistory","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"type","in":"path","required":true,"type":"string","description":"Type of generation history to retrieve"},{"name":"pageSize","in":"query","required":false,"type":"number","description":"Number of items per page (default: 20, max: 100)"},{"name":"pageNumber","in":"query","required":false,"type":"number","description":"Page number (1-based, default: 1)"},{"name":"search","in":"query","required":false,"type":"string","description":"Free-text search; whitespace-separated terms are AND-ed over tags, prompts, user inputs (hints), art style, perspective, and label"},{"name":"tags","in":"query","required":false,"type":"array","items":{"type":"string"},"collectionFormat":"csv","description":"Labels picked deliberately (as opposed to typed). Each must match the item's tags EXACTLY, and they AND with each other and with `search`. Unlike `search`, this never matches prompts or other text, so picking a label narrows the list to the items actually carrying it. Values are normalized the same way stored tags are, so either \"two-handed\" or \"Two Handed\" works."},{"name":"outputsOnly","in":"query","required":false,"type":"boolean","default":false,"description":"When true, return only items that have a finished generated output for this type (drops image-only/intermediate items). Applied before pagination so number_pages reflects the filtered count. Combines with search as AND. No-op for types without outputs (audio, generated_image)."},{"name":"dateFrom","in":"query","required":false,"type":"number","description":"Inclusive lower bound (unix seconds) on the item's creation date. Combines with search/outputsOnly as AND; applied before pagination so number_pages reflects the filtered count."},{"name":"dateTo","in":"query","required":false,"type":"number","description":"Inclusive upper bound (unix seconds) on the item's creation date. Callers pass end-of-day to keep a single-day range inclusive."},{"name":"source","in":"query","required":false,"type":"string","enum":["web","api","all"],"default":"web","description":"Which surface the items were generated from: web (default, studio generations only), api (public API/MCP generations only - these expire 7 days after creation unless kept), or all."},{"name":"renderedFrom","in":"query","required":false,"type":"string","description":"Spritesheets and generated images only: the history id of a 3D model (three_d_asset), to list only the sheets or stills rendered from it (3D to sprite). Combines with the other filters as AND, before pagination. Ignored for other types."},{"name":"id","in":"query","required":false,"type":"string","description":"One item by its history id (at most one result). Lets the client re-read an item it already holds, e.g. a sprite whose sheet urls were served watermarked before the viewer subscribed. Combines with the other filters as AND."}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GenerationHistoryResults"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generation-history/{type}/tags":{"get":{"summary":"Get distinct tags (with counts) for a generation-history type (Roles: user)","tags":["Misc"],"operationId":"getGenerationHistoryTags","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"type","in":"path","required":true,"type":"string","description":"Type of generation history"},{"name":"source","in":"query","required":false,"type":"string","enum":["web","api","all"],"default":"web","description":"Which surface the items were generated from: web (default), api, or all."}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GenerationHistoryTags"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generation-history":{"post":{"summary":"Updates a user's generation history (Roles: user)","tags":["Misc"],"operationId":"updateGenerationHistory","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerationHistory"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GenerationHistory"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"summary":"Removes a user's generation history (Roles: user)","tags":["Misc"],"operationId":"removeGenerationHistory","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"}}}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GenerationHistory"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/generation-history/keep":{"post":{"summary":"Promote an API-generated history item to a permanent web asset (Roles: user)","description":"API/MCP generations expire 7 days after creation (their files live under the api/ storage prefix, which is lifecycle-pruned). Keeping an item copies its files out of the api/ prefix, rewrites the stored URLs, and clears the api flag - after which it behaves exactly like a studio generation: permanent, visible in the default library view, and usable in projects.","tags":["Misc"],"operationId":"keepGenerationHistory","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"GenerationHistory id of the API-generated item to keep"}}}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GenerationHistory"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/gdd/{id}":{"post":{"summary":"Chat with a GDD(Roles: user)","tags":["Misc"],"operationId":"chatGdd","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ChatRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ChatMessage"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/gdd/{id}/conversations":{"get":{"summary":"Lists the caller's stored GDD chat conversations for a project, newest first (Roles: user)","tags":["Misc"],"operationId":"listGddConversations","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"limit","in":"query","required":false,"type":"integer"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GddConversationSummary"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"post":{"summary":"Creates or wholly replaces one of the caller's GDD chat conversations (Roles: user)","tags":["Misc"],"operationId":"saveGddConversation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SaveGddConversationRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GddConversation"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/gdd/{id}/conversations/{conversationId}":{"get":{"summary":"Gets one of the caller's GDD chat conversations (Roles: user)","tags":["Misc"],"operationId":"getGddConversation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"conversationId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GddConversation"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"summary":"Deletes one of the caller's GDD chat conversations and returns the remaining list (Roles: user)","tags":["Misc"],"operationId":"deleteGddConversation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"conversationId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GddConversationSummary"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/assistant":{"post":{"summary":"Chat with the Ludo assistant (Roles: user)","tags":["Misc"],"operationId":"chatAssistant","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AssistantRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AssistantMessage"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/help":{"post":{"summary":"Chat with the Ludo assistant -- focused on docs (Roles: user)","tags":["Misc"],"operationId":"chatHelp","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AssistantRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AssistantMessage"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/assistant/logs":{"get":{"summary":"Gets the chat logs with the Ludo assistant (Roles: user)","tags":["Misc"],"operationId":"getChatAssistantLogs","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AssistantLog"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"post":{"summary":"Persists updates to an existing assistant conversation, e.g. generated media (Roles: user)","tags":["Misc"],"operationId":"saveChatAssistantConversation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/SaveAssistantConversationRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AssistantLog"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/assistant/archive":{"get":{"summary":"Gets the Ludo assistant chat archives (Roles: user)","tags":["Misc"],"operationId":"getChatAssistantArchives","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/AssistantLog"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/chat/assistant/archive/{id}":{"delete":{"summary":"Removes an archive and returns the current archives (Roles: user)","tags":["Misc"],"operationId":"removeChatAssistantArchive","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/AssistantLog"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/cron/{type}":{"get":{"summary":"Runs a cronjob (Roles: none)","tags":["Misc"],"operationId":"runCron","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"type","in":"path","required":true,"type":"string"},{"name":"id","in":"query","type":"string"}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/showcase":{"get":{"summary":"Gets items for a showcase gallery (Roles: none)","tags":["Misc"],"operationId":"getShowcaseGallery","security":[{"Bearer":[]}],"x-security-scopes":["none"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShowcaseGallery"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/showcase/unity":{"get":{"summary":"Gets items for a showcase gallery in unity (Roles: none)","tags":["Misc"],"operationId":"getShowcaseGalleryUnity","security":[{"Bearer":[]}],"x-security-scopes":["none"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ShowcaseGallery"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/feedback":{"post":{"summary":"Submits user feedback (Roles: user)","tags":["Misc"],"operationId":"submitFeedback","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/UserFeedback"}}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/health-check":{"get":{"summary":"Checks if the service is running (Roles: user, none)","tags":["Misc"],"operationId":"healthCheck","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/credits/costs":{"get":{"summary":"Gets all credit costs for platform features (Roles: none)","tags":["Misc"],"operationId":"getCreditCosts","security":[{"Bearer":[]}],"x-security-scopes":["none"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CreditCosts"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/service-notice":{"get":{"summary":"Gets the live service notice for the web app, if any (Roles: user, none)","tags":["Misc"],"operationId":"getServiceNotice","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ServiceNoticeResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/initial-payload":{"get":{"summary":"Gets the user the inital data necessary to use the platform (Roles: user)","tags":["Misc"],"operationId":"getInitialPayload","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"in":"query","name":"projects","type":"string","enum":["full","summary"],"default":"full","required":false,"description":"summary: each project carries a small `summary` (title, icon, last edit, vault element id, asset ids) in place of its gdd3. The web app asks for it; the whole document comes from GET /project/{projectId}."},{"in":"query","name":"documentation","type":"string","enum":["include","omit"],"default":"include","required":false,"description":"omit: the payload has no `documentation`; the web app fetches it from GET /documentation when the help modal opens."}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/UserRefreshPayload"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/documentation":{"get":{"summary":"Gets the help documentation shown in the web app's help modal (Roles: user)","tags":["Misc"],"operationId":"getDocumentation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Documentation"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/documentation/site":{"get":{"summary":"Gets the help documentation for the public ludo.ai /docs and /faq pages (Roles: user, none)","tags":["Misc"],"operationId":"getSiteDocumentation","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/SiteDocument"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/whats-new/site":{"get":{"summary":"Gets every What's New entry for the public ludo.ai /whats-new page (Roles: user, none)","tags":["Misc"],"operationId":"getSiteWhatsNew","security":[{"Bearer":[]}],"x-security-scopes":["none","user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/SiteWhatsNewEntry"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/checkout-session":{"post":{"summary":"Creates a Stripe Checkout session for subscription. (Roles: user)","tags":["Payments"],"operationId":"checkoutSession","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"auth","in":"body","required":true,"schema":{"$ref":"#/definitions/CheckoutSessionPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/StringValue"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/billing":{"get":{"summary":"Proceeds to the Stripe Billing portal. (Roles: user)","tags":["Payments"],"operationId":"goToBillingPortal","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"flow","in":"query","description":"Where in the portal to land. Omitted: the portal home (subscription, cards, invoice history). payment_methods: straight to adding/updating a payment method.","required":false,"type":"string","enum":["payment_methods"]}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/StringValue"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/plans":{"get":{"summary":"Gets available payment plans (Roles: user, none)","tags":["Payments"],"operationId":"getPaymentPlans","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/PaymentPlan"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscribe/{planId}":{"post":{"summary":"Subscribes to a payment plan (Roles: user)","tags":["Payments"],"operationId":"subscribe","security":[{"Bearer":[]}],"consumes":["application/octet-stream"],"x-security-scopes":["user"],"parameters":[{"name":"planId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/confirm/setup-intent":{"post":{"summary":"Confirms a setup intent (Roles: user)","tags":["Payments"],"operationId":"confirmSetupIntent","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/CreditCard"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/confirm/payment-intent":{"post":{"summary":"Confirms a payment intent (Roles: user)","tags":["Payments"],"operationId":"confirmPaymentIntent","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscription/renew-credits":{"get":{"summary":"Renews credits immediately for active subscribers (Roles: user)","tags":["Payments"],"operationId":"renewCredits","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/topup":{"post":{"summary":"Purchases extra credits via slider top-up (Roles: user)","tags":["Payments"],"operationId":"purchaseTopup","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/TopupPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TopupResult"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/topup/config":{"get":{"summary":"Returns slider top-up pricing brackets (Roles: user, none)","description":"Public, like /payment/plans: ludo.ai/pricing reads it at build time so the advertised top-up prices come from the same brackets the charge uses.","tags":["Payments"],"operationId":"getTopupConfig","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/TopupConfig"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/topup/auto":{"get":{"summary":"Returns the user's auto top-up settings (Roles: user)","tags":["Payments"],"operationId":"getAutoTopup","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AutoTopupSettings"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"put":{"summary":"Updates the user's auto top-up settings (Roles: user)","tags":["Payments"],"operationId":"setAutoTopup","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AutoTopupPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/AutoTopupSettings"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscription/pause":{"post":{"summary":"Pauses subscription for specified duration (Roles: user)","tags":["Payments"],"operationId":"pauseSubscription","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/PauseSubscriptionRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscription/pause/extend":{"post":{"summary":"Extends a running pause, once, up to six months total (Roles: user)","tags":["Payments"],"operationId":"extendPause","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ExtendPauseRequest"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscription/unpause":{"post":{"summary":"Resumes paused subscription immediately (Roles: user)","tags":["Payments"],"operationId":"unpauseSubscription","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["application/octet-stream"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscription/reactivate":{"post":{"summary":"Reverses a scheduled cancellation (Roles: user)","tags":["Payments"],"operationId":"reactivateSubscription","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["application/octet-stream"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/credit-card/default":{"post":{"summary":"Sets a default credit card (Roles: user)","tags":["Payments"],"operationId":"makeCardDefault","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/CreditCard"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/credit-card":{"post":{"summary":"Adds a credit card (Roles: user)","tags":["Payments"],"operationId":"addCreditCard","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/CreditCard"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"get":{"summary":"Gets the user's credit card (Roles: user)","tags":["Payments"],"operationId":"getCreditCards","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/CreditCard"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"summary":"Removes a credit card (Roles: user)","tags":["Payments"],"operationId":"removeCreditCard","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/CreditCard"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/invoices":{"get":{"summary":"Gets the user's invoices (Roles: user)","tags":["Payments"],"operationId":"getInvoices","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Invoice"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscription/retry-payment":{"get":{"summary":"Retries the failed payment for a subscription (Roles: user)","tags":["Payments"],"operationId":"retryPayment","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/subscription":{"get":{"summary":"Gets the user's subscription (Roles: user)","tags":["Payments"],"operationId":"getSubscription","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"summary":"Unsubscribes (Roles: user)","tags":["Payments"],"operationId":"unsubscribe","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Subscription"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/promo":{"get":{"summary":"Gets the active promo code (Roles: user)","tags":["Payments"],"operationId":"getPromoCode","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Coupon"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/payment/promo/{code}":{"get":{"summary":"Adds a promo code (Roles: user)","tags":["Payments"],"operationId":"addPromoCode","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"code","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/StringValue"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/stripe-event":{"post":{"tags":["Payments"],"operationId":"receiveStripeEvent","summary":"Receives a stripe event (Role: none)","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"event","in":"body","required":true,"schema":{"$ref":"#/definitions/StripeEvent"}}],"responses":{"204":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/images/{id}":{"get":{"summary":"Gets an image (Roles: user)","tags":["Misc"],"operationId":"getImage","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"format","in":"query","required":false,"type":"string"}],"produces":["image/png","image/jpeg","image/gif","image/webp"],"responses":{"200":{"description":"Success","schema":{"type":"string","format":"binary"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/images/upload":{"post":{"summary":"Uploads user images (Roles: user)","tags":["Misc"],"operationId":"uploadImage","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/UploadFilesPayload"}}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GameImage"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/images/pixel-art-conversion":{"post":{"summary":"Converts an image to pixel art at about its own size, synchronously. Free (Roles: user)","tags":["Misc"],"operationId":"convertImageToPixelArt","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/PixelArtConversionPayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GameImage"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/images/remove-background":{"post":{"summary":"Removes the background of an image (Roles: user)","tags":["Misc"],"operationId":"removeImageBackground","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"crop","in":"query","type":"boolean"},{"name":"image","in":"body","required":true,"schema":{"$ref":"#/definitions/GameImage"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GameImage"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/images/original/{filename}":{"get":{"summary":"Gets an original image (Roles: user)","tags":["Misc"],"operationId":"getOriginalImage","security":[{"Bearer":[]}],"x-security-scopes":["none"],"parameters":[{"name":"filename","in":"path","required":true,"type":"string"}],"produces":["image/png","image/jpeg","image/gif"],"responses":{"200":{"description":"Success","schema":{"type":"string","format":"binary"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/images/proxy":{"post":{"summary":"Proxies an image (Roles: user)","tags":["Misc"],"operationId":"proxyImage","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GameImage"}}],"produces":["image/png","image/jpeg","image/gif"],"responses":{"200":{"description":"Success","schema":{"type":"string","format":"binary"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}":{"delete":{"summary":"Archives the project (Roles: user)","tags":["Misc"],"operationId":"archiveProject","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"get":{"summary":"Gets a project (Roles: user)","tags":["Misc"],"operationId":"getProject","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/favorites":{"get":{"summary":"Gets all of the user's active favorites (Roles: user)","tags":["Misc"],"operationId":"getAllActiveFavorites","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Favorite"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/gdd3/history":{"get":{"summary":"Gets GDD3 version history (Roles: user)","tags":["Misc"],"operationId":"getGDD3History","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"},{"name":"limit","in":"query","required":false,"type":"integer","description":"Number of history entries to return (default: 50)"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GDD3Version"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/restore":{"get":{"summary":"Restores GDD3 to specific version (Roles: user)","tags":["Misc"],"operationId":"restoreGddVersion","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"},{"name":"version","in":"query","required":true,"type":"integer","description":"Version to restore to"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/restore/preview":{"get":{"summary":"Previews GDD3 state if restored to specific version (Roles: user)","tags":["Misc"],"operationId":"previewGddVersionRestore","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"},{"name":"version","in":"query","required":true,"type":"integer","description":"Version to preview restoration to"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/generation/cancel":{"get":{"summary":"Cancels GDD3 generation and clears loading states (Roles: user)","tags":["Misc"],"operationId":"cancelGdd3Generation","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/favorite":{"post":{"summary":"Adds a favorite item (unified endpoint) (Roles: user)","tags":["Misc"],"operationId":"addFavorite","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/AddFavoritePayload"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Favorite"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables":{"get":{"summary":"Gets the user's playables (Roles: user)","tags":["Projects"],"operationId":"getPlayables","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Playable"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playable-templates":{"get":{"summary":"Gets playable templates (Roles: user)","tags":["Projects"],"operationId":"getPlayableTemplates","security":[{"Bearer":[]}],"x-security-scopes":["user"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/SharedPlayable"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables/{id}/remix":{"get":{"summary":"Remixes a playable (Roles: user)","tags":["Projects"],"operationId":"remixPlayable","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Playable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables/{id}/share":{"get":{"summary":"Shares a playable (Roles: user)","tags":["Projects"],"operationId":"getSharedPlayable","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SharedPlayable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"post":{"summary":"Shares a playable (Roles: user)","tags":["Projects"],"operationId":"sharePlayable","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["application/json"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"allowPublic","in":"query","required":false,"type":"boolean"},{"name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/Playable"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/SharedPlayable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables/showcase":{"get":{"summary":"Gets the 10 most recent public shared playables, newest first (Roles: user)","tags":["Projects"],"operationId":"getPlayablesShowcase","security":[{"Bearer":[]}],"x-security-scopes":["user","none"],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/SharedPlayable"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables/{id}":{"post":{"summary":"Generates or iterates on a Playable prototype (Roles: user)","tags":["Projects"],"operationId":"promptPlayable","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"streamingKey","in":"query","required":false,"type":"string"},{"name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/PlayableRequest"}},{"name":"disableAssets","in":"query","type":"boolean","required":false}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Playable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"put":{"summary":"Saves a playable (Roles: user)","tags":["Projects"],"operationId":"savePlayable","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"playable","in":"body","required":true,"schema":{"$ref":"#/definitions/Playable"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Playable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"delete":{"summary":"Removes a playable (Roles: user)","tags":["Projects"],"operationId":"removePlayable","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"object","properties":{"success":{"type":"boolean"}}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables/{id}/revert-code/{messageIndex}":{"post":{"summary":"Reverts the code to a previous version (Roles: user)","tags":["Projects"],"operationId":"revertPlayableCode","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["application/octet-stream"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"messageIndex","in":"path","required":true,"type":"number"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Playable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables/{id}/image":{"post":{"summary":"Adds an image to a playable (Roles: user)","tags":["Projects"],"operationId":"updatePlayableIcon","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["application/json"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/GameImage"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Playable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/playables/{id}/title":{"post":{"summary":"Updates the title of a playable (Roles: user)","tags":["Projects"],"operationId":"updatePlayableTitle","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["application/json"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"},{"name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Playable"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/generate-code":{"get":{"summary":"Generates code for the project (Roles: user)","tags":["Projects"],"operationId":"generateProjectCode","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"},{"name":"streamingKey","in":"query","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ProjectCode"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/generate-code/prompt":{"post":{"summary":"Generates script for the project with a prompt (Roles: user)","tags":["Projects"],"operationId":"generateScriptPrompt","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"},{"name":"scriptId","in":"query","required":false,"type":"string"},{"name":"prompt","in":"body","required":true,"schema":{"$ref":"#/definitions/StringValue"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/ProjectCode"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/project/{projectId}/code":{"get":{"summary":"Gets the project's code (Roles: user)","tags":["Projects"],"operationId":"getProjectCode","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/ProjectCode"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/favorites/{id}":{"delete":{"summary":"Removes a favorite (Roles: user)","tags":["Misc"],"operationId":"removeFavorite","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success"},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/guide/project/{projectId}":{"get":{"summary":"Gets the current guide content for a project (Roles: user)","tags":["Guide"],"operationId":"getProjectGuideContent","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"projectId","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GuideContent"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/guide/content":{"post":{"summary":"Gets guide content for a game concept (Roles: user)","tags":["Guide"],"operationId":"getGuideContent","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GuideContentPayload"}},{"name":"regenerate","in":"query","required":false,"type":"boolean"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/GuideContent"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/game-concept/import":{"post":{"summary":"Imports a game concept from a PDF (Roles: user)","tags":["Misc"],"operationId":"importGameConceptPDF","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["multipart/form-data"],"parameters":[{"name":"file","in":"formData","description":"The uploaded file data","required":true,"type":"file"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/game-concept/import/text":{"post":{"summary":"Imports a game concept from text (Roles: user)","tags":["Misc"],"operationId":"importGameConceptText","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"gdd","in":"body","required":true,"schema":{"$ref":"#/definitions/GDD3"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/game-concept/import/{id}":{"post":{"summary":"Imports a game concept from an existing game (Roles: user)","tags":["Misc"],"operationId":"importGameConceptGame","security":[{"Bearer":[]}],"x-security-scopes":["user"],"consumes":["application/octet-stream"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/projects":{"get":{"summary":"Gets the user projects (Roles: user)","tags":["Misc"],"operationId":"getProjects","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"in":"query","name":"projects","type":"string","enum":["full","summary"],"default":"full","required":false,"description":"summary: each project carries a small `summary` (title, icon, last edit, vault element id, asset ids) in place of its gdd3. The web app asks for it; the whole document comes from GET /project/{projectId}."}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/Project"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}},"post":{"summary":"Add a project (Roles: user)","tags":["Misc"],"operationId":"addProject","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"project","in":"body","required":true,"schema":{"$ref":"#/definitions/Project"}}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/Project"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs":{"get":{"summary":"List the user's active generation jobs (Roles: user)","tags":["Jobs"],"operationId":"listJobs","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"status","in":"query","required":false,"type":"string","description":"Comma-separated statuses to include. Defaults to queued,running,failed."},{"name":"includeAcknowledged","in":"query","required":false,"type":"boolean","description":"Include jobs the user has dismissed. Defaults to false."}],"responses":{"200":{"description":"Success","schema":{"type":"array","items":{"$ref":"#/definitions/GenerationJob"}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/dismiss-all":{"post":{"summary":"Dismiss all the user's terminal-state jobs (failed/canceled/succeeded). (Roles: user)","tags":["Jobs"],"operationId":"dismissAllJobs","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"body","in":"body","required":false,"schema":{"type":"object","properties":{"statuses":{"type":"array","items":{"type":"string"},"description":"Statuses to dismiss. Defaults to [failed,canceled,succeeded]."}}}}],"responses":{"200":{"description":"Success","schema":{"type":"object","properties":{"count":{"type":"integer"}}}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/{id}":{"delete":{"summary":"Cancel a queued job. Running jobs cannot be canceled. (Roles: user)","tags":["Jobs"],"operationId":"cancelJob","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Job is not in a cancelable state (e.g. already running)","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/{id}/dismiss":{"post":{"summary":"Dismiss a terminal-state job (failed/canceled/succeeded) so it stops appearing in the queue panel. (Roles: user)","tags":["Jobs"],"operationId":"dismissJob","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Job not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Job is not in a terminal state","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/{id}/retry":{"post":{"summary":"Retry a failed job. Re-enqueues with the original task type and payload, dismisses the failed doc. (Roles: user)","tags":["Jobs"],"operationId":"retryJob","consumes":["application/octet-stream"],"security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"id","in":"path","required":true,"type":"string"}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"404":{"description":"Job not found","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Job is not in a retryable state","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-images":{"post":{"summary":"Enqueue an image generation job (Roles: user)","tags":["Jobs"],"operationId":"generateImagesQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-3d-images":{"post":{"summary":"Enqueue a 3D image generation job (Roles: user)","tags":["Jobs"],"operationId":"generate3DImagesQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-refined-3d-images":{"post":{"summary":"Enqueue a refined 3D image generation job (Roles: user)","tags":["Jobs"],"operationId":"generateRefined3DImagesQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Refined3DPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/rig-model":{"post":{"summary":"Enqueue a 3D model rigging job (Roles: user)","tags":["Jobs"],"operationId":"rigModelQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/RigModelPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}},"409":{"description":"Conflict - model has existing animations that must be deleted before re-rigging","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/animate-3d":{"post":{"summary":"Enqueue a text-driven 3D animation job (Roles: user)","tags":["Jobs"],"operationId":"animate3DQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Animate3DPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/animate-3d-preset":{"post":{"summary":"Enqueue a 3D animation-preset retargeting job (Roles: user)","tags":["Jobs"],"operationId":"animate3DPresetQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/Animate3DPresetPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-images-inpainting":{"post":{"summary":"Enqueue an image inpainting job (Roles: user)","tags":["Jobs"],"operationId":"generateImagesInpaintingQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-video":{"post":{"summary":"Enqueue a video generation job (Roles: user)","tags":["Jobs"],"operationId":"generateVideoQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateVideoPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-video-first-frame":{"post":{"summary":"Enqueue a video first-frame generation job (Roles: user)","tags":["Jobs"],"operationId":"generateVideoFirstFrameQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-audio":{"post":{"summary":"Enqueue an audio generation job (Roles: user)","tags":["Jobs"],"operationId":"generateAudioQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateAudioPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-image-to-spritesheet":{"post":{"summary":"Enqueue an image-to-spritesheet job (Roles: user)","tags":["Jobs"],"operationId":"generateImageToSpritesheetQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-sprite":{"post":{"summary":"Enqueue a sprite generation job (Roles: user)","tags":["Jobs"],"operationId":"generateSpriteQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateNewPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-spritesheet-motion-transfer":{"post":{"summary":"Enqueue a spritesheet motion transfer job (Roles: user)","tags":["Jobs"],"operationId":"generateSpritesheetMotionTransferQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateSpritesheetMotionTransferPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/render-sprite-3d":{"post":{"summary":"Enqueue a 3D-to-sprite render: each view asked for (directions) as its own styled spritesheet, rendered together so they line up. The job's result is the list of sheets. One RENDER_SPRITE_3D charge per sheet (Roles: user).","tags":["Jobs"],"operationId":"renderSprite3DQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/RenderSprite3DPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/render-sprite-3d-still":{"post":{"summary":"Enqueue 3D-to-sprite stills: the model as it is, one large styled image per view asked for (directions), rendered together so they line up. The job's result is the list of images, saved as generated images. One RENDER_SPRITE_3D_STILL charge per image (Roles: user).","tags":["Jobs"],"operationId":"renderSprite3DStillQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/RenderSprite3DStillPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-spritesheet-new-view":{"post":{"summary":"Enqueue a spritesheet new-view job (Roles: user)","tags":["Jobs"],"operationId":"generateSpritesheetNewViewQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/GenerateSpritesheetNewViewPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-edit-spritesheet":{"post":{"summary":"Enqueue a spritesheet-edit job (Roles: user)","tags":["Jobs"],"operationId":"editSpritesheetQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/EditSpritesheetPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-references-to-video":{"post":{"summary":"Enqueue a references-to-video job (Roles: user)","tags":["Jobs"],"operationId":"referencesToVideoQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/ReferencesToVideoPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-video-edit":{"post":{"summary":"Enqueue a video-edit job (Roles: user)","tags":["Jobs"],"operationId":"videoEditQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/VideoEditPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/generate-upscale-video":{"post":{"summary":"Enqueue a video-upscale (2x) job. Only for videos below 960x960 pixels. (Roles: user)","tags":["Jobs"],"operationId":"upscaleVideoQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/UpscaleVideoPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"402":{"description":"Insufficient credits","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/test-wait":{"post":{"summary":"Enqueue a no-op test job that sleeps for a random duration. Disabled in production unless JOB_TEST_TASK_ENABLED=true. (Roles: user)","tags":["Jobs"],"operationId":"testWaitQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/JobTestWaitPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}},"503":{"description":"Test task type disabled","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/remove-image-background":{"post":{"summary":"Enqueue a background-removal job (Roles: user)","tags":["Jobs"],"operationId":"removeImageBackgroundQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/QueueRemoveImageBackgroundPayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}},"/jobs/upscale-image":{"post":{"summary":"Enqueue an image upscale (up to 4x, capped at 2048px; the image must be below 2048px on its longest side). One GENERATE_IMAGE charge (Roles: user)","tags":["Jobs"],"operationId":"upscaleImageQueued","security":[{"Bearer":[]}],"x-security-scopes":["user"],"parameters":[{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/QueueUpscaleImagePayload"}}],"responses":{"202":{"description":"Accepted","schema":{"$ref":"#/definitions/JobAcceptedResponse"}},"400":{"description":"Error","schema":{"$ref":"#/definitions/ErrorResponse"}}}}}},"definitions":{"User":{"type":"object","required":["_id","email","role"],"properties":{"_id":{"type":"string"},"name":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"teams":{"type":"array","items":{"type":"string"}},"email":{"type":"string"},"stripped_email":{"type":"string"},"password":{"type":"string"},"role":{"type":"string","enum":["user"]},"stripe_id":{"type":"string"},"selected_project":{"type":"string"},"number_generations":{"type":"integer","format":"int64"},"registration_date":{"type":"integer","format":"int64"},"signup_source":{"type":"string"},"whats_new_date":{"type":"integer","format":"int64"},"trial_end":{"type":"integer","format":"int64"},"tax":{"$ref":"#/definitions/Tax"},"address":{"$ref":"#/definitions/Address"},"tutorials_closed":{"type":"array","items":{"type":"string"}},"missions_completed":{"type":"array","items":{"type":"string"}},"credits":{"$ref":"#/definitions/GenerationCreditsInfo"},"auto_topup":{"$ref":"#/definitions/AutoTopupSettings"},"country":{"type":"string"},"genres":{"type":"array","items":{"type":"string"}},"platform":{"type":"string"},"welcome_form":{"type":"boolean"},"paywall":{"type":"boolean"},"video_generator":{"type":"boolean"},"subscription_stats":{"$ref":"#/definitions/SubscriptionStats"},"date_whats_new":{"type":"integer","format":"int64"},"api_key":{"type":"string"},"cookie_consent":{"type":"boolean"},"double_optin_sent":{"type":"boolean"},"brevo_contact":{"type":"boolean"},"credits_exhausted_at":{"type":"integer","format":"int64"},"credits_exhausted_synced_at":{"type":"integer","format":"int64"},"credits_low_notified_at":{"type":"integer","format":"int64"},"first_generation_at":{"type":"integer","format":"int64"},"first_generation_synced_at":{"type":"integer","format":"int64"},"generation_days":{"type":"integer"},"last_generation_day":{"type":"string"},"review_prompt":{"$ref":"#/definitions/ReviewPrompt"},"risk_hold":{"type":"boolean"},"marketing_consent":{"$ref":"#/definitions/MarketingConsent"},"fbp":{"type":"string"},"fbc":{"type":"string"},"fbclid":{"type":"string"},"rdt_cid":{"type":"string"},"last_login_ip":{"type":"string","description":"IP address of last login"},"registration_ip":{"type":"string","description":"Client IP address the account was created from"},"registration_user_agent":{"type":"string","description":"User-Agent header (first 200 characters) the account was created with"},"purchase_ips":{"type":"array","description":"Capped history of client IPs captured at purchase actions (subscribe, checkout, card/payment confirmation, renewal). Used as chargeback evidence to tie a disputed transaction to the rightful cardholder.","items":{"$ref":"#/definitions/PurchaseIp"}},"preferred_content_type":{"type":"string"},"questionnaire":{"$ref":"#/definitions/Questionnaire"},"rate_limits_override":{"type":"integer","format":"int64"},"referral_code":{"type":"string"},"referred_by":{"type":"string","description":"User ID of the referrer"},"referral_credited":{"type":"boolean","description":"Whether referral bonus credits have been issued for this user"},"subscriber_welcome_sent_at":{"type":"integer","format":"int64"},"referral_announce_sent_at":{"type":"integer","format":"int64"},"discord_link_id":{"type":"string","description":"Opaque uuid shown on the user's Discord profile as their Ludo connection. Rotatable. Never the user id."},"discord_user_id":{"type":"string","description":"Discord snowflake of the linked account."},"discord_linked_at":{"type":"string","format":"date-time","description":"When the Discord account was last linked."},"totp":{"$ref":"#/definitions/UserTotp"},"webauthn_credentials":{"type":"array","description":"Registered passkeys (WebAuthn credentials) for this user.","items":{"$ref":"#/definitions/UserWebauthnCredential"}},"recovery_codes":{"type":"array","description":"Single-use 2FA recovery codes, stored hashed. Regenerating replaces the whole array.","items":{"$ref":"#/definitions/UserRecoveryCode"}},"sessions_version":{"type":"integer","default":0,"description":"Bumped whenever every existing session must be invalidated (2FA enable/disable, password change). Tokens carry it as the `sv` claim and are rejected when it no longer matches."}}},"UserTotp":{"type":"object","properties":{"secret_enc":{"type":"string","description":"TOTP shared secret, encrypted at rest (encryptionUtils.encryptSecret)."},"confirmed":{"type":"boolean","description":"True once the user proved possession of the secret with a code. An unconfirmed enrolment does not count as 2FA being enabled."},"last_timestep":{"type":"integer","format":"int64","description":"Timestep of the last accepted code. Anti-replay: a code whose timestep is not greater than this is rejected."},"created_at":{"type":"string","format":"date-time"}}},"UserWebauthnCredential":{"type":"object","properties":{"credential_id":{"type":"string","description":"base64url credential id returned by the authenticator."},"public_key":{"type":"string","description":"base64url COSE public key used to verify assertions."},"counter":{"type":"integer","format":"int64","description":"Signature counter of the last accepted assertion (clone detection)."},"transports":{"type":"array","items":{"type":"string"}},"name":{"type":"string","description":"User-visible label for the passkey."},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time"}}},"UserRecoveryCode":{"type":"object","properties":{"hash":{"type":"string","description":"bcrypt hash of the recovery code; the plaintext is shown once."},"used_at":{"type":"string","format":"date-time","description":"When the code was redeemed. Unset while the code is still usable."}}},"PurchaseIp":{"type":"object","properties":{"ip":{"type":"string"},"date":{"type":"integer","format":"int64","description":"Unix epoch seconds when this IP was captured at a purchase action"}}},"MarketingConsent":{"type":"object","properties":{"granted":{"type":"boolean"},"date":{"type":"integer","format":"int64"},"source":{"type":"string"}}},"ReviewPrompt":{"type":"object","properties":{"state":{"type":"string","enum":["shown","later","never","clicked"]},"asked_count":{"type":"integer"},"asked_at":{"type":"integer","format":"int64"},"answered_at":{"type":"integer","format":"int64"}}},"ReviewPromptPayload":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["ask","later","never","clicked"]}}},"ReviewPromptResponse":{"type":"object","properties":{"show":{"type":"boolean"},"asked_count":{"type":"integer"}}},"Questionnaire":{"type":"object","properties":{"date":{"type":"integer","format":"int64"},"questions":{"type":"array","items":{"$ref":"#/definitions/Question"}}}},"Question":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":"string"},"detail":{"type":"string","maxLength":200}}},"SubscriptionStats":{"type":"object","properties":{"revenue_year":{"type":"number","format":"float"},"revenue_12months":{"type":"number","format":"float"},"revenue_total":{"type":"number","format":"float"},"plan_name":{"type":"string"},"plan_interval":{"type":"string"},"is_customer":{"type":"boolean"},"became_customer_date":{"type":"integer","format":"int64"}}},"UserInfoPayload":{"type":"object","properties":{"name":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"number_generations":{"type":"integer","format":"int64"},"tax":{"$ref":"#/definitions/Tax"},"address":{"$ref":"#/definitions/Address"},"questionnaire":{"$ref":"#/definitions/Questionnaire"},"preferred_content_type":{"type":"string"},"tutorials_closed":{"type":"array","items":{"type":"string"}},"missions_completed":{"type":"array","items":{"type":"string"}},"selected_project":{"type":"string"},"password":{"type":"string"},"genres":{"type":"array","items":{"type":"string"}},"platform":{"type":"string"},"whats_new_date":{"type":"integer","format":"int64"},"welcome_form":{"type":"boolean"},"cookie_consent":{"type":"boolean"},"marketing_consent":{"type":"boolean"},"marketing_consent_source":{"type":"string"},"fbc":{"type":"string"},"fbp":{"type":"string"},"fbclid":{"type":"string"},"rdt_cid":{"type":"string"},"referral_code":{"type":"string"}}},"Team":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"subscription_id":{"type":"string"},"owner_id":{"type":"string"},"users":{"type":"array","items":{"$ref":"#/definitions/TeamUser"}},"invitations":{"type":"array","items":{"$ref":"#/definitions/TeamInvitation"}}}},"UpdateTeamPayload":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":60}}},"TeamInvitation":{"type":"object","properties":{"email":{"type":"string"},"role":{"enum":["editor"],"type":"string"},"token_hash":{"type":"string"}}},"TeamUser":{"type":"object","properties":{"user_id":{"type":"string"},"role":{"enum":["owner","editor"],"type":"string"},"subscription_id":{"type":"string"}}},"TeamUserInfo":{"type":"object","properties":{"user_id":{"type":"string"},"role":{"enum":["owner","editor"],"type":"string"},"email":{"type":"string"},"subscription_id":{"type":"string"},"subscription_status":{"type":"string"}}},"Tax":{"type":"object","properties":{"value":{"type":"string"},"type":{"type":"string"}}},"Address":{"type":"object","properties":{"line1":{"type":"string"},"line2":{"type":"string"},"city":{"type":"string"},"postal_code":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"}}},"LoginPayload":{"type":"object","properties":{"token":{"type":"string"},"user":{"$ref":"#/definitions/User"},"subscription":{"$ref":"#/definitions/Subscription"},"registered":{"type":"boolean"},"magic_link_sent":{"type":"boolean"},"magic_link_message":{"type":"string"},"two_factor_required":{"type":"boolean","description":"Credentials were accepted but the account has 2FA enabled, so no token is issued. Exchange the challenge_ticket for a full payload via /auth/2fa/verify."},"challenge_ticket":{"type":"string","description":"Single-use, short-lived ticket identifying the pending 2FA challenge."},"two_factor_methods":{"type":"array","description":"Second factors the user can present for this challenge.","items":{"type":"string"}}}},"Authentication":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string"},"password":{"type":"string"}}},"TwoFactorVerifyPayload":{"type":"object","required":["challenge_ticket"],"properties":{"challenge_ticket":{"type":"string","description":"The ticket returned by the login endpoint that was challenged."},"totp_code":{"type":"string"},"recovery_code":{"type":"string"},"webauthn_assertion":{"type":"object","description":"The authenticator's response to the options from /auth/2fa/webauthn/login-options. Exactly one of totp_code, recovery_code or webauthn_assertion must be present."}}},"TwoFactorLoginOptionsPayload":{"type":"object","properties":{"challenge_ticket":{"type":"string","description":"Present for a login challenge (there is no session yet). Omitted for a step-up ceremony, where the Bearer token identifies the user."}}},"TwoFactorStepUpPayload":{"type":"object","properties":{"totp_code":{"type":"string"},"recovery_code":{"type":"string"},"webauthn_assertion":{"type":"object"}},"description":"A fresh second-factor proof for a change to the second factors themselves. Exactly one field; an empty body is answered with error_payload.code STEP_UP_REQUIRED."},"TwoFactorTotpConfirmPayload":{"type":"object","required":["totp_code"],"properties":{"totp_code":{"type":"string"}}},"TwoFactorRegisterPayload":{"type":"object","required":["attestation"],"properties":{"attestation":{"type":"object","description":"The authenticator's response to the register-options challenge."},"name":{"type":"string","description":"User-visible label for the passkey. Defaults to \"Passkey\"."},"totp_code":{"type":"string"},"recovery_code":{"type":"string"},"webauthn_assertion":{"type":"object"}}},"TwoFactorSetupResponse":{"type":"object","properties":{"otpauth_url":{"type":"string"},"qr_data_uri":{"type":"string"},"secret":{"type":"string"}}},"TwoFactorEnrollResponse":{"type":"object","properties":{"token":{"type":"string","description":"Fresh token. Enrolling revokes every existing session, so the client must adopt this one immediately or log itself out."},"recovery_codes":{"type":"array","description":"Shown exactly once. EMPTY when the account already had recovery codes (a second factor type was added) — render that as \"your existing recovery codes still apply\", never as an empty list of ten.","items":{"type":"string"}}}},"TwoFactorRegisterResponse":{"type":"object","properties":{"credential":{"$ref":"#/definitions/TwoFactorPasskey"},"recovery_codes":{"type":"array","description":"Same exactly-once rule as TwoFactorEnrollResponse.","items":{"type":"string"}},"token":{"type":"string","description":"Present only when this passkey is what enabled 2FA."}}},"TwoFactorDisableResponse":{"type":"object","properties":{"token":{"type":"string","description":"Fresh token; disabling revokes every existing session."}}},"TwoFactorRemoveCredentialResponse":{"type":"object","properties":{"disabled":{"type":"boolean","description":"True when this was the last passkey and no authenticator app was confirmed, so removing it turned two-factor authentication off entirely rather than leaving an unsatisfiable requirement."},"token":{"type":"string","description":"Present only when `disabled` is true."}}},"TwoFactorRecoveryCodesResponse":{"type":"object","properties":{"recovery_codes":{"type":"array","items":{"type":"string"}}}},"TwoFactorPasskey":{"type":"object","properties":{"credential_id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time"}}},"TwoFactorStatusResponse":{"type":"object","properties":{"enabled":{"type":"boolean"},"totp_confirmed":{"type":"boolean"},"passkeys":{"type":"array","items":{"$ref":"#/definitions/TwoFactorPasskey"}},"recovery_codes_remaining":{"type":"integer"}}},"RegistrationPayload":{"type":"object","required":["email","password","name","code"],"properties":{"email":{"type":"string"},"name":{"type":"string"},"password":{"type":"string"},"code":{"type":"string"},"captcha_token":{"type":"string"}}},"CheckoutSessionPayload":{"type":"object","required":["planId"],"properties":{"planId":{"type":"string"}}},"RegistrationPaywallTeamPayload":{"type":"object","required":["password","name","team_id","email"],"properties":{"name":{"type":"string"},"password":{"type":"string"},"team_id":{"type":"string"},"email":{"type":"string"},"token":{"type":"string"},"code":{"type":"string"},"captcha_token":{"type":"string"}}},"DocumentationResponse":{"type":"object","properties":{"models":{"$ref":"#/definitions/ModelLineup"},"docs":{"type":"array","items":{"$ref":"#/definitions/DocumentationEntry"}},"unmatched_sections":{"type":"array","description":"Requested section titles that matched no section of `doc`. Present only when some, but not all, requested titles matched.","items":{"type":"string"}},"available_sections":{"type":"array","description":"Every section title of `doc`, for correcting the titles in `unmatched_sections`. Present only alongside `unmatched_sections`.","items":{"type":"string"}}}},"ModelLineup":{"type":"object","description":"Which models to use. Present on the table of contents only (no `doc` parameter).","properties":{"current":{"$ref":"#/definitions/CurrentModelsByAction"},"legacy":{"type":"array","description":"Legacy models: still accepted for existing integrations but scheduled for removal. Do not use them for new work.","items":{"$ref":"#/definitions/LegacyModel"}}}},"CurrentModelsByAction":{"type":"object","description":"Per credit action, the current model codenames in preference order. Pick from these for new work.","properties":{"ANIMATE_SPRITE":{"type":"array","items":{"type":"string"}},"TRANSFER_SPRITE_MOTION":{"type":"array","items":{"type":"string"}},"EDIT_SPRITESHEET":{"type":"array","items":{"type":"string"}},"NEW_VIEW_SPRITESHEET":{"type":"array","items":{"type":"string"}},"GENERATE_VIDEO":{"type":"array","items":{"type":"string"}},"REFERENCES_TO_VIDEO":{"type":"array","items":{"type":"string"}},"VIDEO_EDIT":{"type":"array","items":{"type":"string"}}}},"LegacyModel":{"type":"object","properties":{"codename":{"type":"string"},"name":{"type":"string"},"legacy_since":{"type":"string","description":"Date (YYYY-MM-DD) the model became legacy."},"features":{"type":"array","description":"Credit actions the model still accepts; empty once fully retired.","items":{"type":"string"}}}},"DocumentationEntry":{"type":"object","properties":{"id":{"type":"string","description":"Stable document id, e.g. sprite-generator - pass it back as the `doc` parameter"},"label":{"type":"string","description":"Human-readable document name, e.g. Sprite Generator"},"sections":{"type":"array","items":{"$ref":"#/definitions/DocumentationSection"}}}},"PricingGuide":{"type":"object","properties":{"currency":{"type":"string","description":"Currency of every price, e.g. usd."},"summary":{"type":"string"},"plans":{"type":"array","description":"Cheapest first. Free Trial comes first and has no prices.","items":{"$ref":"#/definitions/PricingPlan"}},"top_ups":{"$ref":"#/definitions/PricingTopUps"},"rules":{"type":"array","description":"What to know before advising a user to pay, each with a stable id.","items":{"$ref":"#/definitions/PricingRule"}},"costs":{"type":"array","description":"What each priced API operation (MCP tool) costs.","items":{"$ref":"#/definitions/PricingCost"}},"links":{"$ref":"#/definitions/PricingLinks"}}},"PricingPlan":{"type":"object","properties":{"id":{"type":"string","description":"free-trial, indie, pro or studio."},"name":{"type":"string"},"summary":{"type":"string"},"api_access":{"type":"boolean","description":"Whether the API, MCP and CLI generation work on this plan."},"credits_per_month":{"type":"integer","format":"int64","x-nullable":true,"description":"Credits each month, for the whole team on Studio. Null on Free Trial."},"credits_at_signup":{"type":"integer","format":"int64","x-nullable":true,"description":"Free Trial only - its credits, given once."},"seats":{"type":"integer","format":"int32"},"active_projects":{"type":"string","description":"A number, or unlimited."},"queue_concurrency":{"type":"integer","format":"int32","description":"How many generations run at once."},"prices":{"type":"array","items":{"$ref":"#/definitions/PricingPlanPrice"}},"included":{"type":"array","items":{"type":"string"}},"not_included":{"type":"array","items":{"type":"string"}}}},"PricingPlanPrice":{"type":"object","properties":{"interval":{"type":"string","enum":["month","year"]},"amount":{"type":"number","format":"double","description":"Price per interval, in currency units (50 = $50)."},"per_month":{"type":"number","format":"double","description":"Annual prices only - the price divided by 12."},"credits_granted":{"type":"integer","format":"int64","description":"Credits per interval. Annual plans grant the whole year at once."}}},"PricingTopUps":{"type":"object","properties":{"currency":{"type":"string"},"min_credits":{"type":"integer","format":"int64"},"max_credits":{"type":"integer","format":"int64"},"step_credits":{"type":"integer","format":"int64"},"brackets":{"type":"array","description":"The price at each bracket, in cents. An amount between two brackets is priced on the straight line between them.","items":{"$ref":"#/definitions/TopupBracket"}},"eligible":{"type":"string"}}},"PricingRule":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"}}},"PricingCost":{"type":"object","properties":{"operation":{"type":"string","description":"The API operationId, which is also the MCP tool name."},"credits":{"type":"string"}}},"PricingLinks":{"type":"object","properties":{"pricing":{"type":"string"},"billing":{"type":"string","description":"Where the user subscribes, changes plan and buys credits."}}},"DocumentationSearchResponse":{"type":"object","properties":{"results":{"type":"array","description":"Matching documentation sections, best first. Empty when nothing matches closely.","items":{"$ref":"#/definitions/DocumentationSearchResult"}},"message":{"type":"string","description":"Present only when `results` is empty: what to try next."}}},"DocumentationSearchResult":{"type":"object","properties":{"doc":{"type":"string","description":"Document id, e.g. sprite-generator - pass it to getDocs as `doc`"},"label":{"type":"string","description":"Human-readable document name, e.g. Sprite Generator"},"section":{"type":"string","description":"Section title - pass it to getDocs in `sections`"},"relevance":{"type":"number","format":"float","description":"Match score 0-1; only results at or above 0.6 are returned"},"content":{"type":"string","description":"Full markdown body of the section, as getDocs would return it. Request and response schemas for each tool are in the tool definitions, not in these sections."}}},"AnimationPresetsResponse":{"type":"object","properties":{"animations":{"type":"array","items":{"$ref":"#/definitions/AnimationPresetPublic"}},"perspectives":{"type":"array","items":{"$ref":"#/definitions/AnimationPerspective"}},"directions":{"type":"array","items":{"type":"string"}}}},"AnimationPresetPublic":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string"},"description":{"type":"string"},"duration":{"type":"number","format":"float"},"preview_url":{"type":"string"}}},"AnimationPerspective":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"message":{"type":"string"},"metadata":{"type":"string"},"error_payload":{"type":"string"},"intent":{"type":"object","description":"Stripe intent details sent with 3D Secure (406) errors","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["payment","setup"]},"client_secret":{"type":"string"}}}}},"UploadFilesPayload":{"type":"object","properties":{"files":{"type":"array","items":{"type":"string"}},"mimes":{"type":"array","items":{"type":"string"}},"compress":{"type":"boolean"}}},"Coupon":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"valid":{"type":"boolean"},"amount_off":{"type":"number","format":"float"},"percent_off":{"type":"number","format":"float"},"applies_to":{"type":"array","items":{"type":"string"}}}},"Subscription":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"collection_method":{"type":"string"},"cancel_at_period_end":{"type":"boolean"},"canceled_at":{"type":"integer","format":"int64"},"cancel_at":{"type":"integer","format":"int64"},"created":{"type":"integer","format":"int64"},"current_period_end":{"type":"integer","format":"int64"},"current_period_start":{"type":"integer","format":"int64"},"customer":{"type":"string"},"days_until_due":{"type":"integer","format":"int64"},"ended_at":{"type":"integer","format":"int64"},"plan":{"$ref":"#/definitions/PaymentPlan"},"quantity":{"type":"integer","format":"int64"},"start_date":{"type":"integer","format":"int64"},"status":{"type":"string"},"tax_percent":{"type":"integer","format":"int64"},"trial_end":{"type":"integer","format":"int64"},"trial_start":{"type":"integer","format":"int64"},"billing_cycle_anchor":{"type":"integer","format":"int64"},"latest_invoice":{"type":"string"},"from_team":{"type":"boolean"},"pause_collection":{"$ref":"#/definitions/SubscriptionPauseCollection"},"resume_requires_payment":{"type":"boolean","description":"Present (true) only while the subscription is paused and at least one billing cycle was skipped (voided) during the pause, meaning a resume will immediately charge the full plan price for a fresh cycle."},"ended_immediately":{"type":"boolean","description":"Present (true) only on an unsubscribe response when the cancel landed after a new period started but before its renewal was charged: the subscription was ended at once and the renewal discarded, instead of running (and billing) until the end of that new period."},"pause_started_at":{"type":"integer","format":"int64","description":"When the current pause was REQUESTED. Stripe records only where a pause ends (pause_collection.resumes_at), so this is stamped into subscription metadata by pauseSubscription. Audit only: the ceiling and the mid-pause email are measured from pause_billing_from, and fall back to this only when that is absent. Absent on pauses that began before the extension feature shipped."},"pause_billing_from":{"type":"integer","format":"int64","description":"When collection actually stops, i.e. the current_period_end that was current at the moment of pausing, and the anchor the six-month extension ceiling and the mid-pause midpoint are measured from. Must be stamped at pause time: with pause_collection.behavior \"void\" Stripe keeps advancing the billing cycle through the pause, so current_period_end read later names a different period. Absent on pauses that began before the extension feature shipped, which are then capped from pause_started_at instead (stricter, by the length of the paid-through tail)."},"pause_extended_at":{"type":"integer","format":"int64","description":"When the current pause was extended. Present at most once per pause; while it is present the Extend control must not be offered, because extendPause refuses a second extension."},"pause_can_extend":{"type":"boolean","description":"Present (true) only when extendPause would accept at least one more month right now: the pause is running, has not been extended, is not scheduled to cancel, and one more month still fits under the six-month ceiling measured from pause_billing_from (or pause_started_at). Gate the Extend control on this rather than on pause_extended_at alone, which would invite a subscriber already at the ceiling to extend and then refuse them. Absent on pauses with no anchor, where the server cannot tell from Stripe alone."}}},"SubscriptionPauseCollection":{"type":"object","properties":{"behavior":{"type":"string"},"resumes_at":{"type":"integer","format":"int64"}}},"PaymentPlan":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"object":{"type":"string"},"description":{"type":"string"},"amount":{"type":"integer","format":"int64"},"statement_descriptor":{"type":"string"},"trial_period_days":{"type":"integer","format":"int64"},"created":{"type":"integer","format":"int64"},"currency":{"type":"string"},"interval":{"type":"string"},"interval_count":{"type":"integer","format":"int64"},"livemode":{"type":"boolean"},"active":{"type":"boolean"},"billing_scheme":{"type":"string"},"product":{"type":"string"},"name":{"type":"string"},"usage_type":{"type":"string"},"nickname":{"type":"string"},"amount_off":{"type":"integer","format":"int64"},"percent_off":{"type":"integer","format":"int64"},"metadata":{"$ref":"#/definitions/PaymentPlanMetadata"}}},"PaymentPlanMetadata":{"type":"object","properties":{"projects":{"type":"string"},"description":{"type":"string"},"seats":{"type":"string"},"favorites":{"type":"string"},"team":{"type":"string"},"amount_text":{"type":"string"},"credits":{"type":"string"},"premium":{"type":"string"},"magic_link":{"type":"string"},"api":{"type":"string"},"queue_concurrency":{"type":"string"},"rate_limits":{"type":"string"}}},"Game":{"type":"object","properties":{"_id":{"type":"string"},"id":{"type":"string"},"source_id":{"type":"string"},"title":{"type":"string"},"short_description":{"type":"string"},"short_description_clean":{"type":"string"},"summary":{"type":"string"},"description":{"type":"string"},"description_clean":{"type":"string"},"description_annotations":{"type":"array","items":{"$ref":"#/definitions/Annotation"}},"user_rating_count":{"type":"integer","format":"int64"},"user_rating":{"type":"number","format":"float"},"developers":{"type":"array","items":{"type":"string"}},"urls":{"type":"array","items":{"type":"string"}},"release_date":{"type":"integer","format":"int64"},"update_date":{"type":"integer","format":"int64"},"icon":{"$ref":"#/definitions/GameImage"},"header":{"$ref":"#/definitions/GameImage"},"videos":{"type":"array","items":{"$ref":"#/definitions/MediaElement"}},"screenshots":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"url":{"type":"array","items":{"type":"string"}},"actual_genres":{"type":"array","items":{"type":"string"}},"predicted_genres":{"type":"array","items":{"type":"string"}},"augmented_genres":{"type":"array","items":{"type":"string"}},"source":{"type":"string"},"price":{"type":"string"},"platforms":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}},"game_modes":{"type":"array","items":{"type":"string"}},"genres":{"type":"array","items":{"type":"string"}},"advisory_rating":{"type":"string"}}},"Annotation":{"type":"object","properties":{"start":{"type":"integer","format":"int64"},"end":{"type":"integer","format":"int64"},"label":{"type":"string"},"text":{"type":"string"},"label_probs":{"type":"object","properties":{"feature":{"type":"number","format":"float"},"junk":{"type":"number","format":"float"},"mechanic":{"type":"number","format":"float"},"theme":{"type":"number","format":"float"}}}}},"MediaElement":{"type":"object","properties":{"type":{"type":"string"},"url":{"type":"string"}}},"GeneratedGame":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"text_raw":{"type":"string"},"genres":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"description_raw":{"type":"string"},"description_annotations":{"type":"array","items":{"$ref":"#/definitions/Annotation"}},"search_results":{"$ref":"#/definitions/GenerationsSearchResult"},"images":{"type":"array","items":{"$ref":"#/definitions/PrometheusGameImage"}},"screenshots":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"image":{"$ref":"#/definitions/GameImage"},"blending":{"type":"array","items":{"type":"string"}},"blended_games":{"type":"array","items":{"$ref":"#/definitions/Game"}},"platform":{"type":"string"},"perspective":{"type":"string"},"platforms":{"type":"array","items":{"type":"string"}},"sentence":{"type":"string"},"punchline":{"type":"string"},"art_style":{"type":"string"},"quality_score":{"type":"number","format":"float"},"uniqueness_score":{"type":"number","format":"float"},"final_score":{"type":"number","format":"float"},"hints":{"type":"array","items":{"type":"string"}},"request_id":{"type":"string"},"prompt":{"type":"string"},"selected_genres":{"type":"array","items":{"type":"string"}},"mechanics":{"type":"array","items":{"$ref":"#/definitions/GeneratedText"}},"step_choices":{"type":"array","items":{"$ref":"#/definitions/StepValue"}},"score":{"$ref":"#/definitions/LudoScore"},"image_prompt":{"type":"string"}}},"Spritesheet":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"audio_b64":{"type":"string"},"duration":{"type":"number","format":"float"},"billed_duration":{"type":"number","format":"float","description":"Seconds the generation was billed for: the length of the video Prometheus generated, never more than the duration the user requested. `duration` is the sheet's loop-trimmed length as Prometheus reports it and is usually shorter."},"start_timestamp":{"type":"number","format":"integer"},"end_timestamp":{"type":"number","format":"integer"},"num_frames":{"type":"number","format":"integer"},"num_cols":{"type":"number","format":"integer","description":"Columns in the frame grid. Absent on legacy sheets, which use a square grid of ceil(sqrt(num_frames)) per side."},"num_rows":{"type":"number","format":"integer","description":"Rows in the frame grid. Absent on legacy sheets, which use a square grid of ceil(sqrt(num_frames)) per side."},"max_num_frames":{"type":"number","format":"integer"},"margin_ratio":{"type":"number","format":"integer"},"margin_ratio_horizontal":{"type":"number","format":"float"},"margin_ratio_vertical":{"type":"number","format":"float"},"margin_ratio_mode":{"type":"string"},"target_frame_size":{"type":"number","format":"integer"},"loop":{"type":"boolean"},"bad_loop":{"type":"boolean"},"out_of_frame":{"type":"boolean"},"crop":{"type":"boolean"},"original_prompt":{"type":"string"},"motion_prompt":{"type":"string"},"prompt":{"type":"string"},"seed":{"type":"number","format":"integer"},"sprite_sheet":{"$ref":"#/definitions/PILImage"},"sprite_sheet_b64":{"type":"string"},"sprite_sheet_raw_b64":{"type":"string"},"sprite_sheet_watermarked_b64":{"type":"string","description":"URL of the watermarked variant of sprite_sheet_b64 (a keyed \"<token>_wmk\" name the clean key cannot be read from; \"<md5>_wm\" on sheets minted before October 2026). Present only when generated by a free user; served IN PLACE of sprite_sheet_b64 to free viewers. sprite_sheet_b64 itself always stays the clean asset."},"sprite_sheet_raw_watermarked_b64":{"type":"string","description":"Watermarked variant of sprite_sheet_raw_b64 (see sprite_sheet_watermarked_b64)."},"watermarked":{"type":"boolean","readOnly":true,"description":"Response-only. True when the served sprite_sheet_b64 is the watermarked variant (free viewer). Never persisted - stripped from inbound bodies."},"video":{"$ref":"#/definitions/GeneratedVideo"},"preview_video_b64":{"type":"string"},"first_frame_b64":{"type":"string"},"last_frame_b64":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"middle_image":{"$ref":"#/definitions/GameImage"},"final_image":{"$ref":"#/definitions/GameImage"},"has_audio":{"type":"boolean"},"model":{"type":"string","description":"Codename of the model that generated (or last re-rendered) the animation, e.g. blitz, forge, tango. Absent on sheets created before it was recorded."},"is_edited":{"type":"boolean","description":"True when this spritesheet was produced by an edit (prompt, outpaint, or fix_loop) - the whole animation was re-rendered, so frames may drift from the original."},"generation_mode":{"type":"string","description":"How the ML produced the animation, as reported by it (e.g. image, edit, motion_transfer). Absent on sheets saved before it was recorded."},"pixel_art_filter":{"type":"string","description":"Pixel-art filter the ML applied, as reported by it. Absent on sheets saved before it was recorded."},"degraded":{"type":"boolean","description":"Degraded flag as reported by the ML for this render. Absent when the ML did not report a boolean, and on sheets saved before it was recorded. Never null: swagger-tools ignores x-nullable, so a null here fails request validation."},"use_pixel_grid":{"type":"boolean","description":"The use_pixel_grid value this sheet was made with, when the caller set one (absent otherwise: true, the ML default, applied). Adjust, edit and new view inherit it when they do not set their own."},"label":{"type":"string"},"preview_scale":{"type":"number","format":"float"},"pack_alignment":{"$ref":"#/definitions/PackAlignment"},"render_3d":{"$ref":"#/definitions/Sprite3DRender","description":"How a sheet rendered from a 3D model (generation_mode render_3d) was rendered: style, settings, view, animation and model. Absent on every other sheet."}}},"PackAlignment":{"type":"object","properties":{"description":{"type":"string"},"sizeHeightPx":{"type":"number","format":"float"},"groundOffset":{"type":"number","format":"float"},"horizontalOffset":{"type":"number","format":"float"}}},"GeneratedVideo":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"original_prompt":{"type":"string"},"prompt":{"type":"string"},"url":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"pil_image":{"$ref":"#/definitions/PILImage"},"seed":{"type":"integer","format":"int64"},"duration":{"type":"number","format":"float"},"has_audio":{"type":"boolean"},"model":{"type":"string","description":"Codename of the model that generated the video, e.g. eagle. Absent on videos created before it was recorded."},"pixel_grid":{"type":"array","x-nullable":true,"description":"Pixel-art grid dimensions [width, height] detected/used by the generation, or null. Reported by pixel-art-aware models (forge-pixel); must always be persisted with the video.","items":{"type":"integer","format":"int32"}},"reference_url":{"type":"string","description":"Motion clip the animation was driven by (motion transfer presets), as reported by the ML. Absent on videos saved before it was recorded."},"crop_and_center_image":{"type":"boolean"},"middle_image":{"$ref":"#/definitions/GameImage"},"final_image":{"$ref":"#/definitions/GameImage"},"margin_ratio":{"type":"number","format":"float"},"margin_ratio_horizontal":{"type":"number","format":"float"},"margin_ratio_vertical":{"type":"number","format":"float"}}},"GeneratedAudio":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"audio_b64":{"type":"string"},"original_prompt":{"type":"string"},"original_text":{"type":"string"},"prompt":{"type":"string"},"text":{"type":"string"},"sample_url":{"type":"string"},"seed":{"type":"integer","format":"int64"},"type":{"type":"string"},"loop":{"type":"boolean"},"duration":{"type":"number","format":"float"},"voice_preset":{"$ref":"#/definitions/VoicePreset"},"model":{"type":"string","description":"Voice model of a speech-preset generation (blackbird when unset)."},"delivery":{"type":"string","description":"Delivery note the speech was generated with."},"image":{"$ref":"#/definitions/GameImage"}}},"VoicePreset":{"type":"object","properties":{"id":{"type":"string"},"language":{"type":"string"},"emotion":{"type":"string"}}},"VoicePresetList":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"languages":{"type":"array","items":{"type":"string"}},"emotions":{"type":"array","items":{"type":"string"}},"delivery":{"type":"boolean","description":"Whether this voice model takes a delivery note."},"model":{"type":"string","description":"The voice model these presets belong to, to send back with speech-preset generations. Unset means the default model."}}},"PILImage":{"type":"object","properties":{"format":{"type":"string"},"format_description":{"type":"string"}}},"PrometheusGameImage":{"type":"object","properties":{"game_id":{"type":"string"},"hash":{"type":"integer","format":"int64"},"id":{"type":"string"},"origin":{"type":"string"},"similarity":{"type":"number","format":"float"}}},"GameImage":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"image_id":{"type":"string"},"url":{"type":"string"},"type":{"type":"string"},"final_score":{"type":"number","format":"float"},"nsfw_prob":{"type":"number","format":"float"},"similarity":{"type":"number","format":"float"},"width":{"type":"integer","format":"int64"},"height":{"type":"integer","format":"int64"},"game_id":{"type":"string"},"hosted_filename":{"type":"string"},"icon":{"type":"boolean"},"targetSize":{"type":"integer"},"seed":{"type":"integer"},"embedded":{"type":"boolean"},"analysed":{"type":"boolean"},"image":{"type":"string"},"image_type":{"type":"string"},"prompt":{"type":"string"},"motion_prompt":{"type":"string"},"request_id":{"type":"string"},"hints":{"type":"array","items":{"type":"string"}},"selected_genres":{"type":"array","items":{"type":"string"}},"selected_colors":{"type":"array","items":{"type":"string"}},"selected_style":{"type":"string"},"selected_perspective":{"type":"string"},"is_safe":{"type":"boolean"},"platform":{"type":"string"},"loading":{"type":"boolean"},"original_hints":{"type":"array","items":{"type":"string"}},"genres":{"type":"array","items":{"type":"string"}},"edit_mask":{"type":"string"},"edit_mode":{"type":"string"},"camera_rotation":{"type":"integer","minimum":-180,"maximum":180},"camera_elevation":{"type":"integer","minimum":-30,"maximum":60},"edit_mask_pil_image":{"$ref":"#/definitions/PILImage"},"pil_image":{"$ref":"#/definitions/PILImage"},"nsfw":{"type":"number","format":"float"},"pose":{"$ref":"#/definitions/SpritePose"},"render_3d":{"$ref":"#/definitions/Sprite3DRender","description":"How a still rendered from a 3D model (3D to sprite) was rendered: style, settings, view and model. Absent on every other image."},"pixel_grid":{"type":"array","x-nullable":true,"description":"Pixel-art grid dimensions [width, height] the ML detected in (or gave) the image, as on GeneratedVideo. The ML reports it on every image generation; absent when no grid was found and on images saved before it was recorded.","items":{"type":"integer","format":"int32"}},"pixel_size":{"type":"number","format":"float","description":"Pixel art conversion only: the size of one pixel-art pixel, in the source image's pixels (the caller's, or the detected one)."},"model":{"type":"string","description":"Id of the model the ML reports for the image (klimt, frida, ...). Absent on images saved before it was kept."}}},"GenerationList":{"type":"object","properties":{"items":{"type":"array","description":"This page of results, newest first.","items":{"$ref":"#/definitions/GenerationListItem"}},"page":{"type":"integer","description":"The 1-based page number these items came from."},"page_size":{"type":"integer","description":"The page size actually applied (your page_size, clamped to 1-100)."},"has_more":{"type":"boolean","description":"Whether at least one more page exists after this one. Page until this is false rather than until you get a short page."}}},"GenerationListItem":{"type":"object","properties":{"id":{"type":"string","description":"Generation-history id of this item, identifying it across these listings. API-originated results expire after 7 days - download anything you want to retain. (Assets can be kept permanently from the Ludo web app.)"},"type":{"type":"string","description":"The media type of this item, echoing the requested `type`.","enum":["image","spritesheet","video","audio","3d"]},"request_id":{"type":"string"},"created_at":{"type":"integer","description":"When the item was generated (unix seconds)."},"url":{"type":"string","description":"image / video / audio: URL to the generated file."},"duration":{"type":"number","format":"float","description":"video / audio / spritesheet: duration in seconds."},"has_audio":{"type":"boolean","description":"video only."},"audio_type":{"type":"string","description":"audio only: the kind of audio (sfx, music, speech). Named `audio_type` here, not `type`, because `type` reports the media type on every item."},"spritesheet_url":{"type":"string","description":"spritesheet only."},"video_url":{"type":"string","description":"spritesheet only: preview video."},"audio_url":{"type":"string","description":"spritesheet only: URL of the sheet's sound effect (MP3 or WAV; the extension says which), when it has one."},"num_frames":{"type":"integer","description":"spritesheet only."},"num_cols":{"type":"integer","description":"spritesheet only: columns in the frame grid. Frames run left-to-right, top-to-bottom."},"num_rows":{"type":"integer","description":"spritesheet only: rows in the frame grid. The last row may be only partly filled."},"out_of_frame":{"type":"boolean","description":"spritesheet only: true when Ludo detected the sprite running past the frame's edges, so parts of it are cut off. A hint; editSpritesheet edit_mode \"outpaint\" extends the frame."},"bad_loop":{"type":"boolean","description":"spritesheet only: true when Ludo detected that the animation doesn't loop cleanly. A hint; editSpritesheet edit_mode \"fix_loop\" repairs it."},"model_url":{"type":"string","description":"3d only: URL to the generated model file."},"snapshots":{"type":"array","items":{"type":"string"},"description":"3d only: preview snapshot URLs."},"model":{"type":"string","description":"image only: the id of the model that generated or edited the image. Absent on older images."},"prompt":{"type":"string","description":"image: the prompt as the model received it. With augment_prompt on, this is the rewritten prompt, not the one you sent. 3d: the prompt the source image was generated from (text-to-3D); absent for a model made from your own image."},"source_image_url":{"type":"string","description":"3d only: URL of the image the model was built from (the generated concept image, or the image you sent). A text-to-3D concept image saved without a model yet has this and `prompt`, and no `model_url`."}}},"ImageResult":{"type":"object","properties":{"url":{"type":"string"},"request_id":{"type":"string"},"created_at":{"type":"integer"},"model":{"type":"string","description":"The id of the model that generated or edited the image, so you can tell when the model behind an operation changes. Generated and edited images only."},"prompt":{"type":"string","description":"The prompt as the model received it. With augment_prompt on, this is the rewritten prompt, not the one you sent. Generated and edited images only."}}},"PixelArtResult":{"type":"object","properties":{"url":{"type":"string","description":"URL of the pixel art, at about the input's size."},"pixel_size":{"type":"number","format":"float","description":"The pixel size used, in the input image's pixels - yours, or the detected one (which can be fractional)."},"columns":{"type":"integer","description":"Width of the art in pixel-art pixels."},"rows":{"type":"integer","description":"Height of the art in pixel-art pixels."}}},"SpriteResult":{"type":"object","properties":{"spritesheet_url":{"type":"string"},"video_url":{"type":"string"},"audio_url":{"type":"string","description":"URL of the sound effect generated for the animation (MP3 or WAV; the extension says which). hydra and hydra-plus only; absent for forge and forge-pixel."},"gif_url":{"type":"string"},"individual_frame_urls":{"type":"array","items":{"type":"string"}},"num_frames":{"type":"integer","description":"Number of frames in the spritesheet."},"num_cols":{"type":"integer","description":"Columns in the frame grid. Frames run left-to-right, top-to-bottom."},"num_rows":{"type":"integer","description":"Rows in the frame grid. The last row may be only partly filled."},"spritesheet_with_background_url":{"type":"string"},"individual_frame_with_background_urls":{"type":"array","items":{"type":"string"}},"duration":{"type":"number","format":"float"},"loop":{"type":"boolean","description":"The loop setting the sheet was made with. false is a one-shot animation (an attack, a death) that should play once in an engine."},"out_of_frame":{"type":"boolean","description":"true when Ludo detected the sprite running past the edges of the frame during the animation, so parts of it are cut off. It is a hint, not a guarantee: look at the frames. The cut is in the generated video, so adjustSpritesheet can't recover it. Call editSpritesheet with edit_mode \"outpaint\" to extend the frame beyond its borders, or generate again with a larger margin on the axis that was cut (margin_ratio_horizontal / margin_ratio_vertical)."},"bad_loop":{"type":"boolean","description":"true when Ludo detected that the animation doesn't loop cleanly. It is a hint, not a guarantee. Call editSpritesheet with edit_mode \"fix_loop\" to repair the loop."},"request_id":{"type":"string"},"created_at":{"type":"integer"}}},"VideoResult":{"type":"object","properties":{"url":{"type":"string","description":"URL to the generated video file"},"duration":{"type":"number","format":"float","description":"Duration of the video in seconds"},"has_audio":{"type":"boolean"},"request_id":{"type":"string"},"created_at":{"type":"integer"}}},"AudioResult":{"type":"object","properties":{"url":{"type":"string","description":"URL of the generated audio file: MP3 or WAV; the extension (.mp3 or .wav) says which."},"type":{"type":"string"},"duration":{"type":"number","format":"float"},"request_id":{"type":"string"},"created_at":{"type":"integer"}}},"ThreeDModelResult":{"type":"object","properties":{"model_url":{"type":"string"},"snapshots":{"type":"array","items":{"type":"string"}},"prompt":{"type":"string","description":"The prompt the source image was generated from (text-to-3D). Absent for a model made from your own image."},"source_image_url":{"type":"string","description":"URL of the image the model was built from (the generated concept image, or the image you sent)."},"request_id":{"type":"string"},"created_at":{"type":"integer"}}},"GenerateNewPayload":{"type":"object","properties":{"request_id":{"type":"string"},"model":{"type":"string"},"descriptions":{"type":"array","items":{"type":"string"}},"game_ids":{"type":"array","items":{"type":"string"}},"search_images":{"type":"integer"},"image_type":{"type":"string"},"context":{"type":"string"},"art_style":{"type":"string"},"perspective":{"type":"string"},"components":{"type":"array","items":{"$ref":"#/definitions/GeneratedText"}},"generations":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}},"hints":{"type":"array","items":{"type":"string"}},"gdds":{"type":"array","items":{"$ref":"#/definitions/GDD3"}},"context_gdd":{"$ref":"#/definitions/GDD3"},"project_id":{"type":"string"},"filters":{"$ref":"#/definitions/GenerateFilters"},"previous_generations":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}},"initial_game_image":{"$ref":"#/definitions/GameImage"},"initial_generated_image":{"$ref":"#/definitions/GameImage"},"initial_image_file":{"$ref":"#/definitions/GameImage"},"back_image_file":{"$ref":"#/definitions/GameImage","description":"Optional back view (180 degrees) of the subject in initial_image_file, for image-to-3D (force_3d). Prop only; hero takes the front image alone."},"middle_image_file":{"$ref":"#/definitions/GameImage"},"final_image_file":{"$ref":"#/definitions/GameImage"},"additional_input_images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"mask_image":{"type":"string"},"strength":{"type":"number","format":"float"},"aspect_ratio":{"type":"string"},"preserve_color":{"type":"boolean"},"preserve_composition":{"type":"boolean"},"preserve_details":{"type":"boolean"},"edit_mode":{"type":"string"},"camera_rotation":{"type":"integer","minimum":-180,"maximum":180},"camera_elevation":{"type":"integer","minimum":-30,"maximum":60},"loop":{"type":"boolean"},"crop":{"type":"boolean","default":true},"frames":{"type":"number","format":"integer"},"frame_size":{"type":"number","format":"integer"},"use_pixel_grid":{"type":"boolean"},"margin_ratio":{"type":"number","format":"float"},"margin_ratio_horizontal":{"type":"number","format":"float"},"margin_ratio_vertical":{"type":"number","format":"float"},"margin_ratio_mode":{"type":"string"},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"pose":{"$ref":"#/definitions/SpritePose"},"generation_parameters":{"$ref":"#/definitions/ThreeDGenerationParameters"},"duration":{"type":"number","format":"float"},"force":{"type":"boolean","description":"3D images with an initial_image_file - always redraw it as a 3D render, even when it is already a cut-out (by default a cut-out is used as is and any other image only has its background removed)."},"prompt":{"type":"string","maxLength":1000,"description":"3D images with an initial_image_file - instructions for redrawing it as a 3D render, e.g. \"keep the sword in the right hand\". Implies force."}}},"AnimateSpritePayload":{"type":"object","description":"Payload for generating an animated spritesheet from a static image. Input images can either be provided in base64 or URL. If the image was generated using Ludo, ideally it should be generated using the \"sprite\", \"sprite-vfx\" or \"ui_asset\" type.","required":["motion_prompt","initial_image"],"properties":{"motion_prompt":{"type":"string","description":"Text description of the desired animation, e.g. \"walking\", \"idle breathing\", \"attack slash\", \"casting a fireball with both hands\". Describe exactly and unambiguously what the character should do. Specific or complex actions are fine; there is no need to simplify them. Do not over-describe, though: too much detail can harm the animation, and an action the model already knows should be named, not decomposed into its steps (\"walking\", never \"move the left foot forward, then the right foot\"). Do not restate what initial_image already shows (the character, its appearance and equipment, the art style, the background, the lighting, the camera), and leave frame count and timing to the frames and duration fields. Negative phrasing (\"no background\", \"do not move the camera\", \"without a weapon\") works only on hydra and hydra-plus. On forge, forge-pixel and the legacy models it backfires: naming something you do not want makes it more likely to appear, so \"without a weapon\" tends to produce a weapon. On those models never phrase anything negatively; state only what should happen, and control everything else through the image and the other fields. Keep it well under 100 words: in longer prompts details start competing and some get dropped."},"initial_image":{"type":"string","description":"The url OR base64 of the starting frame image to animate. This is the base sprite that will be brought to life."},"final_image":{"type":"string","description":"The url OR base64 of ending frame image. When provided, the animation will interpolate between the initial and final frames."},"loop":{"type":"boolean","default":true,"description":"Trim the animation at the beginning or end to create a seamless loop. Not guaranteed to produce a perfect loop."},"crop":{"type":"boolean","default":true,"description":"Crop sprite frames to fit content. Results in smaller spritesheets but inconsistent frame sizes across different animations."},"frames":{"type":"number","format":"integer","description":"Maximum number of frames in the output spritesheet. Allow about one second of duration per 16 frames - above that the model may not be able to produce them all (64 frames needs a duration of at least 4). The result can have fewer frames when the duration is short or bad frames were removed.","default":36,"enum":[4,9,16,25,36,49,64]},"frame_size":{"type":"number","format":"integer","description":"Size of the sprite in pixels, not of the frame: it sets how big the character is drawn in the exported sheet, and each frame also holds the space around the sprite, so frames come out larger than this value. It changes the export only, not how the sprite is generated. 0 is maximum resolution. -1 is AI 1.5x upscaling. -9 is True Size: frames keep the size and position of the input frame, untrimmed, so the spritesheet lines up exactly with the input image - use it (not margin_ratio_mode \"none\") whenever the output must align with your input; it works with any margin mode. Exporting larger than the input image adds no detail: do not pick a size above the input's own resolution, and 0 or -1 gain nothing on inputs under 512 px. True Size crops at the input image's edges, so anything the animation adds beyond them (an aura, a spell effect, a swung weapon) is cut off: use a fixed size or 0 for those. adjustSpritesheet re-exports a finished animation at another size for free.","default":0,"enum":[32,64,96,128,192,256,384,0,-1,-9]},"margin_ratio":{"type":"number","format":"float","x-deprecated":true,"description":"Deprecated: prefer margin_ratio_horizontal / margin_ratio_vertical. Amount of padding around the sprite as a ratio (0.0 to 1.0). Sets both axes to this value. A per-axis value, when also given, overrides this for that axis. Supplying any margin value selects margin_ratio_mode \"manual\" unless margin_ratio_mode is set explicitly."},"margin_ratio_horizontal":{"type":"number","format":"float","description":"Horizontal padding around the sprite as a ratio (0.0 to 1.0). Every bit of margin is resolution taken from the sprite, so keep it as tight as the motion allows - about 0.15 - and go higher only for motion that extends sideways (sword slashes, punches) or if the sprite gets cut off. Supplying a value selects margin_ratio_mode \"manual\" unless margin_ratio_mode is set explicitly; overrides the legacy margin_ratio on this axis."},"margin_ratio_vertical":{"type":"number","format":"float","description":"Vertical padding around the sprite as a ratio (0.0 to 1.0). Every bit of margin is resolution taken from the sprite, so keep it as tight as the motion allows - about 0.15 - and go higher only for motion that extends up or down (jumps) or if the sprite gets cut off. Supplying a value selects margin_ratio_mode \"manual\" unless margin_ratio_mode is set explicitly; overrides the legacy margin_ratio on this axis."},"margin_ratio_mode":{"type":"string","description":"How the sprite is framed for generation. The model always generates on a canvas of the same size, so any margin is canvas the sprite does not fill: the more margin, the lower the resolution and detail of the sprite. \"auto\" (default, recommended) trims and centers the sprite and picks a margin suited to the motion. \"manual\" uses margin_ratio_horizontal / margin_ratio_vertical; keep them as tight as the motion allows (about 0.15) and raise only the axis the motion needs. \"none\" animates the input exactly as framed, with no trimming and no margin added, so the input must already have suitable margins: a sprite touching the edges gets its limbs, weapons and effects cut off as soon as it moves, and an image that is mostly empty space yields a small, low-detail sprite. \"none\" is the way to keep a deliberately off-center sprite (e.g. a character at the left edge firing a beam to the right), but results can be unpredictable. Do not use \"none\" to make the spritesheet line up with the input image - use frame_size -9 (True Size) for that; it works with any margin mode. Rules: omit margin_ratio_mode and send margin_ratio_horizontal / margin_ratio_vertical to get \"manual\" automatically; \"manual\" with no margin value fails with HTTP 400; \"auto\" or \"none\" sent together with a margin value fails with HTTP 400 (the value would be ignored).","enum":["auto","manual","none"],"default":"auto"},"image_type":{"x-swagger-data":"spriteImageTypes","type":"string","description":"Type of sprite being animated. Affects generation parameters and styling. Tiling types (horizontal / vertical tiles, scrolling backgrounds, parallax layers, textures) stay seamless only on forge and forge-pixel, and only if the input image itself already wraps seamlessly on that axis; otherwise the animation shows a visible seam when tiled.","default":"sprite"},"model":{"type":"string","x-model-notes":"forge-pixel expects real pixel art - a perfect pixel grid at its natural resolution; for other art use hydra or forge. Tiling image types stay seamless only on forge and forge-pixel.","description":"Animation model to use. hydra is the most capable and generates audio; forge is best for basic animations and relatively simple sprites; forge-pixel for pixel art. blitz (default), eagle and eagle-audio are legacy models kept for existing integrations.","enum":["hydra","hydra-plus","griffin-hd","forge","forge-pixel","blitz","eagle","eagle-audio","standard"],"default":"hydra"},"duration":{"type":"number","format":"float","default":3,"description":"Animation length in seconds. Available values depend on the model - the source of truth is MODEL_PRICING (credits.js), surfaced via /credits/costs; the public API docs list them per model (auto-generated)."},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"gif":{"type":"boolean","default":false,"description":"When true, generates an animated GIF from the spritesheet and returns it in gif_url. Disabled by default to reduce response time."},"individual_frames":{"type":"boolean","default":false,"description":"When true, extracts each frame from the spritesheet as an individual image and returns the URLs in individual_frame_urls."},"spritesheet_with_background":{"type":"boolean","default":false,"description":"When true, also returns the spritesheet with background intact (before background removal). Useful for manually fixing background removal issues. The with-background spritesheet URL will be in spritesheet_with_background_url."},"use_pixel_grid":{"type":"boolean","description":"Use the animation's pixel grid if one is detected. Defaults to true. true: when Ludo detects a pixel grid with confidence, the sheet is packed on it (logical pixels, scaled up by whole blocks); when none is detected, this has no effect. false: always pack as ordinary art, for when the detected grid is wrong for your asset (for example a smooth sprite coming back as coarse blocks). Ignored for forge-pixel animations, which are always packed on their grid."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"AnimateSpriteKeyframesPayload":{"type":"object","description":"Payload for generating an animated spritesheet that interpolates through up to three fixed keyframes (initial / middle / final). Runs on hydra (default), hydra-plus, forge or forge-pixel - the models supporting a middle keyframe. Input images can be provided in base64 or URL. initial_image is required; middle_image and final_image are optional.","required":["initial_image"],"properties":{"motion_prompt":{"type":"string","description":"Optional text description of the desired animation, e.g. \"walking\", \"attack slash\". When omitted, the motion is derived purely from the keyframes. When provided, describe exactly and unambiguously what the character should do. Specific or complex actions are fine; there is no need to simplify them. Do not over-describe, though: too much detail can harm the animation, and an action the model already knows should be named, not decomposed into its steps (\"walking\", never \"move the left foot forward, then the right foot\"). Do not restate what the keyframes already show (the character, its appearance and equipment, the art style, the background, the lighting, the camera), and leave frame count and timing to the frames and duration fields. Negative phrasing (\"no background\", \"do not move the camera\", \"without a weapon\") works only on hydra and hydra-plus. On forge and forge-pixel it backfires: naming something you do not want makes it more likely to appear, so \"without a weapon\" tends to produce a weapon. On those models never phrase anything negatively; state only what should happen, and control everything else through the keyframes and the other fields. Keep it well under 100 words: in longer prompts details start competing and some get dropped."},"initial_image":{"type":"string","description":"The url OR base64 of the first keyframe. Required - the animation starts from it."},"middle_image":{"type":"string","description":"The url OR base64 of the middle keyframe the animation passes through between the initial and final frames. Consecutive keyframes must differ - two identical keyframes in a row produce no motion across that span."},"final_image":{"type":"string","description":"The url OR base64 of the final keyframe. Optional."},"model":{"type":"string","x-model-notes":"forge-pixel expects real pixel art - a perfect pixel grid at its natural resolution; for other art use hydra or forge. Tiling image types stay seamless only on forge and forge-pixel.","description":"Backing model. Hydra (most capable, generates audio) and the Forge family support a middle keyframe; forge-pixel is the pixel-art variant, for sprites whose frames are pixel art.","enum":["hydra","hydra-plus","griffin-hd","forge","forge-pixel"],"default":"hydra"},"loop":{"type":"boolean","default":true,"description":"Trim the animation at the beginning or end to create a seamless loop. Not guaranteed to produce a perfect loop."},"crop":{"type":"boolean","default":true,"description":"Crop sprite frames to fit content. Results in smaller spritesheets but inconsistent frame sizes across different animations."},"frames":{"type":"number","format":"integer","description":"Maximum number of frames in the output spritesheet. Allow about one second of duration per 16 frames - above that the model may not be able to produce them all (64 frames needs a duration of at least 4). The result can have fewer frames when the duration is short or bad frames were removed.","default":36,"enum":[4,9,16,25,36,49,64]},"frame_size":{"type":"number","format":"integer","description":"Size of the sprite in pixels, not of the frame: it sets how big the character is drawn in the exported sheet, and each frame also holds the space around the sprite, so frames come out larger than this value. It changes the export only, not how the sprite is generated. 0 is maximum resolution. -1 is AI 1.5x upscaling. -9 is True Size: frames keep the size and position of the input frame, untrimmed, so the spritesheet lines up exactly with the input image - use it (not margin_ratio_mode \"none\") whenever the output must align with your input; it works with any margin mode. Exporting larger than the input image adds no detail: do not pick a size above the input's own resolution, and 0 or -1 gain nothing on inputs under 512 px. True Size crops at the input image's edges, so anything the animation adds beyond them (an aura, a spell effect, a swung weapon) is cut off: use a fixed size or 0 for those. adjustSpritesheet re-exports a finished animation at another size for free.","default":0,"enum":[32,64,96,128,192,256,384,0,-1,-9]},"margin_ratio":{"type":"number","format":"float","x-deprecated":true,"description":"Deprecated: prefer margin_ratio_horizontal / margin_ratio_vertical. Amount of padding around the sprite as a ratio (0.0 to 1.0). Sets both axes to this value. A per-axis value, when also given, overrides this for that axis. Supplying any margin value selects margin_ratio_mode \"manual\" unless margin_ratio_mode is set explicitly."},"margin_ratio_horizontal":{"type":"number","format":"float","description":"Horizontal padding around the sprite as a ratio (0.0 to 1.0). Every bit of margin is resolution taken from the sprite, so keep it as tight as the motion allows - about 0.15 - and go higher only for motion that extends sideways (sword slashes, punches) or if the sprite gets cut off. Supplying a value selects margin_ratio_mode \"manual\" unless margin_ratio_mode is set explicitly; overrides the legacy margin_ratio on this axis."},"margin_ratio_vertical":{"type":"number","format":"float","description":"Vertical padding around the sprite as a ratio (0.0 to 1.0). Every bit of margin is resolution taken from the sprite, so keep it as tight as the motion allows - about 0.15 - and go higher only for motion that extends up or down (jumps) or if the sprite gets cut off. Supplying a value selects margin_ratio_mode \"manual\" unless margin_ratio_mode is set explicitly; overrides the legacy margin_ratio on this axis."},"margin_ratio_mode":{"type":"string","description":"How the sprite is framed for generation. The model always generates on a canvas of the same size, so any margin is canvas the sprite does not fill: the more margin, the lower the resolution and detail of the sprite. \"auto\" (default, recommended) trims and centers the sprite and picks a margin suited to the motion. \"manual\" uses margin_ratio_horizontal / margin_ratio_vertical; keep them as tight as the motion allows (about 0.15) and raise only the axis the motion needs. \"none\" animates the input exactly as framed, with no trimming and no margin added, so the input must already have suitable margins: a sprite touching the edges gets its limbs, weapons and effects cut off as soon as it moves, and an image that is mostly empty space yields a small, low-detail sprite. \"none\" is the way to keep a deliberately off-center sprite (e.g. a character at the left edge firing a beam to the right), but results can be unpredictable. Do not use \"none\" to make the spritesheet line up with the input image - use frame_size -9 (True Size) for that; it works with any margin mode. Rules: omit margin_ratio_mode and send margin_ratio_horizontal / margin_ratio_vertical to get \"manual\" automatically; \"manual\" with no margin value fails with HTTP 400; \"auto\" or \"none\" sent together with a margin value fails with HTTP 400 (the value would be ignored).","enum":["auto","manual","none"],"default":"auto"},"image_type":{"x-swagger-data":"spriteImageTypes","type":"string","description":"Type of sprite being animated. Affects generation parameters and styling. Tiling types (horizontal / vertical tiles, scrolling backgrounds, parallax layers, textures) stay seamless only on forge and forge-pixel, and only if the input image itself already wraps seamlessly on that axis; otherwise the animation shows a visible seam when tiled.","default":"sprite"},"duration":{"type":"number","format":"float","default":3,"description":"Animation length in seconds. This endpoint always runs on the Forge model, so only Forge's durations apply - the source of truth is MODEL_PRICING (credits.js), surfaced via /credits/costs."},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"gif":{"type":"boolean","default":false,"description":"When true, generates an animated GIF from the spritesheet and returns it in gif_url. Disabled by default to reduce response time."},"individual_frames":{"type":"boolean","default":false,"description":"When true, extracts each frame from the spritesheet as an individual image and returns the URLs in individual_frame_urls."},"spritesheet_with_background":{"type":"boolean","default":false,"description":"When true, also returns the spritesheet with background intact (before background removal). Useful for manually fixing background removal issues. The with-background spritesheet URL will be in spritesheet_with_background_url."},"use_pixel_grid":{"type":"boolean","description":"Use the animation's pixel grid if one is detected. Defaults to true. true: when Ludo detects a pixel grid with confidence, the sheet is packed on it (logical pixels, scaled up by whole blocks); when none is detected, this has no effect. false: always pack as ordinary art, for when the detected grid is wrong for your asset (for example a smooth sprite coming back as coarse blocks). Ignored for forge-pixel animations, which are always packed on their grid."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"TransferMotionPayload":{"type":"object","description":"Payload for transferring motion from a video onto a static sprite image, producing an animated spritesheet.","required":["image"],"properties":{"image":{"type":"string","description":"The static sprite to animate, as a URL or base64 image. Ideally an image generated with the \"sprite\", \"sprite-vfx\" or \"ui_asset\" image type."},"video":{"type":"string","description":"URL of the video to use as motion source - the `video_url` of a spritesheet from animateSprite, or your own clip. Videos up to 4 seconds work best. Either `video` or `preset_id` + `perspective` + `direction` must be provided; when both are sent the video is used. A video more than twice as long as `duration` gets heavily compressed and the motion turns rushed - trim it to the part you want, or raise `duration`."},"preset_id":{"type":"string","description":"ID of an animation preset to use instead of a video URL. Use listAnimationPresets to list available presets. When using a preset, `perspective` and `direction` are required."},"direction":{"type":"string","description":"Direction for the animation preset. When using a preset, `direction` is required.","enum":["N","NE","E","SE","S","SW","W","NW"]},"perspective":{"x-swagger-data":"animationPerspectives","type":"string","description":"Camera perspective of the preset clip, by id (the `perspectives` list of listAnimationPresets, the same set for every preset): high = tactical steep top-down, horizon = side view at eye level, isometric = diagonal top-down with depth, low = hero low angle, top = directly overhead. Required when using a preset. Unrelated to the `perspective` of createImage, which is a free-text art direction."},"frames":{"type":"number","format":"integer","description":"Maximum number of frames in the output spritesheet. Allow about one second of duration per 16 frames - above that the model may not be able to produce them all (64 frames needs a duration of at least 4). The result can have fewer frames when the duration is short or bad frames were removed.","default":36,"enum":[4,9,16,25,36,49,64]},"frame_size":{"type":"number","format":"integer","description":"Size of the sprite in pixels, not of the frame: it sets how big the character is drawn in the exported sheet, and each frame also holds the space around the sprite, so frames come out larger than this value. 0 is maximum resolution. Exporting larger than the input image adds no detail: do not pick a size above the input's own resolution, and 0 gains nothing on inputs under 512 px. True Size is not available here: the image is not the first frame of the result, so there is nothing to align to.","default":0,"enum":[32,64,96,128,192,256,384,0]},"loop":{"type":"boolean","default":true,"description":"Trim the animation at the beginning or end to create a seamless loop."},"crop":{"type":"boolean","default":true,"description":"Crop sprite frames to fit content. Results in smaller spritesheets but inconsistent frame sizes across different animations."},"margin_ratio":{"type":"number","format":"float","x-deprecated":true,"description":"Deprecated: prefer margin_ratio_horizontal / margin_ratio_vertical. Amount of padding around the sprite as a ratio (0.0 to 1.0). Sets both axes to this value. A per-axis value, when also given, overrides this for that axis. Defaults to 0.15 when no margin value is given at all."},"margin_ratio_horizontal":{"type":"number","format":"float","description":"Horizontal padding around the sprite as a ratio (0.0 to 1.0). Every bit of margin is resolution taken from the sprite, so keep it as tight as the motion allows - about 0.15 - and go higher only for motion that extends sideways (sword slashes, punches) or if the sprite gets cut off. Overrides the legacy margin_ratio on this axis."},"margin_ratio_vertical":{"type":"number","format":"float","description":"Vertical padding around the sprite as a ratio (0.0 to 1.0). Every bit of margin is resolution taken from the sprite, so keep it as tight as the motion allows - about 0.15 - and go higher only for motion that extends up or down (jumps) or if the sprite gets cut off. Overrides the legacy margin_ratio on this axis."},"margin_ratio_mode":{"type":"string","description":"How the sprite is framed for generation. Defaults to \"manual\" (0.15 on each axis when no margin value is given); transfer motion has no \"auto\". The model always generates on a canvas of the same size, so any margin is canvas the sprite does not fill: the more margin, the lower the resolution and detail of the sprite. Keep the margins tight - about 0.15 - and raise only the axis the motion needs. \"none\" animates the image exactly as framed, so it must already have suitable margins (not touching the edges, not mostly empty space); an off-center sprite can give unpredictable results. Sending \"none\" together with a margin value fails with HTTP 400 (the value would be ignored).","enum":["manual","none"],"default":"manual"},"gif":{"type":"boolean","default":false,"description":"When true, generates an animated GIF from the spritesheet and returns it in gif_url. Disabled by default to reduce response time."},"individual_frames":{"type":"boolean","default":false,"description":"When true, extracts each frame from the spritesheet as an individual image and returns the URLs in individual_frame_urls."},"spritesheet_with_background":{"type":"boolean","default":false,"description":"When true, also returns the spritesheet with background intact (before background removal). Useful for manually fixing background removal issues. The with-background spritesheet URL will be in spritesheet_with_background_url."},"use_pixel_grid":{"type":"boolean","description":"Use the animation's pixel grid if one is detected. Defaults to true. true: when Ludo detects a pixel grid with confidence, the sheet is packed on it (logical pixels, scaled up by whole blocks); when none is detected, this has no effect. false: always pack as ordinary art, for when the detected grid is wrong for your asset (for example a smooth sprite coming back as coarse blocks). Ignored for forge-pixel animations, which are always packed on their grid."},"model":{"type":"string","x-model-notes":"forge is tuned for the motion presets (preset_id) and often fails to follow your own video - use hydra for custom footage. forge-pixel expects real pixel art - a perfect pixel grid at its natural resolution.","description":"Motion transfer model to use. Hydra is the most capable and generates audio; Forge is cost-effective for simple motion; Tango is a legacy model kept for existing integrations.","enum":["hydra","forge","forge-pixel","tango"],"default":"hydra"},"prompt":{"type":"string","description":"Optional extra instructions for the motion transfer (e.g. \"keep the cape still\"), added to the model's own prompt."},"duration":{"type":"number","format":"float","default":1.5,"description":"Animation length in seconds. If the reference video is longer, it will be compressed to this duration. Available values depend on the model - see /credits/costs (per-model lists are auto-generated into the public API docs)."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"CreateUploadPayload":{"type":"object","description":"Payload for starting a file upload.","required":["mime_type","size"],"properties":{"mime_type":{"type":"string","description":"MIME type of the file. Fixed for the upload; the stored file is served with it.","enum":["image/png","image/jpeg","image/webp","image/gif","audio/mpeg","audio/wav","audio/x-wav","audio/ogg","audio/flac","audio/mp4","audio/aac","audio/webm","video/mp4","video/webm","video/quicktime","model/gltf-binary"]},"size":{"type":"integer","format":"int64","minimum":1,"maximum":15728640,"description":"Exact size of the file in bytes (at most 15MB). The PUT to upload_url must send exactly this many bytes."}}},"UploadSession":{"type":"object","required":["upload_url","file_url","mime_type","size"],"properties":{"upload_url":{"type":"string","description":"Single-use URL to PUT the file's bytes to, with no Authorization header."},"file_url":{"type":"string","description":"Public URL of the file once the PUT succeeds. Pass it to any field that accepts a URL. Deleted 7 days after upload."},"mime_type":{"type":"string","description":"The MIME type the file is stored and served with."},"size":{"type":"integer","format":"int64","description":"The exact number of bytes the PUT must send."}}},"ApiKey":{"type":"object","description":"A public API key (helpers/api-keys.js). Only its sha256 is stored; the plaintext (`ludo_...`, or an older unprefixed value for keys migrated from User.api_key) is shown once. Deleted with the account.","properties":{"user_id":{"type":"string"},"key_hash":{"type":"string"},"name":{"type":"string"},"source":{"type":"string","enum":["web","cli","oauth"]},"oauth_client_id":{"type":"string"},"last4":{"type":"string"},"legacy":{"type":"boolean"},"platform":{"type":"string"},"client_version":{"type":"string"},"created_ip":{"type":"string"},"created_at":{"type":"integer","format":"int64"},"last_used_at":{"type":"integer","format":"int64"}}},"ApiKeyInfo":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"source":{"type":"string","enum":["web","cli","oauth"]},"last4":{"type":"string","description":"Last four characters of the key; null for keys created before named keys existed."},"platform":{"type":"string"},"created_at":{"type":"integer","format":"int64"},"last_used_at":{"type":"integer","format":"int64"}}},"CreateApiKeyPayload":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":64}}},"CreatedApiKey":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"source":{"type":"string"},"last4":{"type":"string"},"created_at":{"type":"integer","format":"int64"},"api_key":{"type":"string","description":"The key. Shown only in this response."}}},"CliEventsPayload":{"type":"object","required":["install_id","events"],"properties":{"install_id":{"type":"string","minLength":8,"maxLength":64},"events":{"type":"array","minItems":1,"maxItems":20,"items":{"$ref":"#/definitions/CliEvent"}}}},"CliEvent":{"type":"object","required":["command"],"properties":{"command":{"type":"string","maxLength":64},"id":{"type":"string","maxLength":64,"description":"Random id the CLI gives the event, so one it sends again is stored once (it re-sends when it did not see the answer in time)."},"at":{"type":"integer","format":"int64","description":"When the command ran (unix seconds); defaults to now."},"props":{"type":"object","description":"Command facts (exit code, duration, flag names, versions, agent…); sanitized server-side."}}},"OAuthClient":{"type":"object","description":"An app registered for OAuth (RFC 7591 dynamic registration, common/oauth.js), e.g. ChatGPT. Only the sha256 of a client secret is stored.","properties":{"client_id":{"type":"string"},"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string","enum":["none","client_secret_post","client_secret_basic"]},"secret_hash":{"type":"string"},"created_ip":{"type":"string"},"created_at":{"type":"integer","format":"int64"}}},"OAuthRequest":{"type":"object","properties":{"status":{"type":"string","enum":["pending","approved","denied"]},"client_name":{"type":"string","description":"The name the app registered with. Self-declared, so the page shows redirect_host too."},"redirect_host":{"type":"string","description":"Where approving sends the browser, e.g. chatgpt.com."},"scope":{"type":"string"},"requested_at":{"type":"integer","format":"int64"},"api_access":{"type":"boolean","description":"Whether the user's plan includes API access, which connecting needs."},"plan_name":{"type":"string"}}},"OAuthAnswer":{"type":"object","properties":{"redirect_url":{"type":"string","description":"Where to send the browser next (the app's redirect URI, with the code or the error)."}}},"CliAuthStartPayload":{"type":"object","properties":{"platform":{"type":"string","maxLength":32,"description":"The CLI's OS (darwin, linux, win32), shown on the approval page."},"client_version":{"type":"string","maxLength":32}}},"CliAuthStart":{"type":"object","properties":{"device_code":{"type":"string","description":"Secret the CLI polls with. Never shown to the user."},"user_code":{"type":"string","description":"Short code the user checks on the approval page, e.g. WDJB-MJHT."},"verification_url":{"type":"string"},"verification_url_complete":{"type":"string"},"expires_in":{"type":"integer","description":"Seconds until the codes expire."},"interval":{"type":"integer","description":"Seconds to wait between polls."}}},"CliAuthPollPayload":{"type":"object","required":["device_code"],"properties":{"device_code":{"type":"string","maxLength":128}}},"CliAuthPoll":{"type":"object","properties":{"status":{"type":"string","enum":["pending","approved","denied","expired"]},"api_key":{"type":"string","description":"The CLI key, returned once on the first poll after approval."}}},"CliAuthRequest":{"type":"object","properties":{"user_code":{"type":"string"},"status":{"type":"string","enum":["pending","approved","denied"]},"platform":{"type":"string"},"client_version":{"type":"string"},"requested_at":{"type":"integer","format":"int64"},"requester_ip":{"type":"string"},"same_ip":{"type":"boolean","description":"Whether the CLI asked from the same IP address as this browser."},"api_access":{"type":"boolean","description":"Whether the signed-in user's plan includes API access right now."},"plan_name":{"type":"string"}}},"CliAuthApproved":{"type":"object","properties":{"ok":{"type":"boolean"},"name":{"type":"string"}}},"OkResponse":{"type":"object","properties":{"ok":{"type":"boolean"}}},"ApiFeedbackPayload":{"type":"object","description":"Payload for reporting a bug, limitation or missing capability to Ludo","required":["category","message"],"properties":{"category":{"type":"string","enum":["limitation","bug","feature_request","praise","other"],"description":"bug - the API errored, a job failed or got stuck, or a response did not match the documentation; limitation - the API does not support a parameter, operation or format you needed; feature_request - a new API capability you would like; praise - something that worked well; other - any other API issue. Generated content that looks wrong or does not match the prompt is a model limitation, not a bug: do not report it. A limitation the docs already describe (check with searchDocs) is known: do not report it."},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"What you were trying to do, what happened, and what would have helped."},"operation":{"type":"string","maxLength":100,"description":"The operation the feedback is about, e.g. animateSprite."},"job_id":{"type":"string","maxLength":64,"description":"The id of the job the feedback is about. Same as a one-item job_ids."},"job_ids":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":64},"description":"The ids of the jobs the feedback is about (returned by each generation call; also in getApiJob and listApiJobs). Send them whenever a job is involved: every job that shows the problem, plus the one that worked if you are comparing."},"agent":{"type":"string","maxLength":100,"description":"The agent or app sending it, e.g. Claude Code."}}},"ApiFeedbackReceipt":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"string"}}},"AccountInfo":{"type":"object","properties":{"credits":{"type":"object","properties":{"available":{"type":"number","format":"float","description":"Credits generations can spend right now."},"plan_credits":{"type":"number","format":"float","description":"The plan's credits for the current cycle."},"used":{"type":"number","format":"float","description":"Plan credits used this cycle."},"extra":{"type":"number","format":"float","description":"Credits from top-ups and grants, on top of the plan."},"resets_at":{"type":"integer","format":"int64","description":"When the plan credits refill (unix seconds)."},"team_pool":{"type":"boolean","description":"The balance is a team's shared pool."},"paused":{"type":"boolean"},"grace_until":{"type":"integer","format":"int64","description":"Set when the subscription ended; remaining credits stay spendable until then (unix seconds)."}}},"plan":{"type":"object","properties":{"name":{"type":"string"},"api_access":{"type":"boolean"}}},"auto_topup":{"type":"object","description":"Null for team members; the team owner manages auto top-up.","properties":{"enabled":{"type":"boolean"},"threshold":{"type":"number","format":"float","description":"An automatic top-up is bought when available credits drop below this."},"credits":{"type":"integer","description":"Credits bought on each automatic top-up."}}},"billing_url":{"type":"string","description":"Web page to buy credits, change plan or change auto top-up."}}},"PublicJobError":{"type":"object","properties":{"status":{"type":"integer","format":"int32"},"message":{"type":"string"}}},"PublicJob":{"type":"object","required":["id","task_type","status","created_at"],"properties":{"id":{"type":"string"},"task_type":{"type":"string","description":"The public operationId that created this job (e.g. createImage). Determines the shape of `result`: it is exactly the 200 response body documented for that operation."},"status":{"type":"string","enum":["queued","running","succeeded","failed","canceled"]},"request_id":{"type":"string"},"created_at":{"type":"integer","format":"int64","description":"Unix ms"},"finished_at":{"type":"integer","format":"int64"},"credits_charged":{"type":"number","format":"float","description":"Credits this job cost you. Charged at enqueue; reported as 0 once a job fails or is canceled, since the charge is refunded."},"result":{"type":"object","description":"Present only when status is succeeded. Same shape as the synchronous response of the operation named by task_type."},"error":{"$ref":"#/definitions/PublicJobError"},"poll_after_ms":{"type":"integer","format":"int32","description":"Present while the job is queued or running: wait at least this many milliseconds before polling again. Prefer GET /assets/jobs/{id}?wait=30 (long-poll) over tight loops."}}},"GenerateImagePayload":{"type":"object","description":"Payload for generating an image from text prompt","required":["image_type","prompt"],"properties":{"image_type":{"x-swagger-data":"imageTypes","type":"string","description":"What kind of image to generate."},"prompt":{"type":"string","description":"Text description of the image to generate. The more detailed the prompt, the more accurate the image will be."},"art_style":{"x-swagger-data":"artStyles","type":"string","description":"Visual art style for the sprite (e.g., \"Pixel Art\", \"Cartoonish\", \"Realistic\")."},"perspective":{"x-swagger-data":"perspectives","type":"string","description":"Camera angle/view for the sprite (e.g., \"Side view\", \"Front view\", \"Isometric\")."},"aspect_ratio":{"x-swagger-data":"aspectRatios","type":"string","description":"Aspect ratio of the output image."},"n":{"type":"integer","format":"integer","default":1,"minimum":1,"maximum":8,"description":"Number of image variations to generate"},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateMapTilesPayload":{"type":"object","properties":{"request_id":{"type":"string"},"prompt":{"type":"string"},"layer":{"type":"string"},"tile_coordinates":{"type":"array","items":{"type":"array","items":{"type":"number","format":"integer"}}},"map":{"$ref":"#/definitions/GeneratedMap"}}},"GenerateNewMapPayload":{"type":"object","properties":{"art_style":{"type":"string"},"perspective":{"type":"string"},"prompt":{"type":"string"},"request_id":{"type":"string"},"tile_size":{"type":"integer"}}},"EditorState":{"type":"object","properties":{"grid_snapping":{"type":"boolean"},"viewport_x":{"type":"number","format":"float"},"viewport_y":{"type":"number","format":"float"},"viewport_zoom":{"type":"number","format":"float"},"show_grid":{"type":"boolean"},"grid_color":{"type":"string"},"grid_opacity":{"type":"number","format":"float"}}},"MapSprite":{"type":"object","properties":{"height":{"type":"number","format":"float"},"id":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"layer":{"type":"number","format":"integer"},"meta":{"type":"object"},"opacity":{"type":"number","format":"float"},"width":{"type":"number","format":"float"},"x":{"type":"number","format":"float"},"y":{"type":"number","format":"float"}}},"MapBaseImage":{"type":"object","properties":{"height":{"type":"number","format":"float"},"opacity":{"type":"number","format":"float"},"width":{"type":"number","format":"float"},"x":{"type":"number","format":"float"},"y":{"type":"number","format":"float"},"image":{"$ref":"#/definitions/GameImage"}}},"MapLayer":{"type":"object","properties":{"base_image":{"$ref":"#/definitions/MapBaseImage"},"id":{"type":"string"},"meta":{"type":"object"},"name":{"type":"string"},"order":{"type":"number","format":"integer"},"visible":{"type":"boolean"},"sprites":{"type":"array","items":{"$ref":"#/definitions/MapSprite"}}}},"GeneratedMap":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"art_style":{"type":"string"},"description":{"type":"string"},"editor_state":{"$ref":"#/definitions/EditorState"},"grid_size":{"type":"number","format":"integer"},"layers":{"type":"array","items":{"$ref":"#/definitions/MapLayer"}},"name":{"type":"string"},"perspective":{"type":"string"},"sprites":{"type":"array","items":{"$ref":"#/definitions/MapSprite"}}}},"GenerateSpritesheetAudioPayload":{"type":"object","properties":{"request_id":{"type":"string"},"prompt":{"type":"string"},"spritesheet":{"$ref":"#/definitions/Spritesheet"}}},"GenerateSpritesheetMotionTransferPayload":{"type":"object","properties":{"request_id":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"video":{"type":"string"},"num_frames":{"type":"number","format":"integer"},"target_frame_size":{"type":"number","format":"integer"},"loop":{"type":"boolean"},"crop":{"type":"boolean","default":true},"margin_ratio":{"type":"number","format":"float"},"margin_ratio_horizontal":{"type":"number","format":"float"},"margin_ratio_vertical":{"type":"number","format":"float"},"margin_ratio_mode":{"type":"string","enum":["manual","none"],"default":"manual"},"model":{"type":"string","enum":["hydra","tango","forge","forge-pixel"],"default":"hydra"},"prompt":{"type":"string","description":"Optional extra instructions for the motion transfer, added to the model's own prompt."},"duration":{"type":"number","format":"float","default":1.5,"description":"Animation length in seconds. Available values depend on the model - see /credits/costs (per-model lists are auto-generated into the public API docs)."},"pixel_art_filter":{"type":"boolean"},"spritesheet_with_background":{"type":"boolean"},"use_pixel_grid":{"type":"boolean"}}},"Sprite3DPreviewPayload":{"type":"object","required":["perspective","style_id"],"properties":{"request_id":{"type":"string"},"generated_model":{"$ref":"#/definitions/ThreeDModelInput"},"user_model":{"type":"string","description":"URL of an uploaded GLB (rigged, to animate it). Provide this OR generated_model."},"user_model_file_type":{"type":"string","enum":["glb"]},"animation_clip_id":{"type":"string","description":"One of the model's saved animations (an Animation3DClip id) to render."},"preset_id":{"type":"string","description":"An animation preset (/utils/animation-presets) the ML retargets onto the rigged model and renders."},"crop_loop":{"type":"boolean","description":"With preset_id, trim the preset to the span that loops seamlessly. Omit to follow the preset's own loop flag."},"model_animation":{"type":"string","description":"Name of an animation the model file itself carries. With no animation at all the model renders as a still."},"perspective":{"type":"string","enum":["horizon","low","isometric","high","top"]},"directions":{"type":"array","minItems":1,"maxItems":8,"uniqueItems":true,"description":"Where the camera stands around a character facing S, each view at most once. One result per view, in this order, rendered together on the same frame, scale and floor, so the views line up.","items":{"type":"string","enum":["S","SW","W","NW","N","NE","E","SE"]}},"direction":{"type":"string","description":"Deprecated: one view. Use directions.","enum":["S","SW","W","NW","N","NE","E","SE"]},"style_id":{"type":"string","description":"id of a style from /utils/sprite3d-styles"},"style_version":{"type":"integer","description":"Version of the style; omit for its current one."},"style_params":{"type":"object","description":"Overrides of the style's settings, by name, each one of its listed choices (or true/false, or a colour for a colour setting)."},"keep_horizontal_travel":{"type":"boolean","description":"Keep the animation's ground travel (the character moves across the frame). Off by default."},"keep_vertical_travel":{"type":"boolean","description":"Keep the animation's vertical root motion (jumps, bobbing). On by default."},"target_frame_size":{"type":"integer","minimum":32,"maximum":384,"description":"Sprite size in px; default 384."}}},"RenderSprite3DPayload":{"type":"object","required":["perspective","style_id"],"properties":{"request_id":{"type":"string"},"generated_model":{"$ref":"#/definitions/ThreeDModelInput"},"user_model":{"type":"string","description":"URL of an uploaded GLB (rigged, to animate it). Provide this OR generated_model."},"user_model_file_type":{"type":"string","enum":["glb"]},"animation_clip_id":{"type":"string","description":"One of the model's saved animations (an Animation3DClip id) to render."},"preset_id":{"type":"string","description":"An animation preset (/utils/animation-presets) the ML retargets onto the rigged model and renders."},"crop_loop":{"type":"boolean","description":"With preset_id, trim the preset to the span that loops seamlessly. Omit to follow the preset's own loop flag."},"model_animation":{"type":"string","description":"Name of an animation the model file itself carries. With no animation at all the model renders as a still."},"perspective":{"type":"string","enum":["horizon","low","isometric","high","top"]},"directions":{"type":"array","minItems":1,"maxItems":8,"uniqueItems":true,"description":"Where the camera stands around a character facing S, each view at most once. One result per view, in this order, rendered together on the same frame, scale and floor, so the views line up.","items":{"type":"string","enum":["S","SW","W","NW","N","NE","E","SE"]}},"direction":{"type":"string","description":"Deprecated: one view. Use directions.","enum":["S","SW","W","NW","N","NE","E","SE"]},"style_id":{"type":"string","description":"id of a style from /utils/sprite3d-styles"},"style_version":{"type":"integer","description":"Version of the style; omit for its current one."},"style_params":{"type":"object","description":"Overrides of the style's settings, by name, each one of its listed choices (or true/false, or a colour for a colour setting)."},"keep_horizontal_travel":{"type":"boolean","description":"Keep the animation's ground travel (the character moves across the frame). Off by default."},"keep_vertical_travel":{"type":"boolean","description":"Keep the animation's vertical root motion (jumps, bobbing). On by default."},"target_frame_size":{"type":"integer","minimum":32,"maximum":640,"description":"Sprite size in px (the frame's longer side); default 256."},"num_frames":{"type":"integer","minimum":4,"maximum":100,"description":"Frames per sheet, sampled over the clip (fewer when the clip has fewer keyframes; a still gives one). Default 16."},"loop":{"type":"boolean","description":"Sample the clip as a seamless loop. Omit to follow the animation's own loop flag."}}},"RenderSprite3DStillPayload":{"type":"object","required":["perspective","style_id"],"properties":{"request_id":{"type":"string"},"generated_model":{"$ref":"#/definitions/ThreeDModelInput"},"user_model":{"type":"string","description":"URL of an uploaded GLB. Provide this OR generated_model."},"user_model_file_type":{"type":"string","enum":["glb"]},"perspective":{"type":"string","enum":["horizon","low","isometric","high","top"]},"directions":{"type":"array","minItems":1,"maxItems":8,"uniqueItems":true,"description":"Where the camera stands around a character facing S, each view at most once. One still per view, in this order, rendered together on the same frame, scale and floor, so the views line up.","items":{"type":"string","enum":["S","SW","W","NW","N","NE","E","SE"]}},"style_id":{"type":"string","description":"id of a style from /utils/sprite3d-styles"},"style_version":{"type":"integer","description":"Version of the style; omit for its current one."},"style_params":{"type":"object","description":"Overrides of the style's settings, by name, each one of its listed choices (or true/false, or a colour for a colour setting)."}}},"Sprite3DPreviewResult":{"type":"object","properties":{"request_id":{"type":"string"},"direction":{"type":"string","description":"The view this preview is of."},"video":{"type":"string","description":"The preview as an mp4 data uri (not stored)."},"width":{"type":"integer"},"height":{"type":"integer"},"num_frames":{"type":"integer"},"fps":{"type":"number","format":"float"},"preview_scale":{"type":"number","format":"float"},"render_3d":{"$ref":"#/definitions/Sprite3DRender"}}},"Sprite3DRender":{"type":"object","properties":{"style_id":{"type":"string"},"style_version":{"type":"integer"},"style_params":{"type":"object","description":"Every setting of the style as rendered, by name."},"params":{"type":"object","description":"The renderer's own parameters (e.g. renderer, version)."},"perspective":{"type":"string"},"direction":{"type":"string"},"animation_name":{"type":"string"},"fps":{"type":"number","format":"float"},"keep_horizontal_travel":{"type":"boolean"},"keep_vertical_travel":{"type":"boolean"},"pixel_block":{"type":"integer"},"generated_model":{"type":"object","description":"The model that was rendered (its id, file urls and, from render, history_id - the three_d_asset history entry it came from)."}}},"Sprite3DLook":{"type":"object","properties":{"style_id":{"type":"string"},"style_version":{"type":"integer"},"style_params":{"type":"object","description":"The style's settings that differ from its defaults, by name."},"perspective":{"type":"string","enum":["horizon","low","isometric","high","top"]},"direction":{"type":"string","description":"Where the camera stands around a character facing S.","enum":["S","SW","W","NW","N","NE","E","SE"]},"saved_at":{"type":"integer","description":"When it was saved (unix seconds). Set by the server."},"preview_url":{"type":"string","description":"The look's preview (an mp4 of the saved view), shown in place of the model's turntable. Set by the server from Sprite3DLookPayload.preview_video."}}},"Sprite3DLookPayload":{"type":"object","required":["model_id"],"properties":{"model_id":{"type":"string","description":"The 3D model's history id (its three_d_asset entry, keyed by image.id)."},"look":{"$ref":"#/definitions/Sprite3DLook"},"preview_video":{"type":"string","description":"The preview of this look (the mp4 data uri /games/sprite3d/preview answered for its view), stored as the look's preview_url. Left out, the look has no preview."}}},"Sprite3DAttachmentsPayload":{"type":"object","required":["model_id","attachments"],"properties":{"model_id":{"type":"string","description":"The 3D model's history id (its three_d_asset entry, keyed by image.id)."},"attachments":{"type":"array","maxItems":8,"description":"Every item the model holds, each a Sprite3DAttachment (checked field by field by the server). An empty list clears them.","items":{"type":"object"}}}},"Sprite3DAttachment":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","description":"The item's GLB, on our storage (one of the user's 3D models, or an uploaded file)."},"thumb":{"type":"string","description":"Its picture. Sent as a small image data uri (an upload's snapshot), it is hosted and saved as a url."},"source":{"type":"string","enum":["model","upload"]},"modelId":{"type":"string","description":"The item's own 3D model history id, when it is one of the user's models."},"bone":{"type":"string","description":"The name of the bone that holds it."},"point":{"type":"string","description":"The named place it was fitted to, when it was one.","enum":["right_hand","left_hand","head","back","hips"]},"baseScale":{"type":"number","format":"double"},"flipped":{"type":"boolean","description":"Turned end for end at its grip."},"grip":{"type":"boolean","description":"The fingers closed round it."},"position":{"type":"array","description":"Relative to the bone, x y z.","items":{"type":"number","format":"double"}},"quaternion":{"type":"array","description":"Relative to the bone, x y z w.","items":{"type":"number","format":"double"}},"scale":{"type":"array","items":{"type":"number","format":"double"}}}},"Sprite3DStyle":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"family":{"type":"string"},"pixel":{"type":"boolean"},"url":{"type":"string","description":"Picker thumbnail of the style at its defaults (192px webp)."},"version":{"type":"integer"},"versions":{"type":"array","items":{"type":"integer"}},"settings":{"type":"array","items":{"type":"object"}},"advanced_settings":{"type":"array","items":{"type":"object"}}}},"DirectionalSpritesPayload":{"type":"object","description":"Payload for rendering a 3D model as directional sprite sheets","required":["model"],"properties":{"model":{"type":"string","description":"The 3D model, as a URL or base64-encoded GLB file (not an AI model name). Rigged, to animate it. Up to 30MB; a larger file is rejected with HTTP 400."},"animation_url":{"type":"string","description":"An animation for the model, as a URL or base64-encoded GLB file - the glb_url of an animation from animate3DModel or animate3DModelPreset, made for this model's skeleton. At most one of animation_url, preset_id and model_animation. Up to 30MB; a larger file is rejected with HTTP 400."},"preset_id":{"type":"string","description":"An animation preset id from listAnimationPresets, retargeted onto the model. Any rig type can take one; when the preset cannot be retargeted onto the model, the call returns an error. At most one of animation_url, preset_id and model_animation."},"crop_loop":{"type":"boolean","description":"With preset_id, trim the preset to the span that loops seamlessly. Omit to follow the preset's own loop flag."},"model_animation":{"type":"string","description":"The name of an animation the GLB itself carries. At most one of animation_url, preset_id and model_animation; with none, each sheet is a single frame."},"perspective":{"type":"string","default":"isometric","description":"Camera height, from the side (horizon) to straight down (top).","enum":["horizon","low","isometric","high","top"]},"directions":{"type":"array","default":["SW"],"minItems":1,"maxItems":8,"uniqueItems":true,"description":"Where the camera stands around a character facing S, each direction at most once. One sheet per direction, in this order, rendered together on the same frame size, scale and floor so they line up.","items":{"type":"string","enum":["S","SW","W","NW","N","NE","E","SE"]}},"style_id":{"type":"string","default":"render_3d","description":"A style id from listDirectionalSpriteStyles."},"style_version":{"type":"integer","description":"Version of the style; omit for its current one. A render_3d from an earlier result names the version it was rendered with."},"style_params":{"type":"object","description":"The style's settings to change, by name, each one of its listed option values (true/false for a bool setting, or any colour for a color one). Omit a setting to keep its default."},"keep_horizontal_travel":{"type":"boolean","default":false,"description":"Keep the animation's ground travel (the character moves across the frame). Off keeps it in place."},"keep_vertical_travel":{"type":"boolean","default":true,"description":"Keep the animation's vertical root motion (jumps, bobbing)."},"target_frame_size":{"type":"integer","minimum":32,"maximum":640,"default":640,"description":"Sprite size in pixels - the longer side of the frame, which every direction shares."},"num_frames":{"type":"integer","minimum":4,"maximum":100,"default":36,"description":"Frames per sheet, sampled over the animation; moved to the nearest count that fills the sheet grid, and never more than the animation's keyframes. A still model gives one."},"loop":{"type":"boolean","description":"Sample the animation as a seamless loop. Omit to follow the animation's own loop flag."},"gif":{"type":"boolean","default":false,"description":"Also return each sheet as an animated GIF (gif_url)."},"individual_frames":{"type":"boolean","default":false,"description":"Also return each sheet's frames as separate images (individual_frame_urls)."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"DirectionalStillsPayload":{"type":"object","description":"Payload for rendering a 3D model as one still image per direction","required":["model"],"properties":{"model":{"type":"string","description":"The 3D model, as a URL or base64-encoded GLB file (not an AI model name). Rendered as it is, in its rest pose. Up to 30MB; a larger file is rejected with HTTP 400."},"perspective":{"type":"string","default":"isometric","description":"Camera height, from the side (horizon) to straight down (top).","enum":["horizon","low","isometric","high","top"]},"directions":{"type":"array","default":["SW"],"minItems":1,"maxItems":8,"uniqueItems":true,"description":"Where the camera stands around a character facing S, each direction at most once. One image per direction, in this order, rendered together so they line up.","items":{"type":"string","enum":["S","SW","W","NW","N","NE","E","SE"]}},"style_id":{"type":"string","default":"render_3d","description":"A style id from listDirectionalSpriteStyles."},"style_version":{"type":"integer","description":"Version of the style; omit for its current one."},"style_params":{"type":"object","description":"The style's settings to change, by name, each one of its listed option values (true/false for a bool setting, or any colour for a color one). Omit a setting to keep its default."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"DirectionalPreviewPayload":{"type":"object","description":"Payload for previewing directional sprite frames of a 3D model","required":["model"],"properties":{"model":{"type":"string","description":"The 3D model, as a URL or base64-encoded GLB file (not an AI model name). Rigged, to animate it. Up to 30MB; a larger file is rejected with HTTP 400."},"animation_url":{"type":"string","description":"An animation for the model, as a URL or base64-encoded GLB file - the glb_url of an animation from animate3DModel or animate3DModelPreset, made for this model's skeleton. At most one of animation_url, preset_id and model_animation. Up to 30MB; a larger file is rejected with HTTP 400."},"preset_id":{"type":"string","description":"An animation preset id from listAnimationPresets, retargeted onto the model. Any rig type can take one; when the preset cannot be retargeted onto the model, the call returns an error. At most one of animation_url, preset_id and model_animation."},"crop_loop":{"type":"boolean","description":"With preset_id, trim the preset to the span that loops seamlessly. Omit to follow the preset's own loop flag."},"model_animation":{"type":"string","description":"The name of an animation the GLB itself carries. At most one of animation_url, preset_id and model_animation; with none, the preview is a single frame."},"perspective":{"type":"string","default":"isometric","description":"Camera height, from the side (horizon) to straight down (top).","enum":["horizon","low","isometric","high","top"]},"directions":{"type":"array","default":["SW"],"minItems":1,"maxItems":8,"uniqueItems":true,"description":"Where the camera stands around a character facing S, each direction at most once. One preview per direction, in this order.","items":{"type":"string","enum":["S","SW","W","NW","N","NE","E","SE"]}},"style_id":{"type":"string","default":"render_3d","description":"A style id from listDirectionalSpriteStyles."},"style_version":{"type":"integer","description":"Version of the style; omit for its current one."},"style_params":{"type":"object","description":"The style's settings to change, by name, each one of its listed option values (true/false for a bool setting, or any colour for a color one). Omit a setting to keep its default."},"keep_horizontal_travel":{"type":"boolean","default":false,"description":"Keep the animation's ground travel (the character moves across the frame). Off keeps it in place."},"keep_vertical_travel":{"type":"boolean","default":true,"description":"Keep the animation's vertical root motion (jumps, bobbing)."},"target_frame_size":{"type":"integer","minimum":32,"maximum":384,"default":256,"description":"Sprite size of the preview frames in pixels (the longer side)."}}},"DirectionalSpriteRender":{"type":"object","properties":{"style_id":{"type":"string"},"style_version":{"type":"integer"},"style_params":{"type":"object","description":"The style's settings as rendered, by name."},"perspective":{"type":"string"},"direction":{"type":"string"},"animation_name":{"type":"string"},"fps":{"type":"number","format":"float"},"keep_horizontal_travel":{"type":"boolean"},"keep_vertical_travel":{"type":"boolean"}}},"DirectionalSpriteSheet":{"type":"object","properties":{"direction":{"type":"string","description":"The direction this sheet is seen from."},"spritesheet_url":{"type":"string"},"video_url":{"type":"string"},"gif_url":{"type":"string"},"individual_frame_urls":{"type":"array","items":{"type":"string"}},"num_frames":{"type":"integer","description":"Number of frames in the spritesheet."},"num_cols":{"type":"integer","description":"Columns in the frame grid. Frames run left-to-right, top-to-bottom."},"num_rows":{"type":"integer","description":"Rows in the frame grid. The last row may be only partly filled."},"fps":{"type":"number","format":"float","description":"The frame rate the sheet plays at."},"render_3d":{"$ref":"#/definitions/DirectionalSpriteRender"},"loop":{"type":"boolean","description":"Whether the animation loops: the loop you asked for, else the animation's own. false is a one-shot (a jump, a death) that should play once in an engine."}}},"DirectionalSpritesResult":{"type":"object","properties":{"sheets":{"type":"array","description":"One sheet per direction, in the order asked.","items":{"$ref":"#/definitions/DirectionalSpriteSheet"}}}},"DirectionalStill":{"type":"object","properties":{"direction":{"type":"string","description":"The direction this image is seen from."},"url":{"type":"string"},"width":{"type":"integer"},"height":{"type":"integer"},"render_3d":{"$ref":"#/definitions/DirectionalSpriteRender"}}},"DirectionalStillsResult":{"type":"object","properties":{"images":{"type":"array","description":"One image per direction, in the order asked.","items":{"$ref":"#/definitions/DirectionalStill"}}}},"DirectionalPreview":{"type":"object","properties":{"direction":{"type":"string","description":"The direction this preview is seen from."},"frame_urls":{"type":"array","description":"The preview's frames as images, in order (one for a still model). Kept for 7 days.","items":{"type":"string"}},"width":{"type":"integer"},"height":{"type":"integer"},"num_frames":{"type":"integer"},"fps":{"type":"number","format":"float"},"render_3d":{"$ref":"#/definitions/DirectionalSpriteRender"}}},"DirectionalPreviewResult":{"type":"object","properties":{"previews":{"type":"array","description":"One preview per direction, in the order asked.","items":{"$ref":"#/definitions/DirectionalPreview"}}}},"DirectionalSpriteStyle":{"type":"object","properties":{"id":{"type":"string","description":"Pass as style_id."},"label":{"type":"string"},"description":{"type":"string"},"family":{"type":"string"},"pixel":{"type":"boolean","description":"Whether the style is pixel art."},"thumbnail_url":{"type":"string","description":"The style at its defaults."},"version":{"type":"integer","description":"The style's current version."},"settings":{"type":"array","description":"The style's settings: each has a name (its key in style_params), label, description, kind (choice, bool or color), options ({label, value}) and default.","items":{"type":"object"}},"advanced_settings":{"type":"array","description":"Finer settings, described the same way.","items":{"type":"object"}}}},"DirectionalSpriteStylesResponse":{"type":"object","properties":{"styles":{"type":"array","items":{"$ref":"#/definitions/DirectionalSpriteStyle"}},"perspectives":{"type":"array","items":{"type":"string"}},"directions":{"type":"array","items":{"type":"string"}}}},"GenerateSpritesheetNewViewPayload":{"type":"object","description":"Payload for re-rendering an existing spritesheet's animation from a new view. The source spritesheet supplies the motion and timing; `image` is the character as it must appear in the new view.","required":["spritesheet","image"],"properties":{"request_id":{"type":"string"},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"image":{"$ref":"#/definitions/GameImage"},"final_image":{"$ref":"#/definitions/GameImage"},"prompt":{"type":"string","description":"Optional instructions: the character's facing in the new view (\"its back to the camera\"), where the action goes (\"the kick goes to the left of the frame\"), or anything else about the motion."},"duration":{"type":"number","format":"float","minimum":1,"maximum":5,"description":"Seconds of the source animation to re-render (a longer one is sped up to fit). Defaults to the source's own length, capped at 5."},"model":{"type":"string","enum":["hydra","hydra-plus"],"default":"hydra"},"loop":{"type":"boolean"},"crop":{"type":"boolean","default":true},"num_frames":{"type":"number","format":"integer"},"target_frame_size":{"type":"number","format":"integer"},"margin_ratio":{"type":"number","format":"float"},"margin_ratio_horizontal":{"type":"number","format":"float"},"margin_ratio_vertical":{"type":"number","format":"float"},"margin_ratio_mode":{"type":"string","enum":["auto","manual","none"]},"match_color_palette":{"type":"boolean"},"use_pixel_grid":{"type":"boolean"},"spritesheet_with_background":{"type":"boolean"}}},"EditSpritesheetPayload":{"type":"object","description":"Payload for editing a spritesheet's video. edit_mode selects the operation - prompt-driven edit (default), outpaint, loop fixing, or improve (Hydra's post-processing pass, finer detail, on a sheet generated with hydra only; the result is recorded as hydra-plus).","required":["spritesheet"],"properties":{"request_id":{"type":"string"},"edit_mode":{"type":"string","description":"Edit operation to perform.","enum":["prompt","outpaint","fix_loop","improve"],"default":"prompt"},"prompt":{"type":"string","description":"Edit instruction. Required for edit_mode \"prompt\", optional for \"outpaint\", not accepted for \"fix_loop\" or \"improve\"."},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"images":{"type":"array","maxItems":5,"items":{"$ref":"#/definitions/GameImage"}},"duration":{"type":"number","format":"float","default":3},"model":{"type":"string","description":"Backing model.","enum":["hydra","griffin-hd","forge","forge-pixel"],"default":"forge"},"crop":{"type":"boolean"},"loop":{"type":"boolean","default":true},"num_frames":{"type":"number","format":"integer"},"target_frame_size":{"type":"number","format":"integer"},"spritesheet_with_background":{"type":"boolean"},"use_pixel_grid":{"type":"boolean"}}},"ReferencesToVideoPayload":{"type":"object","description":"Payload for generating a video from 1-5 reference images and a text prompt.","required":["prompt","images"],"properties":{"request_id":{"type":"string"},"prompt":{"type":"string"},"images":{"type":"array","minItems":1,"maxItems":5,"items":{"$ref":"#/definitions/GameImage"}},"duration":{"type":"number","format":"float","default":5},"model":{"type":"string","description":"Backing model.","enum":["griffin-hd","griffin","eagle","eagle-audio","forge","forge-pixel"],"default":"griffin-hd"},"aspect_ratio":{"type":"string","description":"Output aspect ratio. \"default\" lets the model choose.","enum":["default","ar_1_1","ar_16_9","ar_9_16","ar_4_3","ar_3_4","ar_21_9"],"default":"default"}}},"VideoEditPayload":{"type":"object","description":"Payload for editing a video from a text prompt and optional reference images.","required":["prompt","video"],"properties":{"request_id":{"type":"string"},"prompt":{"type":"string"},"video":{"$ref":"#/definitions/GeneratedVideo"},"images":{"type":"array","maxItems":5,"items":{"$ref":"#/definitions/GameImage"}},"duration":{"type":"number","format":"float","default":5},"model":{"type":"string","description":"Backing model.","enum":["griffin-hd","eagle","forge","forge-pixel"],"default":"griffin-hd"}}},"UpscaleVideoPayload":{"type":"object","description":"Payload for upscaling a previously generated video 2x. Both dimensions must be under 960 pixels (griffin-hd 768p output qualifies only when square (768x768); output from the retired griffin 480p does). Billed per second of video (see /credits/costs).","required":["video"],"properties":{"request_id":{"type":"string"},"video":{"$ref":"#/definitions/GeneratedVideo"}}},"EditSpritesheetPayloadPublic":{"type":"object","description":"Payload for editing a previously generated spritesheet. edit_mode selects the operation - prompt-driven edit (default), outpaint, loop fixing, or improve (finer detail, on hydra spritesheets only).","required":["spritesheet_url"],"properties":{"spritesheet_url":{"type":"string","description":"URL of a spritesheet you generated in the last 7 days (returned by animateSprite, transferMotion, or a previous edit as spritesheet_url). External URLs are not accepted. Edit the original animation rather than a previous edit's result: every edit re-renders the whole animation, so colors and detail drift further with each pass. If an edit did not work, retry from the original instead of editing on top."},"edit_mode":{"type":"string","description":"Edit operation to perform. \"improve\" re-renders the animation with finer detail (the extra pass hydra-plus runs when it generates) and only takes a spritesheet generated with hydra; any other source returns HTTP 400, including an already improved spritesheet (or a loop fix or outpaint of one), which has that detail already.","enum":["prompt","outpaint","fix_loop","improve"],"default":"prompt"},"prompt":{"type":"string","description":"Edit instruction. Required for edit_mode \"prompt\", optional for \"outpaint\", not accepted for \"fix_loop\" or \"improve\". Focus it on a single change and keep it short (under about 50 words); run a second edit for more."},"images":{"type":"array","maxItems":5,"items":{"type":"string"},"description":"Optional reference images (up to 5), each a URL or base64, to guide the edit. Two, at most three, work best: more references reduce the model's ability to use any of them correctly."},"duration":{"x-no-example":true,"type":"number","format":"float","description":"Output animation length in seconds. Omit to keep the source spritesheet's duration (recommended). Prices a prompt edit; outpaint, fix_loop and improve are billed on the source spritesheet's video length whatever this is."},"model":{"x-no-example":true,"type":"string","description":"Backing model for edit_mode \"prompt\" (ignored for \"outpaint\", \"fix_loop\" and \"improve\", which run a fixed pipeline). Hydra is the default and the most capable, and generates audio; forge is the cheaper pick for simple edits; forge-pixel is the pixel-art variant, for spritesheets whose frames are pixel art.","enum":["hydra","forge","forge-pixel"],"default":"hydra"},"crop":{"x-no-example":true,"type":"boolean","description":"Crop sprite frames to fit content (omit to keep the source spritesheet's setting). Results in smaller spritesheets but inconsistent frame sizes across different animations."},"loop":{"x-no-example":true,"type":"boolean","default":true,"description":"Trim the animation at the beginning or end to create a seamless loop."},"frames":{"x-no-example":true,"type":"number","format":"integer","description":"Number of frames in the output spritesheet. Omit to keep the source spritesheet's frame count. Allow about one second of duration per 16 frames - above that the model may not be able to produce them all.","enum":[4,9,16,25,36,49,64]},"frame_size":{"x-no-example":true,"type":"number","format":"integer","description":"Size of the sprite in pixels, not of the frame - it sets how big the character is drawn, and each frame also holds the space around it, so frames come out larger than this value. 0 is for maximum resolution. Omit to keep the source spritesheet's setting.","enum":[32,64,96,128,192,256,384,0]},"gif":{"type":"boolean","default":false,"description":"When true, generates an animated GIF from the spritesheet and returns it in gif_url. Disabled by default to reduce response time."},"individual_frames":{"type":"boolean","default":false,"description":"When true, extracts each frame from the spritesheet as an individual image and returns the URLs in individual_frame_urls."},"spritesheet_with_background":{"type":"boolean","default":false,"description":"When true, also returns the spritesheet with background intact (before background removal). The with-background spritesheet URL will be in spritesheet_with_background_url."},"use_pixel_grid":{"type":"boolean","description":"Use the animation's pixel grid if one is detected. true: when Ludo detects a pixel grid with confidence, the sheet is packed on it (logical pixels, scaled up by whole blocks); when none is detected, this has no effect. false: always pack as ordinary art, for when the detected grid is wrong for your asset (for example a smooth sprite coming back as coarse blocks). Ignored for forge-pixel animations, which are always packed on their grid. Unset keeps the source spritesheet's setting (true unless it was made with false)."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"RotateSpritesheetPayload":{"type":"object","description":"Payload for re-rendering a previously generated sprite animation from a new view.","required":["spritesheet_url","image"],"properties":{"spritesheet_url":{"type":"string","description":"URL of a spritesheet you generated in the last 7 days (returned by animateSprite, animateSpriteKeyframes, transferMotion or editSpritesheet as spritesheet_url). External URLs are not accepted."},"image":{"type":"string","description":"The character exactly as it must appear in the new view, as a URL or base64. It becomes the first frame of the animation. rotateSprite produces one from the animation's first frame."},"final_image":{"x-no-example":true,"type":"string","description":"Optional exact last frame (URL or base64). Defaults to the first frame, for cyclic or returning animations."},"prompt":{"type":"string","description":"Optional instructions - the character's facing in the new view (\"its back to the camera\"), where the action goes (\"the kick goes to the left of the frame\"), or anything else about the motion."},"duration":{"x-no-example":true,"type":"number","format":"float","minimum":1,"maximum":5,"description":"Seconds of the source animation to re-render; a longer source is sped up to fit. Omit to use the source's own length, capped at 5 (recommended)."},"model":{"x-no-example":true,"type":"string","description":"Backing model. Only hydra and hydra-plus support new views.","enum":["hydra","hydra-plus"],"default":"hydra"},"loop":{"x-no-example":true,"type":"boolean","description":"Trim the animation at the beginning or end to create a seamless loop. Omit to keep the source spritesheet's setting."},"crop":{"x-no-example":true,"type":"boolean","default":true,"description":"Crop sprite frames to fit content. Results in smaller spritesheets but inconsistent frame sizes across different animations."},"frames":{"x-no-example":true,"type":"number","format":"integer","description":"Number of frames in the output spritesheet. Omit to keep the source spritesheet's frame count. Allow about one second of duration per 16 frames - above that the model may not be able to produce them all.","enum":[4,9,16,25,36,49,64]},"frame_size":{"x-no-example":true,"type":"number","format":"integer","description":"Size of the sprite in pixels, not of the frame - it sets how big the character is drawn, and each frame also holds the space around it, so frames come out larger than this value. 0 is for maximum resolution. Omit to keep the source spritesheet's setting.","enum":[32,64,96,128,192,256,384,0]},"gif":{"type":"boolean","default":false,"description":"When true, generates an animated GIF from the spritesheet and returns it in gif_url. Disabled by default to reduce response time."},"individual_frames":{"type":"boolean","default":false,"description":"When true, extracts each frame from the spritesheet as an individual image and returns the URLs in individual_frame_urls."},"spritesheet_with_background":{"type":"boolean","default":false,"description":"When true, also returns the spritesheet with background intact (before background removal). The with-background spritesheet URL will be in spritesheet_with_background_url."},"use_pixel_grid":{"type":"boolean","description":"Use the animation's pixel grid if one is detected. true: when Ludo detects a pixel grid with confidence, the sheet is packed on it (logical pixels, scaled up by whole blocks); when none is detected, this has no effect. false: always pack as ordinary art, for when the detected grid is wrong for your asset (for example a smooth sprite coming back as coarse blocks). Ignored for forge-pixel animations, which are always packed on their grid. Unset keeps the source spritesheet's setting (true unless it was made with false)."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"AdjustSpritesheetPayload":{"type":"object","description":"Payload for re-exporting a previously generated sprite animation with different sheet settings (Adjust Sprite Sheet in the web app).","required":["spritesheet_url"],"properties":{"spritesheet_url":{"type":"string","description":"URL of a spritesheet you generated through the API in the last 7 days (the spritesheet_url returned by animateSprite, animateSpriteKeyframes, transferMotion, editSpritesheet, rotateSpritesheet or adjustSpritesheet). External URLs are not accepted."},"frames":{"x-no-example":true,"type":"number","format":"integer","description":"Number of frames in the new sheet. Omit to keep the source's frame count.","enum":[4,9,16,25,36,49,64]},"frame_size":{"x-no-example":true,"type":"number","format":"integer","description":"Size of the sprite in pixels, not of the frame: it sets how big the character is drawn, and each frame also holds the space around it, so frames come out larger than this value. Omit to keep the source's setting. 0 is maximum resolution. -1 is AI 1.5x upscaling, which gains nothing on sources under 512 px. -9 is True Size: frames keep the size and position of the source's input image, so anything the animation adds beyond its edges is cut off - pick a fixed size or 0 to get it back.","enum":[32,64,96,128,192,256,384,0,-1,-9]},"loop":{"x-no-example":true,"type":"boolean","description":"Trim the animation at the beginning or end to create a seamless loop. Omit to keep the source's setting."},"crop":{"x-no-example":true,"type":"boolean","description":"Crop frames to fit their content. Results in smaller spritesheets but inconsistent frame sizes across different animations. Omit to keep the source's setting. Ignored with True Size, which never crops."},"gif":{"type":"boolean","default":false,"description":"When true, generates an animated GIF from the spritesheet and returns it in gif_url. Disabled by default to reduce response time."},"individual_frames":{"type":"boolean","default":false,"description":"When true, extracts each frame from the spritesheet as an individual image and returns the URLs in individual_frame_urls."},"spritesheet_with_background":{"type":"boolean","default":false,"description":"When true, also returns the spritesheet with background intact (before background removal). The with-background spritesheet URL will be in spritesheet_with_background_url."},"use_pixel_grid":{"type":"boolean","description":"Use the animation's pixel grid if one is detected. true: when Ludo detects a pixel grid with confidence, the sheet is packed on it (logical pixels, scaled up by whole blocks); when none is detected, this has no effect. false: always pack as ordinary art, for when the detected grid is wrong for your asset (for example a smooth sprite coming back as coarse blocks). Ignored for forge-pixel animations, which are always packed on their grid. Unset keeps the source spritesheet's setting (true unless it was made with false). Re-cutting with false fixes a sheet whose detected grid is wrong without generating it again."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"CreateSpriteAudioPayload":{"type":"object","description":"Payload for generating audio for a previously generated spritesheet animation.","required":["spritesheet_video_url"],"properties":{"spritesheet_video_url":{"type":"string","description":"The video_url of a spritesheet you generated in the last 7 days (returned by animateSprite, animateSpriteKeyframes, transferMotion, or editSpritesheet). External URLs are not accepted."},"prompt":{"type":"string","description":"Optional description of the sound to generate."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. It does not re-tag the spritesheet, which keeps the request_id it was generated with."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"ReferencesToVideoPayloadPublic":{"type":"object","description":"Payload for generating a video from 1-5 reference images and a text prompt.","required":["prompt","images"],"properties":{"prompt":{"type":"string","description":"Text description of the video to generate."},"images":{"type":"array","minItems":1,"maxItems":5,"items":{"type":"string"},"description":"Reference images (1 to 5), each a URL or base64. The generated video borrows characters, objects, and style from them."},"duration":{"type":"number","format":"float","default":5,"description":"Video length in seconds."},"model":{"type":"string","description":"Video model to use. Griffin (768p) is the current model and generates audio; Eagle is legacy.","enum":["griffin-hd","griffin","eagle","eagle-audio"],"default":"griffin-hd"},"aspect_ratio":{"type":"string","description":"Output aspect ratio. \"default\" lets the model choose.","enum":["default","ar_1_1","ar_16_9","ar_9_16","ar_4_3","ar_3_4","ar_21_9"],"default":"default"},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"EditVideoPayloadPublic":{"type":"object","description":"Payload for editing a previously generated video with a text prompt and optional reference images.","required":["prompt","video"],"properties":{"video":{"type":"string","description":"URL of a video you generated in the last 7 days (returned by createVideo, createVideoFromReferences, or a previous edit). External URLs are not accepted."},"prompt":{"type":"string","description":"Edit instruction describing the desired change."},"images":{"type":"array","maxItems":5,"items":{"type":"string"},"description":"Optional reference images (up to 5), each a URL or base64, to guide the edit."},"duration":{"type":"number","format":"float","description":"Output video length in seconds. Defaults to the source video's duration."},"model":{"type":"string","description":"Video model to use. Griffin (768p, generates audio) is the current model; Eagle is legacy.","enum":["griffin-hd","eagle"],"default":"griffin-hd"},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"UpscaleVideoPayloadPublic":{"type":"object","description":"Payload for upscaling a previously generated video to twice its resolution.","required":["video"],"properties":{"video":{"type":"string","description":"URL of a video you generated in the last 7 days (returned by createVideo, createVideoFromReferences, or editVideo). External URLs are not accepted. Both dimensions must be under 960 pixels (griffin-hd 768p output qualifies only when square (768x768); output from the retired griffin 480p does)."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"AlignImagesPayload":{"type":"object","required":["image1","image2"],"properties":{"image1":{"$ref":"#/definitions/GameImage"},"image2":{"$ref":"#/definitions/GameImage"}}},"AlignImagesResult":{"type":"object","properties":{"dx":{"type":"number","format":"integer"},"dy":{"type":"number","format":"integer"},"scale":{"type":"number","format":"float"},"confidence":{"type":"number","format":"float"}}},"SegmentImagePayload":{"type":"object","properties":{"image":{"type":"string"},"negative_points":{"type":"array","items":{"type":"array","items":{"type":"number","format":"integer"}}},"positive_points":{"type":"array","items":{"type":"array","items":{"type":"number","format":"integer"}}}}},"GenerateVideoPayload":{"type":"object","properties":{"request_id":{"type":"string"},"prompt":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"final_image":{"$ref":"#/definitions/GameImage"},"duration":{"type":"number","format":"float"},"model":{"type":"string"}}},"GenerateVideoAudioPayload":{"type":"object","properties":{"request_id":{"type":"string"},"prompt":{"type":"string"},"video":{"$ref":"#/definitions/GeneratedVideo"}}},"GenerateAudioPayload":{"type":"object","properties":{"request_id":{"type":"string"},"audio_type":{"type":"string","enum":["sfx","ambiance","music","audio-transform","human-voice","non-human-voice","speech","speech-preset"]},"prompt":{"type":"string"},"text":{"type":"string"},"duration":{"type":"number","format":"float"},"modification_strength":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"For audio-transform, controls how strongly the source sample is modified. 0 keeps it close to the original, 1 transforms it fully."},"loop":{"type":"boolean"},"n":{"type":"integer","format":"integer"},"voice_preset":{"$ref":"#/definitions/VoicePreset"},"sample":{"$ref":"#/definitions/GeneratedAudio"},"model":{"type":"string","enum":["blackbird","canary"],"description":"Voice model for speech-preset. blackbird takes an emotion, canary a delivery note; each has its own presets."},"delivery":{"type":"string","description":"For speech and speech-preset (canary), how the line is said, e.g. \"angry, shouting\"."},"image":{"$ref":"#/definitions/GameImage","description":"For human-voice and non-human-voice, the speaking character; the voice is designed from it and the prompt becomes optional."}}},"GenerationsSearchResult":{"type":"object","properties":{"similar_games":{"type":"array","items":{"$ref":"#/definitions/SimilarGame"}}}},"GenerateFilters":{"type":"object","properties":{"n":{"type":"integer","format":"int64"},"platforms":{"type":"array","items":{"type":"string"}},"platform":{"type":"string"},"genres":{"type":"array","items":{"type":"string"}},"creative_mode":{"type":"boolean"},"duration":{"type":"number","format":"float"}}},"GeneratedText":{"type":"object","properties":{"id":{"type":"string"},"request_id":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"context_keywords":{"type":"array","items":{"type":"string"}},"text":{"type":"string"},"title":{"type":"string"},"type":{"type":"string"},"image_prompt":{"type":"string"},"final_score":{"type":"number","format":"float"}}},"UserFeedback":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"type":{"type":"string"},"text":{"type":"string"},"category":{"type":"string"},"operation":{"type":"string"},"job_id":{"type":"string"},"job_ids":{"type":"array","items":{"type":"string"}},"generation_ids":{"type":"array","items":{"type":"string"}},"preserved_files":{"type":"array","items":{"type":"string"}},"agent":{"type":"string"},"source":{"type":"string"},"client":{"type":"string"},"images":{"type":"array","items":{"type":"string"}},"date":{"type":"integer","format":"int64"},"addressed_at":{"type":"integer","format":"int64"},"addressed_note":{"type":"string"},"replies":{"type":"array","items":{"$ref":"#/definitions/FeedbackReply"}},"issue_id":{"type":"string"},"reply_draft":{"type":"string"}}},"FeedbackJob":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"job":{"type":"object"},"saved_at":{"type":"integer","format":"int64"}}},"TriageIssue":{"type":"object","properties":{"_id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["triaged","needs_decision","in_progress","closed"]},"verdict":{"type":"string","enum":["bug","limitation","variance","feature_request","needs_info","not_a_bug"]},"confidence":{"type":"string","enum":["low","medium","high"]},"owner":{"type":"string"},"operation":{"type":"string"},"dossier":{"type":"string"},"options":{"type":"array","items":{"$ref":"#/definitions/TriageOption"}},"recommended":{"type":"string"},"decision":{"$ref":"#/definitions/TriageDecision"},"resolution":{"type":"string","enum":["fixed","limitation_documented","wont_fix","cant_reproduce","not_a_bug"]},"resolution_note":{"type":"string"},"docs_ref":{"type":"string"},"created_at":{"type":"integer","format":"int64"},"updated_at":{"type":"integer","format":"int64"},"status_at":{"type":"integer","format":"int64"},"closed_at":{"type":"integer","format":"int64"}}},"TriageOption":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"tradeoff":{"type":"string"}}},"TriageDecision":{"type":"object","properties":{"option":{"type":"string"},"label":{"type":"string"},"note":{"type":"string"},"at":{"type":"integer","format":"int64"}}},"FeedbackReply":{"type":"object","properties":{"date":{"type":"integer","format":"int64"},"text":{"type":"string"},"to":{"type":"string"}}},"PasswordRecovery":{"type":"object","properties":{"_id":{"type":"string"},"email":{"type":"string"},"code":{"type":"string"},"date":{"type":"integer","format":"int64"}}},"EmailConfirmation":{"type":"object","properties":{"_id":{"type":"string"},"email":{"type":"string"},"code":{"type":"string"},"date":{"type":"integer","format":"int64"},"attempts":{"type":"integer"},"name":{"type":"string"}}},"ResetPasswordPayload":{"type":"object","properties":{"code":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}},"DiscordLinkStartResponse":{"type":"object","properties":{"url":{"type":"string","description":"Discord OAuth authorize url the browser should be sent to."}}},"StringValue":{"type":"object","properties":{"value":{"type":"string"},"value2":{"type":"string"}}},"TopupPayload":{"type":"object","required":["credits"],"properties":{"credits":{"type":"integer","format":"int64","description":"Number of credits to purchase (server computes the price from TOPUP brackets)"}}},"AutoTopupSettings":{"type":"object","properties":{"enabled":{"type":"boolean"},"threshold":{"type":"number","format":"float","description":"When available credits (max + extra - current) drop below this, an automatic top-up is attempted"},"credits":{"type":"integer","format":"int64","description":"Fixed credit amount to buy on each automatic top-up (TOPUP bracket pricing)"},"armed":{"type":"boolean","description":"Hysteresis latch: true while the balance is above the threshold; consumed (set false) by the single attempt per below-threshold episode"}}},"AutoTopupPayload":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"threshold":{"type":"number","format":"float"},"credits":{"type":"integer","format":"int64"}}},"TopupResult":{"type":"object","properties":{"invoice_id":{"type":"string"},"credits":{"type":"integer","format":"int64"},"amount_cents":{"type":"integer","format":"int64"},"status":{"type":"string"}}},"TopupBracket":{"type":"object","properties":{"credits":{"type":"integer","format":"int64"},"cents":{"type":"integer","format":"int64"}}},"TopupConfig":{"type":"object","properties":{"currency":{"type":"string"},"min_credits":{"type":"integer","format":"int64"},"max_credits":{"type":"integer","format":"int64"},"step_credits":{"type":"integer","format":"int64"},"brackets":{"type":"array","items":{"$ref":"#/definitions/TopupBracket"}}}},"PauseSubscriptionRequest":{"type":"object","required":["pauseDurationMonths"],"properties":{"pauseDurationMonths":{"type":"integer","format":"int64","description":"Duration to pause subscription in months (e.g., 1, 2, or 3)"}}},"ExtendPauseRequest":{"type":"object","required":["extraMonths"],"properties":{"extraMonths":{"type":"integer","format":"int64","description":"Extra months to add to a running pause (1, 2 or 3). Refused if the resulting resume date would put the pause past six months from the day it started, or if this pause has already been extended once."}}},"CreditCard":{"type":"object","properties":{"id":{"type":"string"},"brand":{"type":"string"},"country":{"type":"string"},"exp_month":{"type":"integer","format":"int64"},"exp_year":{"type":"integer","format":"int64"},"last4":{"type":"string"},"fingerprint":{"type":"string"},"funding":{"type":"string"},"default":{"type":"boolean"}}},"UserTracking":{"type":"object","properties":{"_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"user_id":{"type":"string"},"install_id":{"type":"string"},"event_key":{"type":"string"},"action":{"type":"string"},"action_id":{"type":"string"},"payload":{"type":"string"},"payload_json":{"type":"object"}}},"DocsSearchLog":{"type":"object","properties":{"_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"created_at":{"type":"string","format":"date-time"},"user_id":{"type":"string"},"origin":{"type":"string"},"query":{"type":"string"},"n":{"type":"integer"},"status":{"type":"string","enum":["ok","empty","error"]},"latency_ms":{"type":"integer"},"error":{"type":"string"},"results":{"type":"array","items":{"$ref":"#/definitions/DocsSearchLogResult"}},"ml_hits":{"type":"array","items":{"$ref":"#/definitions/DocsSearchLogHit"}}}},"DocsSearchLogResult":{"type":"object","properties":{"doc":{"type":"string"},"section":{"type":"string"},"relevance":{"type":"number","format":"float"}}},"DocsSearchLogHit":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"title":{"type":"string"},"relevance":{"type":"number","format":"float"}}},"EmailLog":{"type":"object","properties":{"_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"user_id":{"type":"string"},"to":{"type":"string"},"subject":{"type":"string"},"template":{"type":"string"},"status":{"type":"string","enum":["sent","failed","skipped","holdout"]},"message_id":{"type":"string"},"error":{"type":"string"},"attempts":{"type":"integer"},"invoice_id":{"type":"string"},"stage":{"type":"string"},"pause_started_at":{"type":"integer","format":"int64"},"feedback_id":{"type":"string"}}},"RegistrationRiskLog":{"type":"object","properties":{"_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"user_id":{"type":"string"},"domain":{"type":"string"},"ip":{"type":"string"},"score":{"type":"integer"},"tier":{"type":"string"},"mode":{"type":"string"},"withheld":{"type":"boolean"},"captcha":{"type":"boolean"},"signals":{"type":"object","properties":{"dv":{"type":"integer"},"iv":{"type":"integer"},"sv":{"type":"integer"},"badName":{"type":"boolean"},"knownBad":{"type":"boolean"},"site":{"type":"string"},"siteReason":{"type":"string"},"paidDomain":{"type":"boolean"},"farmMx":{"type":"boolean"},"randomAlias":{"type":"boolean"}}}}},"AlertIncident":{"type":"object","properties":{"_id":{"type":"string"},"incident_id":{"type":"string"},"policy_name":{"type":"string"},"policy_id":{"type":"string"},"condition_name":{"type":"string"},"severity":{"type":"string"},"state":{"type":"string","enum":["open","closed"]},"summary":{"type":"string"},"url":{"type":"string"},"started_at":{"type":"integer","format":"int64"},"ended_at":{"type":"integer","format":"int64"},"last_update_at":{"type":"integer","format":"int64"},"update_count":{"type":"integer"},"acknowledged_at":{"type":"integer","format":"int64"},"acknowledged_by":{"type":"string"},"closes_at":{"type":"integer","format":"int64"},"auto_closed":{"type":"boolean"},"closed_by":{"type":"string"},"raw":{"type":"string"}}},"RegistrationBlockLog":{"type":"object","properties":{"_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"code":{"type":"string"},"reason":{"type":"string"},"domain":{"type":"string"},"ip":{"type":"string"}}},"BlockedIp":{"type":"object","properties":{"_id":{"type":"string"},"ip":{"type":"string"},"reason":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"added_by":{"type":"string"},"date":{"type":"integer","format":"int64"}}},"ServiceNotice":{"type":"object","properties":{"_id":{"type":"string"},"message":{"type":"string"},"level":{"type":"string"},"url":{"type":"string"},"audiences":{"type":"array","items":{"type":"string"}},"starts_at":{"type":"integer","format":"int64"},"ends_at":{"type":"integer","format":"int64"},"active":{"type":"boolean"},"created_by":{"type":"string"},"created_at":{"type":"integer","format":"int64"}}},"ServiceNoticeResponse":{"type":"object","properties":{"notice":{"type":"object","x-nullable":true,"properties":{"id":{"type":"string"},"level":{"type":"string"},"message":{"type":"string"},"url":{"type":"string"}}}}},"AppSetting":{"type":"object","properties":{"_id":{"type":"string"},"value":{"type":"string"},"updated_by":{"type":"string"},"updated_at":{"type":"integer","format":"int64"}}},"BlockedDomain":{"type":"object","properties":{"_id":{"type":"string"},"domain":{"type":"string"},"reason":{"type":"string"},"added_by":{"type":"string"},"date":{"type":"integer","format":"int64"}}},"CreditChange":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"reason":{"type":"string","enum":["init","subscription_cycle","new_sub","plan_change","topup","topup_purchase","cron_renewal","referral_bonus","pause","pause_extend","unpause","unpause_with_payment","cancellation","grace_start","grace_expired","team_join","manual"]},"source":{"type":"string"},"plan_name":{"type":"string"},"before":{"$ref":"#/definitions/CreditSnapshot"},"after":{"$ref":"#/definitions/CreditSnapshot"},"delta_max":{"type":"number","format":"double"},"delta_extra":{"type":"number","format":"double"},"notes":{"type":"string"},"invoice_id":{"type":"string"}}},"CreditSnapshot":{"type":"object","properties":{"current":{"type":"number","format":"double"},"max":{"type":"number","format":"double"},"extra":{"type":"number","format":"double"},"reset_date":{"type":"integer","format":"int64"},"paused":{"type":"boolean"}}},"History":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"description":{"type":"string"},"date":{"type":"integer","format":"int64"},"type":{"type":"string","enum":["games","generated_games"]},"game_ids":{"type":"array","items":{"type":"string"}},"generated_games":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}},"parameters":{"$ref":"#/definitions/HistoryParameters"}}},"HistoryParameters":{"type":"object","properties":{"descriptions":{"type":"array","items":{"type":"string"}},"game_ids":{"type":"array","items":{"type":"string"}},"generations":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}},"keywords":{"type":"array","items":{"type":"string"}},"filters":{"$ref":"#/definitions/GenerateFilters"},"strategy":{"type":"string","enum":["popular","trending","mix"]}}},"Project":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"team_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"date":{"type":"integer","format":"int64"},"date_updated":{"type":"integer","format":"int64"},"archived":{"type":"boolean"},"genres":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}},"platforms":{"type":"array","items":{"type":"string"}},"platform":{"type":"string"},"gdd2":{"$ref":"#/definitions/GDD"},"gdd3":{"$ref":"#/definitions/GDD3"}}},"GDD":{"type":"object","properties":{"genres":{"type":"array","items":{"type":"string"}},"platform":{"type":"string"},"art_style":{"type":"string"},"perspective":{"type":"string"},"aspect_ratio":{"type":"string"},"step_choices":{"type":"array","items":{"$ref":"#/definitions/StepValue"}},"score":{"$ref":"#/definitions/LudoScore"},"header":{"$ref":"#/definitions/GameImage"},"icon":{"$ref":"#/definitions/GameImage"},"sections":{"type":"array","items":{"$ref":"#/definitions/GDDSection"}}}},"GDDSection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"date":{"type":"integer","format":"int64"},"loading":{"type":"integer","format":"int64"},"pending":{"type":"boolean"},"value":{"$ref":"#/definitions/GDDSectionValue"}}},"GDDSectionValue":{"type":"object","properties":{"text":{"type":"string"},"country":{"type":"string"},"title":{"type":"string"},"layout":{"type":"string"},"imageType":{"type":"string"},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"assets":{"type":"array","items":{"$ref":"#/definitions/ThreeDAsset"}},"games":{"type":"array","items":{"type":"string"}},"elements":{"type":"array","items":{"$ref":"#/definitions/GeneratedText"}},"comments":{"type":"array","items":{"$ref":"#/definitions/GDDComment"}},"score":{"$ref":"#/definitions/LudoScore"}}},"GDDComment":{"type":"object","properties":{"_id":{"type":"string"},"author_id":{"type":"string"},"author_email":{"type":"string"},"date":{"type":"integer","format":"int64"},"text":{"type":"string"},"section":{"type":"string"},"archived":{"type":"boolean"},"replies":{"type":"array","items":{"$ref":"#/definitions/GDDComment"}}}},"Favorite":{"type":"object","properties":{"_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"project_id":{"type":"string"},"user_id":{"type":"string"},"type":{"type":"string","enum":["game","image","generated_game","text","mechanic","feature","three_d_asset","video","spritesheet","audio"]},"payload_id":{"type":"string"},"text":{"$ref":"#/definitions/GeneratedText"},"generated_game":{"$ref":"#/definitions/GeneratedGame"},"image":{"$ref":"#/definitions/GameImage"},"video":{"$ref":"#/definitions/GeneratedVideo"},"three_d_asset":{"$ref":"#/definitions/ThreeDAsset"},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"audio":{"$ref":"#/definitions/GeneratedAudio"}}},"AddFavoritePayload":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["video","spritesheet","three_d_asset","game","generated_game","image","audio"]},"video":{"$ref":"#/definitions/GeneratedVideo"},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"three_d_asset":{"$ref":"#/definitions/ThreeDAsset"},"game":{"type":"string"},"generated_game":{"$ref":"#/definitions/GeneratedGame"},"image":{"$ref":"#/definitions/GameImage"},"audio":{"$ref":"#/definitions/GeneratedAudio"}}},"GameIds":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}}}},"SimilarGame":{"type":"object","properties":{"id":{"type":"string"},"similarity":{"type":"number","format":"float"}}},"TrendKeywords":{"type":"object","properties":{"keyword":{"type":"string"},"type":{"type":"string"},"weight":{"type":"number","format":"double"}}},"ColorFilter":{"type":"object","properties":{"name":{"type":"string"},"rgb":{"type":"array","items":{"type":"integer"}}}},"UserRefreshPayload":{"type":"object","properties":{"login_payload":{"$ref":"#/definitions/LoginPayload"},"projects":{"type":"array","items":{"$ref":"#/definitions/Project"}},"genres":{"type":"array","items":{"type":"string"}},"generationStyles":{"type":"array","items":{"type":"string"}},"generationStylesDetails":{"type":"array","items":{"$ref":"#/definitions/GeneratorOption"}},"generationTypes":{"type":"array","items":{"type":"string"}},"generationTypesStandalone":{"type":"array","items":{"$ref":"#/definitions/GeneratorOption"}},"generationTypes3d":{"type":"array","items":{"type":"string"}},"generationTypesSprite":{"type":"array","items":{"type":"string"}},"generationTypesNoBg":{"type":"array","items":{"type":"string"}},"componentGroups":{"type":"array","items":{"$ref":"#/definitions/ComponentGroup"}},"componentList":{"type":"array","items":{"$ref":"#/definitions/ComponentInfo"}},"gddPacks":{"type":"array","items":{"$ref":"#/definitions/GDDPack"}},"colors":{"type":"array","items":{"$ref":"#/definitions/ColorFilter"}},"styles":{"type":"array","items":{"type":"string"}},"platforms":{"type":"array","items":{"type":"string"}},"perspectives":{"type":"array","items":{"type":"string"}},"perspectivesDetails":{"type":"array","items":{"$ref":"#/definitions/GeneratorOption"}},"teams":{"type":"array","items":{"$ref":"#/definitions/Team"}},"health":{"type":"boolean"},"credits":{"$ref":"#/definitions/GenerationCreditsInfo"},"plans":{"type":"array","items":{"$ref":"#/definitions/PaymentPlan"}},"documentation":{"type":"array","items":{"$ref":"#/definitions/Documentation"}},"whats_new":{"type":"array","items":{"$ref":"#/definitions/WhatsNew"}},"generation_steps":{"type":"array","items":{"$ref":"#/definitions/GenerationStep"}},"credit_costs":{"type":"array","items":{"$ref":"#/definitions/CreditFeature"}},"voice_presets":{"$ref":"#/definitions/VoicePresetList"},"audio_limits":{"$ref":"#/definitions/AudioLimits"}}},"AudioLimits":{"type":"object","description":"Per audio_type text limits and extra fields of the web audio generator.","properties":{"human-voice":{"$ref":"#/definitions/AudioModeLimits"},"non-human-voice":{"$ref":"#/definitions/AudioModeLimits"},"speech":{"$ref":"#/definitions/AudioModeLimits"},"speech-preset":{"$ref":"#/definitions/AudioModeLimits"}}},"AudioModeLimits":{"type":"object","properties":{"text_max_chars":{"type":"integer"},"delivery_max_chars":{"type":"integer","description":"Set where the mode takes a delivery note (for speech-preset, only with a model whose presets have delivery true)."},"image":{"type":"boolean","description":"Whether the mode takes a character image."}}},"GeneratorOption":{"type":"object","properties":{"id":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}}},"GenerationStep":{"type":"object","properties":{"allow_input":{"type":"boolean"},"fixed_options":{"type":"boolean"},"description":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"}}},"StepByStepPayload":{"type":"object","properties":{"request_id":{"type":"string"},"genre":{"type":"string"},"n":{"type":"integer","format":"int64"},"platform":{"type":"string"},"step":{"type":"string"},"step_choices":{"type":"array","items":{"$ref":"#/definitions/StepValue"}}}},"StepValue":{"type":"object","properties":{"step":{"type":"string"},"description":{"type":"string"},"value":{"type":"string"},"score":{"$ref":"#/definitions/LudoScore"}}},"LudoScore":{"type":"object","properties":{"metrics":{"type":"array","items":{"$ref":"#/definitions/LudoScoreMetric"}},"params":{"type":"string"},"score_text":{"type":"string"},"score_unit":{"type":"string"},"score":{"type":"number","format":"double"},"timestamp":{"type":"integer","format":"int64"},"country":{"type":"string"},"sources":{"type":"array","items":{"type":"string"}},"version":{"type":"string"},"badge":{"$ref":"#/definitions/LudoScoreBadge"},"insights":{"type":"array","items":{"$ref":"#/definitions/LudoScoreInsight"}}}},"LudoScoreBadge":{"type":"object","properties":{"description":{"type":"string"},"icon_url":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"}}},"LudoScoreMetric":{"type":"object","properties":{"_max_similarity":{"type":"number","format":"double"},"_min_similarity":{"type":"number","format":"double"},"description":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"score":{"type":"number","format":"double"},"supporting_games_title":{"type":"string"},"supporting_games":{"type":"array","items":{"type":"string"}},"core_metric":{"type":"boolean"},"plotly_plot":{"$ref":"#/definitions/PlotlyPlot"},"insights":{"type":"array","items":{"$ref":"#/definitions/LudoScoreInsight"}}}},"PlotlyPlot":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/definitions/PlotlyPlotData"}},"layout":{"$ref":"#/definitions/PlotlyPlotData"}}},"PlotlyPlotData":{"type":"object","properties":{"mode":{"type":"string"}}},"LudoScoreInsight":{"type":"object","properties":{"tag":{"type":"string"},"text":{"type":"string"}}},"GDDPack":{"type":"object","properties":{"components":{"type":"array","items":{"$ref":"#/definitions/GDDPackComponent"}},"description":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"}}},"GDDPackComponent":{"type":"object","properties":{"n":{"type":"integer","format":"int64"},"type":{"type":"string"},"title":{"type":"string"}}},"ComponentGroup":{"type":"object","properties":{"components":{"type":"array","items":{"$ref":"#/definitions/ComponentInfo"}},"description":{"type":"string"},"name":{"type":"string"},"title":{"type":"string"}}},"ComponentInfo":{"type":"object","properties":{"description":{"type":"string"},"group":{"type":"string"},"hide":{"type":"boolean"},"images_by_default":{"type":"boolean"},"multiple_allowed":{"type":"string"},"section":{"type":"string"},"label":{"type":"string"},"followups":{"type":"boolean"},"gameElements":{"type":"boolean"},"chat":{"type":"boolean"},"related":{"type":"array","items":{"type":"string"}}}},"WhatsNew":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"integer","format":"int64"},"title":{"type":"string"},"hqTitle":{"type":"string"},"hqThumb":{"type":"string"},"hqText":{"type":"string"},"toolLabel":{"type":"string"},"toolUrl":{"type":"string"},"youtubeUrl":{"type":"string"}}},"Documentation":{"type":"object","properties":{"index":{"type":"string"},"label":{"type":"string"},"section":{"type":"array","items":{"$ref":"#/definitions/DocumentationSection"}}}},"DocumentationSection":{"type":"object","properties":{"title":{"type":"string"},"content":{"type":"string"}}},"HelpDocument":{"type":"object","properties":{"_id":{"type":"string"},"index":{"type":"string"},"label":{"type":"string"},"position":{"type":"integer"},"section":{"type":"array","items":{"$ref":"#/definitions/HelpDocumentSection"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"HelpDocumentSection":{"type":"object","properties":{"title":{"type":"string"},"content":{"type":"string"},"summary":{"type":"string"}}},"SiteDocument":{"type":"object","properties":{"index":{"type":"string"},"label":{"type":"string"},"section":{"type":"array","items":{"$ref":"#/definitions/HelpDocumentSection"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"WhatsNewEntry":{"type":"object","properties":{"_id":{"type":"string"},"entry_id":{"type":"string"},"date":{"type":"string"},"title":{"type":"string"},"text":{"type":"string"},"hq_title":{"type":"string"},"hq_text":{"type":"string"},"hq_thumb":{"type":"string"},"tool_label":{"type":"string"},"tool_url":{"type":"string"},"youtube_url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"SiteWhatsNewEntry":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"string"},"title":{"type":"string"},"text":{"type":"string"},"hq_title":{"type":"string"},"hq_text":{"type":"string"},"hq_thumb":{"type":"string"},"tool_label":{"type":"string"},"tool_url":{"type":"string"},"youtube_url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"GenerationCreditsInfo":{"type":"object","properties":{"current":{"type":"number","format":"float"},"max":{"type":"number","format":"float"},"reset_date":{"type":"integer","format":"int64"},"extra":{"type":"number","format":"float"},"total":{"type":"number","format":"float"},"paused":{"type":"boolean","description":"Set when a pause is requested. The account is paused only once pause_from has passed (helpers/pause-state.js)"},"pause_from":{"type":"integer","format":"int64","description":"When a requested pause starts (unix seconds): the subscription's pause_billing_from, the end of the period already paid for. Missing on pauses that predate it, which are in effect"},"has_tier_logic":{"type":"boolean"},"uses_team_credits":{"type":"boolean"},"credits_upfront":{"type":"boolean"},"grace_until":{"type":"integer","format":"int64","description":"If set, the user's subscription ended and their remaining credits stay spendable until this unix timestamp (30-day grace period)"},"grace_reason":{"type":"string","description":"Stripe cancellation_details.reason at termination (e.g. cancellation_requested, payment_failed)"}}},"GenerationHistoryResults":{"type":"object","properties":{"number_pages":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/definitions/GenerationHistory"}}}},"GenerationHistory":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"type":{"type":"string","enum":["generated_image","generated_game","three_d_asset","video","spritesheet","audio","map"]},"generated_image":{"$ref":"#/definitions/GameImage"},"generated_game":{"$ref":"#/definitions/GeneratedGame"},"three_d_asset":{"$ref":"#/definitions/ThreeDAsset"},"video":{"$ref":"#/definitions/GeneratedVideo"},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"audio":{"$ref":"#/definitions/GeneratedAudio"},"map":{"$ref":"#/definitions/GeneratedMap"},"date":{"type":"integer","format":"int64"},"api":{"type":"boolean"},"source":{"type":"string","enum":["web","api","mcp","cli"],"default":"web"},"request_id":{"type":"string"},"deleted":{"type":"boolean"},"help_requested_at":{"type":"integer","format":"int64"},"help_message":{"type":"string"},"job_id":{"type":"string"},"inputs":{"type":"array","items":{"$ref":"#/definitions/GenerationInput"}}}},"GenerationInput":{"type":"object","properties":{"field":{"type":"string"},"kind":{"type":"string","enum":["image","video","audio","model"]},"url":{"type":"string"},"id":{"type":"string"}}},"GenerationHistoryTag":{"type":"object","properties":{"tag":{"type":"string"},"count":{"type":"integer"}}},"GenerationHistoryTags":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/definitions/GenerationHistoryTag"}}}},"GenerationJob":{"type":"object","required":["_id","user_id","task_type","status","created_at"],"properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"task_type":{"type":"string","enum":["generate_images","generate_3d_images","generate_refined_3d_images","rig_3d_model","animate_3d_model","animate_3d_preset","generate_images_inpainting","generate_video","generate_video_first_frame","generate_audio","generate_image_to_spritesheet","generate_video_to_spritesheet","generate_sprite","generate_spritesheet_motion_transfer","generate_spritesheet_new_view","render_sprite_3d","render_sprite_3d_still","generate_edit_spritesheet","generate_references_to_video","generate_video_edit","generate_upscale_video","remove_image_background","upscale_image","test_wait","api_create_image","api_generate_with_style","api_edit_image","api_edit_image_mask","api_generate_pose","api_rotate_sprite","api_remove_background","api_upscale_image","api_animate_sprite","api_animate_sprite_keyframes","api_transfer_motion","api_edit_spritesheet","api_rotate_spritesheet","api_adjust_spritesheet","api_create_sprite_audio","api_create_video","api_create_video_from_references","api_edit_video","api_upscale_video","api_create_3d_model","api_create_3d_model_image","api_rig_model","api_animate_3d_model","api_animate_3d_model_preset","api_render_sprite_3d","api_render_sprite_3d_still","api_create_sound_effect","api_create_ambiance","api_create_music","api_create_audio_transform","api_create_voice","api_create_speech","api_create_speech_preset"]},"status":{"type":"string","enum":["queued","running","succeeded","failed","canceled"]},"origin":{"type":"string","enum":["web","api","mcp","cli"],"default":"web"},"payload":{"type":"object"},"result":{"type":"object"},"error":{"$ref":"#/definitions/JobError"},"request_id":{"type":"string"},"history_id":{"type":"string"},"worker_id":{"type":"string"},"attempts":{"type":"integer","format":"int32"},"credits_charged":{"type":"number","format":"float"},"credit_pool_id":{"type":"string"},"credit_feature":{"type":"string"},"credit_tier":{"type":"string"},"created_at":{"type":"integer","format":"int64"},"started_at":{"type":"integer","format":"int64"},"finished_at":{"type":"integer","format":"int64"},"heartbeat_at":{"type":"integer","format":"int64"},"acknowledged_at":{"type":"integer","format":"int64"},"description":{"type":"string","description":"User-facing label derived from the payload at enqueue time (e.g. 'Sound effect: laser zap'). Frontend uses this in the queue panel."},"expire_at":{"type":"string","format":"date-time"}}},"JobError":{"type":"object","properties":{"code":{"type":"string","enum":["network","prometheus_5xx","prometheus_4xx","validation","timeout","unknown"]},"subcode":{"type":"string"},"message":{"type":"string"},"retriable":{"type":"boolean"}}},"JobAcceptedResponse":{"type":"object","required":["jobId","status"],"properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["queued","running","succeeded","failed","canceled"]},"queue_position":{"type":"integer","format":"int32"}}},"JobTestWaitPayload":{"type":"object","description":"Payload for the load-test queue endpoint. The worker sleeps for a random duration in [min_ms, max_ms) instead of calling Prometheus.","properties":{"min_ms":{"type":"integer","format":"int32","description":"Minimum sleep in milliseconds. Defaults to 10000 (10s)."},"max_ms":{"type":"integer","format":"int32","description":"Maximum sleep in milliseconds (exclusive). Defaults to 120000 (2min)."},"request_id":{"type":"string","description":"Optional idempotency key, same semantics as the real queue endpoints."}}},"QueueRemoveImageBackgroundPayload":{"type":"object","required":["image"],"properties":{"image":{"$ref":"#/definitions/GameImage"},"crop":{"type":"boolean"}}},"QueueUpscaleImagePayload":{"type":"object","required":["image"],"properties":{"image":{"$ref":"#/definitions/GameImage"},"request_id":{"type":"string"}}},"PixelArtConversionPayload":{"type":"object","required":["image"],"properties":{"image":{"$ref":"#/definitions/GameImage"},"pixel_size":{"type":"number","format":"float","minimum":2,"description":"Size of one pixel-art pixel, in the input's pixels. Absent: the image's own pixel size, which must be detectable."},"palette_size":{"type":"integer","minimum":2,"maximum":256,"description":"At most this many colours. Absent: no colour reduction."}}},"UserRecommendations":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"date":{"type":"integer","format":"int64"},"date_string":{"type":"string"}}},"Invoice":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"integer","format":"integer"},"total":{"type":"number","format":"integer"},"currency":{"type":"string"},"customer":{"type":"string"},"description":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/definitions/InvoiceLine"}},"number":{"type":"string"},"amount_due":{"type":"number","format":"integer"},"amount_paid":{"type":"number","format":"integer"},"amount_remaining":{"type":"number","format":"integer"},"collection_method":{"type":"string"},"hosted_invoice_url":{"type":"string"},"invoice_pdf":{"type":"string"},"metadata":{"$ref":"#/definitions/CouponMetadata"},"period_start":{"type":"number","format":"integer"},"period_end":{"type":"number","format":"integer"},"subscription":{"type":"string"},"subtotal":{"type":"number","format":"integer"},"status":{"type":"string"},"created":{"type":"number","format":"integer"},"attempted":{"type":"boolean"},"payments":{"type":"array","items":{"$ref":"#/definitions/InvoicePayment"}}}},"InvoicePayment":{"type":"object","properties":{"amount_paid":{"type":"number","format":"integer"},"status":{"type":"string"},"charge":{"type":"string"},"payment_intent":{"type":"string"}}},"InvoiceLine":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"amount":{"type":"number","format":"integer"},"currency":{"type":"string"},"description":{"type":"string"},"discountable":{"type":"boolean"},"type":{"type":"string"}}},"CouponMetadata":{"type":"object","properties":{"description":{"type":"string"},"summary":{"type":"string"}}},"StripeEvent":{"type":"object","properties":{"id":{"type":"string"},"created":{"type":"number","format":"float"},"livemode":{"type":"boolean"},"type":{"type":"string"},"object":{"type":"string"},"pending_webhooks":{"type":"number","format":"float"},"api_version":{"type":"string"},"data":{"type":"object"},"request":{"type":"object"}}},"AssistantLog":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"date":{"type":"number","format":"integer"},"title":{"type":"string"},"message_history":{"type":"array","items":{"$ref":"#/definitions/AssistantMessage"}}}},"GddConversation":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"project_id":{"type":"string"},"title":{"type":"string"},"date":{"type":"number","format":"integer"},"date_created":{"type":"number","format":"integer"},"message_history":{"type":"array","items":{"$ref":"#/definitions/ChatMessage"}}}},"GddConversationSummary":{"type":"object","properties":{"_id":{"type":"string"},"title":{"type":"string"},"date":{"type":"number","format":"integer"},"date_created":{"type":"number","format":"integer"},"message_count":{"type":"number","format":"integer"},"preview":{"type":"string"}}},"SaveGddConversationRequest":{"type":"object","required":["conversation_id","message_history"],"properties":{"conversation_id":{"type":"string"},"title":{"type":"string"},"message_history":{"type":"array","items":{"$ref":"#/definitions/ChatMessage"}}}},"ChatLog":{"type":"object","properties":{"_id":{"type":"string"},"conversation_id":{"type":"string"},"user_id":{"type":"string"},"project_id":{"type":"string"},"date":{"type":"number","format":"integer"},"message_history":{"type":"array","items":{"$ref":"#/definitions/ChatMessage"}},"selected_element":{"type":"string"}}},"ChatRequest":{"type":"object","properties":{"message":{"type":"string"},"conversation_id":{"type":"string"},"request_id":{"type":"string"},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"message_history":{"type":"array","items":{"$ref":"#/definitions/ChatMessage"}},"selected_element":{"type":"string"},"is_regeneration":{"type":"boolean"},"ludo_score":{"$ref":"#/definitions/LudoScore"},"ludo_score_metric":{"type":"string"},"guide_page":{"type":"string"},"persist":{"type":"boolean","description":"When true, the conversation is stored server-side under conversation_id (required then) for the calling user and project."}}},"AssistantRequest":{"type":"object","required":["request_id"],"properties":{"message":{"type":"string"},"conversation_id":{"type":"string"},"request_id":{"type":"string"},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"generated_game":{"$ref":"#/definitions/GeneratedGame"},"message_history":{"type":"array","items":{"$ref":"#/definitions/AssistantMessage"}},"platform":{"type":"string"},"genre":{"type":"string"},"is_regeneration":{"type":"boolean"},"page":{"type":"string"},"image_result":{"$ref":"#/definitions/GameImage"},"sprite_result":{"$ref":"#/definitions/Spritesheet"},"video_result":{"$ref":"#/definitions/GeneratedVideo"},"audio_result":{"$ref":"#/definitions/GeneratedAudio"},"asset_result":{"$ref":"#/definitions/AssetGeneration"},"asset_and_image_result":{"$ref":"#/definitions/ThreeDAsset"}}},"SaveAssistantConversationRequest":{"type":"object","required":["conversation_id","message_history"],"properties":{"conversation_id":{"type":"string"},"message_history":{"type":"array","items":{"$ref":"#/definitions/AssistantMessage"}}}},"AssistantMessage":{"type":"object","required":["role"],"properties":{"text":{"type":"string"},"role":{"type":"string"},"timestamp":{"type":"integer","format":"int64"},"followups":{"type":"array","items":{"$ref":"#/definitions/AssistantFollowUp"}},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"videos":{"type":"array","items":{"$ref":"#/definitions/GeneratedVideo"}},"sprites":{"type":"array","items":{"$ref":"#/definitions/Spritesheet"}},"references":{"type":"array","items":{"$ref":"#/definitions/AssistantReference"}},"links":{"type":"array","items":{"$ref":"#/definitions/AssistantLink"}},"function_calls":{"type":"array","items":{"$ref":"#/definitions/AssistantFunctionCall"}},"assets":{"type":"array","items":{"$ref":"#/definitions/ThreeDAsset"}},"game_ideas":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}}}},"AssistantFollowUp":{"type":"object","properties":{"text":{"type":"string"},"type":{"type":"string"}}},"AssistantLink":{"type":"object","properties":{"page":{"type":"string"},"text":{"type":"string"}}},"AssistantFunctionCall":{"type":"object","properties":{"args":{"type":"object"},"name":{"type":"string"}}},"AssistantReference":{"type":"object","properties":{"_word_count":{"type":"integer","format":"int64"},"content":{"type":"string"},"id":{"type":"string"},"new":{"type":"boolean"},"relevance":{"type":"number","format":"float"},"retrieved_motive":{"type":"string"},"retrieved_timestamp":{"type":"integer","format":"int64"},"title":{"type":"string"},"display_title":{"type":"string"},"type":{"type":"string"},"type_title":{"type":"string"},"top_chart":{"$ref":"#/definitions/AssistantReferenceTopCharts"},"trends":{"$ref":"#/definitions/AssistantReferenceTrends"},"help":{"$ref":"#/definitions/AssistantReferenceHelp"},"game":{"$ref":"#/definitions/AssistantReferenceGame"},"function_call":{"$ref":"#/definitions/AssistantFunctionCall"}}},"AssistantReferenceGame":{"type":"object","properties":{"game_id":{"type":"string"}}},"AssistantReferenceTopCharts":{"type":"object","properties":{"country":{"type":"string"},"genre":{"type":"string"},"source":{"type":"string"}}},"AssistantReferenceTrends":{"type":"object","properties":{"category":{"type":"string"},"country":{"type":"string"},"source":{"type":"string"},"sorting":{"type":"string"},"genre":{"type":"string"}}},"AssistantReferenceHelp":{"type":"object","properties":{"flotiq_index":{"type":"string"},"flotiq_label":{"type":"string"},"doc_index":{"type":"integer","format":"int64"}}},"ChatMessage":{"type":"object","required":["role"],"properties":{"text":{"type":"string"},"role":{"type":"string"},"timestamp":{"type":"number","format":"integer"},"followups":{"type":"array","items":{"$ref":"#/definitions/AssistantFollowUp"}},"selected_element":{"type":"string"},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"videos":{"type":"array","items":{"$ref":"#/definitions/GeneratedVideo"}},"sprites":{"type":"array","items":{"$ref":"#/definitions/Spritesheet"}},"function_calls":{"type":"array","items":{"$ref":"#/definitions/AssistantFunctionCall"}},"assets":{"type":"array","items":{"$ref":"#/definitions/ThreeDAsset"}},"game_ideas":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}}}},"ProjectCode":{"type":"object","properties":{"_id":{"type":"string"},"project_id":{"type":"string"},"date":{"type":"number","format":"integer"},"name":{"type":"string"},"filename":{"type":"string"},"overview":{"type":"string"},"main_functions":{"type":"string"},"explanation":{"type":"string"},"implementation_details":{"type":"string"},"dependencies":{"type":"array","items":{"type":"string"}},"script":{"type":"string"},"loading":{"type":"boolean"},"packages":{"type":"array","items":{"type":"string"}}}},"GuideContentPayload":{"type":"object","required":["game_concept_id","action","skill_level","engine"],"properties":{"game_concept_id":{"type":"string"},"action":{"type":"string","enum":["VIEW_TOPICS","SELECT_TOPIC","VIEW_SUBTOPIC"]},"skill_level":{"type":"string","enum":["novice","expert"]},"engine":{"type":"string"},"prompt":{"type":"string"},"current_topic_id":{"type":"string"},"current_subtopic_id":{"type":"string"},"current_path":{"type":"array","items":{"type":"string"}}}},"GuideContent":{"type":"object","required":["action","content_json","skill_level"],"properties":{"_id":{"type":"string"},"project_id":{"type":"string"},"skill_level":{"type":"string"},"engine":{"type":"string"},"prompt":{"type":"string"},"action":{"type":"string","enum":["VIEW_TOPICS","VIEW_SUBTOPIC"]},"content_json":{"type":"string","description":"Stringified JSON of the content"},"date":{"type":"integer"},"date_last_view":{"type":"integer"},"topic_id":{"type":"string"},"topic_title":{"type":"string"},"subtopic_id":{"type":"string"},"subtopic_title":{"type":"string"},"total_steps":{"type":"integer"},"current_step":{"type":"integer"},"next_step":{"$ref":"#/definitions/GuideStep"},"previous_step":{"$ref":"#/definitions/GuideStep"}}},"GuideStep":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}}},"ThreeDGenerationParameters":{"type":"object","properties":{"model":{"type":"string","description":"Image-to-3D model (THREE_D_MODELS in credits.js): prop (default) or hero, which bills GENERATE_3D_MODEL_HERO. Ignored by the other 3D operations.","enum":["prop","hero"]},"rig":{"type":"boolean","description":"Image-to-3D only: also rig the model (skeleton + skin weights) in the same call, as rig-model would. Bills RIG_3D_MODEL on top of the model when the model comes back rigged. Ignored by the other 3D operations."},"rig_type":{"type":"string","enum":["general","humanoid","game","humanoid_template","humanoid_template_hands"],"description":"The rig a model rigged in this call got. Always general: any other value sent is overridden (MediaGeneration.imageTo3D). Kept in the enum so an older client sending one is not refused, and so models rigged before the change keep their recorded type."},"joint_naming":{"type":"string","enum":["smpl","mixamo","humanik","unreal","godot","rigify","vroid"],"description":"With rig, the bone naming convention (as RigModelPayload.joint_naming)."},"target_num_faces":{"type":"integer","default":50000},"dynamic_simplification":{"type":"number","format":"float","default":0.15},"remeshing":{"type":"string","default":"conservative"},"texture_size":{"type":"integer","default":2048},"texture_type":{"type":"string","default":"pbr"},"render_format":{"type":"string"},"render_video":{"type":"boolean"}}},"RetopologyPayload":{"type":"object","properties":{"request_id":{"type":"string"},"asset_generation":{"$ref":"#/definitions/AssetGeneration"},"generation_parameters":{"$ref":"#/definitions/ThreeDGenerationParameters"}}},"ImageTo3DPayload":{"type":"object","properties":{"request_id":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"back_image":{"$ref":"#/definitions/GameImage"},"generation_parameters":{"$ref":"#/definitions/ThreeDGenerationParameters"}}},"Create3DModelPayload":{"type":"object","description":"Payload for generating a 3D model from an image","required":["image"],"properties":{"image":{"type":"string","description":"URL or base64-encoded image to convert to 3D, taken as the front view of the subject"},"model":{"type":"string","default":"prop","description":"Image-to-3D model: prop (default) or hero. The public specs rewrite this from THREE_D_MODELS in credits.js (scripts/generate-public-swagger.js).","enum":["prop","hero"]},"back_image":{"type":"string","description":"Optional URL or base64-encoded image of the SAME subject seen from behind (rotated 180 degrees from image). The model's back then follows it instead of being invented from the front view alone. It must show the same subject, at the same scale and in the same pose and style as image. Only a back view is supported: a side or three-quarter view here produces a distorted model. Prop model only: hero rejects it with HTTP 400. No extra cost."},"target_num_faces":{"type":"integer","default":50000,"minimum":1000,"maximum":200000,"description":"Maximum triangle count for the mesh (1000-200000)"},"texture_size":{"type":"integer","default":2048,"description":"Texture resolution in pixels","enum":[1024,2048]},"texture_type":{"type":"string","default":"pbr","description":"Texture type","enum":["pbr","simple","none"]},"rig":{"type":"boolean","default":false,"description":"Also rig the model (skeleton and skin weights) in the same call, so it can be animated or rendered with createDirectionalSprites. It gets the general rig, which suits any shape; there is no rig type to pick here (rigModel offers the others). Adds the rigModel price, charged only when the model comes back rigged (the result's rigged)."},"joint_naming":{"type":"string","enum":["smpl","mixamo","humanik","unreal","godot","rigify","vroid"],"default":"smpl","description":"With rig, the bone naming convention (as rigModel's joint_naming). Purely a relabel - the skeleton is identical."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"Create3DModelImagePayload":{"type":"object","description":"Payload for making a character image ready for create3DModel. Pass a prompt, an image, or both.","properties":{"prompt":{"type":"string","maxLength":1000,"description":"Without an image, a description of the character (e.g. \"a knight in green armour, full body, standing\"). With an image, the changes to make while redrawing it (e.g. \"add a red cape and a sword\"); optional."},"image":{"type":"string","description":"Optional URL or base64-encoded image of your own, redrawn as a 3D render."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateSoundEffectPayload":{"type":"object","description":"Payload for generating a sound effect from a text description","required":["description"],"properties":{"description":{"type":"string","description":"Text description of the sound effect to generate (e.g., \"laser gun firing\", \"footsteps on gravel\")."},"duration":{"type":"number","format":"float","minimum":0,"maximum":10,"default":0,"description":"Duration in seconds, 0.5 to 10. Use 0 for automatic duration based on the description. Shorter requests are generated at 0.5 s."},"loop":{"type":"boolean","default":false,"description":"Generate a sound effect that loops seamlessly."},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateAmbiancePayload":{"type":"object","description":"Payload for generating a seamless looping ambiance soundscape from a text description","required":["description"],"properties":{"description":{"type":"string","description":"Text description of the ambiance to generate (e.g., \"windy forest at dusk\", \"busy tavern interior\")."},"duration":{"type":"number","format":"float","minimum":0,"maximum":10,"default":0,"description":"Duration in seconds. Use 0 for automatic duration based on the description."},"loop":{"type":"boolean","default":true,"description":"Generate an ambiance that loops seamlessly."},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateMusicPayload":{"type":"object","description":"Payload for generating music from a text description","required":["description"],"properties":{"description":{"type":"string","description":"Text description of the music to generate (e.g., \"epic orchestral battle theme\", \"calm piano melody\")."},"lyrics":{"type":"string","description":"Optional lyrics to include in the generated music."},"duration":{"type":"integer","default":0,"description":"Duration in seconds. Use 0 for automatic duration based on the description.","enum":[0,10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180]},"loop":{"type":"boolean","default":false,"description":"Generate music that loops seamlessly."},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateAudioTransformPayload":{"type":"object","description":"Payload for remixing an audio sample (sound effect, ambiance or music) into a variation guided by a text prompt","required":["sample","prompt"],"properties":{"sample":{"type":"string","description":"URL or base64-encoded source audio sample to remix (15MB max).","example":"<url> OR data:audio/mp3;base64,..."},"prompt":{"type":"string","description":"Text description guiding the remix (e.g., \"make it sound like an 80s synthwave track\", \"turn this into a metal version\")."},"duration":{"type":"integer","default":0,"description":"Duration in seconds. Use 0 for automatic duration matching the source sample.","enum":[0,10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180]},"modification_strength":{"type":"number","format":"float","minimum":0,"maximum":1,"default":0.6,"description":"Controls how strongly the source sample is modified. 0 keeps it close to the original, 1 transforms it fully."},"loop":{"type":"boolean","default":false,"description":"Generate a remix that loops seamlessly."},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateVoicePayload":{"type":"object","description":"Payload for generating a voice sample from a character description and/or a character image","required":["text"],"properties":{"voice_description":{"type":"string","description":"Text description of the voice character (e.g., \"deep-voiced warrior\", \"cheerful young girl\"). Required unless image is given; with an image, a few words of hints."},"image":{"type":"string","description":"URL or base64-encoded image of the speaking character (concept art, at most 15MB). The voice is designed from it, and voice_description becomes optional."},"text":{"type":"string","description":"Text for the voice to speak (max 100 characters).","maxLength":100,"minLength":1},"type":{"type":"string","default":"human","description":"Type of voice to generate.","enum":["human","non-human"]},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateSpeechPayload":{"type":"object","description":"Payload for text-to-speech generation using voice cloning","required":["text","sample"],"properties":{"text":{"type":"string","description":"Text to convert to speech (max 250 characters).","maxLength":250,"minLength":1},"sample":{"type":"string","description":"URL or base64-encoded audio sample for voice cloning."},"delivery":{"type":"string","description":"How the line is said, e.g. \"angry, shouting\" or \"whispering, out of breath\" (max 50 characters).","maxLength":50},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateSpeechPresetPayload":{"type":"object","description":"Payload for text-to-speech generation using a voice preset","required":["text","voice_preset_id"],"properties":{"text":{"type":"string","description":"Text to convert to speech (max 250 characters).","maxLength":250},"voice_preset_id":{"x-swagger-data":"voicePresetIds","type":"string","description":"Voice preset identifier. Two families of voices: the ones named by a first name (e.g. \"Rachel\", \"Roger\") take a delivery note and no emotion; the descriptive ones (e.g. \"Serious woman\", \"Deep voice man\") take an emotion and no delivery note."},"emotion":{"x-swagger-data":"voicePresetEmotions","type":"string","description":"Emotion to apply to the speech. Only for the descriptive voices (e.g. \"Serious woman\"); a first-name voice returns HTTP 400 for any emotion other than Default - use delivery instead."},"delivery":{"type":"string","description":"How the line is said, e.g. \"angry, shouting\" or \"whispering, out of breath\" (max 50 characters). Only for the first-name voices (e.g. \"Rachel\"); a descriptive voice returns HTTP 400 - use emotion instead.","maxLength":50},"language":{"x-swagger-data":"voicePresetLanguages","type":"string","description":"Language of the speech. Each voice family supports its own list of languages; \"auto\" detects it from the text."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"GenerateWithStylePayload":{"type":"object","description":"Payload for generating new content while maintaining the visual style of a reference image","required":["style_image","prompt"],"properties":{"style_image":{"type":"string","description":"URL or base64-encoded reference image whose visual style should be matched."},"prompt":{"type":"string","description":"Text description of what to generate (e.g., \"a warrior character\", \"a forest background\", \"a treasure chest\")."},"image_type":{"x-swagger-data":"imageTypes","type":"string","description":"What kind of image to generate.","default":"sprite"},"n":{"type":"number","format":"integer","description":"Number of variations to generate (1-4).","minimum":1,"maximum":4,"default":1},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"EditImagePayload":{"type":"object","description":"Payload for editing an existing image based on text instructions","required":["image"],"properties":{"image":{"type":"string","description":"URL or base64-encoded source image to edit."},"prompt":{"type":"string","description":"Text description of the changes to make (e.g., \"add clouds to the sky\", \"make it darker\", \"change the color scheme to warmer tones\"). Required, except with mask_mode \"erase\". With a mask, describe what should fill the masked area. Describe only the change, never the background's transparency (\"transparent background\", \"on transparency\", \"transparent padding\"); asking for it often paints a checkerboard into the pixels instead. A sprite keeps its transparent background on its own, and removeBackground is the tool for removing one."},"reference_image":{"type":"string","description":"Optional URL or base64-encoded reference image for style or content guidance. Not used with mask_mode \"erase\"."},"mask":{"type":"string","description":"Optional URL or base64-encoded PNG that limits the edit to one area of the image. White marks the area to change, black the area to keep. It must be exactly the same width and height as `image`, or the call returns 400. Without a mask the whole image is re-rendered, which can shift colours and details outside the part you meant to change. A masked edit costs more than an unmasked one (see the price)."},"mask_mode":{"type":"string","enum":["replace","erase"],"description":"Only with `mask`. \"replace\" (the default) redraws the masked area from `prompt`; \"erase\" removes what is in the masked area and takes no prompt."},"image_type":{"x-swagger-data":"imageTypes","type":"string","example":"sprite","description":"What kind of image the source is (e.g. \"sprite\"). Not needed when `image` is the URL of an image you generated on Ludo in the last 30 days, which keeps its own type. Set it when the image is an upload or a link from elsewhere, so a sprite is edited as a sprite and keeps its transparent background."},"n":{"type":"number","format":"integer","description":"Number of edited variations to generate (1-4).","minimum":1,"maximum":4,"default":1},"augment_prompt":{"type":"boolean","default":true,"x-deprecated":true,"description":"Ignored: edits always rewrite your prompt into the long, structured form the edit model needs (which subjects are in the image, what changes and what stays). Without that rewrite, edits miss the request and lose quality, so it can no longer be turned off. Still accepted so existing requests keep working; omit it."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"RemoveBackgroundPayload":{"type":"object","description":"Payload for removing the background of an image","required":["image"],"properties":{"image":{"type":"string","description":"URL or base64-encoded image to remove the background from."},"crop":{"type":"boolean","default":false,"description":"Whether to crop/trim the result to fit the content."},"creative_edit":{"type":"boolean","default":true,"description":"Higher quality results but the image will not be exactly the same as the input."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"UpscaleImagePayload":{"type":"object","description":"Payload for upscaling an image","required":["image"],"properties":{"image":{"type":"string","description":"URL or base64-encoded image to upscale, below 2048px on its longest side."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"ConvertToPixelArtPayload":{"type":"object","description":"Payload for converting an image to pixel art","required":["image"],"properties":{"image":{"type":"string","description":"URL or base64-encoded image to convert - any art, broken AI pixel art or pixel art."},"pixel_size":{"type":"number","format":"float","minimum":2,"description":"Size of one pixel-art pixel, in the input image's pixels (at least 2): larger values give coarser art. Omit it to use the image's own pixel size, which must be detectable (the image must already be on a pixel grid); when none is detected the call fails with HTTP 400 asking for a pixel_size. On an input already on a pixel grid it snaps to whole steps of that grid."},"palette_size":{"type":"integer","minimum":2,"maximum":256,"description":"At most this many colours (2-256). Omit it to keep the colours as they are."},"request_id":{"type":"string","description":"Optional client-provided identifier to tag the result with, usable as the request_id filter of listGenerations."}}},"GeneratePosePayload":{"type":"object","description":"Payload for generating a new pose for an existing sprite","required":["image","pose"],"properties":{"image":{"type":"string","description":"URL or base64-encoded source sprite image to generate a new pose from."},"pose":{"type":"string","x-swagger-data":"spritePoses","description":"Target pose for the sprite. Use the value \"Other\" to generate other poses not listed in the accepted values, and fill the field description accordingly."},"description":{"type":"string","description":"Optional additional instructions or description to guide the pose generation."},"n":{"type":"number","format":"integer","description":"Number of pose variations to generate (1-4).","minimum":1,"maximum":4,"default":1},"augment_prompt":{"type":"boolean","default":true,"x-deprecated":true,"description":"Ignored: edits always rewrite your prompt into the long, structured form the edit model needs (which subjects are in the image, what changes and what stays). Without that rewrite, edits miss the request and lose quality, so it can no longer be turned off. Still accepted so existing requests keep working; omit it."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"PoseResult":{"type":"object","description":"Result of a pose generation","properties":{"url":{"type":"string","description":"URL of the generated pose image"},"pose":{"type":"string","description":"The pose that was used to generate this image"},"description":{"type":"string","description":"The pose description that was used to generate this image"},"motion_prompt":{"type":"string","description":"A suggested motion prompt optimized for this pose. Can be used directly with 'animateSprite' for best animation results."},"model":{"type":"string","description":"The id of the model that generated the image."},"prompt":{"type":"string","description":"The prompt as the model received it."}}},"RotateSpritePayload":{"type":"object","description":"Payload for rotating the camera view of an existing sprite","required":["image","camera_rotation"],"properties":{"image":{"type":"string","description":"URL or base64-encoded source sprite image to rotate."},"camera_rotation":{"type":"integer","enum":[0,45,90,135,180,-135,-90,-45],"description":"Camera azimuth angle in degrees. 0 = front, 45 = front-right, 90 = right side, 135 = back-right, 180 = back, -135 = back-left, -90 = left side, -45 = front-left."},"camera_elevation":{"type":"integer","enum":[0,30,60],"description":"Optional camera elevation/tilt in degrees. 0 = eye-level, 30 = elevated, 60 = high-angle. Omit to keep the sprite's current elevation."},"n":{"type":"number","format":"integer","description":"Number of variations to generate (1-4).","minimum":1,"maximum":4,"default":1},"augment_prompt":{"type":"boolean","default":true,"x-deprecated":true,"description":"Ignored: edits always rewrite your prompt into the long, structured form the edit model needs (which subjects are in the image, what changes and what stays). Without that rewrite, edits miss the request and lose quality, so it can no longer be turned off. Still accepted so existing requests keep working; omit it."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"RotateSpriteResult":{"type":"object","description":"Result of a sprite camera rotation","properties":{"url":{"type":"string","description":"URL of the generated image"},"camera_rotation":{"type":"integer","description":"The camera azimuth angle that was applied"},"camera_elevation":{"type":"integer","description":"The camera elevation that was applied, if any"},"model":{"type":"string","description":"The id of the model that generated the image."},"prompt":{"type":"string","description":"The prompt as the model received it."}}},"ThreeDAsset":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"asset":{"$ref":"#/definitions/AssetGeneration"},"image":{"$ref":"#/definitions/GameImage"},"back_image":{"$ref":"#/definitions/GameImage","description":"The optional back view (180 degrees) the model was generated from, next to `image`, the front view. Hosted url and dimensions only."},"sprite3d_look":{"type":"object","description":"The look saved for rendering this model as a sprite (3D to sprite), a Sprite3DLook, set through POST /games/sprite3d/look (which validates it). Carried through in-place rewrites of the model and onto a re-rig. Mixed rather than a $ref: a $ref here compiles to a subdocument array, which a dotted-path update writes as an array."},"sprite3d_attachments":{"type":"array","description":"The items this model holds in Directional Sprites, each a Sprite3DAttachment, set through POST /games/sprite3d/attachments (which validates them). Carried through in-place rewrites of the model and onto a re-rig.","items":{"type":"object"}},"animations":{"type":"array","description":"Accepted text-driven animation clips attached to this 3D model. Hydrated onto the history entry when listing 3D generations so the frontend can render the clip selector / management modal. Clips are stored in the Animation3DClip collection, not embedded.","items":{"$ref":"#/definitions/Animation3DClip"}}}},"ThreeDModelInput":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}},"asset":{"$ref":"#/definitions/AssetGeneration"},"image":{"$ref":"#/definitions/GameImage"},"back_image":{"$ref":"#/definitions/GameImage","description":"The optional back view (180 degrees) the model was generated from, next to `image`, the front view. Hosted url and dimensions only."}}},"AssetGeneration":{"type":"object","properties":{"id":{"type":"string"},"rigged":{"type":"boolean","description":"True once the model carries a skeleton and skin weights (set by rig-model). Gates the animation flow and the re-rig guard."},"animatable":{"type":"boolean","description":"The ML's humanoid check on the model's rig, as reported with the model: true when it passed, false when it failed or there is no rig. Gates the animation presets (retargeting needs a humanoid). Absent when unknown: on models rigged before the ML saved the check, and whenever the ML reports null (Prometheus responses drop nulls). Never null: swagger-tools ignores x-nullable, so a null here fails request validation."},"seed":{"type":"integer"},"images_b64":{"type":"array","items":{"type":"string"}},"params_encrypted":{"type":"string"},"request_id":{"type":"string"},"video_b64":{"type":"string"},"mesh_video_b64":{"type":"string"},"snapshots_b64":{"type":"array","items":{"type":"string"}},"model_b64":{"type":"string"},"binary_data_b64":{"type":"string"},"voxels_b64":{"type":"string"},"mask_snapshots_b64":{"type":"array","items":{"type":"string"}},"depth_snapshots_b64":{"type":"array","items":{"type":"string"}},"normal_snapshots_b64":{"type":"array","items":{"type":"string"}},"textured":{"type":"boolean","description":"Whether the ML generated a texture for the model."},"view_azimuths":{"type":"array","description":"Camera azimuths, in degrees, of the input views the ML built the model from: [0] for a front view alone, [0, 180] with a back view.","items":{"type":"number","format":"float"}},"generation_parameters":{"$ref":"#/definitions/ThreeDGenerationParameters"}}},"Model3DResult":{"type":"object","properties":{"model_url":{"type":"string","description":"URL to the generated 3D model file"},"snapshots":{"type":"array","items":{"type":"string"},"description":"URLs to snapshot images from different angles (useful for previews and MCP agents)"},"rigged":{"type":"boolean","description":"True when the model carries a skeleton (asked for with rig)."}}},"Animation3DClip":{"type":"object","required":["_id","user_id","model_asset_id"],"properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"model_asset_id":{"type":"string","description":"Parent 3D asset id (the GenerationHistory _id, which for three_d_asset equals image.id)."},"clip_name":{"type":"string"},"prompt":{"type":"string"},"preset_id":{"type":"string","description":"Set when the clip came from the preset library (retargeting) rather than a text prompt."},"mode":{"type":"string","enum":["rot_trans","rot_only"]},"seed":{"type":"integer"},"glb_url":{"type":"string","description":"Uploaded animation-only GLB (skeleton + one clip, no mesh). May be gzip-compressed like model GLBs."},"preview_url":{"type":"string","description":"Uploaded mp4 preview of the clip playing on the model."},"accepted":{"type":"boolean","description":"Whether this clip is baked into the model's downloadable full GLB. Every generated clip is persisted (so the frontend never loses them); `accepted` is the separate opt-in that controls download inclusion."},"motion":{"type":"number","format":"float"},"fit_rmse":{"type":"number","format":"float"},"date":{"type":"integer","format":"int64"},"deleted":{"type":"boolean"}}},"AnimationClip":{"type":"object","properties":{"clip_name":{"type":"string"},"prompt":{"type":"string"},"preset_id":{"type":"string","description":"Set when the clip came from the preset library (retargeting) rather than a text prompt."},"mode":{"type":"string","enum":["rot_trans","rot_only"]},"seed":{"type":"integer"},"glb_url":{"type":"string"},"preview_url":{"type":"string"},"motion":{"type":"number","format":"float"},"fit_rmse":{"type":"number","format":"float"}}},"AnimationCandidates":{"type":"object","properties":{"animations":{"type":"array","items":{"$ref":"#/definitions/AnimationClip"}}}},"RigModelPayload":{"type":"object","properties":{"request_id":{"type":"string"},"model_asset_id":{"type":"string","description":"Parent 3D asset id whose model this rig replaces in place (image.id for generated, synthetic id for uploads). Ignored when as_new_asset is true."},"generated_model":{"$ref":"#/definitions/ThreeDModelInput"},"user_model":{"type":"string","description":"URL of a user-uploaded GLB to rig."},"user_model_file_type":{"type":"string"},"rig_type":{"type":"string","enum":["general","humanoid","game","humanoid_template","humanoid_template_hands"],"default":"general","description":"Which skeleton to build. general (default) lets the model choose; humanoid/game bias toward anime-style and classic game-character rigs. The humanoid_template options pin a standard humanoid skeleton with named joints: humanoid_template (22 joints) or humanoid_template_hands (52, five fingers per hand). Only for two-armed, two-legged characters."},"joint_naming":{"type":"string","enum":["smpl","mixamo","humanik","unreal","godot","rigify","vroid"],"default":"smpl","description":"Bone naming convention for the joints we can identify. Purely a relabel - the skeleton is identical. Joints outside the humanoid template keep their generic names."},"render_video":{"type":"boolean","default":true},"as_new_asset":{"type":"boolean","default":false,"description":"Re-rig - save the rigged result as a new 3D asset entry instead of overwriting the source, preserving the original model and its saved animations."}}},"Animate3DPayload":{"type":"object","required":["prompt"],"properties":{"request_id":{"type":"string"},"model_asset_id":{"type":"string","description":"Parent 3D asset id the resulting candidate clips belong to."},"prompt":{"type":"string"},"augment_prompt":{"type":"boolean","default":true},"generated_model":{"$ref":"#/definitions/ThreeDModelInput"},"user_model":{"type":"string","description":"URL of an already-rigged GLB to animate."},"user_model_file_type":{"type":"string"},"mode":{"type":"string","enum":["rot_trans","rot_only"],"default":"rot_trans"},"num_variants":{"type":"integer","default":4,"minimum":1,"maximum":8},"loop":{"type":"boolean","default":true,"description":"Return to the initial pose: each clip plays forward then mirrors back to the rest pose for a seamless loop. Best for one-way motions (crouch, punch, wave); reads oddly for cyclic gaits like walking."}}},"Animate3DPresetPayload":{"type":"object","required":["preset_id"],"properties":{"request_id":{"type":"string"},"model_asset_id":{"type":"string","description":"Parent 3D asset id the resulting clip belongs to."},"preset_id":{"type":"string","description":"id of an animation preset (see /utils/animation-presets)."},"generated_model":{"$ref":"#/definitions/ThreeDModelInput"},"user_model":{"type":"string","description":"URL of an already-rigged GLB to retarget onto."},"user_model_file_type":{"type":"string"},"crop_loop":{"type":"boolean","description":"Trim the animation to the span that loops seamlessly. Omit to follow the preset's own loop flag (cyclic motions loop, one-shots keep their full arc). Ignored when no clean loop exists in the motion."},"in_place":{"type":"boolean","description":"Remove the animation's net travel so the character moves on the spot and the engine drives locomotion. Omit to follow crop_loop."}}},"AcceptAnimationPayload":{"type":"object","required":["id","accepted"],"properties":{"id":{"type":"string","description":"The Animation3DClip id to toggle."},"accepted":{"type":"boolean","description":"Whether this clip is included when downloading the full GLB."}}},"Rig3DModelPayload":{"type":"object","description":"Payload for rigging a 3D model with a skeleton","required":["model"],"properties":{"model":{"type":"string","description":"The 3D asset to rig, as a URL or base64-encoded GLB file (not an AI model name). Up to 30MB; a larger file is rejected with HTTP 400."},"rig_type":{"type":"string","enum":["general","humanoid","game","humanoid_template","humanoid_template_hands"],"default":"general","description":"Which skeleton to build: general (default, any asset), humanoid (anime-style characters), game (classic game-character rigs), or the pinned humanoid templates with named joints - humanoid_template (22 joints) / humanoid_template_hands (52, five fingers per hand). Templates only suit two-armed, two-legged characters. Animation presets (animate3DModelPreset) retarget onto any rig type, though not onto every model."},"joint_naming":{"type":"string","enum":["smpl","mixamo","humanik","unreal","godot","rigify","vroid"],"default":"smpl","description":"Bone naming convention for the identified joints: smpl (default), mixamo (Unity's humanoid auto-mapper), humanik (same names unprefixed - Maya/MotionBuilder/FBX), unreal (UE mannequin), godot (SkeletonProfileHumanoid), rigify (Blender) or vroid (VRM). Purely a relabel - the skeleton is identical."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"Rig3DModelResult":{"type":"object","properties":{"model_url":{"type":"string","description":"URL to the rigged GLB (skeleton + skin weights baked in)."},"rigged":{"type":"boolean"}}},"Animate3DModelPayload":{"type":"object","description":"Payload for animating a rigged 3D model from a text prompt","required":["model","prompt"],"properties":{"model":{"type":"string","description":"The already-rigged 3D asset to animate, as a URL or base64-encoded GLB file (not an AI model name). Rig it first with rigModel if it has no skeleton. Up to 30MB; a larger file is rejected with HTTP 400."},"prompt":{"type":"string","description":"Desired motion, e.g. \"walking\" or \"swinging its axe\"."},"mode":{"type":"string","enum":["rot_trans","rot_only"],"default":"rot_trans","description":"Animation representation: rot_trans (per-bone rotation+translation, most faithful) or rot_only (rotation + root translation only, for retargeting)."},"num_variants":{"type":"integer","default":4,"minimum":1,"maximum":8,"description":"Number of candidate animations to generate; each returned as a standalone animation-only GLB with an mp4 preview."},"loop":{"type":"boolean","default":true,"description":"Return to the initial pose: each clip plays forward then mirrors back to the rest pose for a seamless loop. Best for one-way motions (crouch, punch, wave); reads oddly for cyclic gaits like walking."},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites the prompt into a detailed motion caption with an LLM. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"Animate3DPresetModelPayload":{"type":"object","description":"Payload for animating a rigged 3D model with a preset animation","required":["model","preset_id"],"properties":{"model":{"type":"string","description":"The already-rigged 3D asset, as a URL or base64-encoded GLB file (not an AI model name). Rig it first, with rigModel or with create3DModel's rig (any rig type). Up to 30MB; a larger file is rejected with HTTP 400."},"preset_id":{"type":"string","description":"id of an animation preset from listAnimationPresets. Any preset id from that list works."},"crop_loop":{"type":"boolean","description":"Trim the animation to the span that loops seamlessly. Omit to follow the preset's own loop flag. Ignored when no clean loop exists."},"in_place":{"type":"boolean","description":"Remove the animation's net travel so the character moves on the spot (engine-driven locomotion). Omit to keep the preset's own travel."},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}},"Refined3DPayload":{"type":"object","properties":{"art_style":{"type":"string"},"generated_model":{"$ref":"#/definitions/ThreeDModelInput"},"mesh_strength":{"type":"number","format":"float"},"texture_strength":{"type":"number","format":"float"},"n":{"type":"integer"},"prompt":{"type":"string"},"request_id":{"type":"string"},"user_model":{"type":"string"},"user_model_file_type":{"type":"string"},"generation_parameters":{"$ref":"#/definitions/ThreeDGenerationParameters"}}},"Download3DPayload":{"type":"object","properties":{"fill_holes":{"type":"boolean"},"simplify":{"type":"number","format":"float"},"format":{"type":"string"},"request_id":{"type":"string"},"asset_generation":{"$ref":"#/definitions/AssetGeneration"}}},"SuggestPayload":{"type":"object","properties":{"id":{"type":"string"},"image_type":{"type":"string"},"n":{"type":"integer"},"hints":{"type":"array","items":{"type":"string"}},"selected_element":{"type":"string"}}},"ShowcaseGallery":{"type":"object","properties":{"assets":{"type":"array","items":{"$ref":"#/definitions/ThreeDAsset"}},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"ideas":{"type":"array","items":{"$ref":"#/definitions/GeneratedGame"}},"videos":{"type":"array","items":{"$ref":"#/definitions/GeneratedVideo"}},"spritesheets":{"type":"array","items":{"$ref":"#/definitions/Spritesheet"}},"concepts":{"type":"array","items":{"$ref":"#/definitions/Project"}},"playables":{"type":"array","items":{"$ref":"#/definitions/SharedPlayable"}}}},"PlayableRequest":{"type":"object","required":["action","prompt"],"properties":{"action":{"type":"string","enum":["GENERATE_INITIAL","ITERATE"]},"engine":{"type":"string","enum":["phaser","threejs","pixijs"]},"prompt":{"type":"string"},"images":{"type":"array","items":{"$ref":"#/definitions/GameImage"}},"disable_assets":{"type":"boolean"},"perspective":{"type":"string"},"art_style":{"type":"string"}}},"PlayableChatMessage":{"type":"object","properties":{"sender":{"type":"string"},"text":{"type":"string"},"diff":{"type":"string"},"images":{"type":"array","items":{"type":"string"}},"history_index":{"type":"integer","format":"int64"},"followups":{"type":"array","items":{"type":"string"}}}},"Playable":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"engine":{"type":"string","enum":["phaser","threejs","pixijs"]},"title":{"type":"string"},"summary":{"type":"string"},"original_prompt":{"type":"string"},"how_to_play":{"type":"string"},"code":{"type":"string"},"compiled_code":{"type":"string"},"chat_history":{"type":"array","items":{"$ref":"#/definitions/PlayableChatMessage"}},"last_modified":{"type":"integer","format":"int64"},"code_history":{"type":"string"},"assets":{"type":"array","items":{"$ref":"#/definitions/PlayableAsset"}},"image":{"$ref":"#/definitions/GameImage"},"art_style":{"type":"string"},"perspective":{"type":"string"}}},"PlayableAsset":{"type":"object","properties":{"id":{"type":"string"},"image":{"$ref":"#/definitions/GameImage"},"three_d_asset":{"$ref":"#/definitions/ThreeDAsset"},"description":{"type":"string"},"alias":{"type":"string"}}},"SharedPlayable":{"type":"object","properties":{"_id":{"type":"string"},"user_id":{"type":"string"},"engine":{"type":"string","enum":["phaser","threejs","pixijs"]},"title":{"type":"string"},"summary":{"type":"string"},"how_to_play":{"type":"string"},"code":{"type":"string"},"compiled_code":{"type":"string"},"date":{"type":"integer","format":"int64"},"remixes":{"type":"integer","format":"int64"},"views":{"type":"integer","format":"int64"},"allow_public":{"type":"boolean"},"assets":{"type":"array","items":{"$ref":"#/definitions/PlayableAsset"}},"template":{"type":"boolean"},"image":{"$ref":"#/definitions/GameImage"}}},"GDDMedia":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["image","three_d_asset","video","spritesheet","game_id","audio"]},"uploaded":{"type":"boolean"},"image":{"$ref":"#/definitions/GameImage"},"three_d_asset":{"$ref":"#/definitions/ThreeDAsset"},"video":{"$ref":"#/definitions/GeneratedVideo"},"spritesheet":{"$ref":"#/definitions/Spritesheet"},"audio":{"$ref":"#/definitions/GeneratedAudio"},"placeholder":{"$ref":"#/definitions/GDDPlaceholder"},"game_id":{"type":"string"},"loading":{"type":"boolean"}}},"GDDPlaceholder":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string"}}},"GDDElement":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string"},"title":{"type":"string"},"sub_title":{"type":"string"},"text":{"type":"string"},"mod_date":{"type":"integer","format":"int64"},"media":{"type":"array","items":{"$ref":"#/definitions/GDDMedia"}},"loading":{"type":"integer","format":"int64"},"pending":{"type":"boolean"},"collapsed":{"type":"boolean"},"country":{"type":"string"}}},"GDD3":{"type":"object","properties":{"project_id":{"type":"string"},"title":{"type":"string"},"elements":{"type":"array","items":{"$ref":"#/definitions/GDDElement"}},"icon":{"$ref":"#/definitions/GDDMedia"},"header":{"$ref":"#/definitions/GDDMedia"},"genres":{"type":"array","items":{"type":"string"}},"platform":{"type":"string"},"country":{"type":"string"},"art_style":{"type":"string"},"perspective":{"type":"string"},"aspect_ratio":{"type":"string"},"step_choices":{"type":"array","items":{"$ref":"#/definitions/StepValue"}},"score":{"$ref":"#/definitions/LudoScore"}}},"GDD3Version":{"type":"object","required":["project_id","version","timestamp","user_id","command"],"properties":{"_id":{"type":"string"},"project_id":{"type":"string"},"version":{"type":"integer","format":"int64"},"timestamp":{"type":"integer","format":"int64"},"user_id":{"type":"string"},"command":{"type":"string","enum":["element:create","element:update","element:delete","element:reorder","title:update","summary:update","field:update"]},"element_id":{"type":"string"},"old_value":{"type":"string"},"new_value":{"type":"string"},"after_id":{"type":"string"},"undone":{"type":"boolean"},"description":{"type":"string"}}},"SpritePose":{"type":"object","properties":{"animation_prompt":{"type":"string","description":"Animation description for the sprite pose"},"margin_ratio":{"type":"number","format":"float","description":"Margin ratio for the sprite animation"},"margin_ratio_mode":{"type":"string"},"name":{"type":"string","description":"Name of the sprite pose"},"prompt":{"type":"string","description":"Prompt description for the sprite pose"}}},"AnimationPreset":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the animation preset"},"name":{"type":"string","description":"Name of the animation preset"},"category":{"type":"string","description":"Category of the animation preset"},"description":{"type":"string","description":"Description of the animation preset"},"duration":{"type":"number","format":"float","description":"Duration of the animation in seconds"},"preview_url":{"type":"string","description":"URL to preview the animation"},"perspectives":{"type":"array","items":{"$ref":"#/definitions/AnimationPresetPerspective"}}}},"AnimationPresetPerspective":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the perspective"},"name":{"type":"string","description":"Name of the perspective"},"description":{"type":"string","description":"Description of the perspective"},"directions":{"description":"Direction mappings with video URLs","$ref":"#/definitions/AnimationPresetPerspectiveDirections"}}},"AnimationPresetPerspectiveDirections":{"type":"object","properties":{"E":{"type":"string"},"N":{"type":"string"},"NE":{"type":"string"},"NW":{"type":"string"},"S":{"type":"string"},"SE":{"type":"string"},"SW":{"type":"string"},"W":{"type":"string"}}},"PlanInfo":{"type":"object","properties":{"indie":{"type":"string","description":"How credits work for Indie plan"},"pro_studio":{"type":"string","description":"How credits work for Pro/Studio plans"}}},"FeatureCategory":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}}},"CreditFeature":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"category":{"$ref":"#/definitions/FeatureCategory"},"indie_cost":{"type":"string","description":"Cost for Indie plan (number or \"Unlimited\")"},"pro_studio_cost":{"type":"string","description":"Cost for Pro/Studio plans (number or \"Unlimited\")"},"variable":{"type":"boolean"},"tooltip":{"type":"string","description":"Optional tooltip explaining cost calculation"}}},"ModelDurationCost":{"type":"object","description":"A valid duration for a model with its pre-computed credit cost.","properties":{"duration":{"type":"number","format":"float"},"cost":{"type":"number","format":"float"}}},"PricingModel":{"type":"object","description":"Per-model pricing configuration with durations and pre-computed costs.","properties":{"codename":{"type":"string","description":"Canonical model identifier used by the API (e.g. \"blitz\")."},"name":{"type":"string","description":"User-facing display name (e.g. \"Blitz\")."},"description":{"type":"string","description":"Short free-form description. Optional; may be empty."},"aliases":{"type":"array","description":"Legacy identifiers that resolve to this codename (e.g. \"standard\" → \"blitz\").","items":{"type":"string"}},"legacy":{"type":"boolean","description":"True if this model is superseded and should be shown in a \"legacy models\" group in UIs."},"features":{"type":"array","description":"Credit actions this model is available for (e.g. [\"GENERATE_VIDEO\", \"ANIMATE_SPRITE\"]).","items":{"type":"string"}},"credits_per_second":{"type":"number","format":"float","description":"Per-second rate. Null for a model priced per generation (see flat_charge)."},"flat_charge":{"type":"number","format":"float","description":"Fixed credits per generation, whatever the duration, for a model priced per generation rather than per second. Null otherwise."},"min_charge":{"type":"number","format":"float","description":"Minimum credit charge regardless of duration. Null when not enforced."},"durations":{"type":"array","items":{"$ref":"#/definitions/ModelDurationCost"}}}},"ChatCostExample":{"type":"object","properties":{"description":{"type":"string"},"assets":{"type":"number","format":"float"},"images":{"type":"number","format":"float"},"ideas":{"type":"number","format":"float"},"indie_cost":{"type":"number","format":"float"},"pro_studio_cost":{"type":"string"}}},"VideoCost":{"type":"object","properties":{"feature_id":{"type":"string"},"description":{"type":"string"},"models":{"type":"array","items":{"$ref":"#/definitions/PricingModel"}}}},"SpriteCost":{"type":"object","properties":{"feature_id":{"type":"string"},"description":{"type":"string"},"models":{"type":"array","items":{"$ref":"#/definitions/PricingModel"}}}},"FlatRateCost":{"type":"object","description":"Variable cost billed at a fixed per-second rate, independent of model.","properties":{"feature_id":{"type":"string"},"description":{"type":"string"},"credits_per_second":{"type":"number","format":"float"}}},"ImageTo3DCost":{"type":"object","description":"Image-to-3D models offered in this environment. Each is a flat price per generation, billed as its own credit action (feature_id): its cost is that entry of features.","properties":{"feature_id":{"type":"string"},"default_model":{"type":"string"},"models":{"type":"array","items":{"$ref":"#/definitions/ImageTo3DCostModel"}}}},"ImageTo3DCostModel":{"type":"object","properties":{"codename":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"feature_id":{"type":"string"},"back_view":{"type":"boolean","description":"Whether the model takes the optional back view."},"retexture":{"type":"boolean","description":"Whether Texture Generation runs on this model."}}},"ChatCost":{"type":"object","properties":{"feature_id":{"type":"string"},"description":{"type":"string"},"indie_base_cost":{"type":"number","format":"float"},"pro_studio_base_cost":{"type":"string"},"formula":{"type":"string"},"examples":{"type":"array","items":{"$ref":"#/definitions/ChatCostExample"}}}},"VariableCosts":{"type":"object","properties":{"video":{"$ref":"#/definitions/VideoCost"},"sprite":{"$ref":"#/definitions/SpriteCost"},"edit_spritesheet":{"$ref":"#/definitions/SpriteCost","description":"Per-action pricing for spritesheet editing. models is limited to the models that support the action; credits_per_second / durations reflect the action-specific rate."},"references_to_video":{"$ref":"#/definitions/VideoCost","description":"Per-action pricing for video-from-references."},"video_edit":{"$ref":"#/definitions/VideoCost","description":"Per-action pricing for video editing."},"upscale_video":{"$ref":"#/definitions/FlatRateCost","description":"Flat per-second pricing for 2x video upscaling (model-independent)."},"image_to_3d":{"$ref":"#/definitions/ImageTo3DCost"},"chat":{"$ref":"#/definitions/ChatCost"}}},"CreditCosts":{"type":"object","description":"All credit costs for platform features","properties":{"plans":{"$ref":"#/definitions/PlanInfo"},"features":{"type":"array","items":{"$ref":"#/definitions/CreditFeature"}},"variable_costs":{"$ref":"#/definitions/VariableCosts"}}},"GenerateVideoPayloadPublic":{"type":"object","description":"Payload for generating a video from a source image and motion prompt","required":["image","prompt"],"properties":{"image":{"type":"string","description":"URL or base64-encoded source image (the starting frame for the video)","example":"<url> OR data:image/png;base64,..."},"prompt":{"type":"string","description":"Text description of the motion or action for the video (e.g., \"walking forward\", \"waving hand\", \"camera zoom in\")"},"augment_prompt":{"type":"boolean","default":true,"description":"Rewrites your prompt behind the scenes into the form the model works best with. Leave it on (the default). Turning it off does not give you more control - it usually gives worse results. Do not disable it unless you really know what you are doing and have tested your prompts extensively."},"final_image":{"type":"string","description":"URL or base64-encoded end frame image. When provided, the video will interpolate between the initial and final frames.","example":"<url> OR data:image/png;base64,..."},"duration":{"type":"number","format":"float","default":5,"description":"Video duration in seconds. Available values depend on the model -\nthe source of truth is MODEL_PRICING (credits.js), surfaced via\n/credits/costs; the public API docs list them per model\n(auto-generated).\n","example":5},"model":{"type":"string","description":"Video generation model to use. Griffin (768p) is the current model and generates audio; Blitz and Eagle are legacy.","enum":["griffin-hd","griffin","blitz","eagle","eagle-audio","standard"],"default":"griffin-hd"},"request_id":{"type":"string","description":"Optional client-provided identifier, unique per request. Re-sending the same request_id returns the existing job instead of generating again. Also usable as the request_id filter of listGenerations."},"async":{"type":"boolean","default":true,"description":"Defaults to true: the call returns 202 immediately with a job id to poll with getApiJob. Set false for a synchronous response (deprecated but supported): the call then blocks until the result is ready."}}}}}