通用约定

本页文档版本 v2.10,适用于 DJWork v2.10.0;沿用《DJWork外部API文档.md》的参数表、报文示例、集成代码与流程图,并新增管理 Agent(管理员 JWT)接口及交互安全约定。

1.1 Base URL 与认证

项值
Base URLhttp(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_idstring✅—业务系统用户唯一标识(工号/手机号/UUID)
nicknamestring否—昵称(首次注册写入)
emailstring否—邮箱(首次注册写入)
expires_inint否86400用户 JWT 有效期秒,60 ~ 31536000(最长 365 天);SSO 页面跳转建议 expires_in=300 + scope=page 短时效
scopestring否api,pageapi(仅 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
  }
}

响应参数含义

字段类型说明
tokenstring用户 JWT(业务 API 与页面免登凭证)
token_typestring固定 bearer
expires_inint有效秒数
registeredbool本次是否首次注册(true=自动注册)
user.idint平台用户 ID
user.usernamestring平台用户名(自动注册为 ext_<组织code>_<user_id>)
user.external_user_idstring业务系统用户标识(回显)
user.is_active / is_frozenbool用户状态(冻结后 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_idstring✅—业务系统用户唯一标识
emailstring否—邮箱
quotaint否—审核配额(次数)

请求报文示例

application/json
{
  "user_id": "10086",
  "email": "zhangsan@corp.com",
  "quota": 50
}

响应报文示例

application/json
{
  "success": true,
  "id": 42,
  "username": "ext_erpsap_10086"
}

响应参数含义

字段类型说明
successbool是否成功
idint平台用户 ID
usernamestring生成的平台用户名

集成示例

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/usersGET列表: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}/statusPOST冻结/解冻 {frozen:truefalse}(即时阻断 JWT)
/api/v1/users/{user_id}DELETE删除用户(级联清理数据)
/api/v1/users/{user_id}/tokenPOST重发该用户凭证
/api/v1/users/{user_id}/dataGET数据概览(任务/文件/知识库/用量)
/api/v1/users/{user_id}/dataDELETE清理用户全部业务数据
/api/v1/usageGET组织用量统计

合同审核 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

请求参数

字段类型必填默认说明
filesfile[]✅—.docx/.pdf,单文件≤50MB,一次 1~5 个
contract_type_codestring✅—合同类型编码(GET /api/contract-types)
stancestring✅—party_a / party_b / neutral
intensitystring✅—quick / standard / deep
concurrencyint否1并发审核 1~5
force_reauditbool否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_idstringCelery 任务 ID(轮询用)
tasks[].file_idstring文件标识
tasks[].file_namestring原始文件名

集成示例

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_idpath✅—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_idstring任务 ID
statusstringpending/queued/running/completed/failed/cancelled
progressfloat0~100;failed 时为 -1
messagestring当前阶段描述
resultobjectcompleted 时含完整审核结果,否则 null
errorstringfailed 时含错误信息,否则 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"
}

响应参数含义

字段类型说明
idint记录 ID
file_id / file_namestring文件标识与原始文件名
contract_type_code / stance / intensitystring审核参数快照
statusstring任务状态
scorefloat综合评分 0~100
total_issues / high_risk_count / medium_risk_count / low_risk_countint问题总数与风险分级计数
audit_result.issues[]object问题清单:issue_type/severity/location/suggestion
audit_result.summarystring审核总结
extracted_fieldsobject提取的合同要素
created_at / completed_atstring创建/完成时间

3.4 审核接口速览

接口方法说明
/api/tasks/{task_id}/downloadGET审核报告文件流(仅 completed)
/api/tasks/{task_id}/logGET执行日志
/api/tasks/{task_id}/log/sseGETSSE 实时日志(事件:progress/log/result/error)
/api/tasks/{task_id}DELETE取消(仅 pending/queued/running)
/api/tasksGET列表: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

请求参数

字段类型必填默认说明
filefile✅—PDF/图片(png/jpg/jpeg/gif/webp/bmp/tiff)/DOCX/DOC,≤50MB
rolestring✅—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_idstring文件 ID(start 时使用)
file_namestring原始文件名
size_bytesint文件大小
rolestring回显 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_namestring✅原文档 file_id 与文件名(与上传一致)
target_file_id / target_file_namestring✅被比对文档 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(addeddeletedmodified)/内容前后
data.statobject差异统计: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}/progressGET{task_id,status,progress,message}
/api/doc-compare/{task_id}/detailGET详情(含参数快照)
/api/doc-compare/tasksGET列表: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

