# V2 API Response Schema 细稿 — Batch Auto-Import

日期：2026-05-22  
关联文档：

- `docs/2026-05-21-BATCH_AUTO_IMPORT_SPEC.md`
- `docs/2026-05-21-BATCH_AUTO_IMPORT_TDD_PLAN.md`
- `docs/2026-05-22-BATCH_AUTO_IMPORT_V2_ARCHITECTURE.md`
- `docs/openapi.yaml`

## 1. 目标

这份文档把 V2 结果 API 细化到 handler 和前端都可直接对照实现的粒度。

覆盖：

- 路由
- query/filter
- response schema
- error schema
- badge / 文案映射
- 示例 JSON

## 2. 资源模型

V2 API 只暴露 3 层资源：

1. `run`
2. `run items`
3. `run item events`

不直接暴露：

- 宿主数据库结构
- `import_batches` 细节
- legacy repo 的内部实现字段

## 3. Endpoint 列表

### 3.1 创建 run

`POST /api/batch-import/runs`

作用：

- 创建 V2 run
- 立即返回 `run_id`
- 可选等待一个短暂 confirm window，但不保证全部 item 已 done

### 3.2 列表 runs

`GET /api/batch-import/runs`

作用：

- 查看最近导入批次
- 支持状态筛选和关键词搜索

### 3.3 run 详情

`GET /api/batch-import/runs/{run_id}`

作用：

- 返回 run summary
- 返回汇总告警摘要

### 3.4 item 列表

`GET /api/batch-import/runs/{run_id}/items`

作用：

- 展示批次内各 URL/provider 的状态
- 支持按 stage/access/warning 过滤

### 3.5 item 详情

`GET /api/batch-import/runs/{run_id}/items/{item_id}`

作用：

- 返回一个 item 的全量投影
- 包括 capability、模型纠错、event trail

## 4. Query 参数约定

### 4.1 `GET /api/batch-import/runs`

支持：

- `state`
  - `running`
  - `completed`
  - `completed_with_warnings`
  - `failed`
  - `cancelled`
- `access_mode`
  - `subscription`
  - `self_service`
- `q`
  - 搜索 `run_id / provider_id / base_url`
- `limit`
- `cursor`

说明：

- 页面显示可以把 `completed_with_warnings` 渲染成 `warning`
- API 不应引入第二套 `warning` 枚举

### 4.2 `GET /api/batch-import/runs/{run_id}/items`

支持：

- `current_stage`
  - `probe`
  - `provision`
  - `confirm`
  - `validate`
  - `done`
- `confirmation_status`
  - `pending`
  - `confirmed`
  - `advisory`
  - `failed`
- `access_status`
  - `unknown`
  - `active`
  - `degraded`
  - `broken`
- `has_warning`
  - `true/false`
- `provider_id`
- `matched_account_state`
- `account_resolution`
- `q`
  - 搜索 `provider_id / base_url / item_id`
- `limit`
- `cursor`

## 5. 通用错误结构

建议统一：

```json
{
  "error": {
    "code": "invalid_request",
    "message": "subscription_users is required when access_mode=subscription"
  }
}
```

常用 `code`：

- `invalid_request`
- `not_found`
- `conflict`
- `internal_error`

## 6. 响应 Schema

### 6.1 `POST /api/batch-import/runs`

```json
{
  "run_id": "run_20260522_0001",
  "state": "running",
  "result_page": "/batch-import/runs/run_20260522_0001",
  "total_items": 2,
  "active_items": 0,
  "degraded_items": 0,
  "broken_items": 0,
  "warning_items": 0
}
```

约束：

- 这是 `run created` 语义，不是 final result
- `state` 初始一般是 `running`

### 6.2 `GET /api/batch-import/runs`

