# Ziin 标准 API 接入规范 v1

版本：v1

适用范围：Web、PC 客户端、微信小程序、App 容器、宿主服务端、第三方智能体

公网基址：`https://ziin.shenliu.cc`

本规范不是概念说明，而是按当前可运行代码整理的对外联调标准。目标是让宿主系统、实施团队、第三方智能体直接按统一协议接入，不必自己猜字段、流程和错误处理方式。

## 1. 产品边界

Ziin 既可以作为前端可见助手，也可以作为无界面的能力内核。

在 API 模式下：

1. 宿主系统或第三方智能体接收用户问题
2. 宿主系统附带权限上下文、页面上下文、多模态输入调用 Ziin
3. Ziin 返回结构化答案
4. 宿主系统决定如何展示、转述或继续追问

Ziin 不接管宿主账号密码，权限边界由宿主 Token 和上下文决定。

## 2. Query 标准接口

接口：`POST /api/ziin/query`

用途：回答“这个模块做什么”“怎么操作”“为什么报错”“我有没有权限”这类软件使用问题。

### 2.1 请求体

```json
{
  "question": "为什么入库单提交失败？",
  "mode": "embedded-agent",
  "module": "inventory",
  "softwareId": "erp-suite",
  "tenantId": "tenant-east-cn",
  "userId": "u-1001",
  "context": {
    "route": "/inventory/inbound/create",
    "pageTitle": "入库管理",
    "selectedMenu": "仓储中心 / 入库管理",
    "platform": "web"
  },
  "inputs": [
    {
      "type": "error-dialog",
      "content": "报错弹窗：无权限提交入库单"
    }
  ]
}
```

### 2.2 Token 传递

支持两种方式：

- `Authorization: Bearer <token>`
- body 中 `token`

推荐优先使用请求头。

当前服务会从 Token 或显式字段中解析：

- `softwareId`
- `tenantId`
- `userId`
- token role

### 2.3 mode 枚举

- `ui-assistant`
- `api-engine`
- `mini-program`
- `mobile-app`
- `embedded-agent`

### 2.4 响应体

```json
{
  "traceId": "q_1713235200000",
  "memoryId": "cm-1713235200000",
  "mode": "embedded-agent",
  "intent": "troubleshoot",
  "question": "为什么入库单提交失败？",
  "module": "inventory",
  "answer": "当前更像是权限不足或页面上下文不完整导致的提交失败，建议先检查提交权限和必填字段。",
  "answerRationale": [
    "问题更接近排障类问答",
    "当前页面和菜单上下文已参与判断"
  ],
  "permissions": [
    "inventory.read",
    "inventory.write"
  ],
  "steps": [
    "进入入库管理页面",
    "点击新建入库单",
    "填写仓库、供应商和物料明细",
    "保存并提交"
  ],
  "warnings": [
    "当前权限不是完整权限，步骤可能不包含审批或高级操作。"
  ],
  "sources": [
    "入库管理操作手册",
    "入库单常见报错 FAQ"
  ],
  "learningLoop": [
    "本次问答已进入会话记忆",
    "如果用户反馈无效，会生成学习任务"
  ],
  "recognized": {
    "hasAudio": false,
    "hasImage": false,
    "hasDocument": false,
    "hasPageContext": true,
    "extractedSignals": [
      "检测到错误弹窗，可优先抽取报错码、标题和按钮状态。",
      "当前路由：/inventory/inbound/create"
    ]
  },
  "suggestedActions": [
    "先检查当前账号是否具备提交权限",
    "补充截图或错误弹窗内容提高命中率"
  ],
  "learningImpact": [
    {
      "assetId": "ka_001",
      "title": "入库管理操作手册",
      "type": "manual",
      "baseScore": 1,
      "feedbackScore": 0,
      "finalScore": 1,
      "positiveSignals": 0,
      "negativeSignals": 0
    }
  ],
  "softwareId": "erp-suite",
  "tenantId": "tenant-east-cn",
  "userId": "u-1001"
}
```

### 2.5 宿主侧消费建议

至少消费这些字段，不要只显示 `answer`：

- `answer`
- `steps`
- `warnings`
- `sources`
- `suggestedActions`
- `learningImpact`

## 3. Feedback 标准接口

接口：`POST /api/ziin/feedback`

用途：把问答结果是否有用回流给 Ziin，用于排序和学习闭环。

请求体：

```json
{
  "memoryId": "cm-1713235200000",
  "feedback": "useful"
}
```

可选值：

- `useful`
- `not_useful`

成功响应：

```json
{
  "updated": {
    "id": "cm-1713235200000",
    "feedback": "useful"
  }
}
```

