> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rixapi.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 异步任务

> 统一异步任务接口使用说明，适用于视频生成、图片生成、音乐生成等耗时任务。

统一异步任务接口提供标准化的提交 → 轮询 → 获取结果流程，屏蔽不同厂商 API 差异。

## 概述

视频生成、图片生成、音乐生成等 AI 任务通常需要几十秒甚至数分钟才能完成。为保障用户体验与系统稳定性，这些任务统一通过**异步任务接口**处理：

1. **提交任务** — 发送生成请求，立即获得任务 ID
2. **查询状态** — 使用任务 ID 轮询进度，或通过回调地址接收通知
3. **获取结果** — 任务完成后从响应中获取生成的资源 URL

```mermaid theme={null}
sequenceDiagram
    participant 客户端
    participant API as ePhone AI
    客户端->>API: POST /v1/task/submit
    API-->>客户端: 返回 {id, status: "queued"}
    loop 轮询（或等待回调）
        客户端->>API: GET /v1/task/{id}
        API-->>客户端: 返回 {status: "in_progress"}
    end
    API-->>客户端: 返回 {status: "completed", outputs: [...]}
```

***

## 提交任务

`POST /v1/task/submit`

### 请求参数

| 字段 | 类型 | 必填 | 说明 |
| - | - | - | - |
| `model` | string | 是 | 模型名称 |
| `input` | object | 是 | 模型所需的生成参数（因模型而异） |
| `callback_url` | string | 否 | 任务状态变更时的回调地址 |

### 请求示例

```bash cURL theme={null} theme={null}
curl -X POST https://api.ephone.ai/v1/task/submit \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-v3/text-to-video",
    "input": {
      "prompt": "一只金色的柴犬在樱花树下奔跑，慢镜头，电影质感",
      "duration": "5",
      "aspect_ratio": "16:9"
    },
    "callback_url": "https://your-server.com/webhook/task"
  }'
```

```python Python theme={null} theme={null}
import requests

response = requests.post(
    "https://api.ephone.ai/v1/task/submit",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "model": "kling-v3/text-to-video",
        "input": {
            "prompt": "一只金色的柴犬在樱花树下奔跑，慢镜头，电影质感",
            "duration": "5",
            "aspect_ratio": "16:9",
        },
        "callback_url": "https://your-server.com/webhook/task",
    },
)

data = response.json()
print(data)
# {"id": "task_abc123", "status": "queued", "created_at": 1711234567}
```

```javascript Node.js theme={null} theme={null}
const response = await fetch("https://api.ephone.ai/v1/task/submit", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "kling-v3/text-to-video",
    input: {
      prompt: "一只金色的柴犬在樱花树下奔跑，慢镜头，电影质感",
      duration: "5",
      aspect_ratio: "16:9",
    },
    callback_url: "https://your-server.com/webhook/task",
  }),
});

const data = await response.json();
console.log(data);
// {"id": "task_abc123", "status": "queued", "created_at": 1711234567}
```

### 响应示例

```json theme={null}
{
  "id": "task_abc123",
  "status": "queued",
  "created_at": 1711234567
}
```

***

## 查询任务

`GET /v1/task/{task_id}`

### 请求示例

```bash cURL theme={null} theme={null}
curl https://api.ephone.ai/v1/task/task_abc123 \
  -H "Authorization: Bearer $API_KEY"
```

```python Python theme={null} theme={null}
import requests

response = requests.get(
    "https://api.ephone.ai/v1/task/task_abc123",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)

data = response.json()
print(data["status"])   # "queued" | "in_progress" | "completed" | "failed"
print(data["outputs"])  # 完成后包含资源 URL 列表
```

```javascript Node.js theme={null} theme={null}
const response = await fetch("https://api.ephone.ai/v1/task/task_abc123", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});

const data = await response.json();
console.log(data.status);   // "queued" | "in_progress" | "completed" | "failed"
console.log(data.outputs);  // 完成后包含资源 URL 列表
```

### 响应示例

**任务进行中：**

```json theme={null}
{
  "id": "task_abc123",
  "status": "in_progress",
  "created_at": 1711234567
}
```

**任务完成（按 token 计费）：**