```json
{
  "runs": [
    {
      "run_id": "run_20260522_0001",
      "state": "completed_with_warnings",
      "mode": "partial",
      "access_mode": "subscription",
      "total_items": 2,
      "completed_items": 2,
      "active_items": 1,
      "degraded_items": 1,
      "broken_items": 0,
      "warning_items": 1,
      "started_at": "2026-05-22T12:20:00+08:00",
      "finished_at": "2026-05-22T12:20:07+08:00"
    }
  ],
  "next_cursor": null
}
```

### 6.3 `GET /api/batch-import/runs/{run_id}`

```json
{
  "run": {
    "run_id": "run_20260522_0001",
    "state": "completed_with_warnings",
    "mode": "partial",
    "access_mode": "subscription",
    "total_items": 2,
    "completed_items": 2,
    "active_items": 1,
    "degraded_items": 1,
    "broken_items": 0,
    "warning_items": 1,
    "started_at": "2026-05-22T12:20:00+08:00",
    "finished_at": "2026-05-22T12:20:07+08:00"
  },
  "recent_warnings": [
    "该批次包含 1 条 advisory item，建议检查 capability profile 与 retry 轨迹"
  ]
}
```

### 6.4 `GET /api/batch-import/runs/{run_id}/items`

```json
{
  "items": [
    {
      "item_id": "item_01",
      "base_url": "https://kimi.a7m.com.cn/v1",
      "provider_id": "kimi-a7m-7d7ac291",
      "api_key_fingerprint": "sha256:8d8c4b5f",
      "requested_models": ["kimi-k2.6"],
      "canonical_model_families": ["kimi-k2.6"],
      "resolved_smoke_model": "kimi-k2.6",
      "current_stage": "done",
      "confirmation_status": "advisory",
      "access_status": "active",
      "matched_account_state": "active",
      "account_resolution": "reused",
      "provision_reused": true,
      "retry_count": 2,
      "last_retry_at": "2026-05-22T12:20:05+08:00",
      "advisory_messages": [
        "该上游不支持 /v1/responses，系统已自动回退到 /v1/chat/completions"
      ],
      "last_error_stage": "confirm",
      "last_error": "API returned 403: Forbidden"
    }
  ],
  "next_cursor": null
}
```

### 6.5 `GET /api/batch-import/runs/{run_id}/items/{item_id}`

```json
{
  "item_id": "item_01",
  "base_url": "https://kimi.a7m.com.cn/v1",
  "provider_id": "kimi-a7m-7d7ac291",
  "api_key_fingerprint": "sha256:8d8c4b5f",
  "requested_models": ["kimi-k2.6"],
  "raw_models": ["kimi-k2.6"],
  "normalized_models": ["kimi-k2.6"],
  "canonical_model_families": ["kimi-k2.6"],
  "recommended_models": [],
  "resolved_smoke_model": "kimi-k2.6",
  "current_stage": "done",
  "confirmation_status": "advisory",
  "access_status": "active",
  "matched_account_state": "deprecated",
  "account_resolution": "reactivated",
  "provision_reused": true,
  "reused_from_provider_id": "kimi-a7m-7d7ac291",
  "reused_from_account_id": 4,
  "retry_count": 2,
  "last_retry_at": "2026-05-22T12:20:05+08:00",
  "channel_id": 12,
  "account_id": 4,
  "advisory_messages": [
    "该上游不支持 /v1/responses，系统已自动回退到 /v1/chat/completions"
  ],
  "last_error_stage": "confirm",
  "last_error": "API returned 403: Forbidden",
  "capability_profile": {
    "transport_profile": {
      "supports_openai_models": true,
      "supports_openai_chat_completions": true,
      "supports_openai_responses": false,
      "supports_anthropic_messages": false,
      "auth_style": "bearer",
      "model_id_style": "canonical",
      "known_advisories": [
        "responses_unsupported_but_chat_ok",
        "initial_probe_race_expected"
      ]
    },
    "model_profiles": [
      {
        "raw_model_id": "kimi-k2.6",
        "normalized_model_id": "kimi-k2.6",
        "canonical_model_family": "kimi-k2.6",
        "supports_stream": true,
        "supports_tools": "unknown",
        "supports_reasoning_fields": "unknown",
        "smoke_chat_ok": true
      }
    ]
  },
  "events": [
    {
      "event_id": "evt_01",
      "event_type": "retry_scheduled",
      "stage": "confirm",
      "attempt": 1,
      "message": "initial 503 no available accounts, retry scheduled",
      "payload_json": "{\"delay_ms\":500}",
      "created_at": "2026-05-22T12:20:04+08:00"
    }
  ]
}
```

