
Zentao Api
- 207 installs
- 59 repo stars
- Updated May 25, 2026
- easysoft/zentao-skills
Integrate Zentao project-management APIs to sync bugs, stories, sprints, and workflow status into custom dashboards, bots, or delivery automation.
About
Documents Zentao API integration for engineering workflows: authenticating requests, fetching and updating bugs, stories, and sprint data, and mapping PM entities into automation, dashboards, or agent tooling.
- Zentao REST authentication
- Bug and story synchronization
- Sprint and workflow queries
- Webhook or polling patterns
- PM data mapping for agents
Zentao Api by the numbers
- 207 all-time installs (skills.sh)
- Ranked #1,955 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/easysoft/zentao-skills --skill zentao-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 207 |
|---|---|
| repo stars | ★ 59 |
| Last updated | May 25, 2026 |
| Repository | easysoft/zentao-skills ↗ |
What it does
Integrate Zentao project-management APIs to sync bugs, stories, sprints, and workflow status into custom dashboards, bots, or delivery automation.
Files
禅道 API v2.0
配置
优先级从高到低:
| 变量 | 说明 |
|---|---|
ZENTAO_URL | 服务器地址,如 http://zentao.example.com |
ZENTAO_TOKEN | 直接指定 token,跳过登录和缓存(最高优先级),仍需提供服务器地址 |
ZENTAO_ACCOUNT | 登录账号,有 token 时可选,但提供可更好回答与当前用户相关的问题 |
ZENTAO_PASSWORD | 登录密码,有 token 时无需提供 |
首次登录后 `ZENTAO_URL`、`ZENTAO_TOKEN`、`ZENTAO_ACCOUNT` 写入 `~/.zentao-token.json`,后续无需重复设置。
若必要变量缺失,提示用户并给出 export 命令。用户直接提供服务器、账号和密码时直接使用,同时告知尽量设为环境变量。
认证流程
所有业务 API 需在 Header 携带 token。运行 scripts/get-token.sh 自动获取:
eval "$(bash scripts/get-token.sh)"
# 执行后可直接使用 $ZENTAO_URL、$ZENTAO_TOKEN、$ZENTAO_ACCOUNT脚本依赖:curl、node
后续所有请求 Header 携带:token: $ZENTAO_TOKEN
执行 API 调用的步骤
1. 运行 eval "$(bash scripts/get-token.sh)" 获取凭证(自动处理缓存;仍缺失时提示用户) 2. 根据用户意图选择正确的 API 端点(参见 api-reference.md) 3. 若为 PUT 编辑操作且用户未提供全部必填字段,先调用对应 GET 详情接口取回当前数据,再将用户指定的字段覆盖进去 4. 构造请求(方法、URL、Header、Body)并向用户确认写操作内容 5. 执行请求,解析响应 6. 以清晰易读的格式向用户展示结果
模块总览
API 基础路径:$ZENTAO_URL/api.php/v2
| 模块 | 资源路径 | 支持操作 |
|---|---|---|
| 项目集 Program | /programs | CRUD + 关联产品/项目列表 |
| 产品 Product | /products | CRUD + 关联需求/Bug/用例/计划/发布/反馈/工单/测试单/应用 |
| 项目 Project | /projects | CUD + 关联执行/需求/Bug/用例/版本/测试单 |
| 执行 Execution | /executions | CRUD + 关联需求/任务/Bug/用例/版本/测试单 |
| 需求 Story | /stories | CRUD + change/close/activate |
| 业务需求 Epic | /epics | CRUD + change/close/activate |
| 用户需求 Requirement | /requirements | CRUD + change/close/activate |
| Bug | /bugs | CRUD + resolve/close/activate |
| 任务 Task | /tasks | CRUD + start/finish/close/activate |
| 测试用例 Testcase | /testcases | CRUD |
| 产品计划 Productplan | /productplans | CUD + 按产品查列表 |
| 版本 Build | /builds | CUD + 按项目/执行查列表 |
| 发布 Release | /releases | CUD + 按产品查列表 |
| 测试单 Testtask | /testtasks | CUD + 按产品/项目/执行查列表 |
| 反馈 Feedback | /feedbacks | CRUD + close/activate |
| 工单 Ticket | /tickets | CRUD + close/activate |
| 应用 System | /systems | CU + 按产品查列表 |
| 用户 User | /users | CRUD |
| 文件 File | /files | 编辑名称 + 删除 |
CRUD = 创建(POST) + 读取(GET) + 更新(PUT) + 删除(DELETE);CUD = 无独立全局列表接口
分页与筛选
所有列表接口支持统一的查询参数:
| 参数 | 说明 |
|---|---|
browseType 或 status | 筛选状态,如 all, doing, unclosed, undone 等(不同模块参数名和可选值不同,详见 api-reference.md) |
orderBy | 排序,格式 字段_asc 或 字段_desc,如 id_desc, title_asc |
recPerPage | 每页数量,最大 1000 |
pageID | 页码,从 1 开始 |
筛选参数名不一致:Program 列表、Execution 全局列表、Task 列表用 status,其余用 browseType。
常用操作示例
获取进行中的项目及其执行
curl -s "$ZENTAO_URL/api.php/v2/projects?browseType=doing&recPerPage=100" -H "token: $ZENTAO_TOKEN"
curl -s "$ZENTAO_URL/api.php/v2/projects/{projectID}/executions?browseType=doing" -H "token: $ZENTAO_TOKEN"创建需求(必填:productID, title)
curl -s -X POST "$ZENTAO_URL/api.php/v2/stories" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"productID": 1, "title": "需求标题", "grade": 1, "pri": 3, "assignedTo": "admin", "spec": "需求描述"}'创建业务需求(Epic)
curl -s -X POST "$ZENTAO_URL/api.php/v2/epics" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"productID": 1, "title": "业务需求标题", "grade": 1, "pri": 3, "reviewer": ["admin"]}'创建用户需求(Requirement)
curl -s -X POST "$ZENTAO_URL/api.php/v2/requirements" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"productID": 1, "title": "用户需求标题", "parent": 1001, "grade": 1, "pri": 3, "reviewer": ["admin"]}'创建 Bug(必填:productID, title, openedBuild)
curl -s -X POST "$ZENTAO_URL/api.php/v2/bugs" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"productID": 1, "title": "Bug标题", "openedBuild": ["trunk"], "severity": 2, "type": "codeerror"}'解决 Bug(必填:resolution)
curl -s -X PUT "$ZENTAO_URL/api.php/v2/bugs/{bugID}/resolve" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"resolution": "fixed"}'创建任务(必填:name, executionID)
curl -s -X POST "$ZENTAO_URL/api.php/v2/tasks" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"executionID": 1, "name": "任务名", "type": "devel", "assignedTo": "admin", "estimate": 4}'完成任务(必填:currentConsumed, realStarted, finishedDate)
curl -s -X PUT "$ZENTAO_URL/api.php/v2/tasks/{taskID}/finish" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"currentConsumed": 4, "realStarted": "2026-03-25", "finishedDate": "2026-03-25"}'关闭需求(必填:closedReason)
curl -s -X PUT "$ZENTAO_URL/api.php/v2/stories/{storyID}/close" \
-H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \
-d '{"closedReason": "done"}'常用枚举值速查
| 字段 | 可选值 |
|---|---|
项目模式 model | scrum, waterfall, kanban, agileplus, waterfallplus |
Bug 类型 type | codeerror, config, install, security, performance, standard, automation, designdefect, others |
Bug 解决方案 resolution | fixed, notrepro, bydesign, duplicate, external, postponed, willnotfix, tostory |
需求关闭原因 closedReason | done, subdivided, duplicate, postponed, willnotdo, cancel, bydesign |
需求来源 source | customer, user, po, market, service, operation, support, competitor, partner, dev, tester, bug, forum, other |
需求类别 category | feature, interface, performance, safe, experience, improve, other |
用例类型 type | unit, interface, feature, install, config, performance, security, other |
测试单类型 type | integrate, system, acceptance, performance, safety |
发布状态 status | wait, normal, fail, terminate |
反馈关闭原因 closedReason | commented, repeat, refuse |
工单类型 type | code, data, stuck, security, affair |
产品类型 type | normal, branch, platform |
产品访问控制 acl | open, private |
执行类型 lifetime | short, long, ops |
意图识别规则
| 用户意图关键词 | 对应操作 |
|---|---|
| 进行中的执行/迭代/Sprint | GET /projects?browseType=doing → GET /projects/{id}/executions |
| 获取所有产品/项目/项目集 | GET /products, /projects, /programs |
| 某产品/项目/执行的 Bug | GET /products/{id}/bugs, /projects/{id}/bugs, /executions/{id}/bugs |
| 创建/新增 Bug | POST /bugs(必填:productID, title, openedBuild) |
| 更新/修改 Bug | PUT /bugs/{id} |
| 解决 Bug | PUT /bugs/{id}/resolve(必填:resolution) |
| 关闭 Bug | PUT /bugs/{id}/close |
| 激活 Bug | PUT /bugs/{id}/activate |
| 创建需求 | POST /stories(必填:productID, title) |
| 关闭/激活/变更需求 | PUT /stories/{id}/close, /activate, /change |
| 业务需求 | /epics(同 stories 结构) |
| 用户需求 | /requirements(同 stories 结构) |
| 创建任务 | POST /tasks(必填:name, executionID) |
| 启动任务 | PUT /tasks/{id}/start(必填:realStarted) |
| 完成任务 | PUT /tasks/{id}/finish(必填:currentConsumed, realStarted, finishedDate) |
| 关闭任务 | PUT /tasks/{id}/close |
| 测试用例 | /testcases(CRUD) |
| 测试单 | /testtasks(CUD + 按产品/项目/执行查列表) |
| 产品计划 | /productplans(CUD + 按产品查列表) |
| 版本/Build | /builds(CUD + 按项目/执行查列表) |
| 发布 | /releases(CUD + 按产品查列表) |
| 反馈 | /feedbacks(CRUD + close/activate) |
| 工单 | /tickets(CRUD + close/activate) |
| 应用/系统 | /systems(CU + 按产品查列表) |
| 获取用户列表 | GET /users |
注意事项
- URL 中的
{id}需替换为实际 ID;不知道 ID 时先调列表接口获取 - 创建 Epic / Requirement / Story 时,建议始终显式传 `grade`,不要依赖接口默认值。 已有用户反馈某些禅道实例在未传
grade时会把需求层级写成0,导致界面中 BR / UR / SR 标签显示异常。 - PUT 编辑接口:先 GET 详情获取当前完整数据,再将用户修改的字段覆盖进去一并提交
- 状态流转操作 (resolve/close/activate/start/finish/change) 通常有独立的必填字段,不需要先 GET 详情
- 写操作前向用户确认,用户明确要求不确认则直接执行
- 401 响应表示 token 已失效,执行
rm ~/.zentao-token.json清除缓存后重新运行 - 字段名不一致注意:POST builds 用
executionID,PUT builds 用execution;PUT testcases 的模块字段为moudule(规范中的拼写)
完整 API 参考
详细的端点列表、必填/可选字段、枚举值和查询参数见 api-reference.md。
备用资源
- 禅道 API 2.0 官方文档:https://www.zentao.net/book/api/2309.html
- 1.0 API 文档(备用):https://www.zentao.net/book/api/1397.html
禅道 API v2.0 端点参考
基础路径:{ZENTAO_URL}/api.php/v2 认证方式:所有接口(除登录外)需在 Header 携带 token: <token值> 写操作需额外携带 Content-Type: application/json
字段标注说明:粗体 = 必填,普通 = 可选
---
认证(Token)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /users/login | 登录获取 token |
请求体:account(string), password(string) 响应:{"status":"success","token":"..."}
---
用户(User)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /users | 创建用户 |
| GET | /users | 获取用户列表 |
| GET | /users/{userID} | 获取用户详情 |
| PUT | /users/{userID} | 修改用户信息 |
| DELETE | /users/{userID} | 删除用户 |
POST 创建:account(string), realname(string), password(string)
PUT 修改:realname, dept(int), join(date), group(string[]), email, visions(string[] rnd|lite), mobile, weixin, password
GET 列表参数:browseType(inside|outside), orderBy(id|realname|account+_asc/_desc), recPerPage, pageID
---
项目集(Program)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /programs | 创建项目集 |
| GET | /programs | 获取项目集列表 |
| GET | /programs/{programID} | 获取项目集详情 |
| PUT | /programs/{programID} | 修改项目集 |
| DELETE | /programs/{programID} | 删除项目集 |
| GET | /programs/{programID}/products | 获取项目集的产品列表 |
| GET | /programs/{programID}/projects | 获取项目集的项目列表 |
POST/PUT:name(string), begin(date), end(date), PM(string), desc(string)
GET 列表参数:status(all|unclosed|wait|doing|suspended|delayed|closed), orderBy(id|name|begin|end+_asc/_desc), recPerPage, pageID
---
产品(Product)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /products | 创建产品 |
| GET | /products | 获取产品列表 |
| GET | /products/{productID} | 获取产品详情 |
| PUT | /products/{productID} | 修改产品 |
| DELETE | /products/{productID} | 删除产品 |
产品关联资源列表:
| GET 路径 | 说明 |
|---|---|
/products/{id}/stories | 需求列表 |
/products/{id}/epics | 业务需求列表 |
/products/{id}/requirements | 用户需求列表 |
/products/{id}/bugs | Bug 列表 |
/products/{id}/testcases | 测试用例列表 |
/products/{id}/productplans | 产品计划列表 |
/products/{id}/releases | 发布列表 |
/products/{id}/testtasks | 测试单列表 |
/products/{id}/feedbacks | 反馈列表 |
/products/{id}/tickets | 工单列表 |
/products/{id}/systems | 应用列表 |
POST/PUT:name(string), program(int), line(int), type(normal|branch|platform), PO(string), reviewer(string[]), desc(string[]), QD(string), RD(string), acl(open|private)
GET 列表参数:browseType(all|noclosed|closed), orderBy(id|title|begin|end+_asc/_desc), recPerPage, pageID
---
项目(Project)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /projects | 创建项目 |
| GET | /projects | 获取项目列表 |
| PUT | /projects/{projectID} | 修改项目 |
| DELETE | /projects/{projectID} | 删除项目 |
项目关联资源列表:
| GET 路径 | 说明 |
|---|---|
/projects/{id}/executions | 执行列表 |
/projects/{id}/stories | 需求列表 |
/projects/{id}/bugs | Bug 列表 |
/projects/{id}/testcases | 测试用例列表 |
/projects/{id}/builds | 版本列表 |
/projects/{id}/testtasks | 测试单列表 |
POST/PUT:name(string), model(scrum|waterfall|kanban|agileplus|waterfallplus), begin(date), end(date), workflowGroup(int, 付费版功能开源版可不填), products(string[]), parent(int), PM(string)
GET /projects 参数:browseType(all|undone|wait|doing,默认undone), orderBy(id|name|begin|end+_asc/_desc), recPerPage, pageID
GET /programs/{id}/projects 参数:同上
GET /projects/{id}/executions 参数:browseType(all|undone|wait|doing,默认undone), orderBy(rawID|nameCol|begin|end+_asc/_desc), recPerPage, pageID
---
执行(Execution / Sprint)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /executions | 创建执行 |
| GET | /executions | 获取执行列表 |
| GET | /executions/{executionID} | 获取执行详情 |
| PUT | /executions/{executionID} | 修改执行 |
| DELETE | /executions/{executionID} | 删除执行 |
执行关联资源列表:
| GET 路径 | 说明 |
|---|---|
/executions/{id}/stories | 需求列表 |
/executions/{id}/tasks | 任务列表 |
/executions/{id}/bugs | Bug 列表 |
/executions/{id}/testcases | 测试用例列表 |
/executions/{id}/builds | 版本列表 |
/executions/{id}/testtasks | 测试单列表 |
POST 创建:project(int), name(string), begin(date), end(date), lifetime(short|long|ops), days(int), products(string[]), plans(string[]), PO(string), QD(string), PM(string), RD(string), acl(open|private)
PUT 修改:同上但 project 变为可选
GET /executions 参数:status(all|undone|wait|doing,默认undone), orderBy(rawID|nameCol|begin|end+_asc/_desc), recPerPage, pageID
---
需求(Story)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /stories | 创建需求 |
| GET | /stories/{storyID} | 获取需求详情 |
| PUT | /stories/{storyID} | 修改需求 |
| DELETE | /stories/{storyID} | 删除需求 |
| PUT | /stories/{storyID}/change | 变更需求 |
| PUT | /stories/{storyID}/close | 关闭需求 |
| PUT | /stories/{storyID}/activate | 激活需求 |
列表通过父资源获取:/products/{id}/stories, /projects/{id}/stories, /executions/{id}/stories
POST 创建:productID(int), title(string), grade(int 需求层级,建议显式传入), pri(int, 默认3), module(int), parent(int), estimate(float), spec(string 需求描述), category(feature|interface|performance|safe|experience|improve|other), source(customer|user|po|market|service|operation|support|competitor|partner|dev|tester|bug|forum|other), verify(string 验收标准), assignedTo(string), reviewer(string[]), project(int), execution(int)
PUT 修改:title(string),其余字段可选
PUT change 变更:reviewer(string[]), title(string), spec(string), verify(string)
PUT close 关闭:closedReason(done|subdivided|duplicate|postponed|willnotdo|cancel|bydesign), comment(string)
PUT activate 激活:assignedTo(string), comment(string)
GET 列表参数:browseType(allstory|assignedtome|openedbyme|reviewbyme|draftstory,默认unclosed), orderBy(id|title|status+_asc/_desc), recPerPage, pageID
建议:创建 Story / Epic / Requirement 时显式传 grade,不要依赖接口默认值;在某些禅道实例中,不传 grade 可能会把需求层级写成 0。
---
业务需求(Epic)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /epics | 创建业务需求 |
| GET | /epics/{storyID} | 获取业务需求详情 |
| PUT | /epics/{epicID} | 修改业务需求 |
| DELETE | /epics/{epicID} | 删除业务需求 |
| PUT | /epics/{epicID}/change | 变更业务需求 |
| PUT | /epics/{epicID}/close | 关闭业务需求 |
| PUT | /epics/{epicID}/activate | 激活业务需求 |
列表:/products/{id}/epics
字段结构同 Story,差异:无 project/execution 字段,parent 指父业务需求。
POST 创建建议:沿用 Story 的字段结构,并显式传 grade。典型 BR 场景可传 grade: 1。
GET 列表参数:同 Story
---
用户需求(Requirement)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /requirements | 创建用户需求 |
| GET | /requirements/{storyID} | 获取用户需求详情 |
| PUT | /requirements/{requirementID} | 修改用户需求 |
| DELETE | /requirements/{requirementID} | 删除用户需求 |
| PUT | /requirements/{requirementID}/change | 变更用户需求 |
| PUT | /requirements/{requirementID}/close | 关闭用户需求 |
| PUT | /requirements/{requirementID}/activate | 激活用户需求 |
列表:/products/{id}/requirements
字段结构同 Story,差异:无 project/execution 字段,change 操作中 reviewer 为可选(非必填)。
POST 创建建议:沿用 Story 的字段结构,并显式传 grade。典型 UR 场景可传 grade: 1。
GET 列表参数:同 Story
---
Bug
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /bugs | 创建 Bug |
| GET | /bugs/{bugID} | 获取 Bug 详情 |
| PUT | /bugs/{bugID} | 修改 Bug |
| DELETE | /bugs/{bugID} | 删除 Bug |
| PUT | /bugs/{bugID}/resolve | 解决 Bug |
| PUT | /bugs/{bugID}/close | 关闭 Bug |
| PUT | /bugs/{bugID}/activate | 激活 Bug |
列表通过父资源获取:/products/{id}/bugs, /projects/{id}/bugs, /executions/{id}/bugs
POST 创建:productID(int), title(string), openedBuild(string[], 如["trunk"]), project(int), execution(int), severity(int, 默认3), pri(int, 默认3), type(codeerror|config|install|security|performance|standard|automation|designdefect|others), steps(string), story(int)
PUT 修改:所有字段均为可选
PUT resolve 解决:resolution(fixed|notrepro|bydesign|duplicate|external|postponed|willnotfix|tostory), resolvedDate(string), resolvedBuild(string), assignedTo(string), comment(string)
PUT close 关闭:comment(string)
PUT activate 激活:openedBuild(string[]), assignedTo(string), comment(string)
GET /products/{id}/bugs 参数:browseType(all|unclosed|assignedtome|openedbyme|assignedbyme,默认unclosed), orderBy(id|title|status+_asc/_desc), recPerPage, pageID
GET /projects/{id}/bugs 和 /executions/{id}/bugs 参数:browseType(all|unresolved,默认all), orderBy 同上
---
任务(Task)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /tasks | 创建任务 |
| GET | /tasks/{taskID} | 获取任务详情 |
| PUT | /tasks/{taskID} | 修改任务 |
| DELETE | /tasks/{taskID} | 删除任务 |
| PUT | /tasks/{taskID}/start | 启动任务 |
| PUT | /tasks/{taskID}/finish | 完成任务 |
| PUT | /tasks/{taskID}/close | 关闭任务 |
| PUT | /tasks/{taskID}/activate | 激活任务 |
列表:/executions/{id}/tasks
POST 创建:name(string), executionID(int), type(string), assignedTo(string), estStarted(date), deadline(date), pri(int), estimate(float), module(int), story(int), desc(string)
PUT 修改:所有字段可选
PUT start 启动:realStarted(date), assignedTo(string), consumed(float), left(float), comment(string)
PUT finish 完成:currentConsumed(float), realStarted(date), finishedDate(date), assignedTo(string), consumed(float), comment(string)
PUT close 关闭:comment(string)
PUT activate 激活:left(float), assignedTo(string), comment(string)
GET 列表参数:status(all|unclosed|assignedtome|myinvolved|assignedbyme,默认unclosed), orderBy(id|name|status+_asc/_desc), recPerPage, pageID
---
测试用例(Testcase)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /testcases | 创建测试用例 |
| GET | /testcases/{caseID} | 获取测试用例详情 |
| PUT | /testcases/{testcasID} | 修改测试用例 |
| DELETE | /testcases/{testcasID} | 删除测试用例 |
列表通过父资源获取:/products/{id}/testcases, /projects/{id}/testcases, /executions/{id}/testcases
POST 创建:productID(int), title(string), module(int), story(int), pri(int), type(unit|interface|feature|install|config|performance|security|other), precondition(string), steps(string[]), expects(string[]), stepType(string[] step|group), project(int), execution(int)
PUT 修改:title(string),其余可选(注意:模块字段名为 moudule)
GET 列表参数:browseType(all|wait|needconfirm,默认all), orderBy(id|title|status或pri+_asc/_desc), recPerPage, pageID
---
产品计划(Productplan)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /productplans | 创建产品计划 |
| GET | /productplans/{planID} | 获取产品计划详情 |
| PUT | /productplans/{productplanID} | 修改产品计划 |
| DELETE | /productplans/{productplanID} | 删除产品计划 |
列表:/products/{id}/productplans
POST 创建:productID(int), title(string), parent(int), begin(date), end(date), branchID(int), desc(string)
PUT 修改:title(string),productID 不再必填,其余可选
GET 列表参数:browseType(all|undone|wait|doing,默认undone), orderBy(id|title|begin|end|status+_asc/_desc), recPerPage, pageID
---
版本(Build)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /builds | 创建版本 |
| PUT | /builds/{buildID} | 修改版本 |
| DELETE | /builds/{buildID} | 删除版本 |
列表:/projects/{id}/builds, /executions/{id}/builds(无查询参数)
POST 创建:executionID(int), product(int), name(string), system(int), builder(string), date(date), scmPath(string), filePath(string), desc(string)
PUT 修改:同上但字段名为 execution(int)(非 executionID)
---
发布(Release)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /releases | 创建发布 |
| PUT | /releases/{releasID} | 修改发布 |
| DELETE | /releases/{releasID} | 删除发布 |
列表:/products/{id}/releases(无查询参数)
POST 创建:productID(int), system(int), name(string), build(string[]), date(date), status(wait|normal|fail|terminate), desc(string)
PUT 修改:productID 不再必填,其余同上
路径参数名为 releasID(非 releaseID)---
测试单(Testtask)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /testtasks | 创建测试单 |
| PUT | /testtasks/{testtaskID} | 修改测试单 |
| DELETE | /testtasks/{testtaskID} | 删除测试单 |
列表:/products/{id}/testtasks, /projects/{id}/testtasks, /executions/{id}/testtasks(无查询参数)
POST 创建:productID(int), name(string), build(int), begin(date), end(date), execution(int), type(string[] integrate|system|acceptance|performance|safety), owner(string), status(wait|doing|done|blocked), desc(string)
PUT 修改:productID 不再必填,其余同上
---
反馈(Feedback)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /feedbacks | 创建反馈 |
| GET | /feedbacks/{feedbackID} | 获取反馈详情 |
| PUT | /feedbacks/{feedbackID} | 修改反馈 |
| DELETE | /feedbacks/{feedbackID} | 删除反馈 |
| PUT | /feedbacks/{feedbackID}/close | 关闭反馈 |
| PUT | /feedbacks/{feedbackID}/activate | 激活反馈 |
列表:/products/{id}/feedbacks
POST/PUT:product(int), title(string), module(int), type(story|task|bug|todo|advice|issue|risk|opportunity), desc(string), feedbackBy(string), source(string)
PUT close 关闭:closedReason(commented|repeat|refuse), comment(string)
PUT activate 激活:assignedTo(string), comment(string)
GET 列表参数:browseType(all|wait|doing|toclosed|review|assigntome|openedbyme,默认wait), orderBy(id|title|status+_asc/_desc), recPerPage, pageID
---
工单(Ticket)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /tickets | 创建工单 |
| GET | /tickets/{ticketID} | 获取工单详情 |
| PUT | /tickets/{ticketID} | 修改工单 |
| DELETE | /tickets/{ticketID} | 删除工单 |
| PUT | /tickets/{ticketID}/close | 关闭工单 |
| PUT | /tickets/{ticketID}/activate | 激活工单 |
列表:/products/{id}/tickets
POST 创建:product(int), title(string), module(int), type(code|data|stuck|security|affair), desc(string), assignedTo(string), deadline(date), openedBuild(string[])
PUT 修改:所有字段可选
PUT close 关闭:closedReason(commented|repeat|refuse), comment(string)
PUT activate 激活:assignedTo(string), comment(string)
GET 列表参数:browseType(all|unclosed|wait|doing|done|finishedbyme|assigntome|openedbyme,默认wait), orderBy(id|title|status+_asc/_desc), recPerPage, pageID
---
应用(System)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /systems | 创建应用 |
| PUT | /systems/{systemID} | 修改应用 |
列表:/products/{id}/systems(无查询参数)
POST 创建:productID(int), integrated(int, 0=否 1=是), children(string[], 非集成传[]), name(string), desc(string)
PUT 修改:name(string), children(string[]), desc(string)
---
文件(File)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /files | 编辑附件名称 |
| DELETE | /files/{fileID} | 删除附件 |
POST:fileName(string)
#!/usr/bin/env bash
# 获取禅道 API 调用所需的 URL、token 和用户名,按优先级:缓存文件 > 环境变量 > 账号密码登录。
# 用法:eval "$(bash get-token.sh)" → 设置 ZENTAO_URL、ZENTAO_TOKEN、ZENTAO_ACCOUNT
# 依赖:curl, node
# 缓存文件 ~/.zentao-token.json 保存 token、url、account,下次可免密直接使用。
# 注:禅道 token 永久有效;如需切换账号/服务器,删除缓存文件后重新运行即可。
set -euo pipefail
CACHE_FILE="${HOME}/.zentao-token.json"
# 输出三元组(KEY=VALUE 格式,可直接 eval)并退出
output_and_exit() {
local url="$1" token="$2" account="$3"
echo "ZENTAO_URL=${url}"
echo "ZENTAO_TOKEN=${token}"
echo "ZENTAO_ACCOUNT=${account}"
exit 0
}
# ── 1. 优先:从缓存文件读取 url、token、account(单次 node 调用)────────────
if [[ -f "$CACHE_FILE" ]]; then
_cache_url='' _cache_token='' _cache_account=''
{
IFS= read -r _cache_url
IFS= read -r _cache_token
IFS= read -r _cache_account
} < <(node -e "
try {
const d = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'));
process.stdout.write((d.url||'') + '\n' + (d.token||'') + '\n' + (d.account||'') + '\n');
} catch(e) { process.stdout.write('\n\n\n'); }
" "$CACHE_FILE" 2>/dev/null || printf '\n\n\n')
# 用缓存补全缺失的环境变量
[[ -z "${ZENTAO_URL:-}" && -n "$_cache_url" ]] && ZENTAO_URL="$_cache_url"
[[ -z "${ZENTAO_ACCOUNT:-}" && -n "$_cache_account" ]] && ZENTAO_ACCOUNT="$_cache_account"
# 缓存 token 有效且 url/account 均匹配,直接输出三元组(无需密码)
if [[ -n "$_cache_token" \
&& "${ZENTAO_URL:-}" == "$_cache_url" \
&& ( -z "${ZENTAO_ACCOUNT:-}" || "${ZENTAO_ACCOUNT:-}" == "$_cache_account" ) ]]; then
output_and_exit "$_cache_url" "$_cache_token" "$_cache_account"
fi
fi
# ── 2. 其次:从环境变量读取 token(仍需 ZENTAO_URL)────────────────────────
if [[ -n "${ZENTAO_TOKEN:-}" ]]; then
if [[ -z "${ZENTAO_URL:-}" ]]; then
echo "错误:设置了 ZENTAO_TOKEN 但缺少 ZENTAO_URL,请同时提供服务器地址。" >&2
exit 1
fi
# 写入缓存,方便下次无需环境变量直接使用
node - "$CACHE_FILE" "${ZENTAO_TOKEN}" "${ZENTAO_URL}" "${ZENTAO_ACCOUNT:-}" <<'JSEOF'
const [,, cachePath, token, url, account] = process.argv;
const fs = require('fs');
fs.writeFileSync(cachePath, JSON.stringify({ token, url, account }, null, 2));
JSEOF
output_and_exit "${ZENTAO_URL}" "${ZENTAO_TOKEN}" "${ZENTAO_ACCOUNT:-}"
fi
# ── 3. 再次:用账号密码重新登录(需 ZENTAO_URL、ZENTAO_ACCOUNT、ZENTAO_PASSWORD)
if [[ -z "${ZENTAO_URL:-}" || -z "${ZENTAO_ACCOUNT:-}" || -z "${ZENTAO_PASSWORD:-}" ]]; then
echo "错误:Token 获取失败。请通过以下任一方式提供鉴权信息:" >&2
echo " · 缓存文件 ~/.zentao-token.json(含 url、token、account 字段)" >&2
echo " · 环境变量 ZENTAO_TOKEN + ZENTAO_URL(直接提供 token 和服务器地址)" >&2
echo " · 环境变量 ZENTAO_URL、ZENTAO_ACCOUNT、ZENTAO_PASSWORD(账号密码登录)" >&2
exit 1
fi
RESPONSE=$(curl -s -X POST "${ZENTAO_URL}/api.php/v2/users/login" \
-H "Content-Type: application/json" \
-d "{\"account\": \"${ZENTAO_ACCOUNT}\", \"password\": \"${ZENTAO_PASSWORD}\"}")
TOKEN=$(echo "$RESPONSE" | node -e "
const chunks = [];
process.stdin.on('data', d => chunks.push(d));
process.stdin.on('end', () => {
try {
const data = JSON.parse(chunks.join(''));
const token = (data.data && data.data.token) || data.token || '';
if (!token) {
process.stderr.write('登录失败,服务器响应:' + JSON.stringify(data) + '\n');
process.exit(1);
}
process.stdout.write(token);
} catch (e) {
process.stderr.write('解析登录响应失败:' + e.message + '\n');
process.exit(1);
}
});
") || { echo "错误:登录失败,请查看上方错误信息" >&2; exit 1; }
# ── 4. 缓存:写入 token、url、account ────────────────────────────────────────
node - "$CACHE_FILE" "$TOKEN" "$ZENTAO_URL" "$ZENTAO_ACCOUNT" <<'JSEOF'
const [,, cachePath, token, url, account] = process.argv;
const fs = require('fs');
fs.writeFileSync(cachePath, JSON.stringify({ token, url, account }, null, 2));
JSEOF
output_and_exit "$ZENTAO_URL" "$TOKEN" "$ZENTAO_ACCOUNT"