# 异步任务接口

自行开发应用时，推荐提交任务后查询结果；需要生成过程时再增加 SSE。鉴权使用本站 API Key，所有请求均携带 Bearer Token。

| 方法 | 路径 | 返回 |
| --- | --- | --- |
| POST | `/v1/novelai/estimate` | 费用预估，不创建生成任务 |
| POST | `/v1/novelai/tasks` | HTTP 202，返回任务 ID |
| GET | `/v1/novelai/tasks/{task_id}` | 状态、错误、文件与结算信息 |
| GET | `/v1/novelai/tasks/{task_id}/events` | SSE 状态、预览和成图事件 |

## 提交任务

请求外层是本站任务格式；`payload` 是对应的官方操作请求。通用的[绘图参数](https://api.hamubox.com/docs/novelai/models/)放在 `payload.parameters`，不要放到外层。

| 字段 | 说明 |
| --- | --- |
| `model` | 必填。当前 Key 可用的 NovelAI 模型 ID |
| `operation` | 必填。`generation`、`director`、`upscale` 或 `encode-vibe` |
| `payload` | 必填。对应操作的完整请求对象 |
| `stream` | 可选布尔值，默认关闭。仅 V4 及更新模型的 `generation` 可用；POST 仍返回 JSON 任务 |

V5 单图示例，开启生成过程。下载[novelai-generate.json](https://api.hamubox.com/docs/examples/novelai-generate.json)后直接发送：

```json title="novelai-generate.json"
{
  "model": "nai-diffusion-5-curated",
  "operation": "generation",
  "stream": true,
  "payload": {
    "input": "a blue ceramic cup on a wooden table, soft daylight, illustration",
    "model": "nai-diffusion-5-curated",
    "action": "generate",
    "parameters": {
      "params_version": 4,
      "width": 832,
      "height": 1216,
      "steps": 23,
      "scale": 5,
      "sampler": "k_euler_ancestral",
      "noise_schedule": "karras",
      "seed": 42,
      "n_samples": 1,
      "image_format": "png",
      "uc": "lowres, blurry",
      "v4_prompt": {
        "caption": {
          "base_caption": "a blue ceramic cup on a wooden table, soft daylight, illustration",
          "char_captions": []
        },
        "use_coords": false,
        "use_order": true
      },
      "v4_negative_prompt": {
        "caption": {
          "base_caption": "lowres, blurry",
          "char_captions": []
        },
        "use_coords": false,
        "use_order": false
      }
    }
  }
}
```

```bash
curl --fail-with-body 'https://api.hamubox.com/v1/novelai/tasks' \
  -H "Authorization: Bearer $HAMUBOX_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @novelai-generate.json
```

HTTP 202 示例，服务端还可能返回 `usage` 等字段：

```json
{
  "id": "task_your_existing_id",
  "object": "novelai.image.task",
  "created_at": 1790000000,
  "updated_at": 1790000000,
  "status": "queued",
  "progress": 0,
  "model": "nai-diffusion-5-curated",
  "operation": "generation"
}
```

开启 `stream` 不会让这个 POST 变成 SSE。保存 `id` 后再[订阅事件](https://api.hamubox.com/docs/novelai/streaming/#异步任务加-sse)。

### 参数错误

可提前识别的错误在入队、预扣前返回 HTTP 400，不创建任务。例如请求已包含正向结构，但漏掉负向结构：

```json
{
  "error": {
    "code": "invalid_request",
    "type": "novelai_error",
    "message": "invalid NovelAI generation parameters: parameters.v4_negative_prompt is required; provide caption.char_captions: [] when there are no characters"
  }
}
```

错误里的 `parameters.*` 指 `payload.parameters.*`。例如 `parameters.v4_prompt.caption.char_captions must be an array` 表示应补上角色数组，没有角色时填写 `[]`；`missing required pricing parameters: parameters.steps` 表示缺少用于计费的步数。

修正字段后再提交，不要原样自动重试 400。模型范围、节约模式例外与完整结构见[必填结构与提交校验](https://api.hamubox.com/docs/novelai/models/#必填结构与提交校验)。已被接受的任务若后续失败，仍通过任务查询返回 `failed`，不要将其与提交前拒绝混淆。

## 查询与恢复

每隔 5 秒查询同一个任务：

```bash
curl --fail-with-body \
  'https://api.hamubox.com/v1/novelai/tasks/task_your_existing_id' \
  -H "Authorization: Bearer $HAMUBOX_API_KEY"
```

| `status` | 客户端行为 |
| --- | --- |
| `queued` / `in_progress` | 继续等待；也可以订阅 SSE |
| `completed` | 停止查询；图片读取 `result.images[].url`，氛围编码读取 `result.encoded_vibe` |
| `failed` | 停止查询，展示 `error.message`，保留 `id` 和 `error.code` |
| `unknown` | 不当作成功或失败；保留任务 ID，稍后查询或联系支持 |

图片文件对象包含 `id`、`url`、`filename`、`mime_type`、`size`，可能还包含 `md5`。这与通用图片接口的 `outputs[]` 不同。读取结果 URL 时不要携带本站 Authorization。最终图片进入发起用户的资产库，中间预览不入库。

刷新、断线或切换页面后，继续查询同一个任务。通过原生接口或 OpenAI Images 兼容入口提交时，保存响应头 `X-NovelAI-Task-Id`，也能在这里查询。客户端断开连接不会取消已接受的任务。

排队等待默认最多 10 分钟，管理员可以在 NAI 运营设置中调整。达到等待上限仍未开始的任务返回 `failed` / `queue_timeout`，退回预扣；执行中的任务使用单独的运行超时。

## 幂等提交

提交任务、原生生成及 OpenAI Images 兼容入口均接受可选的 `Idempotency-Key` 请求头。每次用户主动生成时创建一个新值；网络重试保留原值与原请求内容，最多 128 字节。

```bash
curl --fail-with-body 'https://api.hamubox.com/v1/novelai/tasks' \
  -H "Authorization: Bearer $HAMUBOX_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: my-unique-image-request-001' \
  --data-binary @novelai-generate.json
```

- 相同用户、操作、标识和请求内容：取回原任务或原结果，不再次生成或预扣。分组、模型、素材、提示词及参数变化会视为不同内容。
- 同一标识对应不同内容：HTTP 409，`idempotency_conflict`。
- 首次请求尚在准备中：HTTP 409，`idempotency_in_progress`，按 `Retry-After` 等待后用同一标识重试。若长时间未能确认是否接受，请保留标识与任务 ID 联系支持，不要换标识重复生成。
- 首次请求在任务接受前已失败：HTTP 409，`idempotency_rejected`。先解决原错误，再由用户主动发起新的生成。
- 原任务已失败：仍返回原任务的失败状态；重新生成必须使用新的标识。

不提供此请求头的旧客户端仍按每次 POST 创建任务。不能仅依靠相同提示词去重，因为用户可能确实希望连续生成多张。原生错误继续使用其原有的字符串错误格式。

## 费用预估

同一个完整请求可先发送到估价接口：

```bash
curl --fail-with-body 'https://api.hamubox.com/v1/novelai/estimate' \
  -H "Authorization: Bearer $HAMUBOX_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @novelai-generate.json
```

读取 `usage.estimated_cost_usd` 与 `usage.estimated_anlas`。估价不启动生成，不代表锁定报价。正式任务提交时预扣，终态返回 `usage.actual_cost_usd` 等结算信息；尚未返回的字段不能当作零。Anlas 与本站美元余额是不同单位，免费 Anlas 不表示本站服务免费。

报价接口也兼容只包含计费参数的请求，不要求完整的 `v4_prompt` / `v4_negative_prompt`。**获得报价不代表请求可直接生成**；提交到 `/tasks` 前仍需补齐对应模型的提示词结构。

## 其它操作示例

下列文件都包含完整的 `model`、`operation`、`payload`，可使用上面的提交命令，替换 JSON 文件名即可。需要素材的示例先将 `https://example.com/...` 替换为可访问的真实文件 URL。

| 操作 | 请求 JSON | 成功结果 |
| --- | --- | --- |
| 图生图 | [novelai-img2img.json](https://api.hamubox.com/docs/examples/novelai-img2img.json) | `result.images[]` |
| 局部重绘 | [novelai-inpaint.json](https://api.hamubox.com/docs/examples/novelai-inpaint.json) | `result.images[]` |
| Director 提取线稿 | [novelai-director.json](https://api.hamubox.com/docs/examples/novelai-director.json) | `result.images[]` |
| 放大 | [novelai-upscale.json](https://api.hamubox.com/docs/examples/novelai-upscale.json) | `result.images[]` |
| 氛围编码 | [novelai-vibe.json](https://api.hamubox.com/docs/examples/novelai-vibe.json) | `result.encoded_vibe`，编码二进制的 Base64，不是图片 |

Director、放大与氛围编码不支持 `stream`。模型能力、素材字段、角色提示词和操作边界统一见[模型与绘图参数](https://api.hamubox.com/docs/novelai/models/)。

也可以使用[Python 异步客户端](https://api.hamubox.com/docs/examples/client.py)：

```bash
python client.py novelai novelai-generate.json
python client.py novelai --task-id task_your_existing_id
```