请求参数

字段类型必填默认说明
filefile✅—.pdf/.docx/.doc/.txt,≤50MB

请求报文示例

application/json
(multipart/form-data)
file=@发票.pdf

响应报文示例

application/json
{ "file_id": "a1b2c3d4e5f6", "file_name": "发票.pdf", "size_bytes": 20480 }

响应参数含义

字段类型说明
file_idstring文件 ID(提取触发时使用)
file_namestring原始文件名
size_bytesint文件大小

集成示例

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[].idint模板 ID(触发提取用)
templates[].code / namestring模板编码与名称
templates[].is_defaultbool是否系统预置默认模板
templates[].fields[]object字段 schema:key/label/type

POST/api/extractions/quick

认证:用户 JWT

请求参数

字段类型必填默认说明
file_idstring✅—5.1 返回的 file_id
template_idint✅—模板 ID(5.2)
modestring否standardquick(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_idstring任务 ID(轮询用)
statusstring初始 pending
messagestring提示

集成示例

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
}

响应参数含义

字段类型说明
statusstringpending/running/completed/failed
progressfloat0~100;failed 为 -1
messagestring阶段描述
fieldsobjectcompleted 时返回结构化提取结果(按模板字段)
errorstringfailed 时错误信息

5.5 提取接口速览

接口方法说明
/api/extractions/{file_id}/detailGET完整结果(含 template_name/result_json)
/api/extractions/{file_id}/logsGET提取过程日志
/api/extractions/tasksGET列表:page/per_page
/api/extractions/{file_id}/cancelDELETE取消执行中任务
/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

请求参数

字段类型必填说明
namestring✅知识库名称,≤128 字符
descriptionstring否简要描述
kb_typestring✅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" } }

响应参数含义

字段类型说明
successbool是否成功
idint知识库 ID(后续所有 /api/kb/{kb_id} 使用)
data.statusstringactive/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_idpath✅—知识库 ID
filesfile[]✅—批量,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_idstring文档 ID
created[].statusstring初始 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

请求参数

字段类型必填默认说明
querystring✅—检索查询
kb_idsint[]否—限定知识库;不传自动路由
top_kint否5返回片段数
historyobject[]否—多轮对话:[{role,content}]
web_searchbool否false联网补充
kb_modestring否autodisable/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 } ]
}

响应参数含义

字段类型说明
answerstringLLM 生成的回答
chunks[]object召回片段
chunks[].kb_id / kb_nameint/string来源知识库
chunks[].doc_id / chunk_idstring来源文档与分块
chunks[].scorefloat相关性分数
chunks[].contentstring片段原文
chunks[].pageint来源页码
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/kbGET知识库列表
/api/kb/{kb_id}GET/PUT/DELETE详情/更新/删除
/api/kb/{kb_id}/documentsGET文档列表
/api/kb/{kb_id}/documents/{doc_id}/statusGET索引状态:pending/indexing/ready/failed
/api/kb/{kb_id}/documents/{doc_id}DELETE删除文档(含向量)
/api/kb/search/streamPOSTSSE 流式检索(事件:delta/chunks/done)
/api/kb/{kb_id}/chunksGET分块列表: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/healthGET健康检查+能力列表
/api/v3/ocr/imagePOST图片识别(多文件,含坐标)
/api/v3/ocr/orientationPOST方向检测
/api/v3/ocr/layoutPOST版面解析(Markdown)
/api/v3/ocr/sealPOST印章识别
/api/v3/ocr/seal/cropPOST印章检测裁剪(url/base64)
/api/v3/ocr/seal/download/{path}GET印章图片下载(二进制)
/api/v3/ocr/detect/cropPOST文字检测与裁剪(表格/PDF多页/紧切)
/api/v3/ocr/idcardPOST身份证识别
/api/v3/ocr/extractPOST自然语言提示词结构化提取
/api/v3/ocr/templatesGET模板列表
/api/v3/ocr/templates/{name}GET模板详情
/api/v3/ocr/extract/templatePOST模板提取(预置/内联)
/api/v3/ocr/templates/reloadPOST模板热重载(仅管理员)
/api/v3/ocr/dcs/generalPOSTDCS 兼容通用识别
/api/v3/watermark/embedPOST盲水印嵌入
/api/v3/watermark/extractPOST盲水印提取

