通用约定
本页文档版本 v2.10,适用于 DJWork v2.10.0;沿用《DJWork外部API文档.md》的参数表、报文示例、集成代码与流程图,并新增管理 Agent(管理员 JWT)接口及交互安全约定。
1.1 Base URL 与认证
| 项 | 值 |
| Base URL | http(s)://<host>:8000 |
| 组织 Key(管理面) | Authorization: Bearer djk_<api_id>.<secret> |
| 用户 JWT(业务面) | Authorization: Bearer <JWT>(POST /api/v1/token 签发,默认 24h) |
| 管理员 JWT(管理面) | Authorization: Bearer <JWT>(后台登录签发) |
1.2 凭证与适用范围
| 凭证 | 适用 |
| 组织 Key | 用户生命周期与凭证 API(users 模块);亦可直连印章核验(§十一) |
| 用户 JWT | 业务 API(审核/比对/提取/知识库/OCR/LLM/脱敏/印章核验/NJCA审核)+ SSO |
| 管理员 JWT | 管理 API |
| dkp_ 个人密钥 | 个人中心创建,等同本人用户 JWT(业务 API + SSO 直跳) |
1.3 通用规则
- 时间格式 ISO 8601;分页
?page=1&per_page=10 → {total,page,per_page,pages};错误 {"detail":"<原因>"};
- 文件上传
multipart/form-data;JSON application/json;
1.4 统一错误码
| 状态码 | 含义 | 典型场景 |
| 400 | 参数错误 | 无效 stance/intensity、文件类型不支持、比对类型不一致 |
| 401 | 认证失败 | Key/JWT 无效或过期、用户禁用 |
| 403 | 无权限 | Key 模块权限不足、用户冻结、组织不允许自动注册 |
| 404 | 不存在 | 任务/文件/模板/知识库/用户/模型不存在 |
| 413 | 文件过大 | 单文件超过 50MB |
| 410 | 已弃用 | POST /api/v1/ticket(一次性票据,改用 token 直跳) |
| 429 | 限流 | Key 超频或系统并发达上限 |
| 500 | 服务错误 | 内部异常 |
用户生命周期与凭证 API(组织 Key)
flowchart TB
subgraph ORG[业务系统侧]
A[组织 Key
djk_xxx] --> B[用户标识 user_id]
end
subgraph DJ[DJWork]
C[/api/v1/token/] --> D{用户存在?}
D -- 否 --> E[自动注册
ext_orgcode_userid]
D -- 是 --> F[校验 is_active/is_frozen]
E --> G[签发用户 JWT
scope=api,page]
F --> G
end
B --> C
G --> H[业务 API / 页面免登]
sequenceDiagram
autonumber
participant Biz as 业务系统
participant API as DJWork API
participant DB as SQLite
Biz->>API: POST /api/v1/token (组织Key + user_id)
API->>DB: 查 User(org + external_user_id)
alt 用户不存在
API->>DB: 自动注册(随机密码不可登录)
else 用户存在
API->>DB: 校验 is_active / is_frozen
end
API-->>Biz: { token: 用户JWT, expires_in, registered }
Note over API: 组织 Key 管理用户生命周期
/api/v1/users 预创建/查询/冻结/删除/数据清理

组织 Key 认证。业务系统注册/管理用户并签发用户凭证。
2.1 签发用户凭证(注册或换证)

