{"openapi":"3.1.0","info":{"title":"白噪音 API","version":"1.0.0","description":"接口按用途分为 /adminapi/v1 和 /api/v1。所有管理接口都需要 X-Internal-Token；App 公开读取与登录注册接口无需令牌，用户接口使用 Bearer access token。"},"servers":[{"url":"https://whitenoise.xf69.com","description":"当前 API"}],"tags":[{"name":"System","description":"服务状态与文档"},{"name":"Soundscapes","description":"声音方案及轨道播放配置"},{"name":"Sound assets","description":"MP3 素材上传、管理与播放"},{"name":"Covers","description":"封面图片上传、管理与读取"},{"name":"Localization","description":"名称翻译"},{"name":"Authentication","description":"客户端注册、登录及会话"}],"paths":{"/health":{"get":{"tags":["System"],"summary":"健康检查","operationId":"getHealth","responses":{"200":{"description":"服务正常","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/openapi.json":{"get":{"tags":["System"],"summary":"下载 OpenAPI 规范","operationId":"getOpenApi","responses":{"200":{"description":"OpenAPI 3.1 JSON","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/adminapi/v1/soundscapes":{"get":{"tags":["Soundscapes"],"summary":"管理端列出全部声音方案","operationId":"adminListSoundscapes","security":[{"InternalToken":[]}],"parameters":[{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"最多返回最近更新的 100 个方案","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Soundscape"}}}}}}}}},"post":{"tags":["Soundscapes"],"summary":"创建声音方案","operationId":"createSoundscape","security":[{"InternalToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoundscapeInput"},"example":{"name":"雨夜书房","defaultLocale":"zh-CN","isPublic":true,"translations":{"en":"Rainy Night Study","ja":"雨の夜の書斎"},"schemaVersion":1,"config":{"coverKey":"covers/550e8400-e29b-41d4-a716-446655440000-rainy-room.png","tracks":[{"assetKey":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3","playbackMode":"loop","pauseSeconds":10,"volume":70}]}}}}},"responses":{"201":{"description":"已创建","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Soundscape"}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/soundscapes/{uuid}":{"parameters":[{"$ref":"#/components/parameters/SoundscapeUuid"}],"get":{"tags":["Soundscapes"],"summary":"管理端读取声音方案","operationId":"adminGetSoundscape","security":[{"InternalToken":[]}],"parameters":[{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"声音方案","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Soundscape"}}}}}},"404":{"description":"声音方案不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Soundscapes"],"summary":"更新声音方案","operationId":"updateSoundscape","security":[{"InternalToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoundscapeInput"}}}},"responses":{"200":{"description":"已更新","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Soundscape"}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"声音方案不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Soundscapes"],"summary":"删除声音方案","operationId":"deleteSoundscape","security":[{"InternalToken":[]}],"responses":{"204":{"description":"已删除"},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"声音方案不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/assets":{"get":{"tags":["Sound assets"],"summary":"管理端列出声音素材","operationId":"adminListSoundAssets","security":[{"InternalToken":[]}],"parameters":[{"name":"cursor","in":"query","schema":{"type":"string"},"description":"上一页返回的 R2 cursor"},{"name":"prefix","in":"query","schema":{"type":"string","maxLength":256},"example":"sounds/"},{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"声音素材和下一页 cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoundAssetList"}}}}}},"post":{"tags":["Sound assets"],"summary":"上传 MP3 素材","description":"请求体直接发送 MP3 二进制，最大 50 MiB。对象键固定生成为 sounds/{uuid}.mp3，不保留原文件名。","operationId":"uploadSoundAsset","security":[{"InternalToken":[]}],"parameters":[{"$ref":"#/components/parameters/FileName"},{"$ref":"#/components/parameters/DisplayName"},{"$ref":"#/components/parameters/DefaultLocaleHeader"}],"requestBody":{"required":true,"content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"上传成功","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/SoundAssetUpload"}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"411":{"description":"缺少 Content-Length","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"超过 50 MiB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"不是支持的 MP3 Content-Type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/assets/{key}":{"parameters":[{"$ref":"#/components/parameters/AssetKey"}],"get":{"tags":["Sound assets"],"summary":"管理端读取任意 R2 对象","operationId":"adminGetAssetObject","security":[{"InternalToken":[]}],"responses":{"200":{"description":"对象二进制内容"},"404":{"description":"对象不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"head":{"tags":["Sound assets"],"summary":"管理端读取对象元数据","operationId":"adminHeadAssetObject","security":[{"InternalToken":[]}],"responses":{"200":{"description":"对象响应头"},"404":{"description":"对象不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Sound assets"],"summary":"上传非托管对象","description":"通用对象上传。sounds/ 下的 MP3 必须改用 POST /adminapi/v1/assets，以确保使用 UUID 文件名。","operationId":"putAssetObject","security":[{"InternalToken":[]}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"已保存","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"description":"无效键或托管 MP3 路径","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Sound assets"],"summary":"删除对象","operationId":"deleteAssetObject","security":[{"InternalToken":[]}],"responses":{"204":{"description":"已删除"},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"对象不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"素材正被声音方案使用","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sounds/{key}":{"parameters":[{"$ref":"#/components/parameters/PublicSoundKey"}],"get":{"tags":["Sound assets"],"summary":"公开播放声音","operationId":"streamSound","responses":{"200":{"description":"MP3 音频","content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"素材不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"head":{"tags":["Sound assets"],"summary":"读取公开声音元数据","operationId":"headPublicSound","responses":{"200":{"description":"音频响应头"},"404":{"description":"素材不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/asset-names":{"put":{"tags":["Sound assets","Localization"],"summary":"更新声音素材多语种名称","operationId":"updateAssetName","security":[{"InternalToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetNameInput"},"example":{"assetKey":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3","defaultLocale":"zh-CN","defaultName":"夜雨","translations":{"en":"Night Rain","ja":"夜の雨"}}}}},"responses":{"200":{"description":"已更新","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AssetName"}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"素材不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/assets/import/pixabay":{"post":{"tags":["Sound assets"],"summary":"从 Pixabay 导入 MP3","operationId":"importPixabayAsset","security":[{"InternalToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PixabayImportInput"},"example":{"url":"https://pixabay.com/sound-effects/rain-12345/","defaultLocale":"zh-CN","defaultName":"绵绵细雨"}}}},"responses":{"201":{"description":"导入成功，保存为 UUID 文件名","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/PixabayImportResult"}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Pixabay 解析或下载失败","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/covers":{"get":{"tags":["Covers"],"summary":"管理端列出封面","operationId":"adminListCovers","security":[{"InternalToken":[]}],"parameters":[{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"封面和下一页 cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverList"}}}}}},"post":{"tags":["Covers"],"summary":"上传封面","description":"请求体直接发送 JPEG、PNG 或 WebP 二进制，最大 10 MiB。","operationId":"uploadCover","security":[{"InternalToken":[]}],"parameters":[{"$ref":"#/components/parameters/FileName"},{"$ref":"#/components/parameters/DisplayName"},{"$ref":"#/components/parameters/CoverDefaultLocaleHeader"}],"requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"上传成功","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Cover"}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"411":{"description":"缺少 Content-Length","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"超过 10 MiB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"图片内容不是 JPEG、PNG 或 WebP","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/covers/{key}":{"parameters":[{"$ref":"#/components/parameters/CoverKey"}],"delete":{"tags":["Covers"],"summary":"删除封面","operationId":"deleteCover","security":[{"InternalToken":[]}],"responses":{"204":{"description":"已删除"},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"封面不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"封面正被声音方案使用","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/covers/{key}":{"parameters":[{"$ref":"#/components/parameters/PublicCoverKey"}],"get":{"tags":["Covers"],"summary":"公开读取封面","operationId":"getPublicCover","responses":{"200":{"description":"图片二进制"},"404":{"description":"封面不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"head":{"tags":["Covers"],"summary":"读取公开封面元数据","operationId":"headPublicCover","responses":{"200":{"description":"图片响应头"},"404":{"description":"封面不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/cover-names":{"put":{"tags":["Covers","Localization"],"summary":"更新封面多语种名称","operationId":"updateCoverName","security":[{"InternalToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverNameInput"},"example":{"coverKey":"covers/550e8400-e29b-41d4-a716-446655440000-rainy-room.png","defaultLocale":"zh-CN","defaultName":"雨夜书房","translations":{"en":"Rainy Night Study"}}}}},"responses":{"200":{"description":"已更新","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CoverName"}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"封面不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/adminapi/v1/translations":{"post":{"tags":["Localization"],"summary":"自动翻译名称","operationId":"translateName","security":[{"InternalToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationInput"},"example":{"text":"雨夜书房","sourceLocale":"zh-CN","targetLocales":["en","ja","de"]}}}},"responses":{"200":{"description":"翻译结果","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["translations"],"properties":{"translations":{"$ref":"#/components/schemas/NameTranslations"}}}}}}}},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"内部令牌缺失或无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求内容未通过校验","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"自动翻译暂时不可用","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/soundscapes":{"get":{"tags":["Soundscapes"],"summary":"App 公开方案列表","description":"仅返回已设为公开的声音方案。","operationId":"listPublicSoundscapes","parameters":[{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"最多返回最近更新的 100 个公开方案","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Soundscape"}}}}}}}}}},"/api/v1/soundscapes/{uuid}":{"parameters":[{"$ref":"#/components/parameters/SoundscapeUuid"}],"get":{"tags":["Soundscapes"],"summary":"App 读取公开方案","description":"私有或不存在的方案均返回 404。","operationId":"getPublicSoundscape","parameters":[{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"公开声音方案","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Soundscape"}}}}}},"404":{"description":"公开声音方案不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/assets":{"get":{"tags":["Sound assets"],"summary":"App 公开声音素材列表","operationId":"listPublicSoundAssets","parameters":[{"name":"cursor","in":"query","schema":{"type":"string"},"description":"上一页返回的 R2 cursor"},{"name":"prefix","in":"query","schema":{"type":"string","maxLength":256},"example":"sounds/"},{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"声音素材和下一页 cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoundAssetList"}}}}}}},"/api/v1/assets/{key}":{"parameters":[{"$ref":"#/components/parameters/AssetKey"}],"get":{"tags":["Sound assets"],"summary":"App 读取声音素材","operationId":"getPublicAssetObject","responses":{"200":{"description":"对象二进制内容"},"404":{"description":"素材不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"head":{"tags":["Sound assets"],"summary":"App 读取声音素材元数据","operationId":"headPublicAssetObject","responses":{"200":{"description":"对象响应头"},"404":{"description":"素材不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sounds/{key}":{"parameters":[{"$ref":"#/components/parameters/PublicSoundKey"}],"get":{"tags":["Sound assets"],"summary":"App 播放声音","operationId":"streamAppSound","responses":{"200":{"description":"MP3 音频","content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"素材不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"head":{"tags":["Sound assets"],"summary":"App 读取声音元数据","operationId":"headAppSound","responses":{"200":{"description":"音频响应头"},"404":{"description":"素材不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/covers":{"get":{"tags":["Covers"],"summary":"App 公开封面列表","operationId":"listPublicCovers","parameters":[{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"locale","in":"query","description":"返回 localizedName 时使用的语种；不支持的值会被忽略。","schema":{"$ref":"#/components/schemas/Locale"}}],"responses":{"200":{"description":"封面和下一页 cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverList"}}}}}}},"/api/v1/covers/{key}":{"parameters":[{"$ref":"#/components/parameters/PublicCoverKey"}],"get":{"tags":["Covers"],"summary":"App 读取封面","operationId":"getAppCover","responses":{"200":{"description":"图片二进制"},"404":{"description":"封面不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"head":{"tags":["Covers"],"summary":"App 读取封面元数据","operationId":"headAppCover","responses":{"200":{"description":"图片响应头"},"404":{"description":"封面不存在","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/email/register":{"post":{"tags":["Authentication"],"summary":"注册邮箱账户","operationId":"registerEmail","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterInput"}}}},"responses":{"202":{"description":"验证码已发送或请求已接受","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/VerificationPending"}}}}}},"400":{"description":"请求体不是有效 JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"凭据或令牌无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求字段无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"请求过于频繁","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/email/resend":{"post":{"tags":["Authentication"],"summary":"重发邮箱验证码","operationId":"resendVerification","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInput"}}}},"responses":{"202":{"description":"验证码已发送或请求已接受","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/VerificationPending"}}}}}},"400":{"description":"请求体不是有效 JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"凭据或令牌无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求字段无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"请求过于频繁","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/email/verify":{"post":{"tags":["Authentication"],"summary":"验证邮箱并登录","operationId":"verifyEmail","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailInput"}}}},"responses":{"200":{"description":"认证成功","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AuthTokens"}}}}}},"400":{"description":"请求体不是有效 JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"凭据或令牌无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求字段无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"请求过于频繁","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/email/login":{"post":{"tags":["Authentication"],"summary":"邮箱密码登录","operationId":"loginEmail","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginInput"}}}},"responses":{"200":{"description":"认证成功","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AuthTokens"}}}}}},"400":{"description":"请求体不是有效 JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"凭据或令牌无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求字段无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"请求过于频繁","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/apple":{"post":{"tags":["Authentication"],"summary":"使用 Apple 登录","operationId":"loginApple","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppleLoginInput"}}}},"responses":{"200":{"description":"认证成功","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AuthTokens"}}}}}},"400":{"description":"请求体不是有效 JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"凭据或令牌无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求字段无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"请求过于频繁","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/google":{"post":{"tags":["Authentication"],"summary":"使用 Google 登录","operationId":"loginGoogle","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleLoginInput"}}}},"responses":{"200":{"description":"认证成功","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AuthTokens"}}}}}},"400":{"description":"请求体不是有效 JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"凭据或令牌无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求字段无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"请求过于频繁","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["Authentication"],"summary":"刷新并轮换令牌","operationId":"refreshSession","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshInput"}}}},"responses":{"200":{"description":"认证成功","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AuthTokens"}}}}}},"400":{"description":"请求体不是有效 JSON","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"凭据或令牌无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"请求字段无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"请求过于频繁","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/logout":{"post":{"tags":["Authentication"],"summary":"注销会话","operationId":"logout","security":[{"BearerAuth":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshInput"}}}},"responses":{"204":{"description":"会话已注销"},"400":{"description":"请求格式错误","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["Authentication"],"summary":"读取当前用户","operationId":"getCurrentUser","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"当前用户","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/User"}}}}}},"401":{"description":"未登录或 access token 已失效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"InternalToken":{"type":"apiKey","in":"header","name":"X-Internal-Token","description":"Console 所有管理接口使用的内部令牌"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"opaque","description":"邮箱验证或登录后签发的短期 accessToken"}},"parameters":{"SoundscapeUuid":{"name":"uuid","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"example":"550e8400-e29b-41d4-a716-446655440000"},"AssetKey":{"name":"key","in":"path","required":true,"description":"URL 编码后的完整 R2 key，可包含斜杠","schema":{"type":"string"},"example":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3"},"PublicSoundKey":{"name":"key","in":"path","required":true,"description":"sounds/ 后面的文件名","schema":{"type":"string"},"example":"550e8400-e29b-41d4-a716-446655440000.mp3"},"CoverKey":{"name":"key","in":"path","required":true,"description":"covers/ 后面的文件名，也可传完整 cover key","schema":{"type":"string"},"example":"550e8400-e29b-41d4-a716-446655440000-rainy-room.png"},"PublicCoverKey":{"name":"key","in":"path","required":true,"description":"covers/ 后面的文件名","schema":{"type":"string"},"example":"550e8400-e29b-41d4-a716-446655440000-rainy-room.png"},"FileName":{"name":"X-File-Name","in":"header","required":true,"description":"encodeURIComponent 编码的原始文件名，仅用于校验和生成默认显示名","schema":{"type":"string"},"example":"Rainy%20Room.mp3"},"DisplayName":{"name":"X-Display-Name","in":"header","required":false,"description":"encodeURIComponent 编码的默认显示名称，1-80 字符","schema":{"type":"string"},"example":"%E9%9B%A8%E5%A4%9C%E4%B9%A6%E6%88%BF"},"DefaultLocaleHeader":{"name":"X-Default-Locale","in":"header","required":false,"schema":{"$ref":"#/components/schemas/Locale"},"example":"zh-CN"},"CoverDefaultLocaleHeader":{"name":"X-Default-Locale","in":"header","required":false,"description":"封面默认名称的语种；未传时使用英文。","schema":{"allOf":[{"$ref":"#/components/schemas/Locale"}],"default":"en"},"example":"en"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"invalid_soundscape"},"message":{"type":"string","example":"Invalid soundscape payload"}}}}},"Health":{"type":"object","required":["status","service"],"properties":{"status":{"type":"string","const":"ok"},"service":{"type":"string","const":"whitenoise-api"}}},"Locale":{"type":"string","enum":["en","ar","bn","zh-HK","zh-CN","zh-TW","da","nl","fil","fi","fr","de","he","hi","id","it","ja","ko","ms","nb","pl","pt-BR","ru","es","sv","th","tr","uk","ur","vi"],"example":"zh-CN"},"NameTranslations":{"type":"object","description":"键为支持的 Locale；默认语种无需重复填写。","propertyNames":{"enum":["en","ar","bn","zh-HK","zh-CN","zh-TW","da","nl","fil","fi","fr","de","he","hi","id","it","ja","ko","ms","nb","pl","pt-BR","ru","es","sv","th","tr","uk","ur","vi"]},"additionalProperties":{"type":"string","minLength":1,"maxLength":80},"example":{"en":"Night Rain","ja":"夜の雨"}},"SoundscapeTrack":{"type":"object","required":["assetKey","playbackMode","pauseSeconds","volume"],"properties":{"assetKey":{"type":"string","pattern":"^sounds/.+\\.mp3$","example":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3"},"playbackMode":{"type":"string","enum":["once","loop"],"description":"once 播放一次；loop 播放完后按 pauseSeconds 间隔循环"},"pauseSeconds":{"type":"integer","minimum":0,"maximum":86400,"description":"单素材播完到下次播放的暂停秒数；once 模式必须为 0","example":10},"volume":{"type":"integer","minimum":0,"maximum":100,"default":100,"description":"单素材音量百分比；0 为静音，100 为满音量。旧方案未包含此字段时按 100 返回。","example":70}}},"SoundscapeConfig":{"type":"object","required":["tracks"],"properties":{"coverKey":{"type":["string","null"],"pattern":"^covers/.+\\.(jpg|jpeg|png|webp)$","example":"covers/550e8400-e29b-41d4-a716-446655440000-rainy-room.png"},"category":{"type":"string","example":"sleep"},"tracks":{"type":"array","maxItems":8,"uniqueItems":true,"items":{"$ref":"#/components/schemas/SoundscapeTrack"}}},"additionalProperties":true},"SoundscapeInput":{"type":"object","required":["name","schemaVersion","config"],"properties":{"name":{"type":"string","minLength":1,"maxLength":80,"example":"雨夜书房"},"schemaVersion":{"type":"integer","minimum":1,"example":1},"config":{"$ref":"#/components/schemas/SoundscapeConfig"},"defaultLocale":{"$ref":"#/components/schemas/Locale"},"isPublic":{"type":"boolean","default":false},"translations":{"$ref":"#/components/schemas/NameTranslations"}}},"Soundscape":{"allOf":[{"$ref":"#/components/schemas/SoundscapeInput"},{"type":"object","required":["id","uuid","localizedName","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"uuid":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"localizedName":{"type":"string","example":"Rainy Night Study"},"createdAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"}}}]},"SoundAsset":{"type":"object","required":["uuid","key","size","etag","uploadedAt","contentType","defaultLocale","name","translations","localizedName"],"properties":{"uuid":{"type":["string","null"],"format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"key":{"type":"string","example":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3"},"size":{"type":"integer","minimum":0,"example":3456789},"etag":{"type":"string"},"uploadedAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"},"contentType":{"type":"string","example":"audio/mpeg"},"defaultLocale":{"$ref":"#/components/schemas/Locale"},"name":{"type":"string","example":"夜雨"},"translations":{"$ref":"#/components/schemas/NameTranslations"},"localizedName":{"type":"string","example":"Night Rain"}}},"SoundAssetList":{"type":"object","required":["data","cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SoundAsset"}},"cursor":{"type":["string","null"]}}},"SoundAssetUpload":{"type":"object","required":["key","uuid","etag","size","url"],"properties":{"key":{"type":"string","example":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3"},"uuid":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"etag":{"type":"string"},"size":{"type":"integer"},"url":{"type":"string","format":"uri","example":"https://whitenoise-sounds.xf69.com/sounds/550e8400-e29b-41d4-a716-446655440000.mp3"}}},"AssetNameInput":{"type":"object","required":["assetKey","defaultLocale","defaultName","translations"],"properties":{"assetKey":{"type":"string","example":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3"},"defaultLocale":{"$ref":"#/components/schemas/Locale"},"defaultName":{"type":"string","minLength":1,"maxLength":80},"translations":{"$ref":"#/components/schemas/NameTranslations"}}},"AssetName":{"allOf":[{"$ref":"#/components/schemas/AssetNameInput"},{"type":"object","required":["uuid"],"properties":{"uuid":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"}}}]},"PixabayImportInput":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"defaultLocale":{"$ref":"#/components/schemas/Locale"},"defaultName":{"type":"string","minLength":1,"maxLength":80}}},"PixabayImportResult":{"type":"object","required":["key","uuid","etag","size","contentType","sourceUrl","downloadUrl"],"properties":{"key":{"type":"string","example":"sounds/550e8400-e29b-41d4-a716-446655440000.mp3"},"uuid":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"etag":{"type":"string"},"size":{"type":"integer"},"contentType":{"type":"string"},"sourceUrl":{"type":"string","format":"uri"},"downloadUrl":{"type":"string","format":"uri"}}},"Cover":{"type":"object","required":["uuid","key","name","defaultLocale","translations","localizedName","size","etag","contentType","url","uploadedAt"],"properties":{"uuid":{"type":["string","null"],"format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"key":{"type":"string","example":"covers/550e8400-e29b-41d4-a716-446655440000-rainy-room.png"},"name":{"type":"string","example":"雨夜书房"},"defaultLocale":{"$ref":"#/components/schemas/Locale"},"translations":{"$ref":"#/components/schemas/NameTranslations"},"localizedName":{"type":"string"},"size":{"type":"integer"},"etag":{"type":"string"},"contentType":{"type":"string","enum":["image/jpeg","image/png","image/webp","application/octet-stream"]},"url":{"type":"string","format":"uri"},"uploadedAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"}}},"CoverList":{"type":"object","required":["data","cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Cover"}},"cursor":{"type":["string","null"]}}},"CoverNameInput":{"type":"object","required":["coverKey","defaultLocale","defaultName","translations"],"properties":{"coverKey":{"type":"string","example":"covers/550e8400-e29b-41d4-a716-446655440000-rainy-room.png"},"defaultLocale":{"$ref":"#/components/schemas/Locale"},"defaultName":{"type":"string","minLength":1,"maxLength":80},"translations":{"$ref":"#/components/schemas/NameTranslations"}}},"CoverName":{"allOf":[{"$ref":"#/components/schemas/CoverNameInput"},{"type":"object","required":["uuid"],"properties":{"uuid":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"}}}]},"TranslationInput":{"type":"object","required":["text","sourceLocale","targetLocales"],"properties":{"text":{"type":"string","minLength":1,"maxLength":80},"sourceLocale":{"$ref":"#/components/schemas/Locale"},"targetLocales":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/Locale"}}}},"User":{"type":"object","required":["id","uuid","email","emailVerified","displayName","providers","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"uuid":{"type":"string","format":"uuid","example":"550e8400-e29b-41d4-a716-446655440000"},"email":{"type":"string","format":"email"},"emailVerified":{"type":"boolean"},"displayName":{"type":["string","null"],"maxLength":80},"providers":{"type":"array","items":{"type":"string","enum":["apple","google"]}},"createdAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"}}},"AuthTokens":{"type":"object","required":["user","accessToken","accessTokenExpiresAt","refreshToken","refreshTokenExpiresAt","tokenType"],"properties":{"user":{"$ref":"#/components/schemas/User"},"accessToken":{"type":"string","example":"example_access_token_not_a_real_credential"},"accessTokenExpiresAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"},"refreshToken":{"type":"string","example":"example_refresh_token_not_a_real_credential"},"refreshTokenExpiresAt":{"type":"string","format":"date-time","example":"2026-08-26T08:00:00.000Z"},"tokenType":{"type":"string","const":"Bearer"}}},"VerificationPending":{"type":"object","required":["verificationRequired","expiresIn"],"properties":{"verificationRequired":{"type":"boolean","const":true},"expiresIn":{"type":"integer","example":600}}},"EmailInput":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"}}},"RegisterInput":{"allOf":[{"$ref":"#/components/schemas/EmailInput"},{"type":"object","required":["password"],"properties":{"password":{"type":"string","format":"password","minLength":8,"maxLength":128,"example":"correct-horse-battery-staple"},"displayName":{"type":"string","minLength":1,"maxLength":80,"example":"Lin"}}}]},"VerifyEmailInput":{"allOf":[{"$ref":"#/components/schemas/EmailInput"},{"type":"object","required":["code"],"properties":{"code":{"type":"string","pattern":"^[0-9]{6}$","example":"123456"}}}]},"LoginInput":{"allOf":[{"$ref":"#/components/schemas/EmailInput"},{"type":"object","required":["password"],"properties":{"password":{"type":"string","format":"password","example":"correct-horse-battery-staple"}}}]},"AppleLoginInput":{"type":"object","required":["identityToken"],"properties":{"identityToken":{"type":"string","description":"Apple identity token"},"nonce":{"type":"string"}}},"GoogleLoginInput":{"type":"object","required":["idToken"],"properties":{"idToken":{"type":"string","description":"Google ID token"},"nonce":{"type":"string"}}},"RefreshInput":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string","example":"example_refresh_token_not_a_real_credential"}}}}}}