7.2 代表接口:图片识别

认证:用户 JWT | POST /api/v3/ocr/image(multipart)

请求参数

字段类型必填默认说明
filesfile[]✅—待识别图片,多文件
model_typestring否ppocrv5-server模型类型
pre_rotatestring否autoauto/0/90/180/270
include_coordinatesbool否true是否含坐标
return_full_textbool否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": "全文拼接文本" } ]
  }
}

响应参数含义

字段类型说明
codeint状态码,1000=成功(原样透传)
msg / logIdstring状态描述 / 日志追踪 ID
data.totalImagesint处理图片数
data.results[].imageIndex / fileNameint/string序号与文件名
data.results[].width / height / rotateAngleint尺寸与 EXIF 旋转
data.results[].textCountint文本块数
data.results[].ocrResult[]objecttext(文本)/confidence(置信度)/box(四边形坐标)
data.results[].fullTextstring全文拼接(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[].idstring模型名(chat/completions 的 model 取值)
data[].owned_bystring提供方(deepseek/vllm 等)
data[].activebool是否当前生效模型

集成示例

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 + 扩展)

字段类型必填说明
modelstring✅模型名,必须 ∈ 8.1 返回列表
messagesobject[]✅OpenAI 标准消息[{role,content}]
temperature / top_p / max_tokensfloat/int否采样参数
streambool否SSE 流式,默认 false
tools / tool_choiceobject[]/string否function calling 透传
kb_idsint[]否挂载该用户知识库(检索注入上下文)
kb_top_kint否召回片段数,默认 5
kb_modestring否auto(自动路由)/manual(按 kb_ids)
web_searchbool否联网补充

请求报文示例

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 }
}

响应参数含义