认证:组织 Key Authorization: Bearer djk_xxx
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| user_id | string | ✅ | — | 业务系统用户唯一标识(工号/手机号/UUID) |
| nickname | string | 否 | — | 昵称(首次注册写入) |
| email | string | 否 | — | 邮箱(首次注册写入) |
| expires_in | int | 否 | 86400 | 用户 JWT 有效期秒,60 ~ 31536000(最长 365 天);SSO 页面跳转建议 expires_in=300 + scope=page 短时效 |
| scope | string | 否 | api,page | api(仅 API)/ page(含页面免登) |
请求报文示例
application/json
{
"user_id": "10086",
"nickname": "张三",
"expires_in": 86400
}
响应报文示例
application/json
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 86400,
"registered": false,
"user": {
"id": 42,
"username": "ext_erpsap_10086",
"external_user_id": "10086",
"is_active": true,
"is_frozen": false
}
}
响应参数含义
| 字段 | 类型 | 说明 |
| token | string | 用户 JWT(业务 API 与页面免登凭证) |
| token_type | string | 固定 bearer |
| expires_in | int | 有效秒数 |
| registered | bool | 本次是否首次注册(true=自动注册) |
| user.id | int | 平台用户 ID |
| user.username | string | 平台用户名(自动注册为 ext_<组织code>_<user_id>) |
| user.external_user_id | string | 业务系统用户标识(回显) |
| user.is_active / is_frozen | bool | 用户状态(冻结后 JWT 即时失效) |
集成示例
Python
import requests
BASE = "http://localhost:8000"
# 1. 换取用户凭证(组织 Key → 用户 JWT)
r = requests.post(f"{BASE}/api/v1/token",
headers={"Authorization": "Bearer djk_<组织Key>"},
json={"user_id": "10086"})
token = r.json()["token"]
# 2. 换证
r = requests.post(f"{BASE}/api/v1/token",
headers={"Authorization": f"Bearer {token}"}
json={}
print(r.status_code)
print(r.json())
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 签发用户凭证
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/v1/token"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer djk_<组织Key>")
.POST(HttpRequest.BodyPublishers.ofString({"user_id": "10086"}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript
const BASE = 'http://localhost:8000';
// 换取用户凭证(组织 Key → 用户 JWT)
const r = await fetch(`${BASE}/api/v1/token`, {
method: 'POST',
headers: { Authorization: 'Bearer djk_<组织Key>', 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: '10086' })
});
const { token } = await r.json();
// 签发用户凭证
const resp = await fetch(`${BASE}/api/v1/token`, {
method: 'POST',
headers: { Authorization: 'Bearer djk_<组织Key>', 'Content-Type': 'application/json' },
body: JSON.stringify({"user_id": "10086"})
});
console.log(resp.status);
console.log(await resp.json());
2.2 预创建用户(auto_register=false 时)

认证:组织 Key
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| user_id | string | ✅ | — | 业务系统用户唯一标识 |
| email | string | 否 | — | 邮箱 |
| quota | int | 否 | — | 审核配额(次数) |
请求报文示例
application/json
{
"user_id": "10086",
"email": "zhangsan@corp.com",
"quota": 50
}
响应报文示例
application/json
{
"success": true,
"id": 42,
"username": "ext_erpsap_10086"
}
响应参数含义
| 字段 | 类型 | 说明 |
| success | bool | 是否成功 |
| id | int | 平台用户 ID |
| username | string | 生成的平台用户名 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 预创建用户
r = requests.post(f"{BASE}/api/v1/users",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={"user_id": "10086", "quota": 50}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 预创建用户
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/v1/users"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer djk_<组织Key>")
.POST(HttpRequest.BodyPublishers.ofString({"user_id": "10086", "quota": 50}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 换取用户凭证(组织 Key → 用户 JWT)
const r = await fetch(`${BASE}/api/v1/token`, {
method: 'POST',
headers: { Authorization: 'Bearer djk_<组织Key>', 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: '10086' })
});
const { token } = await r.json();
// 预创建用户
const resp = await fetch(`${BASE}/api/v1/users`, {
method: 'POST',
headers: { Authorization: 'Bearer djk_<组织Key>', 'Content-Type': 'application/json' },
body: JSON.stringify({"user_id": "10086", "quota": 50})
});
console.log(resp.status);
console.log(await resp.json());
2.3 用户生命周期接口速览
| 接口 | 方法 | 说明 |
| /api/v1/users | GET | 列表:page/per_page/status/q |
| /api/v1/users/{user_id} | GET | 单个查询(配额/冻结/数据统计) |
| /api/v1/users/{user_id} | PUT | 更新 {nickname?,email?,quota?} |
| /api/v1/users/{user_id}/status | POST | 冻结/解冻 {frozen:true | false}(即时阻断 JWT) |
| /api/v1/users/{user_id} | DELETE | 删除用户(级联清理数据) |
| /api/v1/users/{user_id}/token | POST | 重发该用户凭证 |
| /api/v1/users/{user_id}/data | GET | 数据概览(任务/文件/知识库/用量) |
| /api/v1/users/{user_id}/data | DELETE | 清理用户全部业务数据 |
| /api/v1/usage | GET | 组织用量统计 |
合同审核 API(用户 JWT)
flowchart LR
SUB[提交审核
files + 合同类型/立场/强度] --> TASK[异步任务
Celery]
TASK --> ST{状态}
ST -->|completed| DET[详情 score/问题清单]
ST -->|failed| ERR[错误信息]
DET --> DL[下载报告文件]
sequenceDiagram
autonumber
participant Biz as 业务系统
participant API as DJWork API
participant W as Celery Worker
Biz->>API: POST /api/tasks/submit (Bearer 用户JWT + 文件)
API-->>Biz: { tasks: [{ task_id, file_id, file_name }] }
loop 轮询
Biz->>API: GET /api/tasks/{task_id}/status
API-->>Biz: { status: running, progress: 60 }
end
Biz->>API: GET /api/tasks/{task_id}/detail
API-->>Biz: { score, issues[], extracted_fields }
Biz->>API: GET /api/tasks/{task_id}/download
API-->>Biz: 文件流(审核报告)
POST/api/tasks/submit

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| files | file[] | ✅ | — | .docx/.pdf,单文件≤50MB,一次 1~5 个 |
| contract_type_code | string | ✅ | — | 合同类型编码(GET /api/contract-types) |
| stance | string | ✅ | — | party_a / party_b / neutral |
| intensity | string | ✅ | — | quick / standard / deep |
| concurrency | int | 否 | 1 | 并发审核 1~5 |
| force_reaudit | bool | 否 | false | 强制重审(忽略秒传) |
请求报文示例
application/json
(multipart/form-data)
files=@合同.docx
contract_type_code=sale
stance=party_a
intensity=standard
响应报文示例
application/json
{
"tasks": [
{ "task_id": "a1b2c3d4e5", "file_id": "f123", "file_name": "采购合同.docx" }
]
}
响应参数含义
| 字段 | 类型 | 说明 |
| tasks[] | object | 成功提交的任务列表 |
| tasks[].task_id | string | Celery 任务 ID(轮询用) |
| tasks[].file_id | string | 文件标识 |
| tasks[].file_name | string | 原始文件名 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
token = "<用户JWT>" # POST /api/v1/token 换取
r = requests.post(f"{BASE}/api/tasks/submit",
headers={"Authorization": f"Bearer {token}"},
files={"file": open("文件.pdf", "rb")},
data={})
print(r.status_code, r.json())
JavaScript(fetch + FormData)
JavaScript
const BASE = 'http://localhost:8000';
const fd = new FormData();
fd.append("file", file); // 前端文件对象
const resp = await fetch(`${BASE}/api/tasks/submit`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: fd
});
console.log(await resp.json());
Java:multipart 建议使用 OkHttp 的 MultipartBody;标准库手工构造 boundary 较繁琐,JSON 接口示例见下文模板。
GET/api/tasks/{task_id}/status

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| task_id | path | ✅ | — | 3.1 返回的任务 ID |
请求报文示例
application/json
GET /api/tasks/a1b2c3d4e5/status
响应报文示例
application/json
{
"task_id": "a1b2c3d4e5",
"status": "running",
"progress": 50.0,
"message": "正在进行AI审核...",
"result": null,
"error": null
}
响应参数含义
| 字段 | 类型 | 说明 |
| task_id | string | 任务 ID |
| status | string | pending/queued/running/completed/failed/cancelled |
| progress | float | 0~100;failed 时为 -1 |
| message | string | 当前阶段描述 |
| result | object | completed 时含完整审核结果,否则 null |
| error | string | failed 时含错误信息,否则 null |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 查询任务状态
r = requests.post(f"{BASE}/api/tasks/a1b2c3d4e5/status",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 查询任务状态
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/tasks/a1b2c3d4e5/status"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 查询任务状态
const resp = await fetch(`${BASE}/api/tasks/a1b2c3d4e5/status`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
console.log(resp.status);
console.log(await resp.json());
3.3 查询任务详情

认证:用户 JWT | GET /api/tasks/{task_id}/detail
响应报文示例
application/json
{
"id": 1, "file_id": "f123", "file_name": "采购合同.docx",
"contract_type_code": "purchase", "stance": "party_a", "intensity": "standard",
"status": "completed", "score": 85.0,
"total_issues": 5, "high_risk_count": 1, "medium_risk_count": 2, "low_risk_count": 2,
"audit_result": {
"issues": [
{ "issue_type": "条款缺失", "severity": "high",
"location": "第4条", "suggestion": "建议补充违约责任条款" }
],
"summary": "整体合同质量良好,存在 1 项高风险问题..."
},
"extracted_fields": { "合同金额": "100万", "甲方": "XX公司" },
"created_at": "2026-08-10T10:00:00", "completed_at": "2026-08-10T10:30:00"
}
响应参数含义
| 字段 | 类型 | 说明 |
| id | int | 记录 ID |
| file_id / file_name | string | 文件标识与原始文件名 |
| contract_type_code / stance / intensity | string | 审核参数快照 |
| status | string | 任务状态 |
| score | float | 综合评分 0~100 |
| total_issues / high_risk_count / medium_risk_count / low_risk_count | int | 问题总数与风险分级计数 |
| audit_result.issues[] | object | 问题清单:issue_type/severity/location/suggestion |
| audit_result.summary | string | 审核总结 |
| extracted_fields | object | 提取的合同要素 |
| created_at / completed_at | string | 创建/完成时间 |
3.4 审核接口速览
| 接口 | 方法 | 说明 |
| /api/tasks/{task_id}/download | GET | 审核报告文件流(仅 completed) |
| /api/tasks/{task_id}/log | GET | 执行日志 |
| /api/tasks/{task_id}/log/sse | GET | SSE 实时日志(事件:progress/log/result/error) |
| /api/tasks/{task_id} | DELETE | 取消(仅 pending/queued/running) |
| /api/tasks | GET | 列表:page/per_page/status/q |
文档比对 API(用户 JWT)
flowchart LR
SRC[上传 source] --> TGT[上传 target]
TGT --> START[启动比对
类型一致性校验]
START --> PROG[进度轮询]
PROG --> RES[差异清单
增删改/相似度]
sequenceDiagram
autonumber
participant Biz as 业务系统
participant API as DJWork API
Biz->>API: POST /api/doc-compare/upload (role=source)
API-->>Biz: { file_id }
Biz->>API: POST /api/doc-compare/upload (role=target)
API-->>Biz: { file_id }
Biz->>API: POST /api/doc-compare/start
API-->>Biz: { task_id, status: queued }
loop 轮询
Biz->>API: GET /api/doc-compare/{task_id}/progress
API-->>Biz: { status: running, progress: 45 }
end
Biz->>API: GET /api/doc-compare/{task_id}/result
API-->>Biz: { data: 差异清单 }

流程:upload(source)→ upload(target)→ start → 轮询 progress → result
POST/api/doc-compare/upload

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| file | file | ✅ | — | PDF/图片(png/jpg/jpeg/gif/webp/bmp/tiff)/DOCX/DOC,≤50MB |
| role | string | ✅ | — | source(原文档)/ target(被比对文档) |
请求报文示例
application/json
(multipart/form-data)
file=@合同v1.pdf
role=source
响应报文示例
application/json
{ "file_id": "abc123def456", "file_name": "合同v1.pdf", "size_bytes": 123456, "role": "source" }
响应参数含义
| 字段 | 类型 | 说明 |
| file_id | string | 文件 ID(start 时使用) |
| file_name | string | 原始文件名 |
| size_bytes | int | 文件大小 |
| role | string | 回显 source/target |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
token = "<用户JWT>" # POST /api/v1/token 换取
r = requests.post(f"{BASE}/api/doc-compare/upload",
headers={"Authorization": f"Bearer {token}"},
files={"file": open("文件.pdf", "rb")},
data={})
print(r.status_code, r.json())
JavaScript(fetch + FormData)
JavaScript
const BASE = 'http://localhost:8000';
const fd = new FormData();
fd.append("file", file); // 前端文件对象
const resp = await fetch(`${BASE}/api/doc-compare/upload`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: fd
});
console.log(await resp.json());
Java:multipart 建议使用 OkHttp 的 MultipartBody;标准库手工构造 boundary 较繁琐,JSON 接口示例见下文模板。
4.2 启动比对

认证:用户 JWT | POST /api/doc-compare/start(multipart)
| 字段 | 类型 | 必填 | 说明 |
| source_file_id / source_file_name | string | ✅ | 原文档 file_id 与文件名(与上传一致) |
| target_file_id / target_file_name | string | ✅ | 被比对文档 file_id 与文件名 |
响应报文示例
application/json
{ "task_id": "compare:ab12cd34", "status": "queued", "message": "比对任务已提交" }

错误:400 两份文件类型不一致(PDF vs 图片)
4.3 比对结果

认证:用户 JWT | GET /api/doc-compare/{task_id}/result(仅 completed)
响应报文示例
application/json
{
"success": true,
"data": {
"page_diffs": [
{ "page": 1, "type": "modified",
"content_before": "甲方: XX公司", "content_after": "甲方: YY公司" }
],
"stat": { "added": 2, "deleted": 1, "modified": 5 }
}
}
响应参数含义
| 字段 | 类型 | 说明 |
| data.page_diffs[] | object | 逐页差异:page/type(added | deleted | modified)/内容前后 |
| data.stat | object | 差异统计:added/deleted/modified 计数 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 获取比对结果
r = requests.post(f"{BASE}/api/doc-compare/compare:ab12cd34/result",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 获取比对结果
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/doc-compare/compare:ab12cd34/result"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 获取比对结果
const resp = await fetch(`${BASE}/api/doc-compare/compare:ab12cd34/result`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
console.log(resp.status);
console.log(await resp.json());
4.4 比对接口速览
| 接口 | 方法 | 说明 |
| /api/doc-compare/{task_id}/progress | GET | {task_id,status,progress,message} |
| /api/doc-compare/{task_id}/detail | GET | 详情(含参数快照) |
| /api/doc-compare/tasks | GET | 列表:page/per_page/status |
| /api/doc-compare/{task_id} | DELETE | 取消/删除 |
文档按模板结构化(智能提取)API(用户 JWT)
flowchart LR
UPL[上传文件] --> TPL[获取模板
含字段 schema]
TPL --> EXT[触发提取
file_id + template_id + mode]
EXT --> POLL[状态轮询]
POLL --> DETAIL[提取结果
fields]
sequenceDiagram
autonumber
participant Biz as 业务系统
participant API as DJWork API
Biz->>API: GET /api/extractions/templates
API-->>Biz: [{ id, name, fields: 甲方/乙方/金额... }]
Biz->>API: POST /api/uploads/quick (file)
API-->>Biz: { file_id }
Biz->>API: POST /api/extractions/quick (file_id + template_id)
API-->>Biz: { task_id, status: pending }
loop 轮询
Biz->>API: GET /api/extractions/{file_id}/status
API-->>Biz: { status: running, progress: 50 }
end
Biz->>API: GET /api/extractions/{file_id}/detail
API-->>Biz: { data: { fields, template_name } }

流程:upload → templates(取模板ID)→ quick → 轮询 status → detail
POST/api/uploads/quick

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| file | file | ✅ | — | .pdf/.docx/.doc/.txt,≤50MB |
请求报文示例
application/json
(multipart/form-data)
file=@发票.pdf
响应报文示例
application/json
{ "file_id": "a1b2c3d4e5f6", "file_name": "发票.pdf", "size_bytes": 20480 }
响应参数含义
| 字段 | 类型 | 说明 |
| file_id | string | 文件 ID(提取触发时使用) |
| file_name | string | 原始文件名 |
| size_bytes | int | 文件大小 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
token = "<用户JWT>" # POST /api/v1/token 换取
r = requests.post(f"{BASE}/api/uploads/quick",
headers={"Authorization": f"Bearer {token}"},
files={"file": open("文件.pdf", "rb")},
data={})
print(r.status_code, r.json())
JavaScript(fetch + FormData)
JavaScript
const BASE = 'http://localhost:8000';
const fd = new FormData();
fd.append("file", file); // 前端文件对象
const resp = await fetch(`${BASE}/api/uploads/quick`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: fd
});
console.log(await resp.json());
Java:multipart 建议使用 OkHttp 的 MultipartBody;标准库手工构造 boundary 较繁琐,JSON 接口示例见下文模板。
5.2 提取模板列表

认证:用户 JWT | GET /api/extractions/templates
响应报文示例
application/json
{
"templates": [
{ "id": 1, "code": "contract_elements", "name": "合同要素", "is_default": true,
"fields": [
{ "key": "party_a", "label": "甲方", "type": "text" },
{ "key": "amount", "label": "合同金额", "type": "money" }
] }
]
}
响应参数含义
| 字段 | 类型 | 说明 |
| templates[].id | int | 模板 ID(触发提取用) |
| templates[].code / name | string | 模板编码与名称 |
| templates[].is_default | bool | 是否系统预置默认模板 |
| templates[].fields[] | object | 字段 schema:key/label/type |

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| file_id | string | ✅ | — | 5.1 返回的 file_id |
| template_id | int | ✅ | — | 模板 ID(5.2) |
| mode | string | 否 | standard | quick(1轮)/standard(≤2轮)/deep(≤3轮) |
请求报文示例
application/json
(multipart/form-data)
file_id=a1b2c3d4e5f6
template_id=1
mode=standard
响应报文示例
application/json
{ "task_id": "extract:a1b2c3d4e5f6:abc12345", "status": "pending", "message": "提取任务已提交" }
响应参数含义
| 字段 | 类型 | 说明 |
| task_id | string | 任务 ID(轮询用) |
| status | string | 初始 pending |
| message | string | 提示 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
token = "<用户JWT>" # POST /api/v1/token 换取
r = requests.post(f"{BASE}/api/extractions/quick",
headers={"Authorization": f"Bearer {token}"},
files={"file": open("文件.pdf", "rb")},
data={})
print(r.status_code, r.json())
JavaScript(fetch + FormData)
JavaScript
const BASE = 'http://localhost:8000';
const fd = new FormData();
fd.append("file", file); // 前端文件对象
const resp = await fetch(`${BASE}/api/extractions/quick`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: fd
});
console.log(await resp.json());
Java:multipart 建议使用 OkHttp 的 MultipartBody;标准库手工构造 boundary 较繁琐,JSON 接口示例见下文模板。
5.4 提取状态与结果

认证:用户 JWT | GET /api/extractions/{file_id}/status
响应报文示例
application/json
{
"status": "completed", "progress": 100.0, "message": "提取完成",
"fields": { "甲方": "XX公司", "乙方": "YY公司", "合同金额": "100万元", "签订日期": "2026年8月10日" },
"error": null
}
响应参数含义
| 字段 | 类型 | 说明 |
| status | string | pending/running/completed/failed |
| progress | float | 0~100;failed 为 -1 |
| message | string | 阶段描述 |
| fields | object | completed 时返回结构化提取结果(按模板字段) |
| error | string | failed 时错误信息 |
5.5 提取接口速览
| 接口 | 方法 | 说明 |
| /api/extractions/{file_id}/detail | GET | 完整结果(含 template_name/result_json) |
| /api/extractions/{file_id}/logs | GET | 提取过程日志 |
| /api/extractions/tasks | GET | 列表:page/per_page |
| /api/extractions/{file_id}/cancel | DELETE | 取消执行中任务 |
| /api/extractions/{file_id} | DELETE | 删除记录 |
知识库 API(用户 JWT)
flowchart TB
subgraph KB[知识库结构]
KB1[知识库 kb_id
Qdrant collection] --> KB2[文档 documents]
KB2 --> KB3[分块 chunks
向量+原文]
end
subgraph USE[使用]
S[检索 search
query + kb_ids] --> R[召回 chunks]
R --> A[rerank + LLM 问答]
end
KB3 --> S
sequenceDiagram
autonumber
participant Biz as 业务系统
participant API as DJWork API
participant Q as Qdrant
Biz->>API: POST /api/kb (创建)
API-->>Biz: { id: 3 }
Biz->>API: POST /api/kb/3/documents (上传)
Note over API: 异步索引, status → ready
Biz->>API: POST /api/kb/search { query }
API->>Q: 向量召回(collection=kb_3)
API->>API: rerank + LLM 问答
API-->>Biz: { answer, chunks[], routed_kbs[] }

知识库按用户隔离:创建 → 上传文档(等 ready)→ search
6.1 创建知识库

认证:用户 JWT | POST /api/kb
请求参数
| 字段 | 类型 | 必填 | 说明 |
| name | string | ✅ | 知识库名称,≤128 字符 |
| description | string | 否 | 简要描述 |
| kb_type | string | ✅ | text(文本库)/ multimodal(图片库) |
请求报文示例
application/json
{ "name": "产品知识库", "description": "产品手册与FAQ", "kb_type": "text" }
响应报文示例
application/json
{ "success": true, "id": 3, "data": { "id": 3, "name": "产品知识库", "kb_type": "text",
"document_count": 0, "chunk_count": 0, "status": "active" } }
响应参数含义
| 字段 | 类型 | 说明 |
| success | bool | 是否成功 |
| id | int | 知识库 ID(后续所有 /api/kb/{kb_id} 使用) |
| data.status | string | active/indexing/error |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 创建知识库
r = requests.post(f"{BASE}/api/kb",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={"name": "产品知识库", "kb_type": "text"}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 创建知识库
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/kb"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({"name": "产品知识库", "kb_type": "text"}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 创建知识库
const resp = await fetch(`${BASE}/api/kb`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"name": "产品知识库", "kb_type": "text"})
});
console.log(resp.status);
console.log(await resp.json());
POST/api/kb/{kb_id}/documents

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| kb_id | path | ✅ | — | 知识库 ID |
| files | file[] | ✅ | — | 批量,PDF/DOCX/TXT/图片等,单文件≤50MB |
请求报文示例
application/json
(multipart/form-data)
files=@手册.pdf
files=@FAQ.docx
响应报文示例
application/json
{ "success": true, "created": [ { "doc_id": "abc123hex16", "file_name": "手册.pdf", "file_format": "pdf", "status": "pending" } ], "failed": [] }
响应参数含义
| 字段 | 类型 | 说明 |
| created[] | object | 成功入库文档 |
| created[].doc_id | string | 文档 ID |
| created[].status | string | 初始 pending(异步索引中) |
| failed[] | object | 失败列表 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
token = "<用户JWT>" # POST /api/v1/token 换取
r = requests.post(f"{BASE}/api/kb/3/documents",
headers={"Authorization": f"Bearer {token}"},
files={"file": open("文件.pdf", "rb")},
data={})
print(r.status_code, r.json())
JavaScript(fetch + FormData)
JavaScript
const BASE = 'http://localhost:8000';
const fd = new FormData();
fd.append("file", file); // 前端文件对象
const resp = await fetch(`${BASE}/api/kb/3/documents`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: fd
});
console.log(await resp.json());
Java:multipart 建议使用 OkHttp 的 MultipartBody;标准库手工构造 boundary 较繁琐,JSON 接口示例见下文模板。
6.3 知识库检索 + AI 问答(核心)