```json theme={null}
{
  "id": "task_abc123",
  "status": "completed",
  "created_at": 1711234567,
  "completed_at": 1711234620,
  "outputs": [
    "https://cdn.example.com/video/result.mp4"
  ],
  "usage": {
    "type": "tokens",
    "input_tokens": 14,
    "input_token_details": {
      "audio_tokens": 14
    },
    "output_tokens": 45,
    "total_tokens": 59
  }
}
```

**任务完成（按次计费）：**

```json theme={null}
{
  "id": "task_def456",
  "status": "completed",
  "created_at": 1711234567,
  "completed_at": 1711234620,
  "outputs": [
    "https://cdn.example.com/image/result.png"
  ],
  "usage": {
    "type": "call",
    "multiplier": 2
  }
}
```

**任务完成（按时长计费）：**

```json theme={null}
{
  "id": "task_xyz789",
  "status": "completed",
  "created_at": 1711234567,
  "completed_at": 1711234620,
  "outputs": [
    "https://cdn.example.com/video/result.mp4"
  ],
  "usage": {
    "type": "duration",
    "seconds": 27
  }
}
```

**任务失败：**

```json theme={null}
{
  "id": "task_abc123",
  "status": "failed",
  "created_at": 1711234567,
  "completed_at": 1711234590,
  "error": "Content policy violation detected"
}
```

***

## 任务状态

| 状态 | 说明 |
| - | - |
| `queued` | 任务已提交，等待处理 |
| `in_progress` | 任务正在生成中 |
| `completed` | 任务完成，`outputs` 中包含结果 |
| `failed` | 任务失败，`error` 中包含原因 |

***

## 回调通知

提交任务时传入 `callback_url`，当任务状态发生变更时，系统会向该地址发送 `POST` 请求，请求体与查询接口响应格式一致：

```json theme={null}
{
  "id": "task_abc123",
  "status": "completed",
  "created_at": 1711234567,
  "completed_at": 1711234620,
  "outputs": [
    "https://cdn.example.com/video/result.mp4"
  ],
  "usage": {
    "type": "tokens",
    "input_tokens": 14,
    "output_tokens": 45,
    "total_tokens": 59
  }
}
```