字段类型说明
idstring对话 ID
choices[].message.contentstring回答内容
choices[].finish_reasonstringstop/length/tool_calls
usageobjecttoken 统计(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/completionsPOST标准补全(后台不支持 501)
/llm/v1/embeddingsPOST文本向量化(后台不支持 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

请求参数

字段类型必填默认说明
filefile✅—PDF,≤50MB

请求报文示例

application/json
(multipart/form-data)
file=@判决书.pdf

响应报文示例

application/json
{ "file_id": "a1b2c3d4e5f6", "file_name": "判决书.pdf", "size_bytes": 1048576 }

响应参数含义

字段类型说明
file_idstring文件 ID(scan 使用)
file_namestring原始文件名
size_bytesint文件大小

集成示例

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_idstring✅—9.1 返回
profilestring否contractcontract/case/all 规则集
detect_sealsbool否true检测印章区域

请求报文示例

application/json
{ "file_id": "a1b2c3d4e5f6", "profile": "contract", "detect_seals": true }

响应报文示例

application/json
{ "task_id": "redact:a1b2c3d4e5f6:ab12cd34", "status": "scanning", "message": "正在侦测敏感项..." }

响应参数含义

字段类型说明
task_idstring任务 ID
statusstringpending/scanning/reviewing/applying/completed/failed/cancelled
messagestring阶段描述

集成示例

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_resultobject按分组(涉案人/签约主体/合同细节等)的候选敏感项
scan_result.<分组>[].fieldstring字段类型(身份证号/手机号/公司名称等)
scan_result.<分组>[].textstring命中原文
scan_result.<分组>[].pageint所在页码
scan_result.<分组>[].confidencefloat置信度

POST/api/redact/apply

认证:用户 JWT

请求参数

字段类型必填默认说明
task_idstring✅—scan 返回
blackliststring[]否[]强制脱敏词条
whiteliststring[]否[]保留不脱敏词条
date_modestring否quarterquarter(降级季度)/full/off
redact_imagesbool否false涂掉内嵌图片

请求报文示例

application/json
{ "task_id": "redact:...", "blacklist": ["客户简称"], "whitelist": ["姓名"], "date_mode": "quarter" }

响应报文示例

application/json
{ "task_id": "redact:...", "status": "applying", "message": "正在生成脱敏副本..." }

响应参数含义

字段类型说明
task_idstring任务 ID
statusstringapplying(异步执行:真删除→残留自检→报告)
messagestring阶段描述

集成示例

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
}

响应参数含义

字段类型说明
statusstringpending/scanning/reviewing/applying/completed/failed/cancelled
verify_result.totalint脱敏总数
verify_result.remainingint残留自检剩余(0=无残留)
接口方法说明
/api/redact/{task_id}/resultGET脱敏副本 PDF(文件流,仅 completed)
/api/redact/{task_id}/reportGETHTML 脱敏报告(命中字段/位置/自检结论)
/api/redact/tasksGET列表: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 参数传递(浏览器顶层导航)

请求参数

参数类型必填说明
tokenstring二选一用户 JWT(§2.1 签发)或 dkp_ 个人 API 密钥(§10.3);两者都传返回 error=ambiguous
ticketstring二选一一次性票据(已弃用,一律返回 error=ticket_invalid)
redirectstring否目标站内路径,默认 /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":"不允许跳转到: ..."}。

校验流程(顺序)

  1. token/ticket 二选一(都缺 → 302 /login?error=token_missing);
  2. redirect 白名单(失败 → HTTP 400);
  3. 凭证解析:JWT 过期 → token_expired;无效 → token_invalid;dkp_ 前缀按个人密钥解析(等同本人,scope 视为 api,page);
  4. scope 必须含 page(否则 HTTP 403);
  5. 用户存在、未冻结、未删除(否则 → user_invalid);
  6. 组织 domain_whitelist 非空时,校验请求 Referer 的 host 在白名单内(否则 HTTP 403)。

落地行为

成功后服务端签发新的 Web JWT,通过引导页写入 localStorage.jwt_token(前端登录态来源,Cookie 同步种一份备用),随后自动跳转 redirect——用户即以普通登录态进入目标页面。

错误重定向参数(302 → /login?error=...)

error原因
token_missingtoken/ticket 均未提供
ambiguoustoken 与 ticket 同时提供
token_expiredJWT 已过期
token_invalidJWT/dkp_ 密钥无效
ticket_invalidticket 已弃用或无效
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 鉴权(双通道)

通道凭证归属语义权限要求
组织 KeyAuthorization: Bearer djk_xxx创建时必须传 user_id 表单字段指定归属用户(自动注册/映射,同 §2.1);查询/删除可访问该组织全部映射用户的任务Key 的 permissions 需勾选 seal_verify
用户 JWTAuthorization: Bearer <用户 JWT>任务归属当前用户,与页面内任务列表互通;?token= 查询参数亦可用户 JWT(dkp_ 个人密钥等同)

11.3 端点速览

接口方法说明
/api/v1/seal-verificationsPOST创建核验任务(multipart 直传)
/api/v1/seal-verifications/{task_id}GET状态 + 完整结果(轮询入口)
/api/v1/seal-verificationsGET任务列表(分页,按状态筛选)
/api/v1/seal-verifications/{task_id}DELETE删除(运行中先协作式取消)
/api/v1/seal-verifications/{task_id}/seals/{index}/imageGET印章裁剪图 PNG 二进制

POST/api/v1/seal-verifications

请求参数(multipart/form-data)

字段类型必填说明
filefile✅PDF / 图片(png/jpg/jpeg/bmp/tif/tiff),≤50MB;OFD 返回 400 并提示先导出 PDF
user_idstring组织 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": { }
    }
  }
}

响应参数含义

字段类型说明
statusstringpending/running/completed/failed/cancelled
phasestringpreprocess→prescreen→detect→enhance→done
candidate_pagesint[]本地预筛命中、送远程检测的页码
seal_countint检出印章总数
result.pages[].pageint页码(1 起)
result.pages[].width/heightfloat页面尺寸(布局占位用)
seals[].indexint印章全局序号(裁剪图 URL 用)
seals[].pageint所在页码(1 起)
seals[].textstringOCR 原始识别文字
seals[].kindstring印章形态:full=完整印章;marginal=骑缝章(贴页面边缘残段)
seals[].normfloat[]位置框 [x0,y0,x1,y1],归一化 0-1(乘以展示尺寸即像素)
seals[].imagestring裁剪图相对路径(经 11.3 裁剪图端点访问)
seals[].ring_textstring环形/上旋文字转写(通常为单位名称);多行已去换行与空格;LLM 未生效时为 null
seals[].horizontal_textstring章面横向文字转写(可能多行,已合并去换行与空格);LLM 未生效时为 null
seals[].codestring印章编号/防伪码转写;无则空串;LLM 未生效时为 null
seals[].calibrated_namestring/nullLLM 看图校准后印章名称(骑缝碎片为拼合结果;失败为 null)
seals[].seal_typestring/null公章/合同专用章/财务专用章/法人名章/骑缝章/其他
seals[].correctedbool/null视觉校准确认 OCR 有误并修正时为 true;无误为 false;LLM 未生效为 null
seals[].match_statusstring/null中文展示:与甲方匹配/与乙方匹配/与其他方匹配/未匹配/无法判断
seals[].match_codestring/null机器可读枚举:match_party_a/match_party_b/match_other/no_match/unknown(调用方程序化判断用)
seals[].matched_partystring/null匹配到的当事方名称(未匹配/无法判断为 null)
result.llmobjectenhanced 是否生效、parties 签署方结论、error 失败原因
result.fallback_pagesint[]预筛零命中时做远程兜底检测的页
result.failed_pagesint[]远程检测失败的页(不影响其他页)