认证:用户 JWT | POST /api/kb/search
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| query | string | ✅ | — | 检索查询 |
| kb_ids | int[] | 否 | — | 限定知识库;不传自动路由 |
| top_k | int | 否 | 5 | 返回片段数 |
| history | object[] | 否 | — | 多轮对话:[{role,content}] |
| web_search | bool | 否 | false | 联网补充 |
| kb_mode | string | 否 | auto | disable/auto/manual |
请求报文示例
application/json
{
"query": "退货运费谁承担",
"kb_ids": [3],
"top_k": 5
}
响应报文示例
application/json
{
"success": true,
"answer": "根据《退货政策》第2条,退货运费由买方承担,但若因商品质量问题导致退货,运费由卖方承担。",
"chunks": [ { "kb_id": 3, "kb_name": "产品知识库", "doc_id": "abc123", "chunk_id": "chunk_01",
"score": 0.92, "content": "第2条 退货运费...", "page": 3 } ],
"routed_kbs": [ { "kb_id": 3, "kb_name": "产品知识库", "score": 0.9 } ]
}
响应参数含义
| 字段 | 类型 | 说明 |
| answer | string | LLM 生成的回答 |
| chunks[] | object | 召回片段 |
| chunks[].kb_id / kb_name | int/string | 来源知识库 |
| chunks[].doc_id / chunk_id | string | 来源文档与分块 |
| chunks[].score | float | 相关性分数 |
| chunks[].content | string | 片段原文 |
| chunks[].page | int | 来源页码 |
| routed_kbs[] | object | 自动路由到的知识库 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 知识库检索
r = requests.post(f"{BASE}/api/kb/search",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={"query": "退货运费谁承担", "top_k": 5}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 知识库检索
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/kb/search"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({"query": "退货运费谁承担", "top_k": 5}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 知识库检索
const resp = await fetch(`${BASE}/api/kb/search`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"query": "退货运费谁承担", "top_k": 5})
});
console.log(resp.status);
console.log(await resp.json());
6.4 知识库接口速览
| 接口 | 方法 | 说明 |
| /api/kb | GET | 知识库列表 |
| /api/kb/{kb_id} | GET/PUT/DELETE | 详情/更新/删除 |
| /api/kb/{kb_id}/documents | GET | 文档列表 |
| /api/kb/{kb_id}/documents/{doc_id}/status | GET | 索引状态:pending/indexing/ready/failed |
| /api/kb/{kb_id}/documents/{doc_id} | DELETE | 删除文档(含向量) |
| /api/kb/search/stream | POST | SSE 流式检索(事件:delta/chunks/done) |
| /api/kb/{kb_id}/chunks | GET | 分块列表:page/per_page/q |
OCR 服务 API(用户 JWT,代理 DianjuOCR v3)
flowchart LR
Biz[业务系统] -->|Bearer 用户JWT| GW[FastAPI 代理层
/api/v3/ocr/* 17 端点]
GW -->|校验 Key/模块 ocr/用户| GW
GW -->|携带后台密钥| OCR[DianjuOCR V3 服务]
OCR -->|原样透传 code/msg/data| Biz
sequenceDiagram
autonumber
participant Biz as 业务系统
participant GW as DJWork API 代理
participant OCR as DianjuOCR V3 服务
Biz->>GW: POST /api/v3/ocr/image (Bearer 用户JWT + files)
GW->>GW: 校验用户/模块权限
GW->>OCR: 转发 multipart(携带后台密钥)
OCR-->>GW: { code:1000, msg, logId, data }
GW-->>Biz: 原样透传
Note over GW: 记录 ApiAccessLog(user_id, path, status)

端点/参数/响应与 DianjuOCR V3 Pro 原始 API 完全一致;认证替换为系统用户 JWT,后台真实密钥由系统持有。
7.1 端点速览(17 个)
| 端点 | 方法 | 说明 |
| /api/v3/ocr/health | GET | 健康检查+能力列表 |
| /api/v3/ocr/image | POST | 图片识别(多文件,含坐标) |
| /api/v3/ocr/orientation | POST | 方向检测 |
| /api/v3/ocr/layout | POST | 版面解析(Markdown) |
| /api/v3/ocr/seal | POST | 印章识别 |
| /api/v3/ocr/seal/crop | POST | 印章检测裁剪(url/base64) |
| /api/v3/ocr/seal/download/{path} | GET | 印章图片下载(二进制) |
| /api/v3/ocr/detect/crop | POST | 文字检测与裁剪(表格/PDF多页/紧切) |
| /api/v3/ocr/idcard | POST | 身份证识别 |
| /api/v3/ocr/extract | POST | 自然语言提示词结构化提取 |
| /api/v3/ocr/templates | GET | 模板列表 |
| /api/v3/ocr/templates/{name} | GET | 模板详情 |
| /api/v3/ocr/extract/template | POST | 模板提取(预置/内联) |
| /api/v3/ocr/templates/reload | POST | 模板热重载(仅管理员) |
| /api/v3/ocr/dcs/general | POST | DCS 兼容通用识别 |
| /api/v3/watermark/embed | POST | 盲水印嵌入 |
| /api/v3/watermark/extract | POST | 盲水印提取 |
7.2 代表接口:图片识别

认证:用户 JWT | POST /api/v3/ocr/image(multipart)
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| files | file[] | ✅ | — | 待识别图片,多文件 |
| model_type | string | 否 | ppocrv5-server | 模型类型 |
| pre_rotate | string | 否 | auto | auto/0/90/180/270 |
| include_coordinates | bool | 否 | true | 是否含坐标 |
| return_full_text | bool | 否 | false | 返回全文拼接 |
响应报文示例
application/json
{
"code": 1000, "msg": "识别成功", "responseTime": 1780367412316,
"requestCostTime": 1.08, "cacheUsed": false, "logId": "094bd90b-...",
"data": {
"totalImages": 1, "modelType": "ppocrv5-server",
"results": [ { "imageIndex": 1, "fileName": "document.png", "width": 1524, "height": 1368,
"rotateAngle": 0, "textCount": 42,
"ocrResult": [ { "text": "识别文本", "confidence": 0.95,
"box": [[100,50],[300,50],[300,100],[100,100]] } ],
"fullText": "全文拼接文本" } ]
}
}
响应参数含义
| 字段 | 类型 | 说明 |
| code | int | 状态码,1000=成功(原样透传) |
| msg / logId | string | 状态描述 / 日志追踪 ID |
| data.totalImages | int | 处理图片数 |
| data.results[].imageIndex / fileName | int/string | 序号与文件名 |
| data.results[].width / height / rotateAngle | int | 尺寸与 EXIF 旋转 |
| data.results[].textCount | int | 文本块数 |
| data.results[].ocrResult[] | object | text(文本)/confidence(置信度)/box(四边形坐标) |
| data.results[].fullText | string | 全文拼接(return_full_text=true 时) |
集成示例
Python
import requests
BASE = "http://localhost:8000"
token = "<用户JWT>"
r = requests.post(f"{BASE}/api/v3/ocr/image",
headers={"Authorization": f"Bearer {token}"},
files={"files": ("document.png", open("document.png", "rb"))},
data={"include_coordinates": "true"})
print(r.status_code, r.json())
JavaScript
const BASE = 'http://localhost:8000';
const fd = new FormData();
fd.append("files", file);
fd.append("include_coordinates", "true");
const resp = await fetch(`${BASE}/api/v3/ocr/image`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: fd
});
console.log(await resp.json());
Java:multipart 建议 OkHttp;JSON 接口的 HttpClient 写法见 §6.3 示例模板。

其余 16 端点请求/响应与 DianjuOCR V3 Pro API 文档第 7 章完全一致;templates/reload 仅平台管理员。
LLM 服务 API(用户 JWT,OpenAI 兼容)
flowchart LR
Biz[业务系统] -->|Bearer 用户JWT| LLM[FastAPI /llm/v1/*
OpenAI 兼容]
LLM --> MOD[后台模型 LlmProfile]
LLM --> KB[知识库挂载
kb_ids 检索注入]
MOD --> RES[标准 OpenAI 响应 / SSE]
KB --> RES
sequenceDiagram
autonumber
participant Biz as 业务系统
participant API as DJWork API
participant LLM as 后台模型
participant KB as 知识库(Qdrant)
Biz->>API: POST /llm/v1/chat/completions (Bearer 用户JWT + messages)
alt 挂载知识库(kb_ids)
API->>KB: 向量召回 → 注入 system 上下文
end
API->>LLM: OpenAI 兼容请求(后台配置)
LLM-->>API: 标准响应 / SSE 流
API-->>Biz: 原样透传

路径 = 根路径 + /llm + 标准 OpenAI 端点;模型 = 后台已接入模型。
8.1 模型列表

认证:用户 JWT | GET /llm/v1/models
响应报文示例
application/json
{
"object": "list",
"data": [
{ "id": "deepseek-v4-pro", "object": "model", "owned_by": "deepseek", "active": true },
{ "id": "qwen2.5-72b", "object": "model", "owned_by": "vllm", "active": false }
]
}
响应参数含义
| 字段 | 类型 | 说明 |
| data[].id | string | 模型名(chat/completions 的 model 取值) |
| data[].owned_by | string | 提供方(deepseek/vllm 等) |
| data[].active | bool | 是否当前生效模型 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 获取模型列表
r = requests.post(f"{BASE}/llm/v1/models",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 获取模型列表
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/llm/v1/models"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 获取模型列表
const resp = await fetch(`${BASE}/llm/v1/models`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
console.log(resp.status);
console.log(await resp.json());
8.2 对话补全(核心)

认证:用户 JWT | POST /llm/v1/chat/completions
请求参数(标准 OpenAI + 扩展)
| 字段 | 类型 | 必填 | 说明 |
| model | string | ✅ | 模型名,必须 ∈ 8.1 返回列表 |
| messages | object[] | ✅ | OpenAI 标准消息[{role,content}] |
| temperature / top_p / max_tokens | float/int | 否 | 采样参数 |
| stream | bool | 否 | SSE 流式,默认 false |
| tools / tool_choice | object[]/string | 否 | function calling 透传 |
| kb_ids | int[] | 否 | 挂载该用户知识库(检索注入上下文) |
| kb_top_k | int | 否 | 召回片段数,默认 5 |
| kb_mode | string | 否 | auto(自动路由)/manual(按 kb_ids) |
| web_search | bool | 否 | 联网补充 |
请求报文示例
application/json
{
"model": "deepseek-v4-pro",
"messages": [ { "role": "user", "content": "退货运费谁承担?" } ],
"kb_ids": [3],
"kb_top_k": 5
}
响应报文示例
application/json
{
"id": "chatcmpl-abc123", "object": "chat.completion", "created": 1780367412,
"model": "deepseek-v4-pro",
"choices": [ { "index": 0, "message": { "role": "assistant",
"content": "根据《退货政策》第2条,退货运费由买方承担..." }, "finish_reason": "stop" } ],
"usage": { "prompt_tokens": 1200, "completion_tokens": 150, "total_tokens": 1350 }
}
响应参数含义
| 字段 | 类型 | 说明 |
| id | string | 对话 ID |
| choices[].message.content | string | 回答内容 |
| choices[].finish_reason | string | stop/length/tool_calls |
| usage | object | token 统计(prompt/completion/total) |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. LLM 对话补全(挂载知识库)
r = requests.post(f"{BASE}/llm/v1/chat/completions",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={"model": "deepseek-v4-pro", "messages": [{"role": "user", "content": "退货运费谁承担?"}], "kb_ids": [3]}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// LLM 对话补全(挂载知识库)
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/llm/v1/chat/completions"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({"model": "deepseek-v4-pro", "messages": [{"role": "user", "content": "退货运费谁承担?"}], "kb_ids": [3]}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// LLM 对话补全(挂载知识库)
const resp = await fetch(`${BASE}/llm/v1/chat/completions`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"model": "deepseek-v4-pro", "messages": [{"role": "user", "content": "退货运费谁承担?"}], "kb_ids": [3]})
});
console.log(resp.status);
console.log(await resp.json());
8.3 其他端点
| 端点 | 方法 | 说明 |
| /llm/v1/completions | POST | 标准补全(后台不支持 501) |
| /llm/v1/embeddings | POST | 文本向量化(后台不支持 501) |
智能脱敏 API(用户 JWT)
flowchart LR
UP[上传 PDF] --> SC[scan 侦测
contract/case 规则集]
SC --> RV[review 候选敏感项
分组展示]
RV --> AP[apply 真删除
+残留自检+报告]
AP --> OUT[副本 PDF + HTML 报告]
sequenceDiagram
autonumber
participant Biz as 业务系统
participant API as DJWork API
Biz->>API: POST /api/redact/upload (PDF)
API-->>Biz: { file_id }
Biz->>API: POST /api/redact/scan (file_id + profile)
API-->>Biz: { task_id, status: scanning }
loop 轮询
Biz->>API: GET /api/redact/{task_id}/status
API-->>Biz: { status: reviewing, scan_result: 候选分组 }
end
Biz->>API: POST /api/redact/apply (task_id + 黑白名单)
API-->>Biz: { task_id, status: applying }
loop 轮询
Biz->>API: GET /api/redact/{task_id}/status
API-->>Biz: { status: completed }
end
Biz->>API: GET /api/redact/{task_id}/result
API-->>Biz: 脱敏副本 PDF
Biz->>API: GET /api/redact/{task_id}/report
API-->>Biz: HTML 脱敏报告

流程:upload → scan(规则侦测)→ apply(真删除+残留自检)→ result/report
POST/api/redact/upload

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| file | file | ✅ | — | PDF,≤50MB |
请求报文示例
application/json
(multipart/form-data)
file=@判决书.pdf
响应报文示例
application/json
{ "file_id": "a1b2c3d4e5f6", "file_name": "判决书.pdf", "size_bytes": 1048576 }
响应参数含义
| 字段 | 类型 | 说明 |
| file_id | string | 文件 ID(scan 使用) |
| file_name | string | 原始文件名 |
| size_bytes | int | 文件大小 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
token = "<用户JWT>" # POST /api/v1/token 换取
r = requests.post(f"{BASE}/api/redact/upload",
headers={"Authorization": f"Bearer {token}"},
files={"file": open("文件.pdf", "rb")},
data={})
print(r.status_code, r.json())
JavaScript(fetch + FormData)
JavaScript
const BASE = 'http://localhost:8000';
const fd = new FormData();
fd.append("file", file); // 前端文件对象
const resp = await fetch(`${BASE}/api/redact/upload`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: fd
});
console.log(await resp.json());
Java:multipart 建议使用 OkHttp 的 MultipartBody;标准库手工构造 boundary 较繁琐,JSON 接口示例见下文模板。
POST/api/redact/scan

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| file_id | string | ✅ | — | 9.1 返回 |
| profile | string | 否 | contract | contract/case/all 规则集 |
| detect_seals | bool | 否 | true | 检测印章区域 |
请求报文示例
application/json
{ "file_id": "a1b2c3d4e5f6", "profile": "contract", "detect_seals": true }
响应报文示例
application/json
{ "task_id": "redact:a1b2c3d4e5f6:ab12cd34", "status": "scanning", "message": "正在侦测敏感项..." }
响应参数含义
| 字段 | 类型 | 说明 |
| task_id | string | 任务 ID |
| status | string | pending/scanning/reviewing/applying/completed/failed/cancelled |
| message | string | 阶段描述 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 触发敏感项侦测
r = requests.post(f"{BASE}/api/redact/scan",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={"file_id": "a1b2c3d4e5f6", "profile": "contract"}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 触发敏感项侦测
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/redact/scan"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({"file_id": "a1b2c3d4e5f6", "profile": "contract"}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 触发敏感项侦测
const resp = await fetch(`${BASE}/api/redact/scan`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"file_id": "a1b2c3d4e5f6", "profile": "contract"})
});
console.log(resp.status);
console.log(await resp.json());
9.3 候选敏感项(reviewing 状态详情)

认证:用户 JWT | GET /api/redact/{task_id}/detail
响应报文示例
application/json
{
"task_id": "redact:...", "status": "reviewing", "profile": "contract",
"scan_result": {
"涉案人/相关人员": [
{ "field": "身份证号", "text": "110101199001011234", "page": 1, "confidence": 0.98 } ],
"涉案单位/签约主体": [
{ "field": "公司名称", "text": "XX科技有限公司", "page": 1, "confidence": 0.9 } ]
}
}
响应参数含义
| 字段 | 类型 | 说明 |
| scan_result | object | 按分组(涉案人/签约主体/合同细节等)的候选敏感项 |
| scan_result.<分组>[].field | string | 字段类型(身份证号/手机号/公司名称等) |
| scan_result.<分组>[].text | string | 命中原文 |
| scan_result.<分组>[].page | int | 所在页码 |
| scan_result.<分组>[].confidence | float | 置信度 |
POST/api/redact/apply

认证:用户 JWT
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| task_id | string | ✅ | — | scan 返回 |
| blacklist | string[] | 否 | [] | 强制脱敏词条 |
| whitelist | string[] | 否 | [] | 保留不脱敏词条 |
| date_mode | string | 否 | quarter | quarter(降级季度)/full/off |
| redact_images | bool | 否 | false | 涂掉内嵌图片 |
请求报文示例
application/json
{ "task_id": "redact:...", "blacklist": ["客户简称"], "whitelist": ["姓名"], "date_mode": "quarter" }
响应报文示例
application/json
{ "task_id": "redact:...", "status": "applying", "message": "正在生成脱敏副本..." }
响应参数含义
| 字段 | 类型 | 说明 |
| task_id | string | 任务 ID |
| status | string | applying(异步执行:真删除→残留自检→报告) |
| message | string | 阶段描述 |
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 应用脱敏
r = requests.post(f"{BASE}/api/redact/apply",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={"task_id": "redact:xxx"}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 应用脱敏
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/redact/apply"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <用户JWT>")
.POST(HttpRequest.BodyPublishers.ofString({"task_id": "redact:xxx"}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 应用脱敏
const resp = await fetch(`${BASE}/api/redact/apply`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"task_id": "redact:xxx"})
});
console.log(resp.status);
console.log(await resp.json());
9.4 脱敏状态 / 结果 / 报告

认证:用户 JWT | GET /api/redact/{task_id}/status
响应报文示例(completed)
application/json
{
"task_id": "redact:...", "status": "completed", "progress": 100.0,
"message": "脱敏副本生成完成",
"verify_result": { "total": 12, "remaining": 0 },
"error": null
}
响应参数含义
| 字段 | 类型 | 说明 |
| status | string | pending/scanning/reviewing/applying/completed/failed/cancelled |
| verify_result.total | int | 脱敏总数 |
| verify_result.remaining | int | 残留自检剩余(0=无残留) |
| 接口 | 方法 | 说明 |
| /api/redact/{task_id}/result | GET | 脱敏副本 PDF(文件流,仅 completed) |
| /api/redact/{task_id}/report | GET | HTML 脱敏报告(命中字段/位置/自检结论) |
| /api/redact/tasks | GET | 列表:page/per_page/status |
| /api/redact/{task_id} | DELETE | 取消/删除 |
SSO 页面免登录
flowchart LR
U[业务系统用户] --> B[业务系统页面]
B -->|组织Key + user_id| T[POST /api/v1/token
换取用户JWT]
T -->|JWT 直跳
或 dkp_ 个人密钥| E[/sso/enter/]
E -->|校验 scope/redirect/用户/组织| W[DJWork 页面
普通登录态]
sequenceDiagram
autonumber
participant User as 业务系统用户
participant Biz as 业务系统后端
participant API as DJWork API
participant Web as DJWork 页面
User->>Biz: 点击功能入口
Biz->>API: POST /api/v1/token (组织Key + user_id
+ expires_in=300 + scope=page)
API-->>Biz: { token: 用户JWT }
Biz-->>User: 跳转 /sso/enter?token=JWT&redirect=/tasks
User->>Web: 打开落地页
Web->>API: GET /sso/enter (校验 redirect 白名单/scope 含 page/用户/组织)
API-->>Web: 引导页写入 localStorage.jwt_token → 自动跳转 redirect
Web->>API: 后续请求带 Web JWT(普通登录态)

变更提示(2026-08-28):一次性 ticket 方案已软弃用——POST /api/v1/ticket 现返回 410, 请统一使用「组织 Key 换取用户 JWT → /sso/enter?token= 直跳」。 为降低 token 出现在 URL 的影响面,页面跳转建议单独签发短时效凭证(expires_in=300, scope=page)。
10.1 跳转前签发短时效凭证(推荐)

认证:组织 Key | POST /api/v1/token(即 §2.1)
页面免登录跳转建议使用与业务 API 不同的短时效凭证:
请求报文示例
application/json
{ "user_id": "10086", "expires_in": 300, "scope": "page" }
响应报文示例
application/json
{
"code": 1000,
"msg": "success",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 300,
"scope": "page",
"user": { "user_id": "10086", "registered": true }
}
}

expires_in 支持 60 ~ 31536000 秒(最长 365 天);scope 传 page(或 api,page)。 长时效演示链接可自行放宽有效期,泄露风险自担。
10.2 页面落地 GET /sso/enter

认证:无需请求头,凭证经 URL 参数传递(浏览器顶层导航)
请求参数
| 参数 | 类型 | 必填 | 说明 |
| token | string | 二选一 | 用户 JWT(§2.1 签发)或 dkp_ 个人 API 密钥(§10.3);两者都传返回 error=ambiguous |
| ticket | string | 二选一 | 一次性票据(已弃用,一律返回 error=ticket_invalid) |
| redirect | string | 否 | 目标站内路径,默认 /tasks;须通过 redirect 白名单校验 |
redirect 白名单规则
- 管理端
SSO_REDIRECT_WHITELIST 含 * 时(当前默认):放行任意站内相对路径,但仍强制以 / 开头、拒绝 //、反斜杠与协议头,防止开放重定向; - 不含
* 时:仅放行白名单枚举路径(如 /tasks、/apps/smart-extract、/apps/doc-compare、/apps/pdf-redact、/apps/seal-verify、/kb-search、/smart-apps、/my-files、/upload); - 校验失败:HTTP 400
{"detail":"不允许跳转到: ..."}。
校验流程(顺序)
token/ticket 二选一(都缺 → 302 /login?error=token_missing);- redirect 白名单(失败 → HTTP 400);
- 凭证解析:JWT 过期 →
token_expired;无效 → token_invalid;dkp_ 前缀按个人密钥解析(等同本人,scope 视为 api,page); - scope 必须含
page(否则 HTTP 403); - 用户存在、未冻结、未删除(否则 →
user_invalid); - 组织
domain_whitelist 非空时,校验请求 Referer 的 host 在白名单内(否则 HTTP 403)。
落地行为
成功后服务端签发新的 Web JWT,通过引导页写入 localStorage.jwt_token(前端登录态来源,Cookie 同步种一份备用),随后自动跳转 redirect——用户即以普通登录态进入目标页面。
错误重定向参数(302 → /login?error=...)
| error | 原因 |
| token_missing | token/ticket 均未提供 |
| ambiguous | token 与 ticket 同时提供 |
| token_expired | JWT 已过期 |
| token_invalid | JWT/dkp_ 密钥无效 |
| ticket_invalid | ticket 已弃用或无效 |
| user_invalid | 用户不存在/已冻结/已删除 |
集成示例
bash
# 1. 组织 Key 换短时效页面凭证(300 秒,仅 page)
curl -X POST https://<host>/api/v1/token \
-H "Authorization: Bearer djk_xxx" -H "Content-Type: application/json" \
-d '{"user_id":"10086","expires_in":300,"scope":"page"}'
# 2. 浏览器跳转(Referer 需在组织 domain_whitelist 内,若组织配置了白名单)
https://<host>/sso/enter?token=<JWT>&redirect=/tasks
10.3 dkp_ 个人密钥直跳
个人中心「API 密钥」创建的 dkp_ 密钥与用户 JWT 全流程等价,可直接用于免登录跳转:
text
https://<host>/sso/enter?token=dkp_xxx&redirect=/tasks
- 等同本人登录:scope 视为
api,page,无需经组织 Key 换证; - 同样受 redirect 白名单、用户状态、组织域名白名单校验;
- 亦可用于业务 API 调用(
Authorization: Bearer dkp_xxx,§一 1.2)。
印章核验 API(组织 Key / 用户 JWT)
flowchart LR
A[业务系统] -->|multipart 上传| B[POST /api/v1/seal-verifications]
B --> C[后台流水线:本地预筛
→ 印章检测裁剪 → LLM 校准]
A -->|轮询 2~3s| D["GET /api/v1/seal-verifications/{task_id}"]
D -->|completed| E[印章位置/文字/裁剪图
骑缝标记/签署方比对]
11.1 能力概览

更细的字段说明与 Python 完整示例另见 docs/seal-verify-external-api.md(本节为全量文档内嵌版)。
- 上传 PDF / 图片,自动定位印章页(本地快速预筛,长文档秒级)、调用印章检测裁剪服务输出每枚印章的位置(归一化坐标)、识别文字与裁剪图;
- LLM 视觉校准(多模态):模型逐枚查看印章裁剪图,对照 OCR 原文,输出分区转写(ring_text 环形文字 / horizontal_text 横向文字 / code 编号,均去换行与空格)、文字校准(仅确认 OCR 有误才修正,
corrected 标记是否修正)、类型判定与签署方一致性比对(match_code 机器可读枚举); - 骑缝章识别:贴页面边缘的残段自动标记
kind="marginal"(骑缝章),完整印章 kind="full";跨页骑缝碎片由 LLM 结合图片按页序尝试拼合完整章名; - LLM 失败不影响任务完成:此时
llm.enhanced=false,印章保留 OCR 原文(text),calibrated_name/ring_text/horizontal_text/code/match_code 等为 null。 - 模型要求:管理端「功能模型」为
seal_verify 绑定支持视觉(多模态)的模型(如 qwen-vl 系列);绑定纯文本模型时视觉校准会失败并按上述方式降级。
11.2 鉴权(双通道)
| 通道 | 凭证 | 归属语义 | 权限要求 |
| 组织 Key | Authorization: Bearer djk_xxx | 创建时必须传 user_id 表单字段指定归属用户(自动注册/映射,同 §2.1);查询/删除可访问该组织全部映射用户的任务 | Key 的 permissions 需勾选 seal_verify |
| 用户 JWT | Authorization: Bearer <用户 JWT> | 任务归属当前用户,与页面内任务列表互通;?token= 查询参数亦可 | 用户 JWT(dkp_ 个人密钥等同) |
11.3 端点速览
| 接口 | 方法 | 说明 |
| /api/v1/seal-verifications | POST | 创建核验任务(multipart 直传) |
| /api/v1/seal-verifications/{task_id} | GET | 状态 + 完整结果(轮询入口) |
| /api/v1/seal-verifications | GET | 任务列表(分页,按状态筛选) |
| /api/v1/seal-verifications/{task_id} | DELETE | 删除(运行中先协作式取消) |
| /api/v1/seal-verifications/{task_id}/seals/{index}/image | GET | 印章裁剪图 PNG 二进制 |
POST/api/v1/seal-verifications
请求参数(multipart/form-data)
| 字段 | 类型 | 必填 | 说明 |
| file | file | ✅ | PDF / 图片(png/jpg/jpeg/bmp/tif/tiff),≤50MB;OFD 返回 400 并提示先导出 PDF |
| user_id | string | 组织 Key 必填 | 归属用户在业务系统的唯一标识(自动注册/映射) |
响应报文示例
application/json
{
"success": true,
"data": {
"task_id": "sealverify:670c9fbddb4d:c27b9a02",
"file_id": "670c9fbddb4d",
"file_name": "订单类合同.pdf",
"file_kind": "pdf",
"status": "pending",
"progress": 0,
"phase": null,
"message": null,
"total_pages": 0,
"candidate_pages": [],
"seal_count": 0,
"duration_ms": null,
"llm_enhanced": false,
"error_message": null,
"created_at": "2026-09-01T06:11:40",
"completed_at": null
}
}
集成示例
bash
# 组织 Key 直连:上传并创建任务(自动注册/映射归属用户)
curl -X POST https://<host>/api/v1/seal-verifications \
-H "Authorization: Bearer djk_xxx" \
-F "file=@订单类合同.pdf" \
-F "user_id=10086"
GET/api/v1/seal-verifications/{task_id}

轮询入口。建议创建后以 2~3 秒间隔轮询,直至 status 进入 completed/failed/cancelled; running 期间 result.pages 会随检测进度增量更新(渐进获取印章)。
响应报文示例(completed)
application/json
{
"success": true,
"data": {
"task_id": "sealverify:670c9fbddb4d:c27b9a02",
"status": "completed",
"progress": 100,
"phase": "done",
"total_pages": 1,
"candidate_pages": [1],
"seal_count": 2,
"duration_ms": 12900,
"llm_enhanced": true,
"result": {
"pages": [
{
"page": 1,
"width": 595,
"height": 842,
"seals": [
{
"index": 1,
"page": 1,
"text": "示例网络科技有限公司 合同专用章",
"kind": "full",
"norm": [0.62, 0.66, 0.93, 0.87],
"image": "seals/1.png",
"width": 352,
"height": 347,
"ring_text": "示例网络科技有限公司",
"horizontal_text": "合同专用章",
"code": "2431846",
"calibrated_name": "示例网络科技有限公司合同专用章",
"seal_type": "合同专用章",
"corrected": false,
"match_status": "与甲方匹配",
"match_code": "match_party_a",
"matched_party": "示例网络科技有限公司"
}
]
}
],
"llm": {
"enhanced": true,
"parties": [
{ "role": "甲方", "name": "示例网络科技有限公司" },
{ "role": "乙方", "name": "某某信息技术有限公司" }
],
"error": null
},
"fallback_pages": [],
"failed_pages": [],
"raw_pages": { }
}
}
}
响应参数含义
| 字段 | 类型 | 说明 |
| status | string | pending/running/completed/failed/cancelled |
| phase | string | preprocess→prescreen→detect→enhance→done |
| candidate_pages | int[] | 本地预筛命中、送远程检测的页码 |
| seal_count | int | 检出印章总数 |
| result.pages[].page | int | 页码(1 起) |
| result.pages[].width/height | float | 页面尺寸(布局占位用) |
| seals[].index | int | 印章全局序号(裁剪图 URL 用) |
| seals[].page | int | 所在页码(1 起) |
| seals[].text | string | OCR 原始识别文字 |
| seals[].kind | string | 印章形态:full=完整印章;marginal=骑缝章(贴页面边缘残段) |
| seals[].norm | float[] | 位置框 [x0,y0,x1,y1],归一化 0-1(乘以展示尺寸即像素) |
| seals[].image | string | 裁剪图相对路径(经 11.3 裁剪图端点访问) |
| seals[].ring_text | string | 环形/上旋文字转写(通常为单位名称);多行已去换行与空格;LLM 未生效时为 null |
| seals[].horizontal_text | string | 章面横向文字转写(可能多行,已合并去换行与空格);LLM 未生效时为 null |
| seals[].code | string | 印章编号/防伪码转写;无则空串;LLM 未生效时为 null |
| seals[].calibrated_name | string/null | LLM 看图校准后印章名称(骑缝碎片为拼合结果;失败为 null) |
| seals[].seal_type | string/null | 公章/合同专用章/财务专用章/法人名章/骑缝章/其他 |
| seals[].corrected | bool/null | 视觉校准确认 OCR 有误并修正时为 true;无误为 false;LLM 未生效为 null |
| seals[].match_status | string/null | 中文展示:与甲方匹配/与乙方匹配/与其他方匹配/未匹配/无法判断 |
| seals[].match_code | string/null | 机器可读枚举:match_party_a/match_party_b/match_other/no_match/unknown(调用方程序化判断用) |
| seals[].matched_party | string/null | 匹配到的当事方名称(未匹配/无法判断为 null) |
| result.llm | object | enhanced 是否生效、parties 签署方结论、error 失败原因 |
| result.fallback_pages | int[] | 预筛零命中时做远程兜底检测的页 |
| result.failed_pages | int[] | 远程检测失败的页(不影响其他页) |
GET/api/v1/seal-verifications(任务列表)
请求参数
| 字段 | 类型 | 必填 | 默认 | 说明 |
| page | int | 否 | 1 | 页码 |
| per_page | int | 否 | 10 | 每页 ≤100 |
| status | string | 否 | — | pending/running/completed/failed/cancelled |
响应报文示例
application/json
{
"success": true,
"data": {
"tasks": [ { "task_id": "sealverify:...", "file_name": "合同.pdf", "status": "completed", "seal_count": 2 } ],
"total": 1, "page": 1, "per_page": 10, "pages": 1
}
}

组织 Key 通道返回该组织全部映射用户的任务;用户 JWT 返回本人任务。
DELETE/api/v1/seal-verifications/{task_id}
删除任务(运行中先协作式取消,再删记录与输出目录):
application/json
{ "success": true, "data": { "task_id": "sealverify:670c9fbddb4d:c27b9a02" } }
GET/api/v1/seal-verifications/{task_id}/seals/{index}/image
印章裁剪图 PNG 二进制。认证:Authorization: Bearer(组织 Key / 用户 JWT);用户 JWT 亦可用 ?token= 查询参数(<img>/iframe 直链场景)。index 为 result.pages[].seals[].index。
集成示例
bash
# 用户 JWT 通道:轮询结果 + 下载第 1 枚印章裁剪图
curl -H "Authorization: Bearer <用户JWT>" \
https://<host>/api/v1/seal-verifications/sealverify:670c9fbddb4d:c27b9a02
curl -H "Authorization: Bearer <用户JWT>" \
https://<host>/api/v1/seal-verifications/sealverify:670c9fbddb4d:c27b9a02/seals/1/image \
--output seal_1.png
11.4 印章核验接口速览
| 接口 | 方法 | 说明 |
| /api/v1/seal-verifications | POST | multipart 上传创建任务(组织 Key 需 user_id) |
| /api/v1/seal-verifications/{task_id} | GET | 状态+完整结果(轮询) |
| /api/v1/seal-verifications | GET | 列表(分页/状态筛选) |
| /api/v1/seal-verifications/{task_id} | DELETE | 取消并删除 |
| /api/v1/seal-verifications/{task_id}/seals/{index}/image | GET | 裁剪图 PNG |

错误:400 文件类型不支持/OFD 未转换/组织 Key 缺 user_id;401 无凭证;403 Key 无 seal_verify 权限;404 任务不存在;413 超 50MB。
NJCA审核 API(组织 Key / 用户 JWT)
flowchart LR
A[业务系统] -->|"multipart 上传(可多文件)"| B["POST /api/v1/njca-audits"]
B --> C[后台流水线:预筛/文字提取/印章检测/LLM 视觉交叉]
A -->|轮询 2~3s| D["GET /api/v1/njca-audits/{task_id}"]
D -->|completed| E[结论: 通过/不通过
三要素 + 原因列表]
12.1 审核规则
对「南京CA 数字证书业务办理授权委托书」核验三要素,齐备 → 结论「通过」,否则「不通过」并逐条给出原因:
| 要素 | 判定 |
| 被授权人姓名 | 提取非空(OCR 正则 + LLM 视觉交叉) |
| 身份证号码 | 18 位格式 + GB 11643 校验位算法通过(15 位旧格式提示人工核实) |
| 印章 | 检测到印章,且印章名称与委托单位全称归一化后一致(相等/互相包含/剥类型后缀/最长公共子串 ≥80% 容错) |
附加提取(不作通过条件):委托单位、法定代表人、委托事项勾选(新办/信息变更/续期/更新/补发/解锁/注销)、签署信息。
12.2 鉴权
与印章核验(§11.2)完全一致:组织 Key(permissions 需勾选 njca_audit,创建时必传 user_id 表单字段)/ 用户 JWT(dkp_ 等同)。组织 Key 查询范围为组织全部映射用户。
12.3 端点速览
| 接口 | 方法 | 说明 |
| /api/v1/njca-audits | POST | 批量创建审核任务(multipart files 可多文件) |
| /api/v1/njca-audits/{task_id} | GET | 状态 + 结论 + 提取字段 + 明细(轮询入口) |
| /api/v1/njca-audits | GET | 任务列表(分页/status 筛选) |
| /api/v1/njca-audits/{task_id} | DELETE | 删除(运行中先协作式取消) |

页面端点组 /api/njca-audits/*(用户 JWT)另有单页预览 .../preview/{page} 与印章图 .../seals/{index}/image,供页面渲染。
POST/api/v1/njca-audits
请求参数(multipart/form-data)
| 字段 | 类型 | 必填 | 说明 |
| files | file[] | ✅ | 一个或多个文件(PDF / 图片),≤50MB/个;OFD 返回 skipped 并提示先导出 PDF |
| user_id | string | 组织 Key 必填 | 归属用户标识(批量文件归属同一映射用户) |
响应报文示例
application/json
{
"success": true,
"data": {
"tasks": [
{ "task_id": "njca:670c9fbddb4d:1a2b3c4d", "file_name": "授权委托书.jpg", "file_kind": "image", "status": "pending", "progress": 0 }
],
"skipped": []
}
}
GET/api/v1/njca-audits/{task_id}(轮询入口)
响应报文示例(completed,data 节选)
application/json
{
"success": true,
"data": {
"task_id": "njca:670c9fbddb4d:1a2b3c4d",
"status": "completed",
"conclusion": "通过",
"fail_reasons": [],
"duration_ms": 5125,
"fields": {
"authorized_name": "史笑艳",
"id_number": "320107199003150027",
"id_valid": true,
"entrust_unit": "南京数字认证有限公司",
"legal_person": "孙高健",
"business_items": ["新办"],
"seal_found": true,
"seal_names": ["南京数字认证有限公司合同专用章"],
"seal_match": true,
"warnings": []
},
"result": {
"pages": [ { "page": 1, "width": 1190, "height": 1680, "seal_count": 1 } ],
"seals": [ { "index": 1, "page": 1, "text": "南京数字认证有限公司...", "norm": [0.6, 0.68, 0.95, 0.92], "image": "seals/1.png" } ],
"llm": { "enhanced": true, "error": null }
}
}
}
关键字段含义
| 字段 | 类型 | 说明 |
| conclusion | string | 通过 / 不通过 / 无法判定 |
| fail_reasons | string[] | 不通过原因逐条(缺姓名/身份证校验位不符/未检测到印章/印章名称与委托单位不一致) |
| fields.authorized_name / id_number | string | 提取的被授权人姓名与证件号 |
| fields.id_valid | bool | GB 11643 校验位算法是否通过 |
| fields.entrust_unit / legal_person | string | 委托单位全称 / 法定代表人 |
| fields.business_items | string[] | 勾选的委托事项 |
| fields.seal_found / seal_names / seal_match | bool/string[]/bool | 印章检测与匹配结论 |
| fields.warnings | string[] | OCR 与 LLM 交叉校验警告 |
| result.seals[] | object | 印章明细(norm 归一化坐标、image 裁剪图路径) |

LLM 视觉提取失败不影响任务完成:以 OCR 正则结果出结论,result.llm.enhanced=false。
集成示例
bash
# 组织 Key 批量创建
curl -X POST https://<host>/api/v1/njca-audits -H "Authorization: Bearer djk_xxx" -F "files=@委托书1.jpg" -F "files=@委托书2.pdf" -F "user_id=10086"
# 轮询结论
curl -H "Authorization: Bearer djk_xxx" https://<host>/api/v1/njca-audits/njca:670c9fbddb4d:1a2b3c4d
12.4 NJCA审核接口速览
| 接口 | 方法 | 说明 |
| /api/v1/njca-audits | POST | 批量创建(multipart files) |
| /api/v1/njca-audits/{task_id} | GET | 结论+字段+明细(轮询) |
| /api/v1/njca-audits | GET | 列表(分页/status) |
| /api/v1/njca-audits/{task_id} | DELETE | 取消并删除 |

错误:400 无 user_id/类型不支持/OFD 未转换;401 无凭证;403 Key 无 njca_audit 权限;404 任务不存在;413 超 50MB。
管理 API(管理员 JWT)
flowchart LR
AD[管理员] --> SYS[外部系统 CRUD
/api/admin/external-systems]
AD --> KEY[API Key 管理
权限/过期/吊销]
AD --> LOG[调用审计
凭证类型/用户/路径]
SYS --> MAP[组织用户查看]
sequenceDiagram
autonumber
actor Admin as 管理员
participant UI as 管理后台
participant API as DJWork API
participant DB as SQLite
Admin->>UI: 创建外部系统
UI->>API: POST /api/admin/external-systems
API->>DB: INSERT external_systems
API-->>UI: { id, code }
Admin->>UI: 生成 API Key(组织 + 模块权限)
UI->>API: POST /api/admin/api-keys
API-->>UI: 明文 Key(仅此一次)
Admin->>UI: 查看调用审计
UI->>API: GET /api/admin/api-access-logs
API-->>UI: 分页日志(凭证/用户/路径/状态)

认证:管理员 JWT Authorization: Bearer <JWT>(后台登录签发);不受 Key 模块限制。
13.1 创建外部系统
请求报文示例
application/json
{ "code": "erp-sap", "name": "SAP ERP", "auto_register": true, "domain_whitelist": ["erp.example.com"] }
响应报文示例
application/json
{ "success": true, "id": 1, "data": { "id": 1, "code": "erp-sap", "name": "SAP ERP", "auto_register": true, "status": 1 } }
集成示例
Python(requests)
Python
import requests
BASE = "http://localhost:8000"
# 2. 创建外部系统
r = requests.post(f"{BASE}/api/admin/external-systems",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
json={"code": "erp-sap", "name": "SAP ERP", "auto_register": true}
print(r.status_code)
print(r.json())
Java(HttpClient)
Java
import java.net.URI;
import java.net.http.*;
public class ApiExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 创建外部系统
HttpRequest req = HttpRequest.newBuilder(
URI.create("http://localhost:8000/api/admin/external-systems"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer <管理员JWT>")
.POST(HttpRequest.BodyPublishers.ofString({"code": "erp-sap", "name": "SAP ERP", "auto_register": true}))
.build();
HttpResponse<String> resp = client.send(req,
HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
JavaScript(fetch)
JavaScript
const BASE = 'http://localhost:8000';
// 创建外部系统
const resp = await fetch(`${BASE}/api/admin/external-systems`, {
method: 'POST',
headers: { Authorization: 'Bearer <管理员JWT>', 'Content-Type': 'application/json' },
body: JSON.stringify({"code": "erp-sap", "name": "SAP ERP", "auto_register": true})
});
console.log(resp.status);
console.log(await resp.json());
13.2 生成 API Key

认证:管理员 JWT | POST /api/admin/api-keys;明文 Key 仅此一次返回。
请求报文示例
application/json
{ "name": "ERP 生产环境", "external_system_id": 1,
"permissions": ["users","audit","extract","kb"], "expires_at": "2027-12-31T00:00:00" }
响应报文示例
application/json
{ "success": true, "key": "djk_ab12cd34.xxxxx", "data": { "id": 1, "api_id": "ab12cd34",
"name": "ERP 生产环境", "key_preview": "djk_ab12***", "status": 1 } }
| 字段 | 类型 | 说明 |
| key | string | 明文 Key,仅创建时返回一次,立即保存 |
| data.api_id | string | 8 位 hex 标识 |
| data.key_preview | string | 脱敏预览(列表展示用) |
13.3 管理接口速览
| 接口 | 方法 | 说明 |
| /api/admin/external-systems | GET/POST | 组织列表/创建 |
| /api/admin/external-systems/{id} | PUT/DELETE | 更新(含 auto_register)/删除;status=0 吊销全部 Key |
| /api/admin/external-systems/{id}/users | GET | 组织注册用户列表 |
| /api/admin/api-keys | GET | Key 列表(系统/模块/过期/last_used_at) |
| /api/admin/api-keys/{key_id} | DELETE | 立即失效 |
| /api/admin/api-access-logs | GET | 调用审计:system/key/user/credential_type/start/end/page/export=1 |
| /api/admin/llm-profiles | GET/POST/PUT/DELETE | 后台模型配置 |
| /api/admin/extraction-templates | GET/POST/PUT/DELETE | 提取模板管理 |
模块权限矩阵(组织 Key permissions)
| 模块 code | 覆盖章节 | 说明 |
| users | §二 | 用户生命周期与凭证(组织 Key 必须) |
| audit | §三 | 合同审核 |
| compare | §四 | 文档比对 |
| extract | §五 | 按模板结构化 |
| kb | §六 | 知识库 |
| ocr | §七 | OCR 代理 |
| llm | §八 | LLM |
| redact | §九 | 智能脱敏 |
| seal_verify | §十一 | 印章核验(/api/v1/seal-verifications,组织 Key 直连或用户 JWT) |
| njca_audit | §十二 | NJCA审核(/api/v1/njca-audits,组织 Key 直连或用户 JWT) |

一个 Key 可勾选多个模块;空数组=仅用户管理。
限流与配额
- 限流:Redis 滑动窗口按 Key(默认 60 次/分钟,可配);超限 429;
- 审核配额:User.audit_quota_remaining(提交时校验,不足 403);
- 并发:系统审核并发达 max_concurrent_audits 上限 429;
- 用量:GET /api/v1/usage 查看组织聚合用量。
MCP 服务(Model Context Protocol)
DJWork 把内置能力(合同审核、智能提取、文档比对、印章核验、NJCA审核、知识库、OCR、LLM 等)以 MCP 工具开放,任何支持 MCP 协议的外部系统(Claude、Cursor、自研 Agent 等)可直接接入调用。
16.1 接入端点与传输
| 项目 | 值 |
| 端点 | POST /mcp/djwork/(建议带尾斜杠,避免 307 跳转) |
| 传输 | MCP Streamable HTTP(无状态模式,多副本部署安全,无需会话粘滞) |
| 认证 | Authorization: Bearer <用户 JWT>(与 REST API 同源,见 §二 组织 Key 换证,或个人 API Key 换取的 JWT) |
| 协议版本 | MCP 2024-11-05(mcp python SDK 1.x 客户端均可) |
认证失败统一返回 401 {"error": "unauthorized"}。
16.2 文件输入约定(file_id / file_path 双入口)
外部系统没有服务器路径,标准流程:
sequenceDiagram
participant E as 外部系统
participant R as DJWork REST
participant M as DJWork MCP
E->>R: 1. POST /api/uploads/quick 上传文件(用户 JWT)
R-->>E: 2. 返回 file_id
E->>M: 3. MCP call_tool(传 file_id)
M-->>E: 4. task_id / 结果
E->>M: 5. 轮询 *_status / *_get
同机调用方可继续传服务器路径 file_path(须在用户工作区/上传目录白名单内)。
16.3 工具清单(18 个)
| 模块 | 工具 | 说明 |
| 合同审核 | audit_submit / audit_get_detail | 提交审核(file_id/file_path、类型/立场/强度)/ 按 audit_id 查结果 |
| 智能提取 | extract_run / extract_get | 按模板结构化抽取 / 按 result_id 查字段 |
| 文档比对 | compare_run / compare_get | 两份同类型文件差异比对 / 查结果 |
| 印章核验 | seal_verify_start / seal_verify_status | 定位印章页、裁剪章面、识别印章名 / 查进度与结果 |
| NJCA审核 | njca_audit_submit / njca_audit_status | 批量委托书三要素审核 / 批量查结论与不通过原因 |
| 知识库 | kb_list / kb_search | 列出可见知识库 / 检索(默认密钥权限范围内自动选库,支持按名称指定,多轮+关联词句增强召回,详见 §16.5) |
| OCR | ocr_* | DianjuOCR v3 代理 |
| LLM | llm_* | OpenAI 兼容对话补全 |
| 智能脱敏 | redact_scan / redact_apply | 仅管理员可见可调用 |
| 管理/用量 | admin_* / usage_* | 管理员专用 / 组织用量查询 |
权限裁剪:普通用户不可见 admin_*、redact_*;印章核验与 NJCA审核 工具在调用时校验智能应用授权(admin / CRM 认证用户 / 管理员单独授权的用户),计次与页面入口同一口径。
16.4 Python 客户端示例
Python
# pip install "mcp>=1.20.0,<2"
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
DJWORK_URL = "https://your-host/mcp/djwork/"
JWT = "<用户 JWT>" # §二 组织 Key 换证获得
async def main():
async with streamablehttp_client(
DJWORK_URL, headers={"Authorization": f"Bearer {JWT}"}
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
# 1) REST 上传文件后(拿到 file_id)提交印章核验
res = await session.call_tool(
"seal_verify_start", {"file_id": "<file_id>"}
)
task_id = __import__("json").loads(res.content[0].text)["task_id"]
# 2) 轮询结果(扫描件通常 1~5 分钟)
import json, time
while True:
r = await session.call_tool(
"seal_verify_status", {"task_id": task_id}
)
task = json.loads(r.content[0].text)["task"]
if task["status"] in ("completed", "failed", "cancelled"):
print(json.dumps(task, ensure_ascii=False, indent=2))
break
time.sleep(10)
asyncio.run(main())
16.5 知识库检索(kb_list / kb_search)
检索范围即密钥权限范围:本人私有库 + 所属组织共享库 + 公开库;密钥看不到的库不会被检索,也不会在 kb_list 中出现。
选库方式(三选一,可组合):
| 方式 | 参数 | 说明 |
| 自动(默认) | 不传 kb_ids/kb_names | auto 模式:MoE 路由在权限范围内自动选库;可见库 < 3 个时全选 |
| 按名称指定 | kb_names: ["库名A", ...] | 对 AI 最友好。精确 → 忽略大小写 → 唯一子串 三级模糊匹配;全部未命中时报错并列出当前可见库名,便于自纠 |
| 按 id 指定 | kb_ids: [1, 2] | 精确指定(可先 kb_list 获取) |
召回增强(multi_round,默认开启): 服务端自动执行「关联词句扩展(LLM 生成同义/相关查询变体,失败回退规则切句)→ 多路并行向量召回 → 关键词精确补充(编号/型号/书名号等 embedding 盲区)→ 覆盖不足时二轮补充检索 → 统一 rerank」。无需客户端手工拆分查询。
追问改写: 传 history(最近对话 [{role, content}]),服务端自动把「它呢?」「上面第二点」等指代改写成独立问题再检索。
kb_search 请求参数一览:
| 参数 | 类型 | 必填 | 说明 |
query | string | 是 | 查询问题(建议完整自然语言) |
kb_names | string[] | 否 | 按名称指定知识库(支持模糊匹配) |
kb_ids | int[] | 否 | 按 id 指定知识库 |
history | object[] | 否 | 最近对话,用于追问改写 |
top_k | int | 否 | 返回条数,默认用户偏好,1~50 |
use_rerank | bool | 否 | 是否重排,默认用户偏好 |
multi_round | bool | 否 | 是否启用多轮增强召回,默认开 |
mode | string | 否 | auto/manual/disable,默认 auto |
返回 results: [{score, text, metadata}],metadata.matched_by 标明每条的召回路径:vector(向量)/ expansion(关联词句扩展)/ keyword(关键词精确)/ refine(二轮补充)。
application/json
{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "kb_search", "arguments": {
"query": "试用期解除劳动合同的条件",
"kb_names": ["劳动法规件"],
"top_k": 5
}}
}
Skill 包: 个人中心「应用工具」页签提供「知识库检索 Skill」下载(SKILL.md 标准格式,含 MCP 配置与零依赖检索脚本)。下载时需选择一个已申请的 API 密钥打包进去;未申请密钥的用户先到「API 密钥」页签申请。
16.6 常见错误
| 现象 | 原因与处理 |
| 401 unauthorized | JWT 缺失/过期;重新走 §二 换证 |
| 无权调用工具 xxx | 工具对当前角色不可见(如 redact_* 仅管理员) |
| 您未获得「xx」的访问权限 | 智能应用未授权,联系管理员在「智能应用管理」开通 |
| 使用次数已用完(x/y) | AppAuthorization 次数耗尽,联系管理员续期 |
| 审核次数不足 | 用户审核配额(audit_quota_remaining)耗尽 |
智能笔记 API(用户 JWT)
录音笔记的创建、管理与 AI 整理。录音由前端录制上传(WAV/WebM),文字按时间轴分段存储(segments),可拖动定位回听。
端点一览
| 方法 | 路径 | 说明 |
| GET | /api/notes | 当前用户笔记列表(最新在前,≤200 条) |
| POST | /api/notes | 创建/更新笔记(multipart:file=录音文件(可选),payload=JSON 字符串) |
| GET | /api/notes/{id} | 笔记详情(含 segments 与 extracted 结果) |
| PUT | /api/notes/{id} | 修改标题/转写文字(JSON body;改动文字会重置 AI 结果) |
| DELETE | /api/notes/{id} | 删除笔记(录音一并删除) |
| GET | /api/notes/{id}/audio | 录音流(支持 Range,可拖动定位) |
| POST | /api/notes/{id}/polish | AI 纠错:修正转写中的同音字/错别字,不增删原文 |
| POST | /api/notes/{id}/summarize | AI 整理:返回 {summary, mindmap}(mindmap 为缩进大纲文本) |
POST/api/notes
| 字段 | 类型 | 必填 | 说明 |
| file | file | 否 | 录音文件(WAV/WebM/MP4,≤100MB) |
| payload | string(JSON) | 否 | {id?, title?, transcript?, segments?:[{s,e,text}], duration?, status?, reset_ai?} |
响应示例
application/json
{
"success": true,
"data": {
"id": 12,
"title": "项目周会录音",
"status": "ready",
"has_audio": true,
"audio_duration": 634.5,
"transcript": "……",
"corrected_text": null,
"summary": null,
"mindmap": null,
"segments": [{"s": 0.0, "e": 3.2, "text": "大家好,今天过一下项目进度"}],
"audio_url": "/api/notes/12/audio",
"updated_at": "2026-09-09T08:00:00"
}
}
错误码
| 状态 | 说明 |
| 400 | payload 非法 / 无可整理文字 |
| 404 | 笔记不存在或无权访问 |
| 502 | AI 纠错/整理调用大模型失败 |
语音服务 API(用户 JWT)
本地语音双引擎:识别 SenseVoice-Small(中英,自动标点/数字归一化,内置 Silero VAD 过滤静音噪声),合成 Piper。
| 方法 | 路径 | 说明 |
| POST | /api/voice/asr | multipart file=音频(WAV 16k 最佳;webm/opus、非 16k 自动重采样)→ {"text","language"};纯静音/噪声返回空文本 |
| POST | /api/voice/tts | JSON {"text", "voice"?, "speed"?} → audio/wav(中英自动分流,text ≤1000 字) |
网页链接附件 API(用户 JWT)
将网页 URL 添加为知识引擎 Agent 附件:服务端抓取页面并抽取正文,Agent 可基于内容回答。内置 SSRF 防护(仅 http/https、解析 IP 必须公网、重定向逐跳校验),单页 3MB/12 万字封顶。
| 方法 | 路径 | 说明 |
| POST | /api/kb/attachments/link | JSON {"url": "https://…"} → {attachment_id, type:"link", filename:页面标题, url, extracted_text, char_count} |
链接附件与文档附件同结构,随 POST /api/kb/search(/stream) 的 attachments 字段发送即可(type="link")。
错误码
| 状态 | 说明 |
| 400 | 链接非法 / 指向内网或保留地址 / 非 HTML 页面 / 抓取失败 |
人脸识别 API(组织 Key)
高安全性人脸库:YuNet 人脸检测 + ArcFace 512 维特征 + Silent-Face 静默活体(抗照片 / 屏幕重放攻击)。录入需多帧且通过活体与帧间一致性校验;全操作写审计日志。组织 Key 通道以 user_id(外部用户标识,需先经 §二 token 接口注册映射)定位平台用户。
20.1 接口一览
| 方法 | 路径 | 说明 |
| POST | /api/v1/face/enroll | 录入人脸(multipart:user_id + frames 多帧,≤10 帧) |
| POST | /api/v1/face/verify | 1:1 验证(user_id + frame)→ {matched, similarity, liveness_score, threshold} |
| POST | /api/v1/face/search | 1:N 检索(frame)→ {matched, user_id, username, similarity},用于扫脸登录 / 签到类场景 |
| GET | /api/v1/face/status | 录入状态(user_id)→ {enabled, liveness_score, quality_score, sample_count, created_at} |
| DELETE | /api/v1/face/{user_id} | 删除该用户人脸 |
| GET | /api/v1/face/list | 人脸库清单(用户 / 录入时间 / 活体分 / 质量分 / 状态,分页) |
平台内部同能力端点(用户 JWT,操作本人):POST /api/face/enroll、POST /api/face/verify、GET /api/face/status、POST /api/face/enabled(启停)、DELETE /api/face、POST /api/face/login(匿名扫脸登录,IP 限频 6 秒)、POST /api/face/pc-enroll/create(PC 出二维码由移动端代录)。
20.2 代表接口:录入人脸

组织 Key | POST /api/v1/face/enroll(multipart/form-data)
请求参数
| 字段 | 类型 | 必填 | 说明 |
| user_id | string | ✅ | 外部用户标识 |
| frames | file[] | ✅ | 同一人多帧照片(≤10 张;活体分 ≥0.7、≥4 帧通过一致性校验方可入库) |
响应报文示例
application/json
{ "success": true, "data": { "sample_count": 6, "liveness_score": 1.0, "quality_score": 1.0 } }
20.3 阈值与错误码
判定阈值:录入活体 ≥0.7、验证活体 ≥0.5;1:N 检索相似度 ≥0.42、1:1 验证 ≥0.45(可在 dianju_office/face 调整)。
| 状态 | 说明 |
| 400 | 缺少 user_id / 帧数据;活体未通过;多帧人脸不一致 |
| 401 | 未匹配到已录入的人脸(search) |
| 404 | user_id 映射不存在(请先通过 /api/v1/token 注册)或尚未录入人脸 |
Python
# Python 集成示例:录入 + 1:1 验证
import requests
BASE = "https://djwork.njca.com.cn"
KEY = "djk_xxx"
# 1. 录入(多帧)
requests.post(f"{BASE}/api/v1/face/enroll",
headers={"X-API-Key": KEY},
data={"user_id": "emp-1001"},
files=[("frames", open("f1.jpg", "rb")), ("frames", open("f2.jpg", "rb"))])
# 2. 1:N 检索
r = requests.post(f"{BASE}/api/v1/face/search",
headers={"X-API-Key": KEY},
files={"frame": open("query.jpg", "rb")}).json()
print(r["data"]) # {'matched': True, 'user_id': 'emp-1001', 'similarity': 0.93, ...}
管理 Agent(管理员 JWT)
DJWork v2.10.0 管理后台的有状态 Agent 接口。请求统一使用 Authorization: Bearer <管理员 JWT>;当前账号必须具有 admin 角色、已启用且未冻结。组织 Key 不是本章的管理认证方式。

澄清不等于批准。 所有真实写操作(包括删除、角色变更和授权)必须单独人工确认;模型文本中的“同意”、澄清选项或自由补充不能代替批准。接口不开放任意 SQL、shell、URL 或文件访问。
sequenceDiagram
participant U as 当前管理员浏览器
participant A as 管理 Agent
participant M as 所选模型
U->>A: POST /sessions
A-->>U: session_id
U->>A: POST /chat
A->>M: 当前用户消息与服务端历史
alt 需求有歧义
A-->>U: intent_required
U->>A: POST /answer
end
alt 需要修改数据
A-->>U: confirmation_required
U->>A: POST /confirm approved=true
A->>A: 重新鉴权并执行白名单 API
opt 生成一次性凭据
A-->>U: credential 仅当前浏览器
end
end
A-->>U: tool_result / delta / done
22.1 端点与会话生命周期
| 方法 | 路径 | 请求与响应 |
| GET | /api/admin/agent/models | 返回 {"models":[...]}。每项含 id/name/model/provider/source;来源为 system 或 personal,仅列当前账号可用的模型,不返回供应商密钥。 |
| GET | /api/admin/agent/tools | 返回 {"tools":[{"name":"...","description":"..."}]},用于发现实际可用工具;不是提交工具执行的 HTTP 入口。 |
| POST | /api/admin/agent/sessions | 无需 JSON 请求体;返回 {"session_id":"<UUID>"}。标识由服务器生成并绑定当前用户;服务端保留最近 7 天活跃会话。 |
| GET | /api/admin/agent/sessions | 列出当前管理员的历史会话,返回 {"sessions":[...]}(标题、更新时间、消息数;默认 30 条,按更新时间倒序)。仅返回当前账号的会话,其他管理员不可见。 |
| GET | /api/admin/agent/sessions/{session_id} | 读取当前管理员历史会话的文本消息,返回 {"session":{...,"history":[...]}}。会话不存在、已过期或不属于当前账号返回 404。 |
| DELETE | /api/admin/agent/sessions/{session_id} | 删除当前用户会话及其历史列表条目,返回 {"deleted":true};仍有活动请求时须先取消,否则返回 409。 |
| POST | /api/admin/agent/sessions/{session_id}/cancel | 取消该会话活动轮次及待确认/待澄清状态,返回 {"cancelled":true}。取消不回滚已经执行的真实写操作。 |
| POST | /api/admin/agent/chat | JSON 请求;响应 text/event-stream。参数见下节。同一会话不能并发开启两个活动轮次。 |
| POST | /api/admin/agent/confirm | 处理服务器发出的写操作确认,接受后返回 {"accepted":true}。只能决定已保存操作,不接收新目标、values 或凭据。 |
| POST | /api/admin/agent/answer | 回答服务器发出的意图澄清,接受后返回 {"accepted":true};保持原 SSE 流继续,不另外发起 chat。 |
模型 ID 必须取自 models 响应,例如 system:1 或 personal:2;相同模型名称不代表相同供应商或同一配置。JSON 参数严格校验,未声明字段会被拒绝。
22.2 流式对话请求
| 字段 | 类型 | 必填 | 说明 |
| session_id | string | 是 | 当前用户已创建的 UUID 会话标识。 |
| model_id | string | 是 | system:<正整数ID> 或 personal:<正整数ID>;必须实际可用且属于可访问来源。 |
| messages | object[] | 是 | 1 至 40 项,每项只有 role(user/assistant)和非空 content;单项最多 12,000 字符,总计最多 60,000 字符,最后一项必须为 user。服务端只取最后一条用户消息,历史由服务器会话提供,客户端不能伪造历史批准。 |
| context | object | 否 | 默认空对象,只接受可选 tab 字符串作为当前页签参考,不改变权限。 |
| mode | string | 否 | 兼容字段,唯一合法值和默认值均为 work。 |
先创建会话,再使用响应中的 session_idHTTP / JSON
POST /api/admin/agent/chat
Authorization: Bearer <ADMIN_JWT>
Content-Type: application/json
Accept: text/event-stream
{
"session_id": "<POST /sessions 返回的 UUID>",
"model_id": "system:1",
"messages": [{"role": "user", "content": "查询近7天的全局Token用量"}],
"context": {"tab": "overview"}
}
单轮最多 200 轮模型交互、1,000 次工具调用、30 分钟;还受输出与上下文预算约束。达到预算可能产生 paused 并保存进度,用户可在同一会话重新发送明确的“继续”消息。暂停不是自动批准,也不应自动重试已经提交的写操作。
22.3 SSE 事件
按标准 SSE 的 event: 与 data: 解析,每个 data 为 JSON。事件以实际供应商输出与真实工具执行为准;先收到 HTTP 200 不表示整个任务已成功。保留连接以接收最终 done 或 error;中途断开不能推断写操作是否完成。
| 事件 | 实际字段 / 用途 |
| status | 进度含 phase/message/round/max_rounds/tool_calls/max_tool_calls/session_id/turn_id,不是虚构的完成百分比。 |
| delta | content:正文增量。 |
| reasoning_delta | content/round:供应商实际返回、经敏感过滤的思考增量;并非所有模型提供。 |
| reasoning_end | 思考段结束边界;用于折叠当前段,不是任务结束。 |
| summary_delta | content:供应商提供的公开摘要增量,与正文和工具结果分开处理。 |
| tool_start | id/name/arguments:实际工具调用开始及规范参数。 |
| tool_result | id/name/result,失败时有 is_error;结果需检查目标 API 状态,不能只按事件名称判断成功。 |
| navigation | tab/subtab:请求前端切换合法页签,不保证客户端已完成导航。 |
| confirmation_required | 写操作计划含 session_id/turn_id/action_id/expires_at/title/description/risk/permissions/resource/operation/record_id/values,等待单独人工确认。 |
| intent_required | session_id/turn_id/question_id/expires_at/question/options/multi_select/allow_other;options 含 id/label/description,2 至 6 项。 |
| credential | session_id/turn_id/action_id/kind/value/label/record_id:原 API 生成的一次性用户初始密码或 API Key,仅交付当前批准管理员浏览器。不得写入模型上下文、聊天历史、日志或持久化浏览器存储;独立窗口关闭后清除。 |
| paused | 预算暂停及继续提示;随后 done 可带 paused:true,不能当作全部工作完成。 |
| done | 本轮结束;可能含 paused:true 或 cancelled:true,正常结束可为空对象。 |
| error | message:流中错误,不得将本轮当作成功结果;人工核对已经提交的操作,勿自动重试写入。 |
事件示意,ID由服务器生成text/event-stream
event: delta
data: {"content":"已查询实际统计,正在整理结果。"}
event: done
data: {}
22.4 写操作一次人工确认
写确认有效期 120 秒。展示 confirmation_required 中的目标、全部变更字段及风险后,由真实用户提交;不得因模型输出“确认”或用户选择澄清选项而自动提交 approved=true。
| 字段 | 类型 | 必填 | 说明 |
| session_id / turn_id / action_id | string | 是 | 必须原样使用当前 confirmation_required 的标识,不能跨用户、会话或轮次复用。 |
| approved | boolean | 是 | true批准,false拒绝;不是字符串。批准只允许一次消费,同一操作重放被拒绝。 |
拒绝操作示例application/json
{
"session_id": "<事件中的session_id>",
"turn_id": "<事件中的turn_id>",
"action_id": "<事件中的action_id>",
"approved": false
}
{"accepted":true}仅表示服务器收到了决定,不是执行成功。真实结果由原 SSE 的 tool_result 返回;目标 API 仍进行当前身份、许可、业务和版本校验。生成凭据时只走 credential 专用事件;未送达须通过管理页面人工重置/重新签发,不盲目重建用户或密钥。
22.5 意图澄清与取消
澄清有效期 300 秒。需求有歧义时服务器发送 intent_required;选择项由用户决定,未选择不代选。澄清必须单独调用,同批混入其他工具时整批不执行。自由补充只作为需求数据,不改变安全规则,更不批准写操作。
| 字段 | 类型 | 必填 | 说明 |
| session_id / turn_id / question_id | string | 是 | 当前 intent_required 的 UUID;错误轮次、跨用户/会话、重复作答或已过期请求拒绝。 |
| selected_option_ids | string[] | 否 | 默认空数组;只能提交当前问题中已列出的 ID,不可重复。multi_select=false 时最多选一项。 |
| other_text | string | 否 | 默认空串,最多 2,000 字符;仅 allow_other=true 时可填写,禁止粘贴密码/密钥。 |
| cancelled | boolean | 否 | 默认false;true取消本次澄清与任务。未取消时须有有效选择,或在允许补充时填写非空other_text。 |
回答澄清示例(单选)application/json
{
"session_id": "<事件中的session_id>",
"turn_id": "<事件中的turn_id>",
"question_id": "<事件中的question_id>",
"selected_option_ids": ["<当前options中的id>"],
"other_text": "",
"cancelled": false
}
取消或过期后原 SSE 以 done {"cancelled":true}结束本轮,不继续后续工具或写操作。若浏览器断开,也不能将未收到的选择或批准视为同意。后续明确需求需要用户重新发起消息。
22.6 身份隔离、错误与集成注意事项
| HTTP 状态 | 本章典型原因 |
| 401 | 管理员登录凭证缺失、无效或已过期(由主应用认证依赖返回)。 |
| 403 | 不是管理员、账户未启用或被冻结、当前许可或权限不足。 |
| 404 | 会话不存在/已过期/不属于当前用户,或所选模型不存在、不可访问、不可用。 |
| 409 | 会话已有活动轮次;操作/问题不存在、过期、轮次不符、重复决定或已消费;活动会话直接删除。 |
| 422 | JSON严格参数校验失败、最后一条非用户消息、选择ID不合法、单选提交多项或不允许的自由补充。 |
| 503 | 服务端Agent会话存储暂不可用;不能假定动作已执行或自动重试真实写入。 |
- 服务端会话绑定当前用户;只有该用户可取消、删除、澄清或确认。客户端不得提交服务器历史、替换已保存操作参数或重放旧标识。
- 收到意图选择后仍须为后续写操作单独人工确认;确认后执行前重新检查管理员身份,原目标API的鉴权、许可、冻结/业务限制和版本保护继续生效。
- 所有本章JSON响应与SSE使用
Cache-Control: no-store;凭据事件只用于当前受信浏览器的一次性安全窗口,不能在共享终端或日志中转存。 - HTTP建立连接前的失败按状态码处理;流开始后的失败按 error/tool_result/done事件处理。取消不是事务回滚,不能承诺已开始的真实写操作被撤销。
变更记录
| 版本 | 日期 | 变更 |
| v2.10 | 2026-10-20 | 应用 v2.10.0:新增管理 Agent(管理员 JWT)章节,涵盖模型与工具发现、隔离会话、SSE 对话、一次人工写确认、意图澄清、一次性凭据私密交付及预算暂停。 |
| v1.0 | 2026-08-13 | 全量端点清单 + 参数表 + curl |
| v2.0 | 2026-08-13 | 核心接口补齐:请求/响应报文示例、响应参数含义表、Python/Java/JavaScript 三语言集成示例 |
| v2.1 | 2026-09-01 | 新增印章核验模块 seal_verify 权限与外部端点组(§十一):/api/v1/seal-verifications 创建/查询/列表/删除/裁剪图,支持组织 Key 与用户 JWT 双通道 |
| v2.3 | 2026-09-01 | 新增 NJCA审核模块 njca_audit 权限与外部端点组(§十二):/api/v1/njca-audits 批量创建/查询/删除,组织 Key 与用户 JWT 双通道;审核三要素(被授权人/身份证号校验位/印章与委托单位一致性)结论 |
| v2.2 | 2026-09-01 | SSO 章节(§十)更新:一次性 ticket 软弃用(POST /api/v1/ticket 返回 410)、dkp_ 个人密钥免登录直跳(与 JWT 等价)、redirect 白名单支持 "*" 通配任意站内路径、token expires_in 上限放宽至 365 天、落地页改写 localStorage.jwt_token |
| v2.5 | 2026-09-12 | 新增人脸识别模块 face 权限与外部端点组(§二十):/api/v1/face/* 录入/1:1验证/1:N检索/状态/删除/清单,组织 Key 按 user_id 外部标识映射;静默活体+多帧一致性校验(抗照片/视频攻击),全操作审计 |
| v2.4 | 2026-09-09 | 新增端点组(用户 JWT):§十七 智能笔记 /api/notes*(录音+时间轴分段转写+AI 纠错/汇总)、§十八 语音服务 /api/voice/asr | tts(SenseVoice+Piper 本地双引擎)、§十九 网页链接附件 /api/kb/attachments/link(服务端抓取正文,SSRF 防护) |