> ## Documentation Index
> Fetch the complete documentation index at: https://quwan-b8010aef.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Batch API 批量对话

> 一次提交多条对话请求，异步查询批次状态，并在任务完成后按实际 Token 后扣费

# Batch API 批量对话

Batch API 适合离线评测、内容分类、数据清洗等无需实时返回结果的批量任务。调用方通过一个 `prompts` 数组提交多条对话请求，平台异步处理整个批次。

> [!NOTE]
>
> 本接口采用内联请求格式，无需预先生成或上传批次文件。请求体包含模型、`prompts` 及可选的批次配置。

## 接口概览

| 操作 | 方法 | 路径 | 说明 |
| - | - | - | - |
| 创建批次 | `POST` | `/v1/batches` | 提交 1～10,000 条对话请求 |
| 查询批次 | `GET` | `/v1/batches/{batch_id}` | 获取批次状态和请求计数 |
| 列出批次 | `GET` | `/v1/batches?limit=20&after={batch_id}` | 查询当前 API Key 创建的批次 |
| 取消批次 | `POST` | `/v1/batches/{batch_id}/cancel` | 取消尚未结束的批次 |

## 完整示例

示例中的 `<API-KEY>` 表示实际 API Key，`<MODEL-NAME>` 表示已开通 Batch API 的模型名称。

```bash cURL theme={null}
# 1. 创建批次
BATCH_ID=$(curl -sS -X POST "https://api.tokenops.ai/v1/batches" \
  -H "Authorization: Bearer <API-KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<MODEL-NAME>",
    "name": "product-review-classification",
    "completion_window": "24h",
    "prompts": [
      {
        "custom_id": "review-001",
        "messages": [
          {"role": "system", "content": "判断用户评价的情感，只回答 positive、neutral 或 negative。"},
          {"role": "user", "content": "物流很快，商品质量也很好。"}
        ],
        "temperature": 0,
        "max_tokens": 16
      },
      {
        "custom_id": "review-002",
        "messages": [
          {"role": "system", "content": "判断用户评价的情感，只回答 positive、neutral 或 negative。"},
          {"role": "user", "content": "包装破损，而且少发了配件。"}
        ],
        "temperature": 0,
        "max_tokens": 16
      }
    ]
  }' | jq -r '.id')

echo "batch_id=$BATCH_ID"

# 2. 轮询直到进入终态
while true; do
  RESPONSE=$(curl -sS \
    -H "Authorization: Bearer <API-KEY>" \
    "https://api.tokenops.ai/v1/batches/$BATCH_ID")
  STATUS=$(echo "$RESPONSE" | jq -r '.status')
  echo "$RESPONSE" | jq .

  case "$STATUS" in
    completed|failed|cancelled|expired) break ;;
  esac
  sleep 30
done

# 3. 批次完成后下载每条 prompt 的结果
if [ "$STATUS" = "completed" ]; then
  OUTPUT_URL=$(echo "$RESPONSE" | jq -r '.output_url')
  curl -fS \
    "$OUTPUT_URL" \
    -o "$BATCH_ID-output.jsonl"
fi
```

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

API_KEY = "<API-KEY>"
MODEL = "<MODEL-NAME>"
BASE_URL = "https://api.tokenops.ai/v1"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}