GET/api/v1/seal-verifications(任务列表)

请求参数

字段类型必填默认说明
pageint否1页码
per_pageint否10每页 ≤100
statusstring否—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-verificationsPOSTmultipart 上传创建任务(组织 Key 需 user_id)
/api/v1/seal-verifications/{task_id}GET状态+完整结果(轮询)
/api/v1/seal-verificationsGET列表(分页/状态筛选)
/api/v1/seal-verifications/{task_id}DELETE取消并删除
/api/v1/seal-verifications/{task_id}/seals/{index}/imageGET裁剪图 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-auditsPOST批量创建审核任务(multipart files 可多文件)
/api/v1/njca-audits/{task_id}GET状态 + 结论 + 提取字段 + 明细(轮询入口)
/api/v1/njca-auditsGET任务列表(分页/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)

字段类型必填说明
filesfile[]✅一个或多个文件(PDF / 图片),≤50MB/个;OFD 返回 skipped 并提示先导出 PDF
user_idstring组织 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 }
    }
  }
}

关键字段含义

字段类型说明
conclusionstring通过 / 不通过 / 无法判定
fail_reasonsstring[]不通过原因逐条(缺姓名/身份证校验位不符/未检测到印章/印章名称与委托单位不一致)
fields.authorized_name / id_numberstring提取的被授权人姓名与证件号
fields.id_validboolGB 11643 校验位算法是否通过
fields.entrust_unit / legal_personstring委托单位全称 / 法定代表人
fields.business_itemsstring[]勾选的委托事项
fields.seal_found / seal_names / seal_matchbool/string[]/bool印章检测与匹配结论
fields.warningsstring[]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-auditsPOST批量创建(multipart files)
/api/v1/njca-audits/{task_id}GET结论+字段+明细(轮询)
/api/v1/njca-auditsGET列表(分页/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 } }
字段类型说明
keystring明文 Key,仅创建时返回一次,立即保存
data.api_idstring8 位 hex 标识
data.key_previewstring脱敏预览(列表展示用)

13.3 管理接口速览

接口方法说明
/api/admin/external-systemsGET/POST组织列表/创建
/api/admin/external-systems/{id}PUT/DELETE更新(含 auto_register)/删除;status=0 吊销全部 Key
/api/admin/external-systems/{id}/usersGET组织注册用户列表
/api/admin/api-keysGETKey 列表(系统/模块/过期/last_used_at)
/api/admin/api-keys/{key_id}DELETE立即失效
/api/admin/api-access-logsGET调用审计:system/key/user/credential_type/start/end/page/export=1
/api/admin/llm-profilesGET/POST/PUT/DELETE后台模型配置
/api/admin/extraction-templatesGET/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)
OCRocr_*DianjuOCR v3 代理
LLMllm_*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())

检索范围即密钥权限范围:本人私有库 + 所属组织共享库 + 公开库;密钥看不到的库不会被检索,也不会在 kb_list 中出现。

选库方式(三选一,可组合):

方式参数说明
自动(默认)不传 kb_ids/kb_namesauto 模式:MoE 路由在权限范围内自动选库;可见库 < 3 个时全选
按名称指定kb_names: ["库名A", ...]对 AI 最友好。精确 → 忽略大小写 → 唯一子串 三级模糊匹配;全部未命中时报错并列出当前可见库名,便于自纠
按 id 指定kb_ids: [1, 2]精确指定(可先 kb_list 获取)

