---
title: "小猫零AI API 完整文档"
description: "小猫零AI开放 API 的鉴权、异步任务、参数、响应、计费与错误处理。"
language: "zh-CN"
api_version: "v1"
base_url: "https://www.qqat.cn/api/v1/open"
openapi: "https://www.qqat.cn/openapi.json"
---

# 小猫零AI API

面向业务系统、内容平台和批量工作流的异步文本改写接口。

- **Base URL**：`https://www.qqat.cn/api/v1/open`
- **鉴权方式**：Bearer API Key
- **数据格式**：JSON
- **计费单位**：输入与输出 Unicode 字符数分别计费，API 套餐余额按实际用量扣减
- **API 版本**：v1
- **OpenAPI 3.1**：[https://www.qqat.cn/openapi.json](https://www.qqat.cn/openapi.json)

API 套餐与网页授权独立计费。网页试用次数和网页授权套餐不能用于
API 调用。OpenAPI 文档可以直接导入 Postman、Apifox 或代码生成工具。

## 快速开始

1. 在 API 控制台购买 API 余额。
2. 创建 API Key，并立即保存只显示一次的完整密钥。
3. 调用创建任务接口并保存 `task_id`。
4. 建议每 2–5 秒查询一次任务状态。
5. 遇到 429 时读取 `Retry-After` 后再重试。
6. 任务进入终态后停止轮询；示例最多轮询 60 次、每次间隔
   2 秒，约 120 秒后主动调用取消接口。生产环境可按业务时限调整。

### 完整 Node.js 流程

```javascript
const baseUrl = 'https://www.qqat.cn/api/v1/open'
const apiKey = process.env.QQAT_API_KEY
const terminalStatuses = new Set([
  'succeeded',
  'failed',
  'recoverable_failed',
  'cancelled'
])

const sleep = (milliseconds) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds))

async function request(path, init = {}) {
  while (true) {
    const response = await fetch(baseUrl + path, {
      ...init,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        ...init.headers
      }
    })
    const payload = await response.json()

    if (response.status === 429) {
      const retryAfter = Number(
        response.headers.get('Retry-After') || 2
      )
      await sleep(retryAfter * 1000)
      continue
    }
    if (!response.ok) {
      throw new Error(
        `${payload.error?.code || response.status}: `
        + `${payload.error?.message || '请求失败'}`
      )
    }
    return payload
  }
}

const created = await request('/rewrite/tasks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID()
  },
  body: JSON.stringify({
    source_text: '需要处理的原始文本',
    intensity: 5
  })
})

const taskId = created.data.task_id

for (let attempt = 0; attempt < 60; attempt += 1) {
  const current = await request(
    '/rewrite/tasks/' + encodeURIComponent(taskId)
  )
  const { status, result_text: resultText } = current.data

  if (status === 'succeeded') {
    console.log(resultText)
    process.exit(0)
  }
  if (terminalStatuses.has(status)) {
    throw new Error(`任务以 ${status} 状态结束`)
  }
  await sleep(2000)
}

await request(
  '/rewrite/tasks/' + encodeURIComponent(taskId) + '/cancel',
  { method: 'POST' }
)
throw new Error('等待任务超时，已请求取消。')
```

Node.js 示例要求 14.17 或更高版本以使用 `crypto.randomUUID()`；更早版本请使用可靠的 UUID 库。

### 完整 Python 流程

```python
import os
import time
import uuid
import requests

BASE_URL = 'https://www.qqat.cn/api/v1/open'
API_KEY = os.environ['QQAT_API_KEY']
TERMINAL_STATUSES = {
    'succeeded',
    'failed',
    'recoverable_failed',
    'cancelled',
}
session = requests.Session()
session.headers['Authorization'] = f'Bearer {API_KEY}'

def request(method, path, **kwargs):
    while True:
        response = session.request(
            method,
            BASE_URL + path,
            timeout=30,
            **kwargs,
        )
        payload = response.json()
        if response.status_code == 429:
            time.sleep(
                int(response.headers.get('Retry-After', '2'))
            )
            continue
        if not response.ok:
            error = payload.get('error') or {}
            raise RuntimeError(
                f"{error.get('code', response.status_code)}: "
                f"{error.get('message', '请求失败')}"
            )
        return payload

created = request(
    'POST',
    '/rewrite/tasks',
    headers={'Idempotency-Key': str(uuid.uuid4())},
    json={
        'source_text': '需要处理的原始文本',
        'intensity': 5,
    },
)
task_id = created['data']['task_id']

for _ in range(60):
    current = request('GET', f'/rewrite/tasks/{task_id}')
    status = current['data']['status']
    if status == 'succeeded':
        print(current['data']['result_text'])
        break
    if status in TERMINAL_STATUSES:
        raise RuntimeError(f'任务以 {status} 状态结束')
    time.sleep(2)
else:
    request('POST', f'/rewrite/tasks/{task_id}/cancel')
    raise TimeoutError('等待任务超时，已请求取消。')
```

Python 示例中的 `requests.Session` 仅在当前调用流程内复用。多线程服务应为每个线程或请求上下文分别创建 Session。

## 鉴权与安全

所有开放接口必须携带以下请求头：

```http
Authorization: Bearer qqat_live_<public_id>.<secret>
```

- API Key 只应保存在服务端密钥管理系统。
- 不要把完整密钥写入浏览器代码、移动端安装包、URL 或公开仓库。
- 生产与测试环境使用不同密钥。
- 生产密钥建议配置固定出口 IP 或 CIDR 白名单。
- 日志只能记录 Key 名称或 Public ID，禁止记录完整 Authorization。
- 密钥轮换后旧密钥立即失效。

## 创建改写任务

按输入字符和预计输出字符预冻结 API 余额并创建异步改写任务。创建成功只表示任务已被受理，需要继续查询任务状态。

- **方法**：`POST`
- **路径**：`/rewrite/tasks`
- **成功状态**：`202 Accepted`
- **所需权限**：`rewrite:create`
- **限流**：按账号套餐配置，多个 API Key 共享

### 请求参数

| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `Authorization` | header | string | 是 | — | Bearer API Key。 |
| `Content-Type` | header | string | 是 | application/json | 请求体使用 JSON 编码。 |
| `Idempotency-Key` | header | string | 是 | — | 1–128 个字符；网络重试时必须复用原值，不同请求内容必须使用新值。 |
| `source_text` | body | string | 是 | — | 非空原文，绝对上限 200000 个 Unicode 字符；套餐可能设置更低的单次输入字符上限。 |
| `intensity` | body | integer | 否 | 5 | 改写强度，允许范围 0–9。 |

### 请求体

```json
{
  "source_text": "需要降低 AI 痕迹的原始文本",
  "intensity": 5
}
```

### 请求示例

### cURL

```bash
curl --request POST               --url 'https://www.qqat.cn/api/v1/open/rewrite/tasks'               --header "Authorization: Bearer ${QQAT_API_KEY}"               --header 'Content-Type: application/json'               --header "Idempotency-Key: $(uuidgen)"               --data '{
    "source_text": "需要处理的原始文本",
    "intensity": 5
  }'
```

### Node.js

```javascript
const apiKey = process.env.QQAT_API_KEY

const response = await fetch(
  'https://www.qqat.cn/api/v1/open/rewrite/tasks',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': crypto.randomUUID()
    },
    body: JSON.stringify({
      source_text: '需要处理的原始文本',
      intensity: 5
    })
  }
)

const payload = await response.json()
if (!response.ok) {
  throw new Error(
    `${payload.error?.code}: ${payload.error?.message}`
  )
}
console.log(payload.data.task_id)
```

### Python

```python
import os
import uuid
import requests

response = requests.post(
    'https://www.qqat.cn/api/v1/open/rewrite/tasks',
    headers={
        'Authorization': (
            f"Bearer {os.environ['QQAT_API_KEY']}"
        ),
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json={
        'source_text': '需要处理的原始文本',
        'intensity': 5,
    },
    timeout=30,
)
response.raise_for_status()
print(response.json()['data']['task_id'])
```

### 成功响应

```json
{
  "ok": true,
  "data": {
    "task_id": "69ce4fe0-your-task-id",
    "status": "queued",
    "result_text": null,
    "source_chars": 18,
    "result_chars": 0,
    "intensity": 5,
    "usage": {
      "id": "usage-request-id",
      "task_id": "69ce4fe0-your-task-id",
      "api_key_id": "api-key-id",
      "idempotency_key": "c73e79de-example",
      "source_chars": 18,
      "input_chars": 18,
      "output_chars": 0,
      "total_chars": 0,
      "reserved_chars": 0,
      "billed_chars": 0,
      "billing_version": 2,
      "input_price_micros_per_10k_chars": 1000000,
      "output_price_micros_per_10k_chars": 3000000,
      "reserved_micros": 12600,
      "input_cost_micros": 0,
      "output_cost_micros": 0,
      "billed_micros": 0,
      "reserved_amount": "0.012600",
      "input_amount": "0.000000",
      "output_amount": "0.000000",
      "total_amount": "0.000000",
      "currency": "CNY",
      "status": "queued",
      "client_ip": "203.0.113.10",
      "error_code": null,
      "error_message": null,
      "started_at": null,
      "finished_at": null,
      "created_at": "2026-08-28T09:00:00+08:00",
      "updated_at": "2026-08-28T09:00:00+08:00"
    },
    "reused": false
  },
  "message": "任务已接受。",
  "error": null,
  "request_id": "request-id"
}
```

### 响应字段

| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `task_id` | response | string | 是 | — | 任务唯一标识。 |
| `status` | response | string | 是 | — | 任务当前状态；进入终态后停止轮询。 |
| `result_text` | response | string \| null | 是 | null | 仅 succeeded 状态返回完整结果。 |
| `source_chars` | response | integer | 是 | — | 输入字符数。 |
| `result_chars` | response | integer | 是 | 0 | 输出字符数，任务未完成时为 0。 |
| `intensity` | response | integer \| null | 是 | — | 任务实际使用的改写强度。 |
| `usage` | response | object \| null | 是 | — | 本次调用的余额预冻结、输入输出费用、错误和时间信息。 |
| `usage.input_chars` | response | integer | 是 | 0 | 本次计费的输入 Unicode 字符数。 |
| `usage.output_chars` | response | integer | 是 | 0 | 已经生成并参与结算的输出 Unicode 字符数。 |
| `usage.total_chars` | response | integer | 是 | 0 | 最终参与计费的输入字符数与输出字符数之和。 |
| `usage.billed_chars` | response | integer | 是 | 0 | 兼容字段；V2 与 total_chars 相同，不可单独用于推算费用。 |
| `usage.billing_version` | response | integer | 是 | 2 | 计费规则版本标识；请以响应中的费用字段为准。 |
| `usage.reserved_amount` | response | string | 是 | 0.000000 | 当前预冻结金额，单位为人民币元。 |
| `usage.input_amount` | response | string | 是 | 0.000000 | 实际输入费用，单位为人民币元。 |
| `usage.output_amount` | response | string | 是 | 0.000000 | 实际输出费用，单位为人民币元。 |
| `usage.total_amount` | response | string | 是 | 0.000000 | 本次任务最终结算总费用，单位为人民币元。 |
| `usage.currency` | response | string | 是 | CNY | 计费币种，当前固定为 CNY。 |
| `reused` | response | boolean | 是 | false | 是否命中相同幂等请求并复用原任务。 |

## 查询改写任务

查询当前 API Key 创建的任务。任务成功后返回完整结果文本、输入输出字符数和费用明细。

- **方法**：`GET`
- **路径**：`/rewrite/tasks/{task_id}`
- **成功状态**：`200 OK`
- **所需权限**：`rewrite:read`
- **限流**：默认 300 次/分钟

### 请求参数

| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `Authorization` | header | string | 是 | — | Bearer API Key。 |
| `task_id` | path | string | 是 | — | 创建任务接口返回的任务唯一标识。 |

### 请求示例

### cURL

```bash
TASK_ID='69ce4fe0-your-task-id'

curl --request GET               --url 'https://www.qqat.cn/api/v1/open/rewrite/tasks/'"${TASK_ID}"               --header "Authorization: Bearer ${QQAT_API_KEY}"
```

### Node.js

```javascript
const apiKey = process.env.QQAT_API_KEY
const taskId = '69ce4fe0-your-task-id'

const response = await fetch(
  'https://www.qqat.cn/api/v1/open/rewrite/tasks/' + encodeURIComponent(taskId),
  {
    headers: {
      Authorization: `Bearer ${apiKey}`
    }
  }
)

const payload = await response.json()
if (!response.ok) {
  throw new Error(
    `${payload.error?.code}: ${payload.error?.message}`
  )
}
console.log(payload.data.status, payload.data.result_text)
```

### Python

```python
import os
import requests

task_id = '69ce4fe0-your-task-id'
response = requests.get(
    f'https://www.qqat.cn/api/v1/open/rewrite/tasks/{task_id}',
    headers={
        'Authorization': (
            f"Bearer {os.environ['QQAT_API_KEY']}"
        ),
    },
    timeout=30,
)
response.raise_for_status()
payload = response.json()
print(payload['data']['status'], payload['data']['result_text'])
```

### 成功响应

```json
{
  "ok": true,
  "data": {
    "task_id": "69ce4fe0-your-task-id",
    "status": "succeeded",
    "result_text": "处理完成后的完整文本",
    "source_chars": 18,
    "result_chars": 16,
    "intensity": 5,
    "usage": {
      "id": "usage-request-id",
      "task_id": "69ce4fe0-your-task-id",
      "api_key_id": "api-key-id",
      "idempotency_key": "c73e79de-example",
      "source_chars": 18,
      "input_chars": 18,
      "output_chars": 16,
      "total_chars": 34,
      "reserved_chars": 0,
      "billed_chars": 34,
      "billing_version": 2,
      "input_price_micros_per_10k_chars": 1000000,
      "output_price_micros_per_10k_chars": 3000000,
      "reserved_micros": 0,
      "input_cost_micros": 1800,
      "output_cost_micros": 4800,
      "billed_micros": 6600,
      "reserved_amount": "0.000000",
      "input_amount": "0.001800",
      "output_amount": "0.004800",
      "total_amount": "0.006600",
      "currency": "CNY",
      "status": "succeeded",
      "client_ip": "203.0.113.10",
      "error_code": null,
      "error_message": null,
      "started_at": "2026-08-28T09:00:02+08:00",
      "finished_at": "2026-08-28T09:00:18+08:00",
      "created_at": "2026-08-28T09:00:00+08:00",
      "updated_at": "2026-08-28T09:00:18+08:00"
    }
  },
  "message": "",
  "error": null,
  "request_id": "request-id"
}
```

### 响应字段

| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `task_id` | response | string | 是 | — | 任务唯一标识。 |
| `status` | response | string | 是 | — | 任务当前状态；进入终态后停止轮询。 |
| `result_text` | response | string \| null | 是 | null | 仅 succeeded 状态返回完整结果。 |
| `source_chars` | response | integer | 是 | — | 输入字符数。 |
| `result_chars` | response | integer | 是 | 0 | 输出字符数，任务未完成时为 0。 |
| `intensity` | response | integer \| null | 是 | — | 任务实际使用的改写强度。 |
| `usage` | response | object \| null | 是 | — | 本次调用的余额预冻结、输入输出费用、错误和时间信息。 |
| `usage.input_chars` | response | integer | 是 | 0 | 本次计费的输入 Unicode 字符数。 |
| `usage.output_chars` | response | integer | 是 | 0 | 已经生成并参与结算的输出 Unicode 字符数。 |
| `usage.total_chars` | response | integer | 是 | 0 | 最终参与计费的输入字符数与输出字符数之和。 |
| `usage.billed_chars` | response | integer | 是 | 0 | 兼容字段；V2 与 total_chars 相同，不可单独用于推算费用。 |
| `usage.billing_version` | response | integer | 是 | 2 | 计费规则版本标识；请以响应中的费用字段为准。 |
| `usage.reserved_amount` | response | string | 是 | 0.000000 | 当前预冻结金额，单位为人民币元。 |
| `usage.input_amount` | response | string | 是 | 0.000000 | 实际输入费用，单位为人民币元。 |
| `usage.output_amount` | response | string | 是 | 0.000000 | 实际输出费用，单位为人民币元。 |
| `usage.total_amount` | response | string | 是 | 0.000000 | 本次任务最终结算总费用，单位为人民币元。 |
| `usage.currency` | response | string | 是 | CNY | 计费币种，当前固定为 CNY。 |

## 取消改写任务

取消尚未完成的任务。终态任务重复取消会幂等返回当前状态，不会重复结算费用。

- **方法**：`POST`
- **路径**：`/rewrite/tasks/{task_id}/cancel`
- **成功状态**：`200 OK`
- **所需权限**：`rewrite:cancel`
- **限流**：默认 60 次/分钟

### 请求参数

| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `Authorization` | header | string | 是 | — | Bearer API Key。 |
| `task_id` | path | string | 是 | — | 创建任务接口返回的任务唯一标识。 |

### 请求示例

### cURL

```bash
TASK_ID='69ce4fe0-your-task-id'

curl --request POST               --url 'https://www.qqat.cn/api/v1/open/rewrite/tasks/'"${TASK_ID}"'/cancel'               --header "Authorization: Bearer ${QQAT_API_KEY}"
```

### Node.js

```javascript
const apiKey = process.env.QQAT_API_KEY
const taskId = '69ce4fe0-your-task-id'

const response = await fetch(
  'https://www.qqat.cn/api/v1/open/rewrite/tasks/'
    + encodeURIComponent(taskId)
    + '/cancel',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`
    }
  }
)