## 4. Sync 标准接口

接口：`POST /api/ziin/sync`

用途：同步宿主的租户、菜单、权限、用户画像和知识资产，让 Ziin 回答不只靠自然语言猜测。

最小请求示例：

```json
{
  "softwareId": "erp-suite",
  "tenant": {
    "id": "tenant-east-cn",
    "softwareId": "erp-suite",
    "name": "华东制造集团",
    "region": "cn-east",
    "enabledModules": ["inventory", "finance"],
    "customTerminology": ["过账=正式入账"]
  },
  "menuNodes": [
    {
      "id": "mn-sync-001",
      "softwareId": "erp-suite",
      "tenantId": "tenant-east-cn",
      "module": "inventory",
      "name": "入库管理",
      "route": "/inventory/inbound/create",
      "permissionKey": "inventory.write"
    }
  ],
  "permissionPoints": [
    {
      "id": "pp-sync-001",
      "softwareId": "erp-suite",
      "tenantId": "tenant-east-cn",
      "module": "inventory",
      "roleCode": "warehouse_clerk",
      "permissionKey": "inventory.write"
    }
  ],
  "userProfiles": [
    {
      "userId": "u-1001",
      "tenantId": "tenant-east-cn",
      "roleCodes": ["warehouse_clerk"],
      "permissionKeys": ["inventory.read", "inventory.write"],
      "lastRoute": "/inventory/inbound/create"
    }
  ]
}
```

## 5. Health 与 Schema

- `GET /api/ziin/health`
- `GET /api/ziin/schema`

用途：

- `health` 用于检查服务状态
- `schema` 用于查看当前公开契约快照

推荐每次联调前先调用一次。

## 6. 媒体网关标准流程

适用场景：

- 语音提问
- 截图排障
- 图片 OCR
- 文档 OCR
- 文件解析

推荐流程：

1. 客户端申请上传凭证
2. 客户端上传原始文件
3. 客户端回调上传完成
4. 发起 OCR / ASR 任务
5. 轮询任务结果

不要长期把大文件直接塞进 JSON base64。

## 7. 上传凭证接口

接口：`POST /api/media/upload-token`

请求体：

```json
{
  "tenantId": "tenant-east-cn",
  "userId": "u-1001",
  "source": "app",
  "scene": "voice-ticket",
  "fileName": "audio.mp3",
  "mimeType": "audio/mpeg",
  "size": 245760
}
```

成功响应：

```json
{
  "uploadUrl": "https://your-upload-target",
  "uploadMethod": "POST",
  "headers": {},
  "fields": {},
  "fileId": "mf-1713235200000",
  "storageDriver": "local",
  "storageKey": "tenant-east-cn/mf-1713235200000/audio.mp3",
  "expireAt": "2026-04-16T09:30:00.000Z"
}
```

## 8. 上传完成接口

接口：`POST /api/media/upload-complete`

支持两种方式：

- `application/json`
- `multipart/form-data`

### 8.1 JSON 方式

```json
{
  "tenantId": "tenant-east-cn",
  "userId": "u-1001",
  "fileId": "mf-1713235200000",
  "storageKey": "tenant-east-cn/mf-1713235200000/audio.mp3",
  "size": 245760,
  "sha256": "8e0f5f...",
  "contentBase64": "<optional>"
}
```

### 8.2 成功响应

```json
{
  "success": true,
  "fileMeta": {
    "id": "mf-1713235200000",
    "tenantId": "tenant-east-cn",
    "userId": "u-1001",
    "scene": "voice-ticket",
    "source": "app",
    "fileName": "audio.mp3",
    "mimeType": "audio/mpeg",
    "sizeBytes": 245760,
    "storageKey": "tenant-east-cn/mf-1713235200000/audio.mp3",
    "storageUrl": "/api/media/storage/...",
    "sha256": "8e0f5f...",
    "status": "validated",
    "createdAt": "2026-04-16T09:20:00.000Z",
    "updatedAt": "2026-04-16T09:20:05.000Z"
  }
}
```

文件状态枚举：

- `pending_upload`
- `uploaded`
- `validated`
- `invalid`
- `deleted`

## 9. OCR / ASR 任务接口

### 9.1 图片 OCR

接口：`POST /api/media/ocr/image`

```json
{
  "tenantId": "tenant-east-cn",
  "userId": "u-1001",
  "fileId": "mf-1713235200000",
  "scene": "error-screenshot",
  "providerHint": "qwen-ocr"
}
```

### 9.2 文档 OCR

接口：`POST /api/media/ocr/document`