召回增强(multi_round,默认开启): 服务端自动执行「关联词句扩展(LLM 生成同义/相关查询变体,失败回退规则切句)→ 多路并行向量召回 → 关键词精确补充(编号/型号/书名号等 embedding 盲区)→ 覆盖不足时二轮补充检索 → 统一 rerank」。无需客户端手工拆分查询。

追问改写: 传 history(最近对话 [{role, content}]),服务端自动把「它呢?」「上面第二点」等指代改写成独立问题再检索。

kb_search 请求参数一览:

参数类型必填说明
querystring是查询问题(建议完整自然语言)
kb_namesstring[]否按名称指定知识库(支持模糊匹配)
kb_idsint[]否按 id 指定知识库
historyobject[]否最近对话,用于追问改写
top_kint否返回条数,默认用户偏好,1~50
use_rerankbool否是否重排,默认用户偏好
multi_roundbool否是否启用多轮增强召回,默认开
modestring否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 unauthorizedJWT 缺失/过期;重新走 §二 换证
无权调用工具 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}/polishAI 纠错:修正转写中的同音字/错别字,不增删原文
POST/api/notes/{id}/summarizeAI 整理:返回 {summary, mindmap}(mindmap 为缩进大纲文本)

POST/api/notes

字段类型必填说明
filefile否录音文件(WAV/WebM/MP4,≤100MB)
payloadstring(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"
  }
}

错误码

状态说明
400payload 非法 / 无可整理文字
404笔记不存在或无权访问
502AI 纠错/整理调用大模型失败

语音服务 API(用户 JWT)

本地语音双引擎:识别 SenseVoice-Small(中英,自动标点/数字归一化,内置 Silero VAD 过滤静音噪声),合成 Piper。

方法路径说明
POST/api/voice/asrmultipart file=音频(WAV 16k 最佳;webm/opus、非 16k 自动重采样)→ {"text","language"};纯静音/噪声返回空文本
POST/api/voice/ttsJSON {"text", "voice"?, "speed"?} → audio/wav(中英自动分流,text ≤1000 字)

网页链接附件 API(用户 JWT)

将网页 URL 添加为知识引擎 Agent 附件:服务端抓取页面并抽取正文,Agent 可基于内容回答。内置 SSRF 防护(仅 http/https、解析 IP 必须公网、重定向逐跳校验),单页 3MB/12 万字封顶。