const payload = await response.json()
if (!response.ok) {
  throw new Error(
    `${payload.error?.code}: ${payload.error?.message}`
  )
}
console.log(payload.data.status)
```

### Python

```python
import os
import requests

task_id = '69ce4fe0-your-task-id'
response = requests.post(
    f'https://www.qqat.cn/api/v1/open/rewrite/tasks/{task_id}/cancel',
    headers={
        'Authorization': (
            f"Bearer {os.environ['QQAT_API_KEY']}"
        ),
    },
    timeout=30,
)
response.raise_for_status()
print(response.json()['data']['status'])
```

### 成功响应

```json
{
  "ok": true,
  "data": {
    "task_id": "69ce4fe0-your-task-id",
    "status": "cancelled",
    "result_text": null,
    "source_chars": 18,
    "result_chars": 0,
    "intensity": 5,
    "usage": {
      "id": "usage-request-id",
      "task_id": "69ce4fe0-your-task-id",
      "api_key_id": "api-key-id",
      "idempotency_key": "c73e79de-example",
      "source_chars": 18,
      "input_chars": 18,
      "output_chars": 0,
      "total_chars": 0,
      "reserved_chars": 0,
      "billed_chars": 0,
      "billing_version": 2,
      "input_price_micros_per_10k_chars": 1000000,
      "output_price_micros_per_10k_chars": 3000000,
      "reserved_micros": 0,
      "input_cost_micros": 0,
      "output_cost_micros": 0,
      "billed_micros": 0,
      "reserved_amount": "0.000000",
      "input_amount": "0.000000",
      "output_amount": "0.000000",
      "total_amount": "0.000000",
      "currency": "CNY",
      "status": "cancelled",
      "client_ip": "203.0.113.10",
      "error_code": null,
      "error_message": null,
      "started_at": null,
      "finished_at": "2026-08-28T09:00:18+08:00",
      "created_at": "2026-08-28T09:00:00+08:00",
      "updated_at": "2026-08-28T09:00:18+08:00"
    }
  },
  "message": "已请求取消任务。",
  "error": null,
  "request_id": "request-id"
}
```

### 响应字段

| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `task_id` | response | string | 是 | — | 任务唯一标识。 |
| `status` | response | string | 是 | — | 任务当前状态；进入终态后停止轮询。 |
| `result_text` | response | string \| null | 是 | null | 仅 succeeded 状态返回完整结果。 |
| `source_chars` | response | integer | 是 | — | 输入字符数。 |
| `result_chars` | response | integer | 是 | 0 | 输出字符数，任务未完成时为 0。 |
| `intensity` | response | integer \| null | 是 | — | 任务实际使用的改写强度。 |
| `usage` | response | object \| null | 是 | — | 本次调用的余额预冻结、输入输出费用、错误和时间信息。 |
| `usage.input_chars` | response | integer | 是 | 0 | 本次计费的输入 Unicode 字符数。 |
| `usage.output_chars` | response | integer | 是 | 0 | 已经生成并参与结算的输出 Unicode 字符数。 |
| `usage.total_chars` | response | integer | 是 | 0 | 最终参与计费的输入字符数与输出字符数之和。 |
| `usage.billed_chars` | response | integer | 是 | 0 | 兼容字段；V2 与 total_chars 相同，不可单独用于推算费用。 |
| `usage.billing_version` | response | integer | 是 | 2 | 计费规则版本标识；请以响应中的费用字段为准。 |
| `usage.reserved_amount` | response | string | 是 | 0.000000 | 当前预冻结金额，单位为人民币元。 |
| `usage.input_amount` | response | string | 是 | 0.000000 | 实际输入费用，单位为人民币元。 |
| `usage.output_amount` | response | string | 是 | 0.000000 | 实际输出费用，单位为人民币元。 |
| `usage.total_amount` | response | string | 是 | 0.000000 | 本次任务最终结算总费用，单位为人民币元。 |
| `usage.currency` | response | string | 是 | CNY | 计费币种，当前固定为 CNY。 |

## 异步状态、生命周期、幂等与计费

### 任务状态

| 状态 | 含义 | 终态 | 计费结果 |
| --- | --- | --- | --- |
| queued | 排队中 | 否 | 已冻结输入费用和预计输出费用。 |
| waiting_worker | 等待可用工作节点 | 否 | 继续保留预冻结余额。 |
| running | 正在执行改写 | 否 | 继续保留预冻结余额。 |
| cancel_requested | 已收到取消请求，正在终止 | 否 | 尚未完成最终结算。 |
| succeeded | 改写成功 | 是 | 按实际输入字符和输出字符结算。 |
| failed | 不可恢复失败 | 是 | 不扣费，预冻结余额全部释放。 |
| recoverable_failed | 可重新提交的临时失败 | 是 | 不扣费，预冻结余额全部释放。 |
| cancelled | 任务已取消 | 是 | 执行前取消不扣费；进入执行后按实际输入字符和已生成输出字符结算。 |

进入任何终态后都应停止轮询。`recoverable_failed` 表示本次
未扣费且额度已释放，可以使用新的 Idempotency-Key 重新创建任务。

当前 API 仅支持轮询查询任务状态，不提供 webhook 回调。建议
每 2–5 秒查询一次，遇到 429 时严格遵循 `Retry-After`。

### 任务保留与结果持久化

当前版本未设置公开的 task_id 查询过期窗口，任务不会因固定天数
自动变为 404。调用方仍应在任务进入终态后立即持久化结果和
request_id，不要把查询接口当作长期内容存储。

### 幂等规则

| 场景 | 行为 |
| --- | --- |
| 相同 Key + 相同内容 | 返回原任务并设置 `reused: true`。 |
| 相同 Key + 不同内容 | 返回 409 `IDEMPOTENCY_CONFLICT`。 |
| Key 有效期 | 当前版本不自动过期；同一 API Key 下不要把已经使用过的 Key 分配给新的业务请求。 |

### 计费规则

输入和输出都按 Unicode 字符数计费，每个汉字、英文字母、数字、
标点或空格均按 1 个字符计算。当前输入单价为
`¥1.000000 / 1万字符`，
输出单价为
`¥3.000000 / 1万字符`。

创建任务时会冻结输入费用，并按输入字符数的
`2 倍`
预计输出冻结输出费用。`succeeded` 按实际输入和输出字符结算，
多冻结的余额自动释放；`failed` 和 `recoverable_failed` 不扣费。
`cancelled` 在任务尚未进入执行阶段时释放全部冻结余额，进入执行
阶段后按实际输入字符和已经生成的输出字符结算。取消已进入终态
的任务只返回当前状态，不会重复结算。

计算公式为 `ceil(字符数 × 每万字符单价 / 10000)`。金额以人民币
计价，金额字段以人民币元字符串返回并保留 6 位小数；如果使用
`*_micros` 字段，请按 `1 元 = 1,000,000 micros` 换算。
API 余额从支付成功发放时开始计算有效期。余额到期后创建任务返回
402 `API_CREDIT_EXPIRED`，购买新套餐后恢复调用。

API 余额、套餐限制和并发上限可在 API 控制台查看；当前未提供使用 API Key 鉴权的余额查询接口。

### 当前可购套餐

套餐的“价格”是购买时实际支付的金额，“到账余额”是调用时用于扣费的余额，不是固定调用次数或固定字符包。下表列出当前可用的 API 套餐及调用限制，具体以购买时页面显示为准。

| 套餐 | 支付价格 | 到账余额（用于扣费） | 有效期 | 每分钟新建任务 | 每日新建任务 | 单次最多输入 | 同时处理任务 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| API 入门包 | ¥59.00 | ¥59.000000 | 365 天 | 10 次/分钟 | 200 次/日 | 20000 字符 | 1 个任务 |
| API 标准包 | ¥249.00 | ¥295.000000 | 365 天 | 20 次/分钟 | 1000 次/日 | 50000 字符 | 1 个任务 |
| API 专业包 | ¥799.00 | ¥1180.000000 | 365 天 | 30 次/分钟 | 3000 次/日 | 50000 字符 | 1 个任务 |
| API 企业包 | ¥2999.00 | ¥5900.000000 | 365 天 | 60 次/分钟 | 10000 次/日 | 50000 字符 | 1 个任务 |

### 默认限制

| 限制项 | 基础默认值 | 说明 |
| --- | --- | --- |
| 每分钟新建任务 | 按账号套餐 | 多个 API Key 共享套餐内的创建频率限制，不是查询次数。 |
| 查询任务 | 300 次/分钟 | 按账号、API Key 与来源 IP 同时限制。 |
| 取消任务 | 60 次/分钟 | 按账号、API Key 与来源 IP 同时限制。 |
| 每日新建任务 | 按账号套餐 | 所有 API Key 共享，当日已受理的新任务计入套餐限制，不统计查询和取消。 |
| 同时处理任务 | 按账号套餐 | 统计预占、排队和运行中的任务。 |
| 单次最多输入 | 按账号套餐 | 限制 source_text 输入字符数，系统绝对上限为 200000 字符。 |

### 响应头

| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `X-RateLimit-Limit` | response | integer | 是 | — | 当前操作在统计窗口内允许的请求总数。 |
| `X-RateLimit-Remaining` | response | integer | 是 | — | 当前统计窗口内剩余的请求数。 |
| `X-RateLimit-Reset` | response | unix timestamp | 是 | — | 当前限流窗口重置时间，单位为秒。 |
| `Retry-After` | response | integer | 否 | — | 限流响应返回，表示建议等待的秒数。 |
| `X-Request-ID` | response | string | 是 | — | 请求追踪标识，排查问题时请提供给客服。 |
| `X-Response-Time-Ms` | response | integer | 是 | — | 服务端处理耗时，单位为毫秒；这是小猫零AI自定义扩展响应头。 |
| `Cache-Control` | response | string | 是 | no-store | 开放接口响应禁止缓存。 |

## 最佳实践

- 查询间隔保持在 2–5 秒，禁止高频轮询。
- 大文本按自然段或业务边界拆分，并为每个子任务生成独立幂等键。
- 仅在网络错误、429 和临时性 5xx 时自动重试；使用带抖动的指数退避。
- 创建任务重试必须复用原 `Idempotency-Key`，新业务请求必须生成新值。
- 任务进入终态后立即保存结果、task_id 和 request_id。
- 日志只记录 request_id、API Key 名称或 Public ID，禁止记录完整密钥和原文。

## 错误处理

业务判断应优先读取 `error.code`，不要依赖错误文案。429 应遵循 `Retry-After`；5xx 可使用带抖动的指数退避，并设置最大重试次数。

| HTTP | 错误码 | 自动重试 | 处理建议 |
| --- | --- | --- | --- |
| 400 | IDEMPOTENCY_KEY_REQUIRED | 否 | 不要提交空白幂等键。 |
| 400 | INVALID_IDEMPOTENCY_KEY | 否 | 使用 1–128 个字符，建议使用 UUID。 |
| 400 | SOURCE_TEXT_REQUIRED | 否 | 提交非空原文。 |
| 400 | API_REQUEST_TOO_LARGE | 否 | 拆分文本或升级套餐。 |
| 401 | INVALID_API_KEY | 否 | 检查密钥、过期时间和轮换状态。 |
| 402 | INSUFFICIENT_API_BALANCE | 否 | 购买套餐补充 API 余额。 |
| 402 | INSUFFICIENT_API_CREDIT | 否 | 旧计费账户字符额度不足；购买新套餐后切换为余额计费。 |
| 402 | API_CREDIT_EXPIRED | 否 | 购买新套餐恢复可用额度。 |
| 403 | API_ACCOUNT_DISABLED | 否 | 确认所属账号状态。 |
| 403 | API_ACCESS_BLOCKED | 否 | 联系支持团队核查账号状态或访问限制。 |
| 403 | API_IP_FORBIDDEN | 否 | 检查 IP 白名单和代理出口。 |
| 403 | API_SCOPE_FORBIDDEN | 否 | 为密钥开通当前接口所需权限。 |
| 404 | API_TASK_NOT_FOUND | 否 | 检查任务 ID 和创建任务的 API Key。 |
| 409 | IDEMPOTENCY_CONFLICT | 否 | 为不同内容生成新的幂等键。 |
| 409 | API_TASK_STATE_INVALID | 否 | 重新查询任务状态后再操作。 |
| 422 | INVALID_REQUEST | 否 | 检查字段类型、长度、必填项和允许范围。 |
| 429 | API_RATE_LIMITED | 是 | 按 Retry-After 退避重试。 |
| 429 | API_DAILY_LIMITED | 否 | 等待次日额度恢复或升级套餐。 |
| 429 | API_CONCURRENCY_LIMITED | 是 | 等待运行中任务结束后重试。 |
| 429 | API_OPERATION_RATE_LIMITED | 是 | 降低查询或取消频率，并遵循 Retry-After。 |
| 500 | INTERNAL_ERROR | 是 | 保留 request_id，退避后有限次数重试。 |

### 错误响应

```json
{
  "ok": false,
  "data": {},
  "message": "",
  "error": {
    "code": "INSUFFICIENT_API_BALANCE",
    "message": "API 余额不足，请充值后重试。"
  },
  "request_id": "request-id"
}
```

仅自动重试网络错误、429 和临时性 5xx。400、401、402、403、
404、409、422 应修正请求或账号状态后再调用。创建任务重试
必须复用原 `Idempotency-Key`。