```json
{
  "tenantId": "tenant-east-cn",
  "userId": "u-1001",
  "fileId": "mf-1713235200001",
  "scene": "manual-import"
}
```

### 9.3 语音 ASR

接口：`POST /api/media/asr/transcribe`

```json
{
  "tenantId": "tenant-east-cn",
  "userId": "u-1001",
  "fileId": "mf-1713235200002",
  "scene": "voice-ticket",
  "language": "zh",
  "providerHint": "faster-whisper"
}
```

### 9.4 创建任务成功响应

```json
{
  "taskId": "mt-1713235200000",
  "status": "pending",
  "result": null,
  "error": null,
  "usage": null,
  "provider": "faster-whisper"
}
```

## 10. 任务轮询接口

接口：`GET /api/media/tasks/:taskId`

成功响应示例：

```json
{
  "taskId": "mt-1713235200000",
  "taskType": "asr.voice",
  "status": "success",
  "result": {
    "text": "BOOM!"
  },
  "error": null,
  "usage": {
    "taskId": "mt-1713235200000",
    "provider": "faster-whisper",
    "scene": "voice-ticket",
    "estimatedCost": 0,
    "latencyMs": 1234,
    "success": true
  },
  "provider": "faster-whisper",
  "providerModel": "faster-whisper-tiny-local",
  "startedAt": "2026-04-16T09:20:10.000Z",
  "finishedAt": "2026-04-16T09:20:12.000Z",
  "fileId": "mf-1713235200002"
}
```

任务状态枚举：

- `pending`
- `processing`
- `success`
- `failed`
- `retrying`
- `cancelled`

## 11. 配额、成本、供应商、审计接口

### 11.1 当前配额

- `GET /api/media/quota/current?tenantId=tenant-east-cn`

### 11.2 成本汇总

- `GET /api/media/costs/summary?dateFrom=2026-04-01&dateTo=2026-04-16`

### 11.3 供应商配置

- `GET /api/media/providers/config`
- `POST /api/media/providers/config`

### 11.4 审计日志

- `GET /api/media/audit-logs?tenantId=tenant-east-cn&action=upload_completed`

## 12. 当前真实能力边界

当前服务不是所有多模态都已经商用完成，真实边界如下：

- 本地 `faster-whisper` 已真实接通，不是 mock
- 当前默认是本机轻量模型，适合先跑通，不代表最终高并发形态
- 无效音频会返回 `ASR_AUDIO_INVALID`
- 静音或无人声会返回 `ASR_RESULT_EMPTY`
- 扫描版 PDF、复杂版式 Word、复杂图片结构化理解，仍建议接外部 OCR / 视觉模型

## 13. 错误码

### 13.1 通用

- `MEDIA_INVALID_PARAM`
- `MEDIA_NOT_FOUND`
- `MEDIA_TOO_LARGE`
- `MEDIA_UNSUPPORTED_TYPE`
- `MEDIA_UNAUTHORIZED`
- `MEDIA_FORBIDDEN`
- `MEDIA_RATE_LIMITED`

### 13.2 上传

- `UPLOAD_TOKEN_EXPIRED`
- `UPLOAD_INCOMPLETE`
- `UPLOAD_HASH_MISMATCH`

### 13.3 OCR / ASR

- `OCR_PROVIDER_TIMEOUT`
- `OCR_PROVIDER_UNAVAILABLE`
- `OCR_RESULT_EMPTY`
- `ASR_PROVIDER_TIMEOUT`
- `ASR_PROVIDER_UNAVAILABLE`
- `ASR_AUDIO_INVALID`
- `ASR_RESULT_EMPTY`

### 13.4 配额与预算

- `QUOTA_EXCEEDED`
- `BUDGET_EXCEEDED`
- `CONCURRENT_LIMIT_EXCEEDED`

### 13.5 供应商路由

- `PROVIDER_NOT_CONFIGURED`
- `PROVIDER_SWITCH_FAILED`
- `ALL_PROVIDERS_FAILED`

## 14. curl 示例

### 14.1 Query

```bash
curl -X POST https://ziin.shenliu.cc/api/ziin/query \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer editor|softwareId:erp-suite|tenantId:tenant-east-cn|userId:u-1001' \
  -d '{
    "question": "怎么做入库单？",
    "mode": "embedded-agent",
    "module": "inventory",
    "context": {
      "route": "/inventory/inbound/create",
      "pageTitle": "入库管理",
      "selectedMenu": "仓储中心 / 入库管理",
      "platform": "web"
    }
  }'
```

### 14.2 ASR 创建任务

```bash
curl -X POST https://ziin.shenliu.cc/api/media/asr/transcribe \
  -H 'Content-Type: application/json' \
  -d '{
    "tenantId": "tenant-east-cn",
    "userId": "u-1001",
    "fileId": "mf-1713235200002",
    "scene": "voice-ticket",
    "language": "zh",
    "providerHint": "faster-whisper"
  }'
```

## 15. Node 示例

```js
import { readFile } from "node:fs/promises";
import { ZiinClient } from "@ziin/sdk-js";

const client = new ZiinClient({
  baseUrl: "https://ziin.shenliu.cc",
  token: "editor|softwareId:erp-suite|tenantId:tenant-east-cn|userId:u-1001",
});

const queryResult = await client.query({
  question: "为什么入库单提交失败？",
  mode: "embedded-agent",
  module: "inventory",
  context: {
    route: "/inventory/inbound/create",
    pageTitle: "入库管理",
    selectedMenu: "仓储中心 / 入库管理",
    platform: "web",
  },
});

console.log(queryResult.answer);

const bytes = await readFile("/absolute/path/to/audio.mp3");
const created = await client.uploadAndCreateAsrTask({
  tenantId: "tenant-east-cn",
  userId: "u-1001",
  source: "api",
  scene: "voice-ticket",
  language: "zh",
  file: {
    fileName: "audio.mp3",
    mimeType: "audio/mpeg",
    bytes,
  },
});

const task = await client.waitForTask(created.task.taskId, {
  intervalMs: 2000,
  maxAttempts: 45,
});

console.log(task.result);
```

## 16. Python 示例

```python
import base64
import hashlib
import requests

BASE_URL = "https://ziin.shenliu.cc"

query_resp = requests.post(
    f"{BASE_URL}/api/ziin/query",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer editor|softwareId:erp-suite|tenantId:tenant-east-cn|userId:u-1001",
    },
    json={
        "question": "这个模块是干嘛的？",
        "mode": "api-engine",
        "module": "inventory",
        "context": {
            "route": "/inventory/inbound/create",
            "platform": "web",
        },
    },
    verify=False,
)
print(query_resp.json())

with open("audio.mp3", "rb") as f:
    audio_bytes = f.read()

sha256 = hashlib.sha256(audio_bytes).hexdigest()
content_base64 = base64.b64encode(audio_bytes).decode("utf-8")

upload_token = requests.post(
    f"{BASE_URL}/api/media/upload-token",
    json={
        "tenantId": "tenant-east-cn",
        "userId": "u-1001",
        "source": "api",
        "scene": "voice-ticket",
        "fileName": "audio.mp3",
        "mimeType": "audio/mpeg",
        "size": len(audio_bytes),
    },
    verify=False,
).json()

upload_complete = requests.post(
    f"{BASE_URL}/api/media/upload-complete",
    json={
        "tenantId": "tenant-east-cn",
        "userId": "u-1001",
        "fileId": upload_token["fileId"],
        "storageKey": upload_token["storageKey"],
        "size": len(audio_bytes),
        "sha256": sha256,
        "contentBase64": content_base64,
    },
    verify=False,
)
print(upload_complete.json())
```

## 17. JS SDK 获取方式

直接下载：

- `https://ziin.shenliu.cc/sdk/js/index.js`
- `https://ziin.shenliu.cc/sdk/js/index.d.ts`
- `https://ziin.shenliu.cc/sdk/js/README.md`
- `https://ziin.shenliu.cc/sdk/js/ziin-sdk-js-latest.tgz`
- `https://ziin.shenliu.cc/sdk/js/metadata.json`

安装示例：

```bash
npm install https://ziin.shenliu.cc/sdk/js/ziin-sdk-js-latest.tgz
```

## 18. TLS / 证书注意事项

当前正式公网地址应为：`https://ziin.shenliu.cc`。

只有在域名尚未正确签发证书时，Node、Python 等服务端 SDK 才会出现证书校验失败。

仅在证书尚未配置完成时的临时联调方案：

- Node 示例可设置 `ZIIN_INSECURE_TLS=true`
- Python 示例可临时 `verify=False`

正式商用建议：

1. 使用正式域名接入
2. 域名与证书完全匹配
3. 不要长期依赖跳过 TLS 校验

## 19. 最低可联调清单

如果对方要最快开工，先打通这 6 步：

1. 调 `GET /api/ziin/health`
2. 调 `POST /api/ziin/query`
3. 调 `POST /api/ziin/feedback`
4. 调 `POST /api/media/upload-token`
5. 调 `POST /api/media/upload-complete`
6. 调 `POST /api/media/asr/transcribe` + `GET /api/media/tasks/:taskId`

这 6 步跑通后，再补 sync、供应商配置、审计和成本治理。