提交任务时在请求头里带上 `X-Billing-Detail: true` 的话，任务结束时的回调请求会带上 `X-Billing-Currency`、`X-Billing-Amount` 两个请求头，给出这个任务的 [实际扣费](#实际扣费)。

> [!WARNING]
>
> 1. 回调地址必须是公网可访问的 HTTPS 地址
> 2. 回调请求超时时间为 10 秒
> 3. 回调地址不能指向 `localhost`、`127.0.0.1` 或平台自身域名
> 4. 即使设置了回调，仍建议实现轮询作为兜底，避免因网络原因错过通知

***

## 完整轮询示例

```python Python theme={null} theme={null}
import time
import requests

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.ephone.ai/v1"

# 1. 提交任务
submit_resp = requests.post(
    f"{BASE_URL}/task/submit",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "model": "kling-v3/text-to-video",
        "input": {
            "prompt": "一只金色的柴犬在樱花树下奔跑",
            "duration": "5",
            "aspect_ratio": "16:9",
        },
    },
)

task = submit_resp.json()
task_id = task["id"]
print(f"任务已提交: {task_id}")

# 2. 轮询等待完成
while True:
    resp = requests.get(
        f"{BASE_URL}/task/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    result = resp.json()
    status = result["status"]
    print(f"状态: {status}")

    if status == "completed":
        print("生成结果:", result["outputs"])
        break
    elif status == "failed":
        print("任务失败:", result.get("error", "Unknown error"))
        break

    time.sleep(5)
```

```javascript Node.js theme={null} theme={null}
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.ephone.ai/v1";

// 1. 提交任务
const submitResp = await fetch(`${BASE_URL}/task/submit`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "kling-v3/text-to-video",
    input: {
      prompt: "一只金色的柴犬在樱花树下奔跑",
      duration: "5",
      aspect_ratio: "16:9",
    },
  }),
});

const task = await submitResp.json();
const taskId = task.id;
console.log(`任务已提交: ${taskId}`);

// 2. 轮询等待完成
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

while (true) {
  const resp = await fetch(`${BASE_URL}/task/${taskId}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  const result = await resp.json();
  console.log(`状态: ${result.status}`);

  if (result.status === "completed") {
    console.log("生成结果:", result.outputs);
    break;
  } else if (result.status === "failed") {
    console.log("任务失败:", result.error);
    break;
  }

  await sleep(5000);
}
```

***

## 响应字段说明

| 字段 | 类型 | 说明 |
| - | - | - |
| `id` | string | 任务唯一标识 |
| `status` | string | 任务状态：`queued` / `in_progress` / `completed` / `failed` |
| `created_at` | integer | 任务创建时间（Unix 时间戳，秒） |
| `completed_at` | integer | 任务完成时间（Unix 时间戳，秒），仅在完成/失败时返回 |
| `outputs` | string\[] | 生成结果的 URL 列表（视频/图片/音频），仅在 `completed` 时返回 |
| `speed` | string | 速度档：`relax` / `fast` / `turbo`，只有 Midjourney 这类按速度分档计价的模型返回，是提交时选的、也是计费的那一档 |
| `usage` | object | 任务用量信息，详见 [用量信息](#用量信息-usage) 章节，仅在终态且模型支持时返回 |
| `error` | string | 错误信息，仅在 `failed` 时返回 |

***

## 用量信息 (Usage)

任务进入终态（`completed` / `failed`）后，部分模型会在响应中返回 `usage` 字段，反映本次任务的实际资源消耗。`usage` 是一个 **标签联合（tagged union）**：通过 `type` 字段区分计量单位。

### type=tokens（按 token 计费）

适用于 Doubao / Vidu / Ali / Kling 文本类、Gemini 等按 token 计费的任务：

```json theme={null}
{
  "type": "tokens",
  "input_tokens": 14,
  "input_token_details": {
    "audio_tokens": 14
  },
  "output_tokens": 45,
  "total_tokens": 59
}
```

| 字段 | 类型 | 说明 |
| - | - | - |
| `type` | string | 固定为 `"tokens"` |
| `input_tokens` | integer | 输入 token 总数 |
| `input_token_details` | object | 按模态拆分的输入 token，可选；其内部子字段为 0 时省略，全部为 0 时该对象整体省略 |
| `input_token_details.text_tokens` | integer | 文本输入 token |
| `input_token_details.audio_tokens` | integer | 音频输入 token |
| `input_token_details.image_tokens` | integer | 图片输入 token |
| `input_token_details.cached_tokens` | integer | 命中提示词缓存的 token（兼容 OpenAI prompt cache） |
| `output_tokens` | integer | 输出 token 总数 |
| `output_token_details` | object | 按模态拆分的输出 token，可选；省略规则同 `input_token_details` |
| `total_tokens` | integer | `input_tokens + output_tokens` 总计 |

### type=duration（按时长计费）

适用于 Sora / Xai-video 等按生成视频时长计费的任务：

```json theme={null}
{
  "type": "duration",
  "seconds": 27
}
```

| 字段 | 类型 | 说明 |
| - | - | - |
| `type` | string | 固定为 `"duration"` |
| `seconds` | number | 任务用量时长（秒，支持小数） |

### type=call（按次计费）

适用于 Midjourney、Suno、可灵视频等按次计费的任务，只在任务成功（`completed`）时返回：

```json theme={null}
{
  "type": "call",
  "multiplier": 2
}
```

| 字段 | 类型 | 说明 |
| - | - | - |
| `type` | string | 固定为 `"call"` |
| `multiplier` | number | 本次的规格倍率：请求参数与实际产出带来的倍数，相对于模型价格表里所选档位的单价 |

`multiplier` 由请求参数与实际产出决定，例如 Midjourney 高清（`--hd`）为 2，V7 草图为 0.5，视频按分辨率和条数计；实际交付数量少于请求时按交付数量计（如只出了 4 张的 V8 草图为 0.5）。按积分、张数、首数计价的模型（如可灵、Vidu 按积分），`multiplier` 就是这次消耗的数量。

`multiplier` 只描述规格，同一个请求不管谁调用都相同：不含分组折扣、个人折扣等账户相关的价格调整，也不含参考图费用这类另外加收的费用。要核对每个任务实际扣了多少钱，请使用 [实际扣费](#实际扣费)。

> [!NOTE]
>
> * 按上游实际费用结算的任务（如 Flux）不返回 `usage` 字段
> * 按次计费的任务失败时全额退款，不返回 `usage`
> * 任务在 `queued` / `in_progress` 状态时不会返回 `usage`
> * 历史任务或上游未返回用量数据的任务也会省略此字段
> * 字段值为 0 的子项（如 `text_tokens: 0`）会被省略

***

## 实际扣费

查询任务时在请求头里加上 `X-Billing-Detail: true`，任务结束后响应会多两个响应头，给出这个任务实际扣了多少钱。折扣、附加费、失败退款都已经算在里面，和账单上的金额一致。响应内容不变。

```bash cURL theme={null} theme={null}
curl -i https://api.ephone.ai/v1/task/task_abc123 \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Billing-Detail: true"
```

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
X-Billing-Currency: CNY
X-Billing-Amount: 0.24
```