方法路径说明
POST/api/kb/attachments/linkJSON {"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/verify1:1 验证(user_id + frame)→ {matched, similarity, liveness_score, threshold}
POST/api/v1/face/search1: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_idstring✅外部用户标识
framesfile[]✅同一人多帧照片(≤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)
404user_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/chatJSON 请求;响应 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_idstring是当前用户已创建的 UUID 会话标识。
model_idstring是system:<正整数ID> 或 personal:<正整数ID>;必须实际可用且属于可访问来源。
messagesobject[]是1 至 40 项,每项只有 role(user/assistant)和非空 content;单项最多 12,000 字符,总计最多 60,000 字符,最后一项必须为 user。服务端只取最后一条用户消息,历史由服务器会话提供,客户端不能伪造历史批准。
contextobject否默认空对象,只接受可选 tab 字符串作为当前页签参考,不改变权限。
modestring否兼容字段,唯一合法值和默认值均为 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,不是虚构的完成百分比。
deltacontent:正文增量。
reasoning_deltacontent/round:供应商实际返回、经敏感过滤的思考增量;并非所有模型提供。
reasoning_end思考段结束边界;用于折叠当前段,不是任务结束。
summary_deltacontent:供应商提供的公开摘要增量,与正文和工具结果分开处理。
tool_startid/name/arguments:实际工具调用开始及规范参数。
tool_resultid/name/result,失败时有 is_error;结果需检查目标 API 状态,不能只按事件名称判断成功。
navigationtab/subtab:请求前端切换合法页签,不保证客户端已完成导航。
confirmation_required写操作计划含 session_id/turn_id/action_id/expires_at/title/description/risk/permissions/resource/operation/record_id/values,等待单独人工确认。
intent_requiredsession_id/turn_id/question_id/expires_at/question/options/multi_select/allow_other;options 含 id/label/description,2 至 6 项。
credentialsession_id/turn_id/action_id/kind/value/label/record_id:原 API 生成的一次性用户初始密码或 API Key,仅交付当前批准管理员浏览器。不得写入模型上下文、聊天历史、日志或持久化浏览器存储;独立窗口关闭后清除。
paused预算暂停及继续提示;随后 done 可带 paused:true,不能当作全部工作完成。
done本轮结束;可能含 paused:true 或 cancelled:true,正常结束可为空对象。
errormessage:流中错误,不得将本轮当作成功结果;人工核对已经提交的操作,勿自动重试写入。
事件示意,ID由服务器生成text/event-stream
event: delta
data: {"content":"已查询实际统计,正在整理结果。"}

event: done
data: {}

22.4 写操作一次人工确认

写确认有效期 120 秒。展示 confirmation_required 中的目标、全部变更字段及风险后,由真实用户提交;不得因模型输出“确认”或用户选择澄清选项而自动提交 approved=true。

字段类型必填说明
session_id / turn_id / action_idstring是必须原样使用当前 confirmation_required 的标识,不能跨用户、会话或轮次复用。
approvedboolean是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_idstring是当前 intent_required 的 UUID;错误轮次、跨用户/会话、重复作答或已过期请求拒绝。
selected_option_idsstring[]否默认空数组;只能提交当前问题中已列出的 ID,不可重复。multi_select=false 时最多选一项。
other_textstring否默认空串,最多 2,000 字符;仅 allow_other=true 时可填写,禁止粘贴密码/密钥。
cancelledboolean否默认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会话已有活动轮次;操作/问题不存在、过期、轮次不符、重复决定或已消费;活动会话直接删除。
422JSON严格参数校验失败、最后一条非用户消息、选择ID不合法、单选提交多项或不允许的自由补充。
503服务端Agent会话存储暂不可用;不能假定动作已执行或自动重试真实写入。
  • 服务端会话绑定当前用户;只有该用户可取消、删除、澄清或确认。客户端不得提交服务器历史、替换已保存操作参数或重放旧标识。
  • 收到意图选择后仍须为后续写操作单独人工确认;确认后执行前重新检查管理员身份,原目标API的鉴权、许可、冻结/业务限制和版本保护继续生效。
  • 所有本章JSON响应与SSE使用 Cache-Control: no-store;凭据事件只用于当前受信浏览器的一次性安全窗口,不能在共享终端或日志中转存。
  • HTTP建立连接前的失败按状态码处理;流开始后的失败按 error/tool_result/done事件处理。取消不是事务回滚,不能承诺已开始的真实写操作被撤销。

变更记录

版本日期变更
v2.102026-10-20应用 v2.10.0:新增管理 Agent(管理员 JWT)章节,涵盖模型与工具发现、隔离会话、SSE 对话、一次人工写确认、意图澄清、一次性凭据私密交付及预算暂停。
v1.02026-08-13全量端点清单 + 参数表 + curl
v2.02026-08-13核心接口补齐:请求/响应报文示例、响应参数含义表、Python/Java/JavaScript 三语言集成示例
v2.12026-09-01新增印章核验模块 seal_verify 权限与外部端点组(§十一):/api/v1/seal-verifications 创建/查询/列表/删除/裁剪图,支持组织 Key 与用户 JWT 双通道
v2.32026-09-01新增 NJCA审核模块 njca_audit 权限与外部端点组(§十二):/api/v1/njca-audits 批量创建/查询/删除,组织 Key 与用户 JWT 双通道;审核三要素(被授权人/身份证号校验位/印章与委托单位一致性)结论
v2.22026-09-01SSO 章节(§十)更新:一次性 ticket 软弃用(POST /api/v1/ticket 返回 410)、dkp_ 个人密钥免登录直跳(与 JWT 等价)、redirect 白名单支持 "*" 通配任意站内路径、token expires_in 上限放宽至 365 天、落地页改写 localStorage.jwt_token
v2.52026-09-12新增人脸识别模块 face 权限与外部端点组(§二十):/api/v1/face/* 录入/1:1验证/1:N检索/状态/删除/清单,组织 Key 按 user_id 外部标识映射;静默活体+多帧一致性校验(抗照片/视频攻击),全操作审计
v2.42026-09-09新增端点组(用户 JWT):§十七 智能笔记 /api/notes*(录音+时间轴分段转写+AI 纠错/汇总)、§十八 语音服务 /api/voice/asrtts(SenseVoice+Piper 本地双引擎)、§十九 网页链接附件 /api/kb/attachments/link(服务端抓取正文,SSRF 防护)