def create_batch():
    response = requests.post(
        f"{BASE_URL}/batches",
        headers=HEADERS,
        json={
            "model": MODEL,
            "name": "product-review-classification",
            "completion_window": "24h",
            "prompts": [
                {
                    "custom_id": "review-001",
                    "messages": [
                        {
                            "role": "system",
                            "content": "判断用户评价的情感，只回答 positive、neutral 或 negative。",
                        },
                        {"role": "user", "content": "物流很快，商品质量也很好。"},
                    ],
                    "temperature": 0,
                    "max_tokens": 16,
                },
                {
                    "custom_id": "review-002",
                    "messages": [
                        {
                            "role": "system",
                            "content": "判断用户评价的情感，只回答 positive、neutral 或 negative。",
                        },
                        {"role": "user", "content": "包装破损，而且少发了配件。"},
                    ],
                    "temperature": 0,
                    "max_tokens": 16,
                },
            ],
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.json()


def wait_batch(batch_id, timeout=24 * 60 * 60):
    terminal_statuses = {"completed", "failed", "cancelled", "expired"}
    deadline = time.time() + timeout
    while time.time() < deadline:
        response = requests.get(
            f"{BASE_URL}/batches/{batch_id}",
            headers=HEADERS,
            timeout=30,
        )
        response.raise_for_status()
        batch = response.json()
        print("status=", batch["status"], "counts=", batch.get("request_counts"))
        if batch["status"] in terminal_statuses:
            return batch
        time.sleep(30)
    raise TimeoutError(f"batch {batch_id} did not finish before timeout")


def download_output(output_url, destination):
    with requests.get(
        output_url,
        stream=True,
        timeout=120,
    ) as response:
        response.raise_for_status()
        with open(destination, "wb") as output:
            for chunk in response.iter_content(chunk_size=64 * 1024):
                output.write(chunk)


created = create_batch()
print("batch_id=", created["id"])
result = wait_batch(created["id"])
print(result)
if result["status"] == "completed":
    download_output(result["output_url"], f'{created["id"]}-output.jsonl')
```

## 接口返回格式

创建、查询和取消接口成功时都返回 Batch 对象。不同状态下，部分字段可能为 `null` 或不出现。所有时间字段均为 Unix 秒级时间戳。

| 字段 | 类型 | 出现位置 | 说明 |
| - | - | - | - |
| `id` | string | 创建、详情、列表项、取消 | 批次 ID，格式为 `batch_...` |
| `object` | string | 创建、详情、列表项、取消 | 对象类型，当前为 `batch` |
| `endpoint` | string | 创建、详情、列表项、取消 | 批次中每条请求调用的接口，当前为 `/v1/chat/completions` |
| `status` | string | 创建、详情、列表项、取消 | 批次状态，取值见“状态与计费” |
| `completion_window` | string | 创建、详情、列表项、取消 | 批次完成窗口 |
| `created_at` | integer | 创建、详情、列表项、取消 | 创建时间 |
| `expires_at` | integer/null | 创建、详情、列表项、取消 | 任务执行窗口的截止时间，不是下载地址失效时间 |
| `in_progress_at` | integer/null | 创建、详情、列表项、取消 | 开始执行时间；尚未执行时为 `null` |
| `completed_at` | integer/null | 创建、详情、列表项、取消 | 上游任务结束时间；未结束时为 `null` |
| `cancelled_at` | integer/null | 创建、详情、列表项、取消 | 取消完成时间；未取消时为 `null` |
| `request_counts` | object | 创建、详情、列表项、取消 | 请求数量统计，包含 `total`、`completed`、`failed` |
| `usage` | object | 详情、列表项 | 已汇总的 Token 用量；上游尚未产生统计时不返回 |
| `errors` | object/null | 详情、列表项、取消 | 批次级错误，格式为 `{"data":[{"code":"...","message":"..."}]}` |
| `error_file_id` | string/null | 创建、详情、列表项、取消 | 上游失败明细文件标识；没有失败明细时为 `null` |
| `output_url` | string | 详情 | 结果文件的临时签名下载地址，仅在已有结果文件时返回 |
| `output_url_expires_at` | integer | 详情 | `output_url` 的失效时间 |

对外响应不会返回平台内部使用的 `input_file_id` 和 `output_file_id`。

### 创建批次

`POST /v1/batches` 创建成功返回 HTTP `202`。任务通常从 `validating` 开始，此时还没有 `usage` 和 `output_url`：

```json theme={null}
{
  "id": "batch_example",
  "object": "batch",
  "endpoint": "/v1/chat/completions",
  "completion_window": "24h",
  "status": "validating",
  "created_at": 1787237196,
  "expires_at": 1787323596,
  "in_progress_at": null,
  "completed_at": null,
  "cancelled_at": null,
  "request_counts": {
    "total": 3,
    "completed": 0,
    "failed": 0
  },
  "error_file_id": null
}
```

### 查询批次详情

`GET /v1/batches/{batch_id}` 成功返回 HTTP `200` 和最新 Batch 对象。建议直接保存并处理完整响应，不要依赖 JSON 字段顺序。

状态为 `completed` 且结果文件已经生成时，详情会额外返回 `usage`、`output_url` 和 `output_url_expires_at`。状态为 `failed` 或 `cancelled` 时，通常会返回 `errors.data`；每个错误项包含 `code` 和 `message`。

### 状态与可选字段

| 状态 | 重点字段 | 字段行为 |
| - | - | - |
| `validating` | `request_counts` | 正在校验；各时间字段通常只有 `created_at` 和 `expires_at` 有值 |
| `in_progress` | `in_progress_at`、`request_counts` | `completed` 和 `failed` 随执行进度更新 |
| `cancelling` | `status` | 已收到取消请求，其他字段继续沿用当前任务数据 |
| `completed` | `completed_at`、`usage`、`output_url` | 有结果文件时返回签名下载地址 |
| `failed` | `errors` | 批次级失败；通常不返回 `usage` 和 `output_url` |
| `cancelled` | `cancelled_at`、`errors` | 未执行的请求不保证计入 `request_counts.failed` |
| `expired` | `expires_at` | 可能存在部分成功结果；有结果文件时仍可能返回 `usage` 和 `output_url` |

### 通用错误响应

鉴权失败、参数错误、批次不存在或依赖服务异常时返回非 `2xx` 状态码，响应格式为：

```json theme={null}
{
  "error": {
    "code": 5,
    "message": "未找到",
    "details": [
      {
        "code": 5,
        "message": "batch not found"
      }
    ],
    "trace_id": "trace_example"
  }
}
```

`details` 可能为空或不出现。排查问题时应提供 `trace_id`，不要只依赖 `message` 文本判断错误类型。

## 下载批次结果

批次状态变为 `completed` 后，详情响应会返回可下载的 `output_url`：

```json theme={null}
{
  "id": "batch_example",
  "object": "batch",
  "endpoint": "/v1/chat/completions",
  "status": "completed",
  "completion_window": "24h",
  "created_at": 1787237196,
  "expires_at": 1787323596,
  "in_progress_at": 1787237200,
  "completed_at": 1787237300,
  "cancelled_at": null,
  "request_counts": {
    "total": 3,
    "completed": 3,
    "failed": 0
  },
  "usage": {
    "input_tokens": 61,
    "output_tokens": 176,
    "cache_tokens": 0
  },
  "error_file_id": null,
  "output_url": "https://example.cos.ap-beijing.myqcloud.com/path/output.jsonl?sign=...",
  "output_url_expires_at": 1787496500
}
```

`output_url` 是 3 天内有效的临时签名地址，下载时无需再次携带 API Key。地址过期后，重新查询批次详情即可获取新的签名地址。请勿记录或转发完整地址。

```bash theme={null}
curl -fS \
  "<OUTPUT-URL>" \
  -o batch-output.jsonl
```

返回内容为 JSONL 文件，每一行对应一条 prompt。可通过 `custom_id` 与创建批次时的输入关联，并从该行的 `response.body` 读取模型结果。只有创建批次的 API Key 能查询批次详情并获取签名地址。

任务尚未生成结果，或终态下没有可下载结果时，详情响应不会包含 `output_url`。

### 结果文件格式

结果文件不是 JSON 数组，而是 JSONL：每一行都是一条完整、非流式的 Chat Completions 结果。结果行顺序不保证与输入顺序一致，应使用 `custom_id` 关联输入。

普通文本成功结果示例：

```json theme={null}
{
  "custom_id": "review-001",
  "response": {
    "status_code": 200,
    "request_id": "req_example",
    "body": {
      "id": "chatcmpl_example",
      "object": "chat.completion",
      "model": "<MODEL-NAME>",
      "choices": [
        {
          "index": 0,
          "message": {
            "role": "assistant",
            "content": "positive"
          },
          "finish_reason": "stop"
        }
      ],
      "usage": {
        "prompt_tokens": 32,
        "completion_tokens": 2,
        "total_tokens": 34
      }
    }
  },
  "error": null
}
```

普通文本读取路径：

* 结果正文：`response.body.choices[0].message.content`
* Token 用量：`response.body.usage`
* 单条请求状态：`response.status_code`

单条请求失败时，该行的 `response` 为 `null`，错误信息位于 `error`：

```json theme={null}
{
  "custom_id": "review-002",
  "response": null,
  "error": {
    "code": "rate_limit",
    "message": "request failed"
  }
}
```

处理结果时，应分别判断 `response != null` 和 `error != null`。响应中的 `choices[].message` 是完整的非流式结果；Batch API 不返回需要拼接的 SSE `choices[].delta` 事件。

## 创建参数

| 参数 | 类型 | 必填 | 说明 |
| - | - | - | - |
| `model` | string | 是 | 已开通 Batch API 的模型名称 |
| `prompts` | array | 是 | 对话请求数组，长度为 1～10,000 |
| `prompts[].custom_id` | string | 是 | 当前批次内唯一的请求标识，用于关联输入与结果 |
| `prompts[].messages` | array | 是 | 非空的 OpenAI Chat Completions 消息数组 |
| `prompts[].tools` | array | 否 | 当前请求可调用的工具定义，格式与 Chat Completions `tools` 一致 |
| `prompts[].tool_choice` | string/object | 否 | 工具选择策略，例如 `auto`、`none`、`required` 或指定函数 |
| `prompts[].parallel_tool_calls` | boolean | 否 | 是否允许模型在一条响应中生成多个工具调用 |
| `completion_window` | string | 否 | 完成窗口，默认 `24h` |
| `name` | string | 否 | 便于识别批次的名称 |

`prompts` 中除 `custom_id` 外的字段会作为单条 Chat Completions 请求参数处理，例如 `messages`、`temperature`、`max_tokens`、`tools` 和 `tool_choice`。顶层 `model` 会统一应用到批次中的所有请求。

> [!WARNING]
>
> 同一批次内的 `custom_id` 不能重复。创建接口返回的 `batch_id` 用于后续查询和取消，应由业务侧妥善保存。

## 工具调用

需要模型选择工具时，在对应 prompt 中传入 `tools` 和 `tool_choice`：

```json theme={null}
{
  "model": "<MODEL-NAME>",
  "prompts": [
    {
      "custom_id": "weather-001",
      "messages": [
        {
          "role": "user",
          "content": "查询北京今天的天气，并给出出行建议。"
        }
      ],
      "tools": [
        {
          "type": "function",
          "function": {
            "name": "get_weather",
            "description": "查询指定城市的天气",
            "parameters": {
              "type": "object",
              "properties": {
                "city": {
                  "type": "string",
                  "description": "城市名称"
                },
                "date": {
                  "type": "string",
                  "description": "日期，格式为 YYYY-MM-DD"
                }
              },
              "required": ["city"]
            }
          }
        }
      ],
      "tool_choice": "auto",
      "parallel_tool_calls": false
    }
  ]
}
```

模型生成的工具调用位于结果行的 `response.body.choices[].message.tool_calls`。其中 `function.arguments` 是 JSON 字符串，调用方应按工具参数定义解析和校验。

工具调用结果示例：

```json theme={null}
{
  "custom_id": "weather-001",
  "response": {
    "status_code": 200,
    "body": {
      "choices": [
        {
          "index": 0,
          "message": {
            "role": "assistant",
            "content": null,
            "tool_calls": [
              {
                "id": "call_weather_001",
                "type": "function",
                "function": {
                  "name": "get_weather",
                  "arguments": "{\"city\":\"北京\",\"date\":\"2026-08-20\"}"
                }
              }
            ]
          },
          "finish_reason": "tool_calls"
        }
      ],
      "usage": {
        "prompt_tokens": 86,
        "completion_tokens": 18,
        "total_tokens": 104
      }
    }
  },
  "error": null
}
```

> [!WARNING]
>
> Batch API 不会执行工具。平台只返回模型生成的工具名称和参数；调用方需要下载结果、执行工具。如果还需要模型基于工具结果继续生成内容，应创建新的批次，并在对应 prompt 的 `messages` 中依次传入原 assistant `tool_calls` 消息和 `role: "tool"` 的执行结果。

继续处理工具结果时，单条 prompt 的 `messages` 示例：

```json theme={null}
{
  "custom_id": "weather-follow-up-001",
  "messages": [
    {
      "role": "assistant",
      "tool_calls": [
        {
          "id": "call_weather_001",
          "type": "function",
          "function": {
            "name": "get_weather",
            "arguments": "{\"city\":\"北京\",\"date\":\"2026-08-20\"}"
          }
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call_weather_001",
      "content": "{\"temperature\":28,\"condition\":\"晴\"}"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {"type": "string"},
            "date": {"type": "string"}
          },
          "required": ["city"]
        }
      }
    }
  ],
  "tool_choice": "auto"
}
```

是否支持工具调用、并行工具调用以及指定 `tool_choice`，取决于所选模型的能力。

## 状态与计费

| 状态 | 含义 | 是否扣费 |
| - | - | - |
| `validating` | 正在校验批次请求 | 否 |
| `in_progress` | 正在执行批次请求 | 否 |
| `cancelling` | 已请求取消，等待任务停止 | 否 |
| `completed` | 批次执行完成 | 按成功请求的实际 Token 后扣费 |
| `failed` | 批次创建或执行失败 | 否 |
| `cancelled` | 批次已取消 | 否 |
| `expired` | 超过完成窗口 | 按已成功产生的实际 Token 后扣费 |

任务完成后，`request_counts` 给出总请求数、成功数和失败数：

```json theme={null}
{
  "request_counts": {
    "total": 2,
    "completed": 2,
    "failed": 0
  }
}
```

完成响应同时通过 `usage` 返回批次汇总 Token 用量：

```json theme={null}
{
  "usage": {
    "input_tokens": 1400,
    "output_tokens": 400,
    "cache_tokens": 0
  }
}
```

| 字段 | 说明 |
| - | - |
| `input_tokens` | 批次中成功请求的输入 Token 总数 |
| `output_tokens` | 批次中成功请求的输出 Token 总数 |
| `cache_tokens` | 批次命中的缓存 Token 总数 |

`usage` 通常在批次完成后返回。平台根据成功结果的实际 Token 用量完成一次后扣费，客户端重复查询批次不会重复扣费。

## 查询批次列表

```bash theme={null}
curl -sS "https://api.tokenops.ai/v1/batches?limit=20" \
  -H "Authorization: Bearer <API-KEY>"
```

成功返回 HTTP `200`：

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "batch_example",
      "object": "batch",
      "endpoint": "/v1/chat/completions",
      "status": "completed",
      "completion_window": "24h",
      "created_at": 1787237196,
      "expires_at": 1787323596,
      "in_progress_at": 1787237200,
      "completed_at": 1787237300,
      "cancelled_at": null,
      "request_counts": {
        "total": 3,
        "completed": 3,
        "failed": 0
      },
      "usage": {
        "input_tokens": 61,
        "output_tokens": 176,
        "cache_tokens": 0
      },
      "error_file_id": null
    }
  ],
  "first_id": "batch_example",
  "last_id": "batch_example",
  "has_more": false
}
```

| 字段 | 类型 | 说明 |
| - | - | - |
| `object` | string | 固定为 `list` |
| `data` | array | Batch 对象数组，按创建时间倒序排列 |
| `first_id` | string | 当前页第一条批次 ID；空列表时不返回 |
| `last_id` | string | 当前页最后一条批次 ID；空列表时不返回 |
| `has_more` | boolean | 是否可能还有下一页 |

列表项不会返回临时签名 `output_url`。需要下载结果时，使用对应 `id` 查询批次详情以获取新的签名地址。

继续读取下一页时，将上一页的 `last_id` 作为 `after`：

```bash theme={null}
curl -sS "https://api.tokenops.ai/v1/batches?limit=20&after=<LAST-BATCH-ID>" \
  -H "Authorization: Bearer <API-KEY>"
```

列表和详情仅返回当前 API Key 创建的批次。

## 取消批次

```bash theme={null}
curl -sS -X POST \
  -H "Authorization: Bearer <API-KEY>" \
  "https://api.tokenops.ai/v1/batches/<BATCH-ID>/cancel"
```

取消接口可能先返回 `cancelling`，此时可继续查询详情，直到状态变为 `cancelled`。

取消请求成功时返回最新 Batch 对象。任务进入 `cancelled` 后，常见响应为：

```json theme={null}
{
  "id": "batch_example",
  "object": "batch",
  "endpoint": "/v1/chat/completions",
  "status": "cancelled",
  "cancelled_at": 1787237300,
  "request_counts": {
    "total": 3,
    "completed": 0,
    "failed": 0
  },
  "errors": {
    "data": [
      {
        "code": "cancelled_by_user",
        "message": "job cancelled by user"
      }
    ]
  }
}
```

已经进入 `completed`、`failed`、`cancelled` 或 `expired` 的任务不能再次取消。

## 使用建议

* 轮询间隔建议设置为 30 秒，避免高频查询。
* `custom_id` 建议使用业务侧稳定且唯一的标识，便于关联每条结果。
* 业务逻辑应分别处理 `failed`、`cancelled` 和 `expired` 状态。


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