| 响应头 | 说明 |
| - | - |
| `X-Billing-Currency` | 模型定价的币种：`CNY` 或 `USD` |
| `X-Billing-Amount` | 实际扣费金额，单位是 `X-Billing-Currency` 给出的币种，例如 `0.24` |

金额按模型定价的币种给：人民币定价的模型是人民币元，美元定价的模型是美元。请直接使用返回的金额，不要自己按汇率换算。

提交任务时也带上 `X-Billing-Detail: true` 的话，之后查询不带这个请求头也会返回这两个响应头；任务结束时的回调请求也会带上这两个请求头。

### 适用范围

所有一次查一个任务的查询接口都支持：本页的 `GET /v1/task/{task_id}`，以及各家官方格式的任务查询接口，包括 Midjourney（`/midjourney/task/{task_id}` 与 `/mj/task/{id}/fetch`）、OpenAI 视频格式 `GET /v1/videos/{id}`、可灵、MiniMax、Suno、Luma、Runway、Replicate、Flux、Vidu、豆包、阿里百炼、Ideogram、Mureka、xAI 视频、Gemini 原生格式的 Veo 视频。官方格式接口的响应内容保持官方原样，金额只在响应头里。

一次查多个任务的接口不返回，例如 Suno、Luma 的批量查询和 Midjourney 按条件查询；可灵新版查询 `GET /kling/tasks?task_ids=` 只填一个任务时返回。

上面这些任务，提交时带了 `X-Billing-Detail: true` 的，任务结束时的回调请求都会带上这两个请求头，回调内容不变。

> [!NOTE]
>
> * 只在任务结束（`completed` / `failed`）后返回；失败的任务费用已退回时金额为 `0`
> * 任务刚结束、结算还没完成的极短时间内可能暂时没有这两个头，稍后再查一次即可
> * 不带 `X-Billing-Detail: true`（提交时也没带）时不返回
> * 在浏览器里调用时，这两个响应头可以直接读取

***

## Flux 模型的完成通知

* 想在任务结束时收到通知，请在请求体顶层传 `callback_url`，所有 Flux 模型都支持。
* `input` 里的 `webhook_url` / `webhook_secret` 不会发给上游。`flux-3-image`、`flux-3-video`、`flux-tools/video-edit-v1`、`flux-tools/outpainting-v1` 的官方接口不接受回调字段，带了也会被忽略，请用 `callback_url` 或轮询。
* 不传回调时，提交后用 `GET /v1/task/{task_id}` 轮询（建议间隔 3\~10 秒；视频任务通常需要几分钟）。

```bash theme={null}
curl -X POST "https://your-gateway/v1/task/submit" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "flux-tools/outpainting-v1",
    "callback_url": "https://your-server.com/hook",
    "input": {
      "images": ["https://example.com/base.png"],
      "width": 1200,
      "height": 800
    }
  }'

# 轮询任务状态
curl "https://your-gateway/v1/task/{task_id}" \
  -H "Authorization: Bearer $API_KEY"
```

***

## 注意事项

> [!WARNING]
>
> 1. 使用标准 `Authorization: Bearer <API_KEY>` 鉴权
> 2. `input` 中的参数因模型而异，请在 [模型列表](https://platform.ephone.ai/models) 中点击对应模型查看详情文档
> 3. 建议轮询间隔 3\~10 秒，避免过于频繁的请求
> 4. 任务结果中的资源 URL 可能有有效期限制，建议及时下载保存
> 5. Flux 模型要接收完成通知请用顶层 `callback_url`，见上文「Flux 模型的完成通知」

## 相关链接

> [card] [视频模型](https://docs.rixapi.com/docs/cn/guides/video)
>
> 各视频模型的官方 API 格式文档

> [card] [图片模型](https://docs.rixapi.com/docs/cn/guides/image.md)
>
> 各图片模型的 API 格式文档


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.