开放接口文档
飞书 Wiki 同步子文档,revision 5。
开放接口文档
1. 使用约定
1.1 地址、鉴权与公共请求头
export BASE_URL='https://api.'
export OPEN_TOKEN=[REDACTED]'
除 POST /open/auth/v1/token 外,其余 20 个接口都必须携带:
Authorization: Bearer
OPEN_TOKEN 是 nco_ 开头、总长 47 的不透明令牌,不是 JWT,只能调用 /open/*。
JSON 写接口还需携带 Content-Type: application/json。当前只有创建任务接口强制要求
Idempotency-Key。
dev 环境示例
后文每个接口末尾的「dev 环境示例」都在 dev 集群实际执行过,响应是 2026-09-16 修复后的复验结果,令牌已打码。先在终端设置变量:
export BASE_URL='https://api.nanocloud-cluster.x2robot.cn'
export TENANT_ID=10100000
export APP_KEY=[REDACTED]
export OPEN_USER='ncadmin@nanocloud'
# SK 和 ncadmin 口令向环境负责人索取,只在终端读入,不要写进文档、脚本或截图
read -rs APP_SECRET
read -rs OPEN_PASSWORD
取令牌见 2.1,成功后令牌保存在 OPEN_TOKEN。后续示例从上一步响应里取 ID 存进变量,建议按以下顺序执行:
- 2.1 取令牌;
- 5.1、5.2、6.1 查采集员、标签和设备,得到
COLLECTOR_ID、COLLECTION_TAG_ID、PROJECT_TAG_ID、DEVICE_ID; - 4.1 创建技能,得到
TASK_ID; - 3.2 创建定制任务,得到
JOB_ID,再依次执行 3.5、3.7、3.9、3.10; - 驳回、关闭、自由采集和恢复采集各用独立任务,见 3.8、3.11 和 3.2。
dev 环境的限制:
- AK
nanocloud只允许代表ncadmin@nanocloud,其它账号取令牌返回30003。审核和分发都用同一个令牌;ncadmin在审核组里,可以审核自己创建的任务。「非审核人审核返回60003」这类需要两个身份的用例,dev 上暂时测不了。 - 示例响应里的
job-ddky…、tsk-ddky…是实测时新建的,状态已经改变,不要照抄,按步骤自己创建。 - 示例创建的数据名称都带
qa-demo-前缀;自己测试时也建议加统一前缀,便于识别和清理。
1.2 统一响应
成功:
{"code":0,"data":{}}
失败:
{"code":10001,"message":"required field is missing"}
| HTTP | code | 含义 |
|---|---|---|
| 400 | 1xxxx | 参数缺失、枚举非法、字段组合不合法 |
| 401 | 2xxxx | AK/SK、用户口令或 OPEN_TOKEN 无效 |
| 403 | 3xxxx | 代表关系、租户或业务权限不满足 |
| 404 | 4xxxx | 任务、技能、字典对象或路由不存在 |
| 405 | 45001 | HTTP 方法不允许 |
| 409 | 5xxxx | 幂等冲突、同键请求在途或任务状态冲突 |
| 422 | 6xxxx | 审核组、质检组、技能、标签或上传位置前置条件不满足 |
| 502 | 7xxxx | 上游异常、超时或不可达 |
| 429 | 8xxxx | 轮询限流;当前代码未接通 |
| 500 | 90001 | 本服务内部错误 |
如果响应包含 data.upstream,它仅供排障,不得作为测试断言或客户端分支条件。
1.3 JSON 类型与分页
expires_in、tenant_id、total、各计数字段是 JSON 数字。deadline、created_at、updated_at、last_connected_at是 Unix 秒级时间戳的字符串。- 所有列表接口都接受
page、page_size;page_size > 100返回 HTTP 400 /10006。 - 未传或传
0时,由下游使用默认分页值;测试不要把默认页大小写成固定断言。
2. 鉴权接口
2.1 获取 OPEN_TOKEN
POST /open/auth/v1/token,匿名接口。
请求体:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
app_key | string | 是 | 调用系统 AK;无效或停用返回 20001 |
app_secret | string | 是 | 调用系统 SK 明文;服务只与 bcrypt 摘要比对 |
tenant_id | int32 | 否 | 目标租户;缺省使用 AK 的默认租户,仍无法确定时返回 10008 |
username | string | 是 | 被代表的平台账号,须在 AK 的允许列表中 |
password | string | 是 | 被代表账号口令;只用于即时登录,不持久化、不回显 |
请求示例:
curl -skS -X POST "$BASE_URL/open/auth/v1/token" \
-H 'Content-Type: application/json' \
-d '{
"app_key":"qa-client",
"app_secret":"",
"tenant_id":10100000,
"username":"system_integrator",
"password":""
}'
成功响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
access_token | string | 新签发的 OPEN_TOKEN |
token_type | string | 固定为 bearer |
expires_in | int32 | 实际剩余秒数;客户端必须读取,不得硬编码 |
tenant_id | int32 | 本令牌绑定的租户 |
username | string | 本令牌代表的账号 |
{
"code":0,
"data":{
"access_token":"nco_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"token_type":"bearer",
"expires_in":257400,
"tenant_id":10100000,
"username":"system_integrator"
}
}
重点反例:空 AK/SK、空用户名、空口令均为 10001;错误 AK/SK 为 20001;错误账号口令为
20006;AK 无权代表指定账号为 30003。
dev 环境示例(2026-09-16 修复后复验)
RESP=$(curl -sS -X POST "$BASE_URL/open/auth/v1/token" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg ak "$APP_KEY" --arg sk "$APP_SECRET" --argjson tid "$TENANT_ID" \
--arg user "$OPEN_USER" --arg pw "$OPEN_PASSWORD" \
'{app_key:[REDACTED],app_secret:[REDACTED],tenant_id:$tid,username:$user,password:[REDACTED]
# 打印时遮住令牌,避免出现在屏幕和截图里
echo "$RESP" | jq 'if .code == 0 then .data.access_token = "nco_***" else . end'
export OPEN_TOKEN=[REDACTED] "$RESP" | jq -r '.data.access_token // empty')
{
"code": 0,
"data": {
"access_token": "nco_***",
"token_type": "bearer",
"expires_in": 257399,
"tenant_id": 10100000,
"username": "ncadmin@nanocloud"
}
}
反例实测(前 4 条改取令牌的请求体,后 2 条调用 GET /open/collect/v1/operators):
| 场景 | 请求差异 | HTTP | 响应 |
|---|---|---|---|
| 请求体为空 | {} | 400 | {"code":10001,"message":"app_key and app_secret are required"} |
| SK 错误 | app_secret 填错 | 401 | {"code":20001,"message":"app_key or app_secret is invalid or revoked; do not retry"} |
| 账号口令错误 | password 填错 | 401 | {"code":20006,"message":"username or password of the represented account is incorrect","data":{"upstream":{"message":"账号不存在或登录凭证错误","reason":"UNAUTHORIZED"}}} |
| 账号不在允许列表 | username 换成 nctest0@nanocloud | 403 | {"code":30003,"message":"app_key \"nanocloud\" is not allowed to represent \"nctest0@nanocloud\" in tenant 10100000"} |
| 业务接口不带令牌 | 去掉 Authorization 请求头 | 401 | {"code":20005,"message":"missing Authorization header; expected: Authorization: Bearer "} |
| 令牌不存在 | Bearer nco_ 后接 43 个 A | 401 | {"code":20002,"message":"token has expired or was never issued; obtain a new one and replay the request once"} |
3. 任务接口
3.1 任务类型、状态和公共返回
job_type:
| 名称 | JSON 值 | 说明 |
|---|---|---|
CUSTOM_COLLECTION | "CUSTOM_COLLECTION" | 定制采集,必须有技能,需审核和分发 |
RECOVERY_COLLECTION | "RECOVERY_COLLECTION" | 恢复采集,必须有技能,需审核和分发 |
FREE_COLLECTION | "FREE_COLLECTION" | 自由采集,禁止技能和多样性,无审核和分发 |
status:
| 名称 | 数值 | 说明 |
|---|---|---|
PENDING_REVIEW | 5 | 待审核 |
REJECTED | 6 | 已驳回,可重提 |
PENDING_EXECUTION | 9 | 已分发、待设备执行 |
PUBLISHED | 10 | 采集中 |
PENDING_DISPATCH | 15 | 审核通过、待分发 |
PAUSED | 20 | 已停止,通过 dispatch 恢复 |
CLOSED | 90 | 已关闭,终态 |
写操作通常返回 JobBrief:
| 字段 | 类型 | 说明 |
|---|---|---|
job_id | string | 任务唯一 ID |
job_name | string | 任务名;部分动作响应可能为空 |
status | enum | 当前对外状态 |
stage | string | 编排阶段,如 JOB_CREATED、REVIEW_APPROVED、RESUBMITTED、DISPATCHED、STOPPED、CLOSED |
next_actions | string[] | 当前建议动作;调用方优先据此展示按钮 |
3.2 创建任务
POST /open/collect/v1/jobs
请求头:
| 参数 | 必填 | 说明 |
|---|---|---|
Authorization | 是 | 集成账号的 Bearer |
Idempotency-Key | 是 | 建议 UUID;同 AK + 同 key + 同 body 返回同一 job_id |
Content-Type | 是 | application/json |
请求体:
| 参数 | 类型 | 必填 | 规则 |
|---|---|---|---|
job_type | enum | 是 | 三种任务类型之一 |
task_id | string | 条件必填 | 定制/恢复必填且技能应启用;自由采集禁止传 |
device_type | string | 是 | 设备型号,大小写不敏感;可从设备列表获得 |
target_count | int32 | 是 | Episode 目标数,必须大于 0 |
label_tag_ids | string[] | 是 | 必须恰好 2 个:collection、project 两维各一个 |
exp_total_valid_cnt | int32 | 否 | 自由采集有效数阈值;0 表示不启用自动停止 |
diversity_id | string | 否 | 定制/恢复可传;自由采集禁止传 |
scene_tag_ids | string[] | 否 | 仅自由采集生效 |
job_name | string | 否 | 任务展示名 |
priority | int32 | 否 | 优先级,当前交由上游处理 |
deadline | string | 否 | Unix 秒级时间戳字符串;非法字符串当前会被转为 0 交给上游 |
qc_requirement | string | 否 | 质检要求 |
description | string | 否 | 任务描述 |
notes | string | 否 | 备注 |
recommend_score | int32 | 否 | 推荐分,当前交由上游处理 |
定制任务示例:
IDEM_KEY="$(uuidgen)"
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H "Idempotency-Key: $IDEM_KEY" \
-H 'Content-Type: application/json' \
-d '{
"job_type":"CUSTOM_COLLECTION",
"task_id":"",
"device_type":"",
"target_count":10,
"label_tag_ids":["",""],
"job_name":"open-api-qa-custom",
"description":"Open API QA"
}'
自由任务示例:
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{
"job_type":"FREE_COLLECTION",
"device_type":"RD001",
"target_count":10,
"exp_total_valid_cnt":8,
"label_tag_ids":["",""],
"scene_tag_ids":[""],
"job_name":"open-api-qa-free"
}'
响应示例:
{"code":0,"data":{"job_id":"","job_name":"open-api-qa-custom","status":"PENDING_REVIEW","stage":"JOB_CREATED","next_actions":["approve","reject","close"]}}
当前实现注意:服务只本地校验标签数量,不校验两个 ID 的实际维度、租户和启用状态;设备型号与任务类型的
双向兼容规则也未在本层校验。自由采集虽写入自动停止观察记录,但没有 reconciler,不能断言达到阈值后自动停止。
dev 环境示例(2026-09-16 修复后复验)
定制任务。TASK_ID 来自 4.1,两个标签 ID 来自 5.2;dev 上定制和恢复采集用 EX001、CX001 这类非自由采集型号:
IDEM_KEY=$(uuidgen)
BODY=$(jq -nc --arg task "$TASK_ID" --arg c "$COLLECTION_TAG_ID" --arg p "$PROJECT_TAG_ID" \
--arg name "qa-demo-custom-$(date +%Y%m%d%H%M)" \
'{job_type:"CUSTOM_COLLECTION",task_id:$task,device_type:"EX001",target_count:2,
label_tag_ids:[$c,$p],job_name:$name,description:"qa-demo 定制任务主链路"}')
RESP=$(curl -sS -X POST "$BASE_URL/open/collect/v1/jobs" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H "Idempotency-Key: $IDEM_KEY" \
-H 'Content-Type: application/json' -d "$BODY")
echo "$RESP" | jq .
JOB_ID=$(echo "$RESP" | jq -r '.data.job_id')
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "qa-demo-custom-202609160020",
"status": "PENDING_REVIEW",
"stage": "JOB_CREATED",
"next_actions": ["approve", "reject", "close"]
}
}
用同一个 IDEM_KEY 和同一个 BODY 再发一次,返回同一个 job_id 和完整任务名,不会新建任务:
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "qa-demo-custom-202609160020",
"status": "PENDING_REVIEW",
"stage": "JOB_CREATED",
"next_actions": ["approve", "reject", "close"]
}
}
同一个 IDEM_KEY 改 description 再发,返回 HTTP 409:
{
"code": 50001,
"message": "Idempotency-Key reused with a different request body"
}
请求指纹已覆盖 job_name、description、notes 和 target_count;只要这些字段变化,复用同一幂等键就返回 50001。
自由采集。不传 task_id,型号用 RD001 这类自由采集专属型号;建单后直接是 PENDING_EXECUTION:
curl -sS -X POST "$BASE_URL/open/collect/v1/jobs" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg c "$COLLECTION_TAG_ID" --arg p "$PROJECT_TAG_ID" --arg name "qa-demo-free-$(date +%Y%m%d%H%M)" \
'{job_type:"FREE_COLLECTION",device_type:"RD001",target_count:2,exp_total_valid_cnt:2,
label_tag_ids:[$c,$p],job_name:$name}')" | jq .
{
"code": 0,
"data": {
"job_id": "job-ddkyv6rcv4sfn4wz",
"job_name": "qa-demo-free-202609160020",
"status": "PENDING_EXECUTION",
"stage": "JOB_CREATED",
"next_actions": ["stop", "close"]
}
}
恢复采集。请求体与定制任务相同,只把 job_type 换成 RECOVERY_COLLECTION(示例型号 CX001):
{
"code": 0,
"data": {
"job_id": "job-ddkyv6ruk6vywkr6",
"job_name": "qa-demo-recovery-202609160020",
"status": "PENDING_REVIEW",
"stage": "JOB_CREATED",
"next_actions": ["approve", "reject", "close"]
}
}
反例实测:
| 场景 | HTTP | 响应 |
|---|---|---|
不带 Idempotency-Key | 400 | {"code":10001,"message":"Idempotency-Key header is required"} |
Idempotency-Key 超过 128 字符 | 400 | {"code":10009,"message":"Idempotency-Key must be at most 128 characters"} |
换幂等键但复用同名 job_name | 400 | {"code":10009,"message":"upstream '/core/api/job/v1/approve-create' rejected the request: job_name already exists: qa-demo-custom-202609160020","data":{"upstream":{"message":"error: code = 400 reason = INVALID_PARAMS message = job_name already exists: qa-demo-custom-202609160020 metadata = map[] cause = ","reason":"JOB_INVALID_PARAMS"}}} |
label_tag_ids 只传一个 | 400 | {"code":10004,"message":"label_tag_ids is required: exactly one collection-category and one project-category tag"} |
定制任务用自由采集型号 RD001 | 400 | {"code":10005,"message":"device_type RD001 is only supported by FREE_COLLECTION"} |
标签传分类节点 collection_category、project_category 的 ID | 422 | {"code":60008,"message":"label tag tag-ddewsefzqwxdr7gf is not an enabled leaf tag available to this tenant; pick tag_id from GET /open/collect/v1/label-tags"} |
型号与标签在调用上游前本地校验;其它上游 400 业务拒绝统一返回 400 / 10009,不再伪装成 502。
3.3 查询任务详情
GET /open/collect/v1/jobs/{job_id}
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
job_id | path | string | 是 | 创建任务返回的 ID |
curl -skS "$BASE_URL/open/collect/v1/jobs/" \
-H "Authorization: Bearer $OPEN_TOKEN"
data 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
job_id、job_name | string | ID 与名称 |
job_type | enum | 对外任务类型 |
status | enum | 对外状态;业务逻辑使用它而不是 status_code |
status_code | int32 | 上游内部状态,仅排障使用 |
next_actions | string[] | 当前可执行动作 |
completed | bool | 完成判定;不要仅靠 status 判断 |
progress.threshold | int32 | 完成阈值 |
progress.threshold_field | string | 定制/恢复为 target_count,自由为 exp_total_valid_cnt |
progress.collected_count | int32 | 当前实现等于 valid_count |
progress.uploaded_count | int32 | 当前固定为 0 |
progress.valid_count | int32 | 有效 Episode 数 |
device_type、task_id | string | 设备型号、技能 ID |
label_tag_ids | string[] | 任务绑定的采集维与项目维标签;列表摘要可能为空,详情会补齐 |
deadline、description | string | 建单配置中的截止时间与描述;未设置时为空字符串 |
created_at、updated_at | string | Unix 秒级时间戳字符串 |
{"code":0,"data":{"job_id":"","job_name":"open-api-qa-custom","job_type":"CUSTOM_COLLECTION","status":"PENDING_REVIEW","status_code":5,"next_actions":["approve","reject","close"],"completed":false,"progress":{"threshold":10,"threshold_field":"target_count","collected_count":0,"uploaded_count":0,"valid_count":0},"device_type":"","task_id":"","label_tag_ids":["",""],"deadline":"","description":"QA 定制任务","created_at":"1789344000","updated_at":"1789344000"}}
dev 环境示例(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/jobs/$JOB_ID" \
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "qa-demo-custom-202609160020",
"job_type": "CUSTOM_COLLECTION",
"status": "PENDING_REVIEW",
"status_code": 5,
"next_actions": ["approve", "reject", "close"],
"completed": false,
"progress": {
"threshold": 2,
"threshold_field": "target_count",
"collected_count": 0,
"uploaded_count": 0,
"valid_count": 0
},
"device_type": "EX001",
"task_id": "tsk-ddkybulkf4tiiv4r",
"label_tag_ids": ["tag-ddewsegd2ms6myxs", "tag-ddewsegdbtjz5uyc"],
"deadline": "",
"description": "qa-demo 定制任务主链路",
"created_at": "1789489232",
"updated_at": "1789489233"
}
}
device_type、description、deadline 和 label_tag_ids 已从上游任务配置与标签关系补齐。
查询不存在的任务 job-notexist000000,返回 HTTP 404,应为 404 / 40001:
{
"code": 40001,
"message": "upstream '/core/api/job/v1/info' rejected the request: job not found: job-notexist000000",
"data": {
"upstream": {"message": "job not found: job-notexist000000", "reason": "JOB_NOT_FOUND"}
}
}
3.4 查询任务列表
GET /open/collect/v1/jobs
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
keyword | query | string | 否 | 按任务名模糊匹配 |
status | query | enum | 否 | 按对外状态过滤,例如 PENDING_REVIEW |
page | query | int32 | 否 | 页码 |
page_size | query | int32 | 否 | 每页数量,上限 100 |
curl -skS "$BASE_URL/open/collect/v1/jobs?keyword=open-api-qa&status=PENDING_REVIEW&page=1&page_size=20" \
-H "Authorization: Bearer $OPEN_TOKEN"
{"code":0,"data":{"items":[{"job_id":"","job_name":"open-api-qa-custom","job_type":"CUSTOM_COLLECTION","status":"PENDING_REVIEW","status_code":5,"next_actions":["approve","reject","close"],"completed":false,"progress":{"threshold":10,"threshold_field":"target_count","collected_count":0,"uploaded_count":0,"valid_count":0},"device_type":"","task_id":"","created_at":"1789344000","updated_at":"1789344000"}],"total":1}}
已关闭任务不出现在列表中,但仍可按 ID 查询详情。
dev 环境示例(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/jobs?keyword=qa-demo&page=1&page_size=20" \
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
{
"code": 0,
"data": {
"items": [
{
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "qa-demo-custom-202609160020",
"job_type": "CUSTOM_COLLECTION",
"status": "PENDING_REVIEW",
"status_code": 5,
"next_actions": ["approve", "reject", "close"],
"completed": false,
"progress": {
"threshold": 2,
"threshold_field": "target_count",
"collected_count": 0,
"uploaded_count": 0,
"valid_count": 0
},
"device_type": "EX001",
"task_id": "tsk-ddkybulkf4tiiv4r",
"label_tag_ids": [],
"deadline": "",
"description": "",
"created_at": "1789489232",
"updated_at": "1789489233"
}
],
"total": 1
}
}
3.5 查询审核流
GET /open/collect/v1/jobs/{job_id}/review-flow
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
job_id | path | string | 是 | 任务 ID |
curl -skS "$BASE_URL/open/collect/v1/jobs//review-flow" \
-H "Authorization: Bearer $OPEN_TOKEN"
| 响应字段 | 类型 | 说明 |
|---|---|---|
strategy | string | 当前为 ANY,任一审核节点通过即整轮通过 |
nodes[].reviewer | string | 审核账号名 |
nodes[].result | string | PENDING、APPROVED 或 REJECTED |
nodes[].comment | string | 审核意见 |
{"code":0,"data":{"strategy":"ANY","nodes":[{"reviewer":"system_reviewer","result":"PENDING","comment":""}]}}
内部 flow_id、node_id 不对外暴露,审核接口不需要这两个参数。
dev 环境示例(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/jobs/$JOB_ID/review-flow" \
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
{
"code": 0,
"data": {
"strategy": "ANY",
"nodes": [
{"reviewer": "ncadmin@nanocloud", "result": "PENDING", "comment": ""},
{"reviewer": "ncqc@nanocloud", "result": "PENDING", "comment": ""},
{"reviewer": "nctest1@nanocloud", "result": "PENDING", "comment": ""},
{"reviewer": "我是数采员", "result": "PENDING", "comment": ""}
]
}
}
dev 审核组有 4 名成员,所以有 4 个审核节点,任一节点通过即整轮通过。reviewer 是展示字段,可能是账号名也可能是姓名;服务审核时使用 JWT 的 uid 与上游 reviewer_id 匹配,不依赖展示文本。
3.6 重新提交审核
POST /open/collect/v1/jobs/{job_id}/resubmit
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
job_id | path | string | 是 | 只允许 REJECTED 状态;PAUSED 改用 dispatch 恢复 |
reason | body | string | 否 | 重提原因,转为上游审核备注 |
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs//resubmit" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"reason":"修正测试数据后重提"}'
{"code":0,"data":{"job_id":"","status":"PENDING_REVIEW","stage":"RESUBMITTED","next_actions":["approve","reject","close"]}}
不允许的状态返回 HTTP 409 / 50002。
dev 环境示例(2026-09-16 修复后复验)
resubmit 只用于驳回任务。对 3.10 停止后的任务(PAUSED)调用会被拒,并提示改用 3.9 的 dispatch 恢复:
curl -sS -X POST "$BASE_URL/open/collect/v1/jobs/$JOB_ID/resubmit" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"reason":"qa-demo 验证 PAUSED 拒绝 resubmit"}' | jq .
返回 HTTP 409:
{
"code": 50002,
"message": "job is PAUSED(20); resume it with dispatch instead of resubmit"
}
对 3.8 驳回后的任务调用同一接口,返回 PENDING_REVIEW,并原样沿用上一轮审核流的策略与 4 个节点:
{
"code": 0,
"data": {
"job_id": "job-ddkyv6qmrxgg5uls",
"job_name": "",
"status": "PENDING_REVIEW",
"stage": "RESUBMITTED",
"next_actions": ["approve", "reject", "close"]
}
}
{
"code": 0,
"data": {
"strategy": "ANY",
"nodes": [
{"reviewer": "ncqc@nanocloud", "result": "PENDING", "comment": ""},
{"reviewer": "ncadmin@nanocloud", "result": "PENDING", "comment": ""},
{"reviewer": "nctest1@nanocloud", "result": "PENDING", "comment": ""},
{"reviewer": "我是数采员", "result": "PENDING", "comment": ""}
]
}
}
3.7 审核通过
POST /open/collect/v1/jobs/{job_id}/approve
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
job_id | path | string | 是 | 待审核任务 ID |
comment | body | string | 否 | 审核意见 |
必须使用实际审核节点中账号的 OPEN_TOKEN:
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs//approve" \
-H "Authorization: Bearer $REVIEWER_OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"comment":"QA 审核通过"}'
{"code":0,"data":{"job_id":"","status":"PENDING_DISPATCH","stage":"REVIEW_APPROVED","next_actions":["dispatch","stop","close"]}}
非实际审核人返回 HTTP 422 / 60003,缺审核组或组内无人分别为 60001、60002。
dev 环境示例(2026-09-16 修复后复验)
dev 上建单人和审核人都是 ncadmin@nanocloud,直接用同一个 OPEN_TOKEN:
curl -sS -X POST "$BASE_URL/open/collect/v1/jobs/$JOB_ID/approve" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"comment":"qa-demo 审核通过"}' | jq .
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "",
"status": "PENDING_DISPATCH",
"stage": "REVIEW_APPROVED",
"next_actions": ["dispatch", "stop", "close"]
}
}
响应会回填请求路径中的 job_id,状态进入 PENDING_DISPATCH。
3.8 驳回任务
POST /open/collect/v1/jobs/{job_id}/reject
参数与审核通过相同,也必须使用实际审核人的令牌。
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs//reject" \
-H "Authorization: Bearer $REVIEWER_OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"comment":"缺少 QA 必要说明"}'
{"code":0,"data":{"job_id":"","status":"REJECTED","stage":"REJECTED","next_actions":["resubmit"]}}
REJECTED 下只允许 resubmit;关闭接口会返回 409 / 50002。
dev 环境示例(2026-09-16 修复后复验)
另建一个定制任务(同 3.2,job_name 换一个),把新的任务 ID 存进 JOB_ID 后驳回:
curl -sS -X POST "$BASE_URL/open/collect/v1/jobs/$JOB_ID/reject" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"comment":"qa-demo 缺少必要说明"}' | jq .
{
"code": 0,
"data": {
"job_id": "job-ddkyv6qmrxgg5uls",
"job_name": "",
"status": "REJECTED",
"stage": "REJECTED",
"next_actions": ["resubmit"]
}
}
驳回后唯一建议动作是 resubmit;关闭会返回 409 / 50002,见 3.11。
3.9 分发任务
POST /open/collect/v1/jobs/{job_id}/dispatch
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
job_id | path | string | 是 | 通常应为 PENDING_DISPATCH |
start_date | body | string | 是 | YYYY-MM-DD;当前只校验非空,具体格式交上游 |
collector_ids | body | string[] | 是 | 采集员 ID,来自采集员列表 |
device_ids | body | string[] | 是 | 设备 ID,来自设备列表 |
qc_inspector_ids | body | string[] | 否 | 质检员 ID;空数组时由平台自动指派并排除采集员 |
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs//dispatch" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"start_date":"2026-09-15",
"collector_ids":[""],
"device_ids":[""],
"qc_inspector_ids":[]
}'
{"code":0,"data":{"job_id":"","status":"PENDING_EXECUTION","stage":"DISPATCHED","next_actions":["stop","close"]}}
缺采集员或设备数组返回 10001。自动指派质检员时,缺组、组内无人、排除采集员后无候选分别返回
60004、60005、60006。upload_env 不是接口参数,由部署配置决定。
dev 环境示例(2026-09-16 修复后复验)
采集员用 5.1 里的 nctest0@nanocloud(opr-ddchzvxizsyejiy7),设备用 6.1 里型号为 EX001 的 dev-ddcrr72ambcqhwr2;qc_inspector_ids 为空时由平台自动指派质检员:
curl -sS -X POST "$BASE_URL/open/collect/v1/jobs/$JOB_ID/dispatch" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg day "$(date +%F)" --arg o "$COLLECTOR_ID" --arg d "$DEVICE_ID" \
'{start_date:$day,collector_ids:[$o],device_ids:[$d],qc_inspector_ids:[]}')" | jq .
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "",
"status": "PENDING_EXECUTION",
"stage": "DISPATCHED",
"next_actions": ["stop", "close"]
}
}
分发后查询详情(3.3),status_code 为 9:
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "qa-demo-custom-202609160020",
"job_type": "CUSTOM_COLLECTION",
"status": "PENDING_EXECUTION",
"status_code": 9,
"next_actions": ["stop", "close"],
"completed": false,
"progress": {
"threshold": 2,
"threshold_field": "target_count",
"collected_count": 0,
"uploaded_count": 0,
"valid_count": 0
},
"device_type": "EX001",
"task_id": "tsk-ddkybulkf4tiiv4r",
"label_tag_ids": ["tag-ddewsegd2ms6myxs", "tag-ddewsegdbtjz5uyc"],
"deadline": "",
"description": "qa-demo 定制任务主链路",
"created_at": "1789489232",
"updated_at": "1789489233"
}
}
3.10 停止任务
POST /open/collect/v1/jobs/{job_id}/stop
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
job_id | path | string | 是 | 任务 ID |
reason | body | string | 否 | 停止原因 |
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs//stop" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"reason":"QA 停止验证"}'
{"code":0,"data":{"job_id":"","status":"PAUSED","stage":"STOPPED","next_actions":["dispatch"]}}
停止与关闭互斥:停止后不能关闭,也不能重新提交审核;调用 dispatch 走平台重启路径恢复。定制/恢复采集会在同一次调用中重新分发,自由采集直接回到 PENDING_EXECUTION。
dev 环境示例(2026-09-16 修复后复验)
curl -sS -X POST "$BASE_URL/open/collect/v1/jobs/$JOB_ID/stop" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"reason":"qa-demo 停止验证"}' | jq .
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "",
"status": "PAUSED",
"stage": "STOPPED",
"next_actions": ["dispatch"]
}
}
停止响应会回填 job_id 和 PAUSED,next_actions 只给出 dispatch。沿用 3.9 的分发请求体恢复定制任务后,同一次调用完成重启和重新分发:
{
"code": 0,
"data": {
"job_id": "job-ddkyv6osv7fhx7qm",
"job_name": "",
"status": "PENDING_EXECUTION",
"stage": "DISPATCHED",
"next_actions": ["stop", "close"]
}
}
自由采集任务不需要采集员或设备,传空对象即可恢复为 PENDING_EXECUTION:
{
"code": 0,
"data": {
"job_id": "job-ddkyv6rcv4sfn4wz",
"job_name": "",
"status": "PENDING_EXECUTION",
"stage": "RESTARTED",
"next_actions": ["stop", "close"]
}
}
3.11 关闭任务
POST /open/collect/v1/jobs/{job_id}/close
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
job_id | path | string | 是 | 只允许状态 5、9、15 |
reason | body | string | 否 | proto 接受,但当前 service 不向上游传递 |
curl -skS -X POST "$BASE_URL/open/collect/v1/jobs//close" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"reason":"QA 撤单验证"}'
{"code":0,"data":{"job_id":"","status":"CLOSED","stage":"CLOSED","next_actions":[]}}
关闭是终态;关闭后列表中不可见,仍可按 ID 查详情。不允许的状态返回 50002。
dev 环境示例(2026-09-16 修复后复验)
关闭一个待审核(PENDING_REVIEW)的任务:
curl -sS -X POST "$BASE_URL/open/collect/v1/jobs/$JOB_ID/close" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' -d '{}' | jq .
{
"code": 0,
"data": {
"job_id": "job-ddkyv6ruk6vywkr6",
"job_name": "",
"status": "CLOSED",
"stage": "CLOSED",
"next_actions": []
}
}
关闭响应会回填 job_id 和 CLOSED。
对停止后(PAUSED)的任务关闭,返回 HTTP 409:
{
"code": 50002,
"message": "close is only allowed in PENDING_REVIEW(5)/PENDING_DISPATCH(15)/PENDING_EXECUTION(9); current=20"
}
对驳回后(REJECTED)的任务关闭,返回 HTTP 409:
{
"code": 50002,
"message": "close is only allowed in PENDING_REVIEW(5)/PENDING_DISPATCH(15)/PENDING_EXECUTION(9); current=6"
}
4. 技能接口
技能没有物理删除;停用后不能用于新建任务,历史任务不受影响。技能以 task_id 寻址,不能把名称当业务键。
4.1 创建技能
POST /open/collect/v1/skills
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
name | body | string | 否 | proto 中存在,但当前 service 忽略 |
alias | body | string | 是 | 租户内唯一;上游按相同 alias 幂等 |
summary | body | string | 是 | 技能摘要 |
instruction | body | string | 是 | 技能指令正文 |
curl -skS -X POST "$BASE_URL/open/collect/v1/skills" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"qa_pick","alias":"qa-pick-20260914","summary":"QA 拾取技能","instruction":"识别目标并完成拾取"}'
{"code":0,"data":{"task_id":"","name":"","alias":"qa-pick-20260916","summary":"QA 拾取技能","instruction":"识别目标并完成拾取","status":"ACTIVE","created_at":"","updated_at":""}}
创建、更新、停用和启用响应都返回完整技能字段。技能接口不要求 Idempotency-Key。
dev 环境示例(2026-09-16 修复后复验)
RESP=$(curl -sS -X POST "$BASE_URL/open/collect/v1/skills" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg alias "qa-demo-skill-$(date +%Y%m%d%H%M)" \
'{alias:$alias,summary:"qa-demo 拾取技能",instruction:"识别桌面上的目标物体并完成拾取"}')")
echo "$RESP" | jq .
TASK_ID=$(echo "$RESP" | jq -r '.data.task_id')
{
"code": 0,
"data": {
"task_id": "tsk-ddkyv6nwjggtpimv",
"name": "tsk-ddkyv6nwjggtpimv",
"alias": "qa-demo-skill-202609160020",
"summary": "qa-demo 拾取技能",
"instruction": "识别桌面上的目标物体并完成拾取",
"status": "ACTIVE",
"created_at": "1789489232",
"updated_at": "1789489232"
}
}
创建、更新、启用、停用和详情接口都返回完整技能字段;上游状态 0 / 90 分别映射为 ACTIVE / INACTIVE。
4.2 更新技能
POST /open/collect/v1/skills/{task_id}
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
task_id | path | string | 是 | 技能 ID |
name | body | string | 否 | 当前 service 忽略 |
alias | body | string | 否 | 新别名 |
summary | body | string | 否 | 新摘要 |
instruction | body | string | 否 | 新指令 |
curl -skS -X POST "$BASE_URL/open/collect/v1/skills/" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"alias":"qa-pick-20260914-v2","summary":"QA 拾取技能 v2","instruction":"先识别,再拾取并回报结果"}'
{"code":0,"data":{"task_id":"","name":"","alias":"qa-pick-20260914-v2","summary":"","instruction":"","status":"ACTIVE","created_at":"0","updated_at":"0"}}
更新响应与创建响应一样是稀疏结果;应再调用详情接口核对可读字段。
dev 环境示例(2026-09-16 修复后复验)
curl -sS -X POST "$BASE_URL/open/collect/v1/skills/$TASK_ID" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg alias "qa-demo-skill-$(date +%Y%m%d%H%M)-v2" \
'{alias:$alias,summary:"qa-demo 拾取技能 v2",instruction:"先识别目标,再拾取并回报结果"}')" | jq .
{
"code": 0,
"data": {
"task_id": "tsk-ddkyv6nwjggtpimv",
"name": "tsk-ddkyv6nwjggtpimv",
"alias": "qa-demo-skill-202609160020-v2",
"summary": "qa-demo 拾取技能 v2",
"instruction": "先识别目标,再拾取并回报结果",
"status": "ACTIVE",
"created_at": "1789489232",
"updated_at": "1789489232"
}
}
4.3 停用技能
POST /open/collect/v1/skills/{task_id}/disable
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
task_id | path | string | 是 | 技能 ID |
curl -skS -X POST "$BASE_URL/open/collect/v1/skills//disable" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' -d '{}'
{"code":0,"data":{"task_id":"","name":"","alias":"qa-pick-20260914-v2","summary":"","instruction":"","status":"INACTIVE","created_at":"0","updated_at":"0"}}
dev 环境示例(2026-09-16 修复后复验)
curl -sS -X POST "$BASE_URL/open/collect/v1/skills/$TASK_ID/disable" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' -d '{}' | jq .
{
"code": 0,
"data": {
"task_id": "tsk-ddkyv6nwjggtpimv",
"name": "tsk-ddkyv6nwjggtpimv",
"alias": "qa-demo-skill-202609160020-v2",
"summary": "qa-demo 拾取技能 v2",
"instruction": "先识别目标,再拾取并回报结果",
"status": "INACTIVE",
"created_at": "1789489232",
"updated_at": "1789489232"
}
}
技能仍被未关闭的任务引用时停用,返回 HTTP 409:
{
"code": 50002,
"message": "upstream '/core/api/task/v1/disable' rejected the request: task is being used by 3 job(s), cannot disable",
"data": {
"upstream": {
"message": "failed to disable task: error: code = 36013 reason = TASK_IN_USE message = task is being used by 3 job(s), cannot disable metadata = map[] cause = ",
"reason": "TASK_UPDATE_FAILED"
}
}
}
TASK_IN_USE 被翻译为 409 / 50002;重复停用同样返回状态冲突,不再报 502。
4.4 启用技能
POST /open/collect/v1/skills/{task_id}/enable
参数只有 path 中的 task_id。
curl -skS -X POST "$BASE_URL/open/collect/v1/skills//enable" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' -d '{}'
{"code":0,"data":{"task_id":"","name":"","alias":"qa-pick-20260914-v2","summary":"","instruction":"","status":"ACTIVE","created_at":"0","updated_at":"0"}}
dev 环境示例(2026-09-16 修复后复验)
curl -sS -X POST "$BASE_URL/open/collect/v1/skills/$TASK_ID/enable" \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H 'Content-Type: application/json' -d '{}' | jq .
{
"code": 0,
"data": {
"task_id": "tsk-ddkyv6nwjggtpimv",
"name": "tsk-ddkyv6nwjggtpimv",
"alias": "qa-demo-skill-202609160020-v2",
"summary": "qa-demo 拾取技能 v2",
"instruction": "先识别目标,再拾取并回报结果",
"status": "ACTIVE",
"created_at": "1789489232",
"updated_at": "1789489232"
}
}
4.5 查询技能详情
GET /open/collect/v1/skills/{task_id}
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
task_id | path | string | 是 | 技能 ID |
curl -skS "$BASE_URL/open/collect/v1/skills/" \
-H "Authorization: Bearer $OPEN_TOKEN"
| 响应字段 | 类型 | 当前说明 |
|---|---|---|
task_id | string | 技能唯一 ID |
name | string | 上游技能名;当前创建路径默认等于 task_id |
alias | string | 技能别名 |
summary | string | 技能摘要 |
instruction | string | 技能指令 |
status | string | ACTIVE 或 INACTIVE |
created_at | string | Unix 秒级时间戳字符串 |
updated_at | string | Unix 秒级字符串 |
{"code":0,"data":{"task_id":"","name":"","alias":"qa-pick-20260916-v2","summary":"QA 拾取技能 v2","instruction":"先识别,再拾取并回报结果","status":"ACTIVE","created_at":"1789343900","updated_at":"1789344000"}}
dev 环境示例(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/skills/$TASK_ID" \
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
{
"code": 0,
"data": {
"task_id": "tsk-ddkyv6nwjggtpimv",
"name": "tsk-ddkyv6nwjggtpimv",
"alias": "qa-demo-skill-202609160020-v2",
"summary": "qa-demo 拾取技能 v2",
"instruction": "先识别目标,再拾取并回报结果",
"status": "ACTIVE",
"created_at": "1789489232",
"updated_at": "1789489232"
}
}
4.6 查询技能列表
GET /open/collect/v1/skills
| 参数 | 位置 | 类型 | 必填 | 当前说明 |
|---|---|---|---|---|
keyword | query | string | 否 | proto 已声明,当前 service 忽略 |
active_only | query | bool | 否 | proto 已声明,当前 service 忽略 |
page | query | int32 | 否 | 页码 |
page_size | query | int32 | 否 | 上限 100 |
curl -skS "$BASE_URL/open/collect/v1/skills?keyword=qa&active_only=true&page=1&page_size=20" \
-H "Authorization: Bearer $OPEN_TOKEN"
{"code":0,"data":{"items":[{"task_id":"","name":"","alias":"qa-pick-20260914-v2","summary":"QA 拾取技能 v2","instruction":"先识别,再拾取并回报结果","status":"ACTIVE","created_at":"0","updated_at":"1789344000"}],"total":1}}
测试过滤参数时,应预期目前不会生效并记录缺陷,不要把返回全量误判成测试数据污染。
dev 环境示例(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/skills?page=1&page_size=5" \
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
响应中 items 实际有 5 条(total 为 18),这里保留前 2 条:
{
"code": 0,
"data": {
"items": [
{
"task_id": "tsk-ddkyv6nwjggtpimv",
"name": "tsk-ddkyv6nwjggtpimv",
"alias": "qa-demo-skill-202609160020-v2",
"summary": "qa-demo 拾取技能 v2",
"instruction": "先识别目标,再拾取并回报结果",
"status": "ACTIVE",
"created_at": "1789489232",
"updated_at": "1789489232"
},
{
"task_id": "tsk-ddkybulkf4tiiv4r",
"name": "tsk-ddkybulkf4tiiv4r",
"alias": "qa-demo-skill-202609152114-v2",
"summary": "qa-demo 拾取技能 v2",
"instruction": "先识别目标,再拾取并回报结果",
"status": "ACTIVE",
"created_at": "1789478063",
"updated_at": "1789478064"
}
],
"total": 18
}
}
5. 字典接口
5.1 查询采集员
GET /open/collect/v1/operators
| 参数 | 位置 | 类型 | 必填 | 当前说明 |
|---|---|---|---|---|
keyword | query | string | 否 | proto 已声明,当前 service 忽略 |
page | query | int32 | 否 | 页码 |
page_size | query | int32 | 否 | 上限 100 |
curl -skS "$BASE_URL/open/collect/v1/operators?page=1&page_size=20" \
-H "Authorization: Bearer $OPEN_TOKEN"
| 响应字段 | 类型 | 说明 |
|---|---|---|
items[].operator_id | string | 分发时填入 collector_ids |
items[].username | string | 展示名 |
items[].is_online | bool | 当前心跳在线状态 |
items[].connected_controller_id | string | 当前连接上位机,可能为空 |
items[].connected_device_id | string | 当前连接设备,可能为空 |
items[].last_connected_at | string | Unix 秒级字符串,可能为 0 |
total | int32 | 总数 |
{"code":0,"data":{"items":[{"operator_id":"","username":"collector_qa","is_online":false,"connected_controller_id":"","connected_device_id":"","last_connected_at":"0"}],"total":1}}
连接字段是 Redis 心跳瞬时态,不能用来反查任务分配关系。
dev 环境示例(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/operators?page=1&page_size=20" \
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
# 分发时用 nctest0@nanocloud 作为采集员
COLLECTOR_ID=$(curl -sS "$BASE_URL/open/collect/v1/operators?page=1&page_size=20" \
-H "Authorization: Bearer $OPEN_TOKEN" | jq -r '.data.items[] | select(.username=="nctest0@nanocloud") | .operator_id')
{
"code": 0,
"data": {
"items": [
{
"operator_id": "opr-ddchzvxi645pdzam",
"username": "ncadmin@nanocloud",
"is_online": false,
"connected_controller_id": "",
"connected_device_id": "",
"last_connected_at": "0"
},
{
"operator_id": "opr-ddchzvxizsyejiy7",
"username": "nctest0@nanocloud",
"is_online": false,
"connected_controller_id": "",
"connected_device_id": "",
"last_connected_at": "0"
},
{
"operator_id": "opr-ddchzvxitdzrdfrs",
"username": "nctest1@nanocloud",
"is_online": false,
"connected_controller_id": "",
"connected_device_id": "",
"last_connected_at": "0"
},
{
"operator_id": "opr-ddchzvxil67lfrzy",
"username": "ncqc@nanocloud",
"is_online": false,
"connected_controller_id": "",
"connected_device_id": "",
"last_connected_at": "0"
}
],
"total": 4
}
}
page_size=101 返回 HTTP 400:`{"code":10006,"message":"page_size must be ","tag_code":"normal_collection","tag_name":"常规采集","status":"ACTIVE"}],"total":1}}
建单必须从两次查询中各选择一个启用标签;空维度为 `10001`,非法维度为 `10002`。
**dev 环境示例**(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/label-tags?dimension=collection&page=1&page_size=100"
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
curl -sS "$BASE_URL/open/collect/v1/label-tags?dimension=project&page=1&page_size=100"
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
`dimension=collection`:
{ "code": 0, "data": { "items": [ {"tag_id": "tag-ddewsegdbtjz5uyc", "tag_code": "normal_collection", "tag_name": "正式任务", "status": "ACTIVE"}, {"tag_id": "tag-ddewsege5rjy7adm", "tag_code": "test_task", "tag_name": "测试任务", "status": "ACTIVE"} ], "total": 2 } }
`dimension=project`:
{ "code": 0, "data": { "items": [ { "tag_id": "tag-ddewsegd2ms6myxs", "tag_code": "pretrain_large_scale", "tag_name": "预训练大规模采集", "status": "ACTIVE" } ], "total": 1 } }
建单时两维各取一个 `tag_id`:
COLLECTION_TAG_ID=$(curl -sS "$BASE_URL/open/collect/v1/label-tags?dimension=collection"
-H "Authorization: Bearer $OPEN_TOKEN" | jq -r '.data.items[] | select(.tag_code=="normal_collection") | .tag_id')
PROJECT_TAG_ID=$(curl -sS "$BASE_URL/open/collect/v1/label-tags?dimension=project"
-H "Authorization: Bearer $OPEN_TOKEN" | jq -r '.data.items[0].tag_id')
不传 `dimension` 返回 HTTP 400:`{"code":10001,"message":"dimension is required: collection or project"}`。
dev 租户项目维只开通了 `pretrain_large_scale`。nano-open-api 修复前本接口只返回分类节点`collection_category`、`project_category`,拿它们建单会被拒,见 3.2 反例。
### 5.3 查询上传位置
`GET /open/collect/v1/upload-envs`
无 path、query 或 body 参数。
curl -skS "$BASE_URL/open/collect/v1/upload-envs"
-H "Authorization: Bearer $OPEN_TOKEN"
| 响应字段 | 类型 | 说明 |
|-|-|-|
| `options[].value` | string | 上传位置值,私有化环境通常为 `s3` |
| `options[].enabled` | bool | 上游是否启用 |
| `options[].is_default` | bool | 当前 service 固定返回 `false` |
{"code":0,"data":{"options":[{"value":"s3","enabled":true,"is_default":false}]}}
本接口只用于排障核对。分发接口不接收 `upload_env`,实际默认值由部署配置决定。
**dev 环境示例**(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/collect/v1/upload-envs"
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
{ "code": 0, "data": { "options": [ {"value": "s3", "enabled": true, "is_default": false} ] } }
## 6. 设备接口
### 6.1 查询设备
`GET /open/device/v1/devices`
| 参数 | 位置 | 类型 | 必填 | 当前说明 |
|-|-|-|-|-|
| `keyword` | query | string | 否 | proto 已声明,当前 service 忽略 |
| `device_model` | query | string | 否 | proto 已声明,当前 service 忽略 |
| `page` | query | int32 | 否 | 页码 |
| `page_size` | query | int32 | 否 | 上限 100 |
curl -skS "$BASE_URL/open/device/v1/devices?device_model=RD001&page=1&page_size=20"
-H "Authorization: Bearer $OPEN_TOKEN"
| 响应字段 | 类型 | 说明 |
|-|-|-|
| `items[].device_id` | string | 分发时写入 `device_ids` |
| `items[].device_sn` | string | 设备序列号 |
| `items[].device_model` | string | 建单时可作为 `device_type` |
| `items[].status` | string | `ACTIVE`、`DISABLED`、`MAINTENANCE` 或 `UNKNOWN` |
| `total` | int32 | 总数 |
{"code":0,"data":{"items":[{"device_id":"","device_sn":"RD001-QA-001","device_model":"RD001","status":"ACTIVE"}],"total":1}}
当前过滤参数不生效。自由采集仅允许
`RD001/RD001_EGO/RD001_VIO/RM003_VIO/RM003_VR/RM003_VR_WIRELESS/RP001_VR`;
定制和恢复应使用这 7 个型号之外的支持型号。该双向排除当前主要由上游兜底。
**dev 环境示例**(2026-09-16 修复后复验)
curl -sS "$BASE_URL/open/device/v1/devices?page=1&page_size=20"
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
DEVICE_ID=$(curl -sS "$BASE_URL/open/device/v1/devices?page=1&page_size=20"
-H "Authorization: Bearer $OPEN_TOKEN" | jq -r '[.data.items[] | select(.device_model=="EX001")][0].device_id')
响应中 `items` 共 10 条,这里保留 `EX001`、`RD001`、`DESKTOP` 各一条:
{ "code": 0, "data": { "items": [ { "device_id": "dev-ddcrr72ambcqhwr2", "device_sn": "XREX21ZD2604200102", "device_model": "EX001", "status": "UNKNOWN" }, { "device_id": "dev-ddch2xuidf4ewxg4", "device_sn": "XRRD00ZD0000000000", "device_model": "RD001", "status": "UNKNOWN" }, { "device_id": "dev-ddeso3j62e5yenw2", "device_sn": "XRZM11ZD2606260001", "device_model": "DESKTOP", "status": "UNKNOWN" } ], "total": 10 } }
dev 设备大多是 `UNKNOWN` 状态,3.9 分发给 `UNKNOWN` 状态的 `EX001` 设备实测成功。
## 7. 当前未注册的目标接口
`GET /open/openapi.json` 当前未注册;接口自描述以后续独立版本为准。
curl -skS "$BASE_URL/open/openapi.json"
-H "Authorization: Bearer $OPEN_TOKEN"
当前预期为 HTTP 404 / `40006`。不要用它做存活探测,也不要把它计入 21 个已实现端点。
**dev 环境示例**(2026-09-16 修复后复验)
返回 HTTP 404:`{"code":40006,"message":"no such endpoint; check the method and path against the interface reference"}`。错误文案只提示核对接口参考,不再指向未注册的 OpenAPI JSON 路径。
## 8. 多样性与场景标签
本节是增量接口说明,不修改前面已有的接口参数和调用示例。
### 8.1 查询场景标签
GET /open/collect/v1/label-tags?dimension=scene&page=1&page_size=100 Authorization: Bearer
返回当前租户可用于自由采集任务 `scene_tag_ids` 的启用叶子标签。场景标签不能代替任务原有的采集维和项目维标签。
SCENE_TAG_ID=$(curl -skS "$BASE_URL/open/collect/v1/label-tags?dimension=scene&page=1&page_size=100"
-H "Authorization: Bearer $OPEN_TOKEN"
| jq -er '.data.items[0].tag_id')
### 8.2 创建多样性
POST /open/collect/v1/diversities Authorization: Bearer Content-Type: application/json
请求体:
{ "name": "qa-open-api-diversity", "description": "QA template", "content": "{"scene":"kitchen"}" }
- `name`:必填;同一租户内唯一。
- `description`:选填。
- `content`:必填;必须是合法 JSON 对象或数组的文本形式,不能传 JSON 标量。
创建后模板类型固定为 `default`,状态固定为 `ACTIVE`。同名重放返回已有模板,不覆盖已经保存的 `description` 或 `content`。
DIVERSITY_ID=$(curl -skS -X POST "$BASE_URL/open/collect/v1/diversities"
-H "Authorization: Bearer $OPEN_TOKEN"
-H "Content-Type: application/json"
-d '{"name":"qa-open-api-diversity","description":"QA template","content":"{"scene":"kitchen"}"}'
| jq -er '.data.diversity_id')
### 8.3 查询多样性列表
GET /open/collect/v1/diversities?keyword=&page=1&page_size=100 Authorization: Bearer
列表只返回当前租户可用的 `ACTIVE` 模板。响应 `data` 包含 `items`、`total`、`page`、`page_size` 和 `has_more`。
### 8.4 查询多样性详情
GET /open/collect/v1/diversities/{diversity_id} Authorization: Bearer
详情字段包括 `diversity_id`、`name`、`diversity_type`、`description`、`content`、`example_images`、`status`、`created_at` 和 `updated_at`。模板不存在或不属于当前租户时返回 HTTP 404 / `40007`。
curl -skS "$BASE_URL/open/collect/v1/diversities/$DIVERSITY_ID"
-H "Authorization: Bearer $OPEN_TOKEN" | jq .
### 8.5 权限与能力边界
- 创建:需要 `collect:diversity:update` 或 `collect:job_manager:create`。
- 列表:接受 `collect:diversity:read` 或 `collect:job_manager:read`。
- 详情:需要 `collect:diversity:read`。
- 修改、停用、删除和示例图片上传当前不通过开放接口提供,继续由管理后台处理。
- 定制或恢复任务可以填写 `diversity_id`;自由采集任务禁止填写该字段,应使用 `dimension=scene` 返回的标签。
### 8.6 多样性 `content` 格式规范
创建多样性的外层请求是 JSON 对象,但 `content` 字段本身是字符串。它必须保存“只序列化一次”的非空 JSON 对象文本:先构造内层对象,再对内层对象执行一次 `JSON.stringify` 或 `json.dumps`。
以下写法不合法,并返回 HTTP 400 / `10009`:
- 数组:`"content":"[]"`
- 标量:`"content":"123"`
- 二次序列化字符串:`"content":"\"{\\\"mode\\\":\\\"tidy_up_items\\\"}\""`
- 不是合法 JSON 的文本
创建接口只校验 `content` 是非空 JSON 对象,不逐项验证对象里的业务字段。字段组合能否生成数据,取决于对应的 `mode`。
#### 推荐格式:`tidy_up_items`
当前对外示例统一使用 `tidy_up_items`。其他 `mode` 的字段并不完全相同;在对应格式单独发布前,应使用平台生成的模板,不要直接照搬本节字段。
| 内层字段 | 类型 | 建议 | 说明 |
|-|-|-|-|
| `mode` | string | 必填 | 模式判别字段,本示例固定为 `tidy_up_items` |
| `task` | string | 必填 | 任务描述 |
| `instructions` | string[] | 必填 | 按执行顺序排列的动作文本 |
| `data_quantity` | integer | 必填 | 生成样本数,使用正整数;生成器缺省值为 `1000` |
| `addition` | object | 可选 | 附加条件;`add_id` 为 `all` 或步骤下标列表,`add_info` 为权重对象列表 |
| `change_frequency` | object | 可选 | 动态维度名到正整数 N 的映射,表示该维度每 N 条样本重新选择一次 |
| `` | object | 可选 | 选项到非负数权重的映射;至少一个权重大于 0 |
| `version` | string | 可选 | 模板格式版本;不传时生成结果当前使用 `0` |
权重按相对大小抽样,不强制总和等于 `1`;为便于评审和人工核对,建议归一化为 `1`。
内层对象的可读形式:
{ "mode": "tidy_up_items", "task": "整理杯子和盘子", "instructions": [ "右臂拿起杯子", "右臂将杯子放到托盘" ], "data_quantity": 10, "addition": { "add_id": "all", "add_info": [ {"无干扰": 0.8, "有干扰": 0.2} ] }, "change_frequency": { "杯子位置": 5 }, "杯子位置": { "左侧": 0.5, "右侧": 0.5 }, "version": "1" }
完整外层请求示例:
{ "name": "qa-open-api-diversity-apifox-v1", "description": "tidy_up_items 多样性格式示例", "content": "{"mode":"tidy_up_items","task":"整理杯子和盘子","instructions":["右臂拿起杯子","右臂将杯子放到托盘"],"data_quantity":10,"addition":{"add_id":"all","add_info":[{"无干扰":0.8,"有干扰":0.2}]},"change_frequency":{"杯子位置":5},"杯子位置":{"左侧":0.5,"右侧":0.5},"version":"1"}" }
同一租户内 `name` 唯一。重复提交相同名称会返回已有模板,不会用新请求覆盖原有 `description` 或 `content`;需要验证新版格式时,请使用新的固定版本后缀名称。