新网站 App 接口接入文档
# 新网站 App 接口接入文档
更新日期:2026-09-15
本文提供给新网站配套 App 的 iOS、Android 和联调开发人员。App 统一请求 App API 网关,不直接请求网站后端、生成微服务、数据库或对象存储。新站上线前,应由服务端提供实际的 `APP_API_BASE_URL`、`SITE_ID` 和 `APP_ID`。
## 1. 接入参数
| 名称 | 示例 | 说明 |
|------|------|------|
| `APP_API_BASE_URL` | `https://app-api.example.com` | App 唯一业务 API 域名,不要使用网页域名代替 |
| `SITE_ID` | UUID | 新网站的站点 ID,区分用户、套餐、余额、内容和创作记录 |
| `APP_ID` | `com.example.app` | App 标识,通常与 iOS Bundle ID 一致 |
| `APP_VERSION` | `1.0.0` | 当前客户端版本 |
| `LOCALE` | `zh-CN` | 当前语言地区 |
本文示例中的占位符不能原样用于生产环境。
## 2. 公共请求约定
### 2.1 公共 Header
| Header | 必填条件 | 格式与用途 |
|--------|----------|------------|
| `Content-Type` | JSON POST 必填 | `application/json` |
| `Accept` | 建议 | `application/json` |
| `X-Site-Id` | 所有站点业务接口必填 | 新网站分配的 `SITE_ID` |
| `Authorization` | 登录后的接口必填 | `Bearer <accessToken>` |
| `X-Locale` | 建议 | 如 `zh-CN`、`en-US` |
| `X-App-Version` | 建议 | 如 `1.0.0` |
| `X-Device-Context` | 分流和更新检查必填 | base64url 编码的设备上下文 JSON |
| `X-App-Id` | 多 App 登录码场景必填 | 目标 App 标识 |
| `X-IOS-Proof` | 后端开启严格校验时必填 | 可信验证服务签发的一次性请求证明 |
网关在缺少 `X-Site-Id` 时可能补默认站点,但 App 不应依赖此行为。新站 App 必须显式发送自己的 `SITE_ID`,防止数据落到其他站点。
### 2.2 统一响应
网站代理接口通常返回:
```json
{
"code": 0,
"info": "ok",
"data": {}
}
```
客户端必须同时判断 HTTP 状态码和 `code`。只有 HTTP 2xx 且 `code === 0` 才按业务成功处理;不要根据 `info` 文案编写固定分支。
网关自有接口可能直接返回业务对象,或者返回:
```json
{
"error": "invalid_device_context"
}
```
通用错误:
| HTTP | 含义 | App 处理建议 |
|------|------|--------------|
| `400` | 参数或设备上下文错误 | 检查请求,不自动重试 |
| `401` | 未登录、Token 过期或登录码失效 | 清理登录态或重新获取登录码 |
| `403` | 站点/App 不匹配或 App 证明失败 | 停止重试并上报配置问题 |
| `404` | 路径、资源或 App 发版记录不存在 | 检查环境与版本配置 |
| `405` | HTTP 方法错误 | 修正请求方法 |
| `429` | 请求过于频繁 | 按退避策略稍后重试 |
| `502` | 网关无法访问上游 | 提示暂时不可用,可有限重试 |
| `503` | 上游功能或依赖未就绪 | 提示暂时不可用,不视为业务成功 |
所有敏感接口响应均按不可缓存处理。不得把密码、访问令牌、登录码、IAP 凭据和 `X-IOS-Proof` 写入日志、埋点或崩溃报告。
## 3. 设备上下文
`X-Device-Context` 是下面 JSON 的 base64url 编码结果,不带 `=` padding 也应可被服务端解析:
```json
{
"v": 1,
"ts": 1789430400000,
"ap": {
"v": "1.0.0",
"b": "1",
"p": "ios",
"id": "com.example.app"
},
"dev": {
"em": 0,
"jb": 0,
"vpn": null
},
"adj": {
"net": "Organic",
"camp": "",
"grp": "",
"cre": ""
}
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `v` | integer | 是 | 上下文协议版本,当前为 `1` |
| `ts` | integer | 是 | 毫秒时间戳 |
| `ap.v` | string | 是 | App 语义版本号 |
| `ap.b` | string | 是 | 构建号 |
| `ap.p` | string | 是 | `ios` 或 `android` |
| `ap.id` | string | 是 | Bundle ID 或 Android 包名 |
| `dev.em` | `0\|1` | 是 | 是否模拟器 |
| `dev.jb` | `0\|1` | 是 | 是否越狱或 Root |
| `dev.vpn` | `0\|1\|null` | 否 | VPN 状态 |
| `adj.net` | string | 否 | Adjust network |
| `adj.camp` | string | 否 | Adjust campaign |
| `adj.grp` | string | 否 | Adjust ad group |
| `adj.cre` | string | 否 | Adjust creative |
## 4. 接口总览
| 方法 | App 请求路径 | 登录 | 用途 |
|------|--------------|------|------|
| `GET` | `/api/create-page/config` | 否 | 获取创作页模型和参数配置 |
| `GET` | `/api/home/tabs` | 否 | 获取首页 Tab |
| `GET` | `/api/home/{feed}` | 否 | 获取首页内容流 |
| `POST` | `/api/home/template/generate` | 是 | 使用首页模板和上传图片创建视频 |
| `GET` | `/api/app/update/check` | 否 | 检查 App 更新 |
| `POST` | `/api/user/register` | 否 | 邮箱注册 |
| `POST` | `/api/user/login` | 否 | 邮箱登录 |
| `POST` | `/api/user/login/app-code` | 否 | 使用网页生成的六位码登录 |
| `GET` | `/api/user/profile` | 是 | 获取用户资料 |
| `POST` | `/api/user/change-password` | 是 | 修改密码 |
| `POST` | `/api/user/delete-account` | 是 | 注销账号 |
| `GET` | `/api/payment/coins/package/list` | 否 | 金币套餐 |
| `GET` | `/api/payment/subscription/package/list` | 否 | 订阅套餐 |
| `GET` | `/api/payment/coins/balance` | 是 | 金币余额 |
| `POST` | `/api/payment/iap/verify` | 是 | 验证金币内购 |
| `POST` | `/api/payment/iap/verify-subscription` | 是 | 验证订阅内购 |
| `POST` | `/api/client/upload` | 否 | 上传生成输入文件 |
| `POST` | `/api/plugins/video/generate` | 是 | 提交图片或视频生成任务 |
| `GET` | `/api/plugins/video/query` | 是 | 查询生成任务 |
| `GET` | `/api/client/creations` | 是 | 获取当前用户近期创作记录 |
| `DELETE` | `/api/client/creations/{taskId}` | 是 | 删除一条已结束的创作记录 |
| `POST` | `/api/ads/facebook/events` | 是 | 上报服务端广告转化事件 |
`HEAD` 只用于基础连通性检查,部分接口由网关直接返回 200,不访问上游,不能用来判断业务接口是否健康。
## 5. 创作页与首页
### 5.1 GET /api/create-page/config
请求 Header:`X-Device-Context`。无 Query,无请求体。
成功响应主要结构:
```json
{
"defaults": {
"creationParams": {},
"defaultTab": "imageToVideo",
"defaultModelByTab": {}
},
"modes": {}
}
```
App 必须以接口返回的模型、参数、枚举和默认值渲染创作页,不要把模型清单写死在客户端。不同流量可能收到不同配置。
### 5.2 GET /api/home/tabs
无参数。响应:
```json
{
"tabs": [
{
"name": "Trending",
"endpoint": "/api/home/trending",
"description": "热门列表"
}
]
}
```
### 5.3 GET /api/home/{feed}
`feed` 可取 `trending`、`new`、`top_rated`、`most_favorited`、`short`。
| Query | 类型 | 必填 | 默认值 |
|-------|------|------|--------|
| `page` | integer | 否 | `1` |
| `perPage` | integer | 否 | `30` |
响应:
```json
{
"feed": "trending",
"page": 1,
"perPage": 14,
"total": 80,
"maxReached": true,
"data": [
{
"vid": "video-id",
"title": "Example",
"username": "Example",
"modelName": "Example",
"isPublic": true,
"isVip": false,
"price": 0,
"keywords": [],
"cover": "https://cdn.example.com/cover.jpg",
"local_path": "https://cdn.example.com/video.mp4",
"input_img": "",
"modelRating": 5,
"status": 2,
"duration": 5,
"config": {
"coin": 0,
"effectId": "",
"templateType": ""
}
}
]
}
```
### 5.4 POST /api/home/template/generate
用途:App 将 Home Feed 返回的模板 `item` 和用户上传后的图片地址提交给网关。网关根据 `item` 选择受支持模型、构造模型参数,再调用现有视频生成服务。
```http
POST <APP_API_BASE_URL>/api/home/template/generate
```
请求 Header:
| Header | 必填 | 格式或说明 |
|--------|------|------------|
| `Authorization` | 是 | `Bearer <accessToken>` |
| `X-Site-Id` | 是 | 当前网站的站点 ID |
| `Content-Type` | 是 | `application/json` |
| `Accept` | 否 | 建议 `application/json` |
| `X-Locale` | 否 | 例如 `zh-CN` |
| `X-App-Version` | 否 | 例如 `1.0.0` |
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `item` | object | 是 | `/api/home/{feed}` 返回的用户所选完整条目 |
| `item.vid` | string | 是 | 模板或业务视频 ID |
| `item.local_path` | string | 广告 Home 模板必填 | 模板参考视频地址 |
| `item.duration` | number | 否 | 广告模板时长;缺失、非数字或不大于 0 时使用 `8` 秒 |
| `item.config` | object | 普通模板需要 | 普通模板配置 |
| `item.config.templateType` | string | 普通模板需要 | `dance`、`vidu` 或 `spicy` |
| `item.config.effectId` | string | 普通模板必填 | 模板效果 ID |
| `imageUrl` | string | 是 | `/api/client/upload` 上传后得到的、生成服务可访问的图片地址 |
请求示例:
```json
{
"item": {
"vid": "video-id",
"local_path": "https://cdn.example.com/template.mp4",
"duration": 8,
"config": { "templateType": "vidu", "effectId": "effect-id" }
},
"imageUrl": "https://cdn.example.com/upload/person.jpg"
}
```
App 应直接传回 Home Feed 中用户点击的完整 `item`,不要只挑选部分展示字段重新组装。App 不传 `model`、`args`、`number` 或价格;即使在 `item` 中附带这些字段,网关也不会使用它们选择模型或计价。
模型选择规则:
| Home item 判定 | 上游模型 | 网关构造的主要参数 |
|----------------|----------|--------------------|
| `config.templateType=dance` | `aitubo_dance` | `sourceImage=imageUrl`、`danceId=config.effectId`、`faceEmotion=false`、`background=false` |
| `config.templateType=vidu` | `aitubo_template` | `effectId=config.effectId`、`images=[imageUrl]` |
| `config.templateType=spicy` | `sora_template` | `template_id=config.effectId`、`image=imageUrl`、`input_images=[imageUrl]` |
| 未提供 `config.templateType` | `mulerouter/w3.0-video` | `reference_images`、`reference_videos`、`template_video_id`、`duration`、`video_time` |
存在非空但不受支持的 `config.templateType` 时,网关拒绝请求,不会降级成广告模板。
完整 curl 示例:
```bash
curl -X POST \
'<APP_API_BASE_URL>/api/home/template/generate' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
-H 'X-Site-Id: <SITE_ID>' \
-H 'Content-Type: application/json' \
--data-raw '{
"item": {
"vid": "video-id",
"local_path": "https://cdn.example.com/template.mp4",
"duration": 8,
"config": {
"templateType": "vidu",
"effectId": "effect-id"
}
},
"imageUrl": "https://cdn.example.com/upload/person.jpg"
}'
```
普通 `vidu` 模板会被网关转换为:
```json
{
"model": "aitubo_template",
"video_id": "video-id",
"number": 1,
"args": {
"effectId": "effect-id",
"images": ["https://cdn.example.com/upload/person.jpg"]
}
}
```
没有 `config.templateType` 的广告 Home 模板会被转换为:
```json
{
"model": "mulerouter/w3.0-video",
"video_id": "",
"number": 1,
"args": {
"reference_images": ["https://cdn.example.com/upload/person.jpg"],
"reference_videos": ["https://cdn.example.com/template.mp4"],
"prompt": "Recreate Video 1 with the person from Image 1 as the primary subject.",
"template_video_id": "video-id",
"duration": 8,
"video_time": 8,
"preprocess": true,
"skip_grok_prompt_optimize": true
}
}
```
成功响应与 `/api/plugins/video/generate` 相同,网关不修改上游响应:
```json
{
"code": 200,
"data": {
"task_id": "task-id",
"model": "aitubo_template"
}
}
```
App 必须保存 `data.task_id` 和 `data.model`,然后调用:
```http
GET /api/plugins/video/query?task_id=<TASK_ID>&model=<MODEL>
```
参数校验错误使用 HTTP 400:
| `error` | 常见原因 |
|---------|----------|
| `invalid_request` | JSON 无效,缺少 `item`、`imageUrl`、`item.vid`、普通模板 `effectId` 或广告模板 `local_path` |
| `unsupported_template_type` | `config.templateType` 不是 `dance`、`vidu`、`spicy` |
错误示例:
```json
{
"error": "invalid_request",
"message": "item.vid 不能为空"
}
```
App 完整调用流程:
1. 调用 `/api/home/{feed}` 获取 Home 模板。
2. 用户选择图片后调用 `/api/client/upload`。
3. 取得上传响应中的图片地址,并确认生成服务可以访问。
4. 将用户选择的完整 `item` 和图片地址传给 `/api/home/template/generate`。
5. 保存成功响应中的 `task_id` 和 `model`。
6. 调用 `/api/plugins/video/query` 轮询;`status=1` 继续等待,`status=2` 展示结果,`status=3` 停止并展示失败信息。
## 6. App 更新
### GET /api/app/update/check
请求必须带完整 `X-Device-Context.ap`。无 Query,无请求体。
有更新:
```json
{
"updateAvailable": true,
"forceUpdate": false,
"release": {
"version": "1.0.1",
"buildNumber": 2,
"forceUpdate": false,
"storeUrl": "https://apps.apple.com/app/id0000000000",
"downloadUrl": null,
"updateUrl": "https://apps.apple.com/app/id0000000000",
"updateKind": "store",
"releaseNotes": "Bug fixes"
}
}
```
无更新时 `release` 为 `null`。HTTP 404 且 `error=release_not_found` 表示服务端尚未为该平台和 Bundle ID 配置发版记录,不表示已经是最新版。
## 7. 用户与登录
### 7.1 POST /api/user/register
```json
{
"email": "user@example.com",
"password": "12345678",
"nickname": "user"
}
```
| 字段 | 类型 | 必填 | 约束 |
|------|------|------|------|
| `email` | string | 是 | 合法邮箱 |
| `password` | string | 是 | 至少 6 个字符 |
| `nickname` | string | 否 | 用户昵称 |
成功 `data`:`accessToken`、`siteId`、`ver`。
### 7.2 POST /api/user/login
```json
{
"email": "user@example.com",
"password": "12345678"
}
```
成功 `data`:
```json
{
"accessToken": "<token>",
"ver": "1.0.0"
}
```
### 7.3 POST /api/user/login/app-code
网页端先为已登录用户生成六位码,App 再调用本接口。登录码必须用字符串保存,以保留前导零。
```json
{
"loginCode": "003821"
}
```
| 字段 | 类型 | 必填 | 约束 |
|------|------|------|------|
| `loginCode` | string | 是 | 恰好六位 ASCII 数字,不能含空格 |
默认单 App 模式可省略 `X-App-Id`;多 App 模式必须带 `X-App-Id: <APP_ID>`。后端启用严格模式时还必须带与本次请求完全匹配的 `X-IOS-Proof`。
成功响应:
```json
{
"code": 0,
"info": "ok",
"data": {
"accessToken": "<new-token>",
"ver": "",
"iosLoginReward": {
"granted": true,
"amount": 500,
"balance": 500
}
}
}
```
登录码有效期默认 5 分钟且只能成功兑换一次。网络失败后不要无限重试;如果服务端已提交事务,原码可能已经失效,应让用户重新获取。
### 7.4 GET /api/user/profile
请求 Header:`Authorization`、`X-Site-Id`。无 Query 和请求体。
`data` 字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 用户 ID |
| `email` | string | 邮箱 |
| `nickname` | string | 昵称 |
| `avatarUrl` | string | 头像 URL |
| `premiumType` | integer | `0` 免费,`1` Premium |
| `onetimeSub` | integer | `1` 表示一次性订阅 |
| `premiumExpiresAt` | integer | 到期 Unix 秒时间戳 |
| `autoUnlock` | boolean | 是否自动解锁 |
| `status` | integer | 用户状态 |
| `pixelId` | string | 当前站点 Meta Pixel ID |
| `loginType` | integer | 登录方式 |
| `hasPurchased` | boolean | 是否有购买记录 |
| `ver` | string | App 版本信息 |
### 7.5 POST /api/user/change-password
请求 Header:`Authorization`、`X-Site-Id`。
```json
{
"newPassword": "new-password"
}
```
`newPassword` 必填,长度 6~64 个字符。
### 7.6 POST /api/user/delete-account
请求 Header:`Authorization`、`X-Site-Id`。无 Query,请求体可为空。成功后 App 必须删除本地 Token 和用户缓存。
## 8. 支付与余额
### 8.1 GET /api/payment/coins/package/list
无请求体。可选 Query:`siteId`。建议仍显式发送 `X-Site-Id`。
`data[]`:`packageId`、`name`、`description`、`features`、`coinAmount`、`price`、`originalPrice`、`currency`、`discountPercentage`、`status`、`iosProductId`。
### 8.2 GET /api/payment/subscription/package/list
参数同金币套餐。`data[]`:`packageId`、`siteId`、`name`、`description`、`interval`、`price`、`originalPrice`、`currency`、`discountPercentage`、`coins`、`rights`、`status`、`iosProductId`、`createdAt`。
### 8.3 GET /api/payment/coins/balance
请求 Header:`Authorization`、`X-Site-Id`。可选 Query:`siteId`。
成功 `data`:
```json
{
"balance": 500,
"totalEarned": 500,
"totalSpent": 0,
"totalRealMoneySpent": 0
}
```
### 8.4 POST /api/payment/iap/verify
请求 Header:`Authorization`、`X-Site-Id`。
```json
{
"transactionId": "2000000123456789",
"account": "apple",
"packageName": "com.example.app",
"packageId": "server-package-id",
"productId": "app-store-product-id"
}
```
| 字段 | 类型 | iOS 要求 | 说明 |
|------|------|----------|------|
| `transactionId` | string | 必填 | StoreKit 交易 ID |
| `account` | string | 必填 | iOS 使用 `apple` |
| `packageName` | string | 必填 | Bundle ID |
| `packageId` | string | 必填 | 网站数据库套餐 ID,不是 App Store Product ID |
| `productId` | string | 建议必填 | App Store Product ID |
| `purchaseToken` | string | Android 使用 | Google Play purchase token |
成功 `data.productId` 为已验证商品 ID。只有服务端验单成功后才能展示到账,不得以客户端支付回调代替服务端结果。
### 8.5 POST /api/payment/iap/verify-subscription
请求字段与金币验单相同。成功 `data`:
```json
{
"isActive": true,
"sandbox": false,
"isInFreeTrial": false,
"autoRenewStatus": true
}
```
## 9. 上传与生成
### 9.1 POST /api/client/upload
`Content-Type` 必须由 HTTP 库按 multipart boundary 自动生成,不要手写固定 boundary。
| Form 字段 | 类型 | 必填 | 说明 |
|-----------|------|------|------|
| `file` | file | 是 | 每次恰好一个文件 |
成功:
```json
{
"code": 0,
"info": "ok",
"data": "res/user_upload/directory/file.jpg"
}
```
`data` 可能是相对存储路径。App 应按服务端约定拼成可公开访问 URL,再传给生成接口;上线验收时必须确认返回的是完整 URL 还是相对路径。
### 9.2 POST /api/plugins/video/generate
请求 Header:`Authorization`、`X-Site-Id`、`Content-Type: application/json`。
```json
{
"model": "viduq2_image",
"video_id": "template-1",
"number": 1,
"args": {
"image": "https://cdn.example.com/input.jpg",
"prompt": "跳舞",
"duration": "5",
"resolution": "720p"
}
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `model` | string | 是 | 必须来自创作页配置 |
| `video_id` | string | 按模型 | 模板或业务视频 ID |
| `number` | integer | 否 | 默认 `1`,影响计价和生成数量 |
| `args` | object | 是 | 模型参数,必须按创作页配置构造 |
常见 `args` 字段包括 `prompt`、`negative_prompt`、`image`、`image_url`、`sourceImage`、`images`、`input_images`、`duration`、`resolution`、`aspect_ratio`、`ratio`、`movement_amplitude`、`generate_audio`、`effectId`、`danceId` 和 `input`。各模型字段不同,客户端不得假定全部通用。
成功:
```json
{
"code": 0,
"info": "ok",
"data": {
"task_id": "task-id",
"model": "viduq2_image"
}
}
```
保存 `task_id` 与 `model`,用于查询任务。
### 9.3 GET /api/plugins/video/query
| Query | 类型 | 必填 | 说明 |
|-------|------|------|------|
| `task_id` | string | 是 | 生成接口返回的任务 ID |
| `model` | string | 建议 | 与生成请求一致的模型标识 |
主要 `data` 字段:`task_id`、`status`、`model`、`videos`、`images`、`error_msg`、`created_at`、`updated_at`。
`status`:`1` 处理中、`2` 成功、`3` 失败。处理中采用有上限的轮询与退避;成功或失败后立即停止。
## 10. 创作记录
### GET /api/client/creations
请求 Header:`Authorization`、`X-Site-Id`。
| Query | 类型 | 必填 | 默认值与限制 |
|-------|------|------|--------------|
| `page` | integer | 否 | 默认 `1` |
| `pageSize` | integer | 否 | 默认 `20`,最大 `100` |
成功:
```json
{
"code": 0,
"info": "ok",
"data": {
"items": [
{
"taskId": "task-id",
"status": 2,
"model": "viduq2_image",
"prompt": "跳舞",
"videos": [
{
"url": "https://cdn.example.com/result.mp4",
"coverUrl": "https://cdn.example.com/cover.jpg"
}
],
"images": [],
"createdAt": 1789430400,
"updatedAt": 1789430460
}
],
"total": 1,
"page": 1,
"pageSize": 20,
"hasMore": false
}
}
```
记录当前只保证最近 24 小时范围。用户 ID 从 JWT 获取,App 不传 `userId`。
### DELETE /api/client/creations/{taskId}
请求 Header:`Authorization: Bearer <accessToken>`、`X-Site-Id`、`Accept: application/json`。无请求体,`taskId` 使用列表返回的 `items[].taskId`。
仅允许删除 `status=2`(成功)或 `status=3`(失败)的任务;`status=1`(处理中)返回 HTTP 409。接口仅将记录从用户列表中隐藏,不删除任务、扣费和退款审计数据。任务不存在或不属于当前用户/站点时返回 HTTP 404,成功返回:
```json
{ "code": 0, "info": "ok", "data": {} }
```
curl 示例:
```bash
curl -X DELETE \
'<APP_API_BASE_URL>/api/client/creations/<TASK_ID>' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
-H 'X-Site-Id: <SITE_ID>' \
-H 'Accept: application/json'
```
Swift 示例:
```swift
func deleteCreation(taskId: String, accessToken: String, siteId: String) async throws {
let encodedTaskId = taskId.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? taskId
let url = URL(string: "<APP_API_BASE_URL>/api/client/creations/\(encodedTaskId)")!
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
request.setValue(siteId, forHTTPHeaderField: "X-Site-Id")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200...299).contains(http.statusCode) else {
let message = String(data: data, encoding: .utf8) ?? "删除失败"
throw NSError(
domain: "CreationAPI",
code: http.statusCode,
userInfo: [NSLocalizedDescriptionKey: message]
)
}
}
```
| HTTP | `code` | App 处理 |
|------|--------|----------|
| `200` | `0` | 从本地列表移除该任务,或重新请求创作列表 |
| `401` | `401` | Token 无效或过期,进入重新登录流程 |
| `404` | `404` | 记录不存在或不属于当前用户/站点,刷新列表 |
| `409` | `409` | 任务仍在处理中,提示 `只能删除已结束任务` |
App 不应对 `status=1` 的记录显示删除入口。删除成功只影响创作列表展示,不代表取消任务,也不会撤销扣费或退款。
## 11. Facebook 服务端事件
### POST /api/ads/facebook/events
请求 Header:`Authorization`、`X-Site-Id`。该接口由网关处理。
```json
{
"eventName": "Lead",
"eventId": "task-123",
"eventTime": 1789430400,
"eventSourceUrl": "https://example.com/",
"customData": {},
"userData": {
"clientUserAgent": "...",
"madid": "...",
"anonId": "..."
}
}
```
允许的 `eventName`:`PageView`、`Lead`、`InitiateCheckout`、`Purchase`。`Lead` 和 `Purchase` 必须提供稳定且唯一的 `eventId`。禁止由 App 提交 `pixelId`、Facebook Access Token、明文邮箱或测试事件码。
## 12. App 联调验收清单
- 使用新站 `SITE_ID` 注册和登录,Token 可访问 profile。
- 网页生成的六位码可在 App 登录一次,第二次兑换失败。
- 套餐的 `iosProductId` 与 App Store Connect 完全一致。
- 金币和订阅验单分别使用沙盒交易验证,重复提交不会重复发货。
- 上传真实图片后,返回路径可被生成服务访问。
- 图生视频提交成功,查询最终得到可播放 URL。
- 创作记录只返回当前 Token 用户和当前站点的数据。
- 新站 Bundle ID 存在发版记录,更新检查不会返回 `release_not_found`。
- 广告用户和自然用户分别验证创作页及首页内容。
- App 的日志和监控中不出现密码、Token、登录码、proof 或交易凭据。