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

# 异步任务模型

> XHuoAPI 长耗时任务（图像/视频/音乐）的轮询与 Webhook 回调机制。

聊天类 API（LLM）是**同步**的——一次请求返回完整结果。但**图像 / 视频 / 音乐**生成都需要数秒到数分钟，走**异步任务**模式。

## 选哪种结果获取方式？

<CardGroup cols={2}>
  <Card title="客户端轮询" icon="arrows-rotate">
    创建任务后，定期 GET 任务状态直到完成。**简单**，适合脚本、CLI、Notebook、测试。
  </Card>

  <Card title="Webhook 回调" icon="webhook">
    提交时传 `callback_url`，完成后我们 POST 结果给你。**节省请求次数**，适合生产服务。
  </Card>
</CardGroup>

两种可以**同时用**——传了 `callback_url` 也仍可主动查询任务结果。

## 创建任务

POST 提交后**同步返回** `200`，主体里含 `task_id`：

```json theme={null}
{
  "success": true,
  "task_id": "27a1128f-14b5-440e-a82d-510c81e6fcd4"
}
```

不同接口同步返回的字段不一样：

* **Midjourney / Suno** 等支持「等到出结果再返回」的接口：直接给完整结果（图片 URL 等）
* **Veo / Sora 等**重型视频接口：只回一个 `task_id`，结果稍后通过轮询或 Webhook 拿

具体看每个 API 的 [API 参考](/api-reference)。

## 客户端轮询

通用模式（以 Midjourney `/midjourney/tasks` 为例）：

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

API = "https://api.xhuoapi.ai/v1"
headers = {"Authorization": "Bearer YOUR_API_TOKEN"}

# 1. 创建任务
r = requests.post(
    f"{API}/midjourney/imagine",
    headers=headers,
    json={"prompt": "A cat sitting on a table"},
).json()
task_id = r["task_id"]

# 2. 轮询
while True:
    info = requests.post(
        f"{API}/midjourney/tasks",
        headers=headers,
        json={"action": "retrieve", "task_id": task_id},
    ).json()

    # finished_at 字段一旦出现，任务即完成
    if info.get("finished_at"):
        print(info["response"])
        break

    time.sleep(5)
```

**注意**：`/midjourney/tasks` retrieve 返回的是 `{id, request, response, created_at, started_at, finished_at, ...}` 结构——**没有** `status` 字段。判断「是否完成」请看 `finished_at` 是否存在，或 `response` 是否非空。其他服务的任务查询接口字段不完全相同，请参考各自 API 文档。

请合理控制轮询频率，避免触发 `too_many_requests`（429）。建议带退避，并优先依据具体接口的「典型耗时」估算等待。

## Webhook 回调

把 `callback_url` 加到请求 body：

```json theme={null}
{
  "prompt": "A cat sitting on a table",
  "callback_url": "https://your-server.com/midjourney/callback"
}
```

任务完成后，我们把结果 POST 到这个地址。回调主体形如：

**成功：**

```json theme={null}
{
  "success": true,
  "task_id": "...",
  "trace_id": "...",
  "image_url": "...",
  "...": "...（其余业务字段，与同步成功响应一致）"
}
```

**失败：**

```json theme={null}
{
  "success": false,
  "task_id": "...",
  "trace_id": "...",
  "error": {
    "code": "...",
    "message": "..."
  }
}
```

### Webhook 服务端要求

* 必须是**公网可达**的 HTTPS（建议）URL
* 收到回调请**尽快返回 2xx**，业务处理放后台
* **回调当前不带签名头**。请：
  * 让 `callback_url` 包含一段不易猜的 token 路径段
  * 或在服务端用 `task_id` 比对自己之前创建任务时记下的 ID

### 最小示例（Flask）

```python theme={null}
from flask import Flask, request

app = Flask(__name__)

# 自己维护的「我提交过的 task_id」集合
my_tasks = set()

@app.post("/midjourney/callback")
def callback():
    payload = request.json
    task_id = payload.get("task_id")

    if task_id not in my_tasks:
        return "", 204  # 不是我的任务，忽略

    if payload.get("success"):
        # 处理成功
        save_image(payload["image_url"])
    else:
        # 处理失败
        log_error(payload.get("error"))

    return "", 200
```

## 选型对比

| 维度   | 轮询         | Webhook             |
| ---- | ---------- | ------------------- |
| 上手   | 简单         | 需要公网 HTTPS endpoint |
| 实时性  | 取决于轮询间隔    | 最快（任务结束即推送）         |
| 请求消耗 | 每次轮询都是请求   | 0（只接收回调）            |
| 失败恢复 | 客户端崩了重启可继续 | 必须自己处理「错过了回调」       |
| 适用场景 | 脚本、本地工具、调试 | 生产服务、批量任务           |

## 失败处理

不论用哪种方式，都可能拿到失败结果。失败响应的 `error.code` 与同步错误码一致——见 [响应格式](/concepts/responses)。**任务失败一般不扣费**；具体策略以服务详情页为准。

## 下一步

<CardGroup cols={2}>
  <Card title="响应格式" icon="brackets-curly" href="/concepts/responses">
    所有错误码与状态码
  </Card>

  <Card title="网关与认证" icon="key" href="/authentication">
    Token、域名、安全
  </Card>
</CardGroup>