## 7. Badge / 文案映射

### 7.1 Run state badge

| API 值 | 页面 badge |
|---|---|
| `running` | 蓝色 `running` |
| `completed` | 绿色 `completed` |
| `completed_with_warnings` | 黄色 `warning` |
| `failed` | 红色 `failed` |
| `cancelled` | 灰色 `cancelled` |

### 7.2 Confirmation badge

| API 值 | 页面 badge |
|---|---|
| `pending` | 蓝色 `pending` |
| `confirmed` | 绿色 `confirmed` |
| `advisory` | 黄色 `advisory` |
| `failed` | 红色 `failed` |

### 7.3 Access badge

| API 值 | 页面 badge |
|---|---|
| `unknown` | 灰色 `unknown` |
| `active` | 绿色 `active` |
| `degraded` | 黄色 `degraded` |
| `broken` | 红色 `broken` |

### 7.4 Reuse badge

| 字段 | 页面 badge |
|---|---|
| `provision_reused=true` | 青色 `reused` |
| `provision_reused=false` | 不显示 |

### 7.5 Account state badge

| 字段 | 页面 badge |
|---|---|
| `matched_account_state=active` | 绿色 `已启用` |
| `matched_account_state=disabled` | 灰色 `已停用` |
| `matched_account_state=deprecated` | 黄色 `已弃用` |
| `matched_account_state=broken` | 红色 `已损坏` |

### 7.6 Account resolution badge

| 字段 | 页面 badge |
|---|---|
| `account_resolution=created` | 蓝色 `新建` |
| `account_resolution=reused` | 青色 `复用` |
| `account_resolution=reactivated` | 绿色 `已快速启用` |
| `account_resolution=replaced` | 红色 `已替换` |

## 8. 分页与排序建议

### 8.1 Runs 列表

默认排序：

- `started_at DESC`

### 8.2 Items 列表

默认排序：

1. `current_stage != done` 的在前
2. `access_status = broken/degraded` 在前
3. `updated_at DESC`

原因：

- 结果页首先服务“快速定位问题”

## 9. Handler 约束

1. 列表接口只返回 projection，不返回原始 JSON 大对象
2. item 详情才返回 `capability_profile` 与 `events`
3. 不在 handler 里拼装 legacy 表结果
4. 任何页面要显示的 warning 文案，都从 projection 层统一生成
5. `provision_reused`、`reused_from_provider_id` 等复用字段必须来自 projection，不允许前端自行推断
6. `matched_account_state` 与 `account_resolution` 也必须来自 projection，不允许前端根据 account_id 是否存在自行推断

## 10. 最小实现建议

实现顺序建议：

1. 先做 `POST /api/batch-import/runs`
2. 再做 `GET /api/batch-import/runs`
3. 再做 `GET /api/batch-import/runs/{run_id}`
4. 再做 item 列表
5. 最后做 item 详情和 event trail

理由：

- 列表页和 run 详情先能支撑运营查看批次
- 复杂度最高的是 item detail / event trail

## 11. 验证点

文档层：

- 是否所有状态枚举都与 `SPEC/TDD/Architecture` 一致
- 是否还存在 `warning` 与 `completed_with_warnings` 混用
- 是否还存在 `confirmed_active` 等旧枚举残留

实现层：

1. `subscription/self_service` 条件必填校验正确
2. run 列表能稳定分页
3. item 详情能展示 capability + event trail
4. 页面无需读取宿主数据库即可解释 warning/broken
