Plane REST API v1 完整使用文档
版本: v0.0.1
Base URL: /api/v1/
官方文档: developers.plane.so
本文档覆盖 Plane API v1 的所有端点,包含详细参数说明和 Go 语言代码示例。
目录
- 认证与客户端基础
- 用户 (Users)
- 项目 (Projects)
- 工作项 (Work Items)
- 工作项链接 (Links)
- 工作项评论 (Comments)
- 工作项活动 (Activities)
- 工作项附件 (Attachments)
- 工作项关系 (Relations)
- 工作项搜索 (Search)
- 迭代 (Cycles)
- 模块 (Modules)
- 状态 (States)
- 标签 (Labels)
- 成员 (Members)
- 收件箱/Intake
- 资源 (Assets)
- 邀请 (Invitations)
- 便签 (Stickies)
- 附录
1. 认证与客户端基础
1.1 认证方式
API v1 使用 API Key 进行认证,在 HTTP Header 中携带:
X-Api-Key: <your-api-key>
API Key 在 Plane 工作空间设置 → API Tokens 中生成。支持设置过期时间。
1.2 通用分页参数
所有列表接口支持以下查询参数:
| 参数 |
类型 |
说明 |
cursor |
string |
分页游标(从上一页响应的 next_cursor 获取) |
per_page |
integer |
每页数量 |
order_by |
string |
排序字段,前缀 - 表示倒序 |
fields |
string |
返回字段过滤(逗号分隔) |
expand |
string |
展开关联对象(逗号分隔),如 assignees,labels |
1.3 通用分页响应格式
{
"total_count": 100,
"next_cursor": "...",
"prev_cursor": "...",
"next_page_results": true,
"prev_page_results": false,
"total_pages": 10,
"results": [...]
}
1.4 Go 客户端基础代码
package plane
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
)
// V1Client Plane API v1 客户端
type V1Client struct {
BaseURL string
APIKey string
HTTP *http.Client
}
// NewV1Client 创建 API v1 客户端
func NewV1Client(baseURL, apiKey string) *V1Client {
return &V1Client{
BaseURL: strings.TrimRight(baseURL, "/"),
APIKey: apiKey,
HTTP: &http.Client{},
}
}
// request 执行 HTTP 请求
func (c *V1Client) request(method, path string, body io.Reader, contentType string) (*http.Response, error) {
req, err := http.NewRequest(method, c.BaseURL+path, body)
if err != nil {
return nil, err
}
req.Header.Set("X-Api-Key", c.APIKey)
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
return c.HTTP.Do(req)
}
// jsonRequest 执行 JSON 请求并返回响应体
func (c *V1Client) jsonRequest(method, path string, payload interface{}) ([]byte, int, error) {
var body io.Reader
if payload != nil {
data, err := json.Marshal(payload)
if err != nil {
return nil, 0, err
}
body = bytes.NewReader(data)
}
resp, err := c.request(method, path, body, "application/json")
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
return respBody, resp.StatusCode, nil
}
1.5 使用示例
func main() {
client := NewV1Client("http://localhost:8000", "your-api-key-here")
// 获取当前用户
body, status, _ := client.jsonRequest("GET", "/api/v1/users/me/", nil)
fmt.Printf("Status: %d, Body: %s\n", status, string(body))
}
2. 用户 (Users)
端点
| 方法 |
路径 |
说明 |
| GET |
/api/v1/users/me/ |
获取当前用户信息 |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
用户 ID |
first_name |
string |
名 |
last_name |
string |
姓 |
email |
string |
邮箱 |
avatar |
string |
头像路径 |
avatar_url |
string |
头像完整 URL |
display_name |
string |
显示名称 |
Go 示例
// GetMe 获取当前用户信息
func (c *V1Client) GetMe() ([]byte, error) {
body, status, err := c.jsonRequest("GET", "/api/v1/users/me/", nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, err: %v", status, err)
}
return body, nil
}
3. 项目 (Projects)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/projects/ |
列出项目 |
| POST |
/api/v1/workspaces/{slug}/projects/ |
创建项目 |
| GET |
/api/v1/workspaces/{slug}/projects/{id}/ |
获取详情 |
| PATCH |
/api/v1/workspaces/{slug}/projects/{id}/ |
更新项目 |
| DELETE |
/api/v1/workspaces/{slug}/projects/{id}/ |
删除项目 |
| POST |
/api/v1/workspaces/{slug}/projects/{id}/archive/ |
归档项目 |
| DELETE |
/api/v1/workspaces/{slug}/projects/{id}/archive/ |
取消归档 |
| GET |
/api/v1/workspaces/{slug}/projects/{id}/summary/ |
获取项目摘要 |
创建项目参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
项目名称(最大 255 字符,不可含特殊字符) |
identifier |
string |
是 |
标识符(最大 12 字符,自动转大写) |
description |
string |
否 |
描述 |
network |
integer |
否 |
0=私密, 2=公开(默认 2) |
project_lead |
uuid |
否 |
负责人(须为工作空间成员) |
default_assignee |
uuid |
否 |
默认指派人 |
icon_prop |
object |
否 |
图标属性 |
emoji |
string |
否 |
表情图标 |
cover_image |
string |
否 |
封面图 URL |
module_view |
boolean |
否 |
启用模块(默认 false) |
cycle_view |
boolean |
否 |
启用迭代(默认 false) |
issue_views_view |
boolean |
否 |
启用自定义视图(默认 false) |
page_view |
boolean |
否 |
启用页面(默认 true) |
intake_view |
boolean |
否 |
启用收件箱(默认 false) |
archive_in |
integer |
否 |
自动归档天数(0-12,默认 0) |
close_in |
integer |
否 |
自动关闭天数(0-12,默认 0) |
timezone |
string |
否 |
时区(默认 UTC) |
external_source |
string |
否 |
外部来源 |
external_id |
string |
否 |
外部 ID |
更新项目额外参数
| 字段 |
类型 |
说明 |
default_state |
uuid |
默认状态 ID(须为本项目状态) |
estimate |
uuid |
估算配置 ID |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
项目 ID |
name |
string |
名称 |
identifier |
string |
标识符 |
network |
integer |
可见性 |
total_members |
integer |
成员总数 |
total_cycles |
integer |
迭代总数 |
total_modules |
integer |
模块总数 |
is_member |
boolean |
当前用户是否为成员 |
member_role |
integer |
当前用户角色 (20/15/5) |
is_deployed |
boolean |
是否已部署 |
cover_image_url |
string |
封面图完整 URL |
created_at |
datetime |
创建时间 |
updated_at |
datetime |
更新时间 |
项目摘要查询参数
| 参数 |
类型 |
说明 |
fields |
string |
逗号分隔,可选:members,states,labels,cycles,modules,issues,intakes,pages |
Go 示例
// ListProjects 列出项目
func (c *V1Client) ListProjects(slug string) ([]byte, error) {
body, status, err := c.jsonRequest("GET", fmt.Sprintf("/api/v1/workspaces/%s/projects/", slug), nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateProject 创建项目
func (c *V1Client) CreateProject(slug string, data map[string]interface{}) ([]byte, error) {
body, status, err := c.jsonRequest("POST", fmt.Sprintf("/api/v1/workspaces/%s/projects/", slug), data)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// GetProject 获取项目详情
func (c *V1Client) GetProject(slug, projectID string) ([]byte, error) {
body, status, err := c.jsonRequest("GET", fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/", slug, projectID), nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// UpdateProject 更新项目
func (c *V1Client) UpdateProject(slug, projectID string, data map[string]interface{}) ([]byte, error) {
body, status, err := c.jsonRequest("PATCH", fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/", slug, projectID), data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteProject 删除项目
func (c *V1Client) DeleteProject(slug, projectID string) error {
_, status, err := c.jsonRequest("DELETE", fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/", slug, projectID), nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// ArchiveProject 归档项目
func (c *V1Client) ArchiveProject(slug, projectID string) error {
_, status, err := c.jsonRequest("POST",
fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/archive/", slug, projectID), map[string]interface{}{})
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// UnarchiveProject 取消归档项目
func (c *V1Client) UnarchiveProject(slug, projectID string) error {
_, status, err := c.jsonRequest("DELETE",
fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/archive/", slug, projectID), nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// GetProjectSummary 获取项目摘要
func (c *V1Client) GetProjectSummary(slug, projectID, fields string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/summary/", slug, projectID)
if fields != "" {
path += "?fields=" + fields
}
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
4. 工作项 (Work Items)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/work-items/ |
列出工作项 |
| POST |
/api/v1/workspaces/{slug}/projects/{pid}/work-items/ |
创建工作项 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/work-items/{id}/ |
获取详情 |
| PATCH |
/api/v1/workspaces/{slug}/projects/{pid}/work-items/{id}/ |
更新工作项 |
| PUT |
/api/v1/workspaces/{slug}/projects/{pid}/work-items/{id}/ |
Upsert(通过 external_id) |
| DELETE |
/api/v1/workspaces/{slug}/projects/{pid}/work-items/{id}/ |
删除工作项 |
| GET |
/api/v1/workspaces/{slug}/work-items/{proj_identifier}-{seq}/ |
通过标识符获取 |
创建工作项参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
名称(最大 255 字符) |
description_html |
string |
否 |
HTML 描述(会进行安全校验) |
priority |
string |
否 |
urgent/high/medium/low/none(默认 none) |
state |
uuid |
否 |
状态 ID(须为本项目状态,不填用默认) |
parent |
uuid |
否 |
父工作项 ID |
assignees |
array[uuid] |
否 |
指派人 ID 列表(须为项目中 role>=15 活跃成员) |
labels |
array[uuid] |
否 |
标签 ID 列表(须为本项目标签) |
start_date |
date |
否 |
开始日期 (YYYY-MM-DD,不可晚于 target_date) |
target_date |
date |
否 |
目标日期 (YYYY-MM-DD) |
type_id |
uuid |
否 |
工作项类型 ID |
estimate_point |
uuid |
否 |
估算点 ID |
external_source |
string |
否 |
外部来源标识 |
external_id |
string |
否 |
外部 ID(同项目内不可重复) |
created_at |
datetime |
否 |
自定义创建时间(导入用) |
created_by |
uuid |
否 |
自定义创建人(导入用) |
列表查询参数
| 参数 |
类型 |
说明 |
order_by |
string |
排序:-created_at(默认),priority,-priority,state__group,-state__group,assignees__first_name |
external_id |
string |
按外部 ID 精确查找 |
external_source |
string |
按外部来源查找(需配合 external_id) |
cursor |
string |
分页游标 |
per_page |
integer |
每页数量 |
fields |
string |
返回字段过滤 |
expand |
string |
展开:assignees,labels |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
工作项 ID |
name |
string |
名称 |
description_html |
string |
HTML 描述 |
priority |
string |
优先级 |
state |
uuid |
状态 ID |
parent |
uuid/null |
父工作项 ID |
assignees |
array[uuid] |
指派人(expand 时返回用户对象) |
labels |
array[uuid] |
标签(expand 时返回标签对象) |
start_date |
date/null |
开始日期 |
target_date |
date/null |
目标日期 |
sequence_id |
integer |
序列号 |
sort_order |
float |
排序权重 |
type_id |
uuid/null |
工作项类型 |
completed_at |
datetime/null |
完成时间 |
sub_issues_count |
integer |
子工作项数量 |
link_count |
integer |
链接数量 |
attachment_count |
integer |
附件数量 |
cycle_id |
uuid/null |
所属迭代 |
external_source |
string/null |
外部来源 |
external_id |
string/null |
外部 ID |
project |
uuid |
项目 ID |
workspace |
uuid |
工作空间 ID |
created_at |
datetime |
创建时间 |
updated_at |
datetime |
更新时间 |
Go 示例
// ListWorkItems 列出工作项
func (c *V1Client) ListWorkItems(slug, projectID string, params map[string]string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/", slug, projectID)
if len(params) > 0 {
parts := []string{}
for k, v := range params {
parts = append(parts, k+"="+v)
}
path += "?" + strings.Join(parts, "&")
}
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateWorkItem 创建工作项
func (c *V1Client) CreateWorkItem(slug, projectID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/", slug, projectID)
body, status, err := c.jsonRequest("POST", path, data)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// GetWorkItem 获取工作项详情
func (c *V1Client) GetWorkItem(slug, projectID, workItemID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/", slug, projectID, workItemID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// UpdateWorkItem 更新工作项
func (c *V1Client) UpdateWorkItem(slug, projectID, workItemID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/", slug, projectID, workItemID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// UpsertWorkItem 通过 external_id Upsert 工作项
func (c *V1Client) UpsertWorkItem(slug, projectID string, data map[string]interface{}) ([]byte, int, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/", slug, projectID)
// PUT 请求需要包含 external_id 和 external_source
body, status, err := c.jsonRequest("PUT", path, data)
return body, status, err // 200=更新, 201=创建
}
// DeleteWorkItem 删除工作项(仅 Admin 或创建者)
func (c *V1Client) DeleteWorkItem(slug, projectID, workItemID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/", slug, projectID, workItemID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// GetWorkItemByIdentifier 通过标识符获取(如 PROJ-42)
func (c *V1Client) GetWorkItemByIdentifier(slug, projectIdentifier string, sequenceID int) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/work-items/%s-%d/", slug, projectIdentifier, sequenceID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
5. 工作项链接 (Links)
端点列表
| 方法 |
路径 |
说明 |
| GET |
.../work-items/{issue_id}/links/ |
列出链接 |
| POST |
.../work-items/{issue_id}/links/ |
创建链接 |
| GET |
.../work-items/{issue_id}/links/{id}/ |
获取链接详情 |
| PATCH |
.../work-items/{issue_id}/links/{id}/ |
更新链接 |
| DELETE |
.../work-items/{issue_id}/links/{id}/ |
删除链接 |
路径前缀: /api/v1/workspaces/{slug}/projects/{project_id}
创建链接参数
| 字段 |
类型 |
必填 |
说明 |
url |
string |
是 |
链接 URL(http/https,同工作项不可重复) |
title |
string |
否 |
链接标题(不填则系统自动爬取) |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
链接 ID |
title |
string/null |
标题 |
url |
string |
URL |
metadata |
object |
元数据 |
issue |
uuid |
所属工作项 ID |
created_by |
uuid |
创建人 |
created_at |
datetime |
创建时间 |
updated_at |
datetime |
更新时间 |
Go 示例
// ListWorkItemLinks 列出链接
func (c *V1Client) ListWorkItemLinks(slug, projectID, issueID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/links/", slug, projectID, issueID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateWorkItemLink 创建链接
func (c *V1Client) CreateWorkItemLink(slug, projectID, issueID, linkURL, title string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/links/", slug, projectID, issueID)
payload := map[string]interface{}{"url": linkURL, "title": title}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// GetWorkItemLink 获取链接详情
func (c *V1Client) GetWorkItemLink(slug, projectID, issueID, linkID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/links/%s/", slug, projectID, issueID, linkID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// UpdateWorkItemLink 更新链接
func (c *V1Client) UpdateWorkItemLink(slug, projectID, issueID, linkID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/links/%s/", slug, projectID, issueID, linkID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteWorkItemLink 删除链接
func (c *V1Client) DeleteWorkItemLink(slug, projectID, issueID, linkID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/links/%s/", slug, projectID, issueID, linkID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
6. 工作项评论 (Comments)
端点列表
| 方法 |
路径 |
说明 |
| GET |
.../work-items/{issue_id}/comments/ |
列出评论 |
| POST |
.../work-items/{issue_id}/comments/ |
创建评论 |
| GET |
.../work-items/{issue_id}/comments/{id}/ |
获取评论详情 |
| PATCH |
.../work-items/{issue_id}/comments/{id}/ |
更新评论 |
| DELETE |
.../work-items/{issue_id}/comments/{id}/ |
删除评论 |
创建评论参数
| 字段 |
类型 |
必填 |
说明 |
comment_html |
string |
是 |
HTML 内容(会校验安全性) |
comment_json |
object |
否 |
JSON 富文本结构 |
access |
string |
否 |
INTERNAL(默认) / EXTERNAL |
external_source |
string |
否 |
外部来源 |
external_id |
string |
否 |
外部 ID(同项目不可重复) |
created_at |
datetime |
否 |
自定义创建时间(导入用) |
created_by |
uuid |
否 |
自定义创建人(导入用) |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
评论 ID |
comment_html |
string |
HTML 内容 |
access |
string |
可见性 |
actor |
uuid |
评论者 ID |
is_member |
boolean |
当前用户是否为项目成员 |
external_source |
string/null |
外部来源 |
external_id |
string/null |
外部 ID |
edited_at |
datetime/null |
最后编辑时间 |
created_at |
datetime |
创建时间 |
Go 示例
// ListWorkItemComments 列出评论
func (c *V1Client) ListWorkItemComments(slug, projectID, issueID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/comments/", slug, projectID, issueID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateWorkItemComment 创建评论
func (c *V1Client) CreateWorkItemComment(slug, projectID, issueID, commentHTML string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/comments/", slug, projectID, issueID)
payload := map[string]interface{}{"comment_html": commentHTML}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// GetWorkItemComment 获取评论详情
func (c *V1Client) GetWorkItemComment(slug, projectID, issueID, commentID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/comments/%s/", slug, projectID, issueID, commentID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// UpdateWorkItemComment 更新评论
func (c *V1Client) UpdateWorkItemComment(slug, projectID, issueID, commentID, commentHTML string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/comments/%s/", slug, projectID, issueID, commentID)
payload := map[string]interface{}{"comment_html": commentHTML}
body, status, err := c.jsonRequest("PATCH", path, payload)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteWorkItemComment 删除评论
func (c *V1Client) DeleteWorkItemComment(slug, projectID, issueID, commentID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/comments/%s/", slug, projectID, issueID, commentID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
7. 工作项活动 (Activities)
端点列表
| 方法 |
路径 |
说明 |
| GET |
.../work-items/{issue_id}/activities/ |
列出活动历史 |
| GET |
.../work-items/{issue_id}/activities/{id}/ |
获取单条活动 |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
活动 ID |
verb |
string |
操作类型(created, updated) |
field |
string/null |
变更字段名(state, priority, name 等) |
old_value |
string/null |
变更前的值 |
new_value |
string/null |
变更后的值 |
old_identifier |
uuid/null |
变更前关联 ID |
new_identifier |
uuid/null |
变更后关联 ID |
comment |
string |
备注 |
actor |
uuid/null |
操作人 |
epoch |
float/null |
时间戳 |
created_at |
datetime |
创建时间 |
Go 示例
// ListWorkItemActivities 列出活动历史
func (c *V1Client) ListWorkItemActivities(slug, projectID, issueID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/activities/", slug, projectID, issueID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// GetWorkItemActivity 获取单条活动
func (c *V1Client) GetWorkItemActivity(slug, projectID, issueID, activityID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/activities/%s/", slug, projectID, issueID, activityID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
8. 工作项附件 (Attachments)
端点列表
| 方法 |
路径 |
说明 |
| GET |
.../work-items/{issue_id}/attachments/ |
列出附件 |
| POST |
.../work-items/{issue_id}/attachments/ |
创建附件(获取预签名 URL) |
| GET |
.../work-items/{issue_id}/attachments/{id}/ |
下载附件(302 重定向) |
| PATCH |
.../work-items/{issue_id}/attachments/{id}/ |
确认上传 |
| DELETE |
.../work-items/{issue_id}/attachments/{id}/ |
删除附件 |
创建附件参数(获取预签名 URL)
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
文件名 |
type |
string |
是 |
MIME 类型(须为服务端允许的类型) |
size |
integer |
是 |
文件大小(字节) |
external_id |
string |
否 |
外部 ID |
external_source |
string |
否 |
外部来源 |
创建附件响应
| 字段 |
类型 |
说明 |
upload_data.url |
string |
S3 上传目标 URL |
upload_data.fields |
object |
上传表单字段 |
asset_id |
uuid |
资源 ID |
asset_url |
string |
资源访问 URL |
attachment |
object |
附件详情 |
确认上传参数
| 字段 |
类型 |
必填 |
说明 |
is_uploaded |
boolean |
是 |
设为 true 确认上传完成 |
附件响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
附件 ID |
attributes |
object |
{"name":"...", "type":"...", "size":...} |
size |
integer |
文件大小 |
is_uploaded |
boolean |
是否已上传 |
external_id |
string/null |
外部 ID |
external_source |
string/null |
外部来源 |
created_at |
datetime |
创建时间 |
上传流程
- POST 获取预签名 URL + asset_id
- POST 文件到 S3(multipart/form-data)
- PATCH 确认上传完成
Go 示例
// ListWorkItemAttachments 列出附件
func (c *V1Client) ListWorkItemAttachments(slug, projectID, issueID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/attachments/", slug, projectID, issueID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateWorkItemAttachment 创建附件(获取预签名 URL)
func (c *V1Client) CreateWorkItemAttachment(slug, projectID, issueID, fileName, mimeType string, size int64) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/attachments/", slug, projectID, issueID)
payload := map[string]interface{}{
"name": fileName,
"type": mimeType,
"size": size,
}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// UploadToS3 上传文件到 S3
func (c *V1Client) UploadToS3(uploadURL string, fields map[string]string, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
for k, v := range fields {
writer.WriteField(k, v)
}
part, _ := writer.CreateFormFile("file", filepath.Base(filePath))
io.Copy(part, file)
writer.Close()
req, _ := http.NewRequest("POST", uploadURL, &buf)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := (&http.Client{}).Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 && resp.StatusCode != 204 {
return fmt.Errorf("S3 upload failed: %d", resp.StatusCode)
}
return nil
}
// ConfirmAttachmentUpload 确认附件上传
func (c *V1Client) ConfirmAttachmentUpload(slug, projectID, issueID, assetID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/attachments/%s/", slug, projectID, issueID, assetID)
payload := map[string]interface{}{"is_uploaded": true}
_, status, err := c.jsonRequest("PATCH", path, payload)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// DeleteWorkItemAttachment 删除附件
func (c *V1Client) DeleteWorkItemAttachment(slug, projectID, issueID, assetID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/attachments/%s/", slug, projectID, issueID, assetID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// UploadAttachment 完整附件上传流程
func (c *V1Client) UploadAttachment(slug, projectID, issueID, filePath, mimeType string) (string, error) {
info, err := os.Stat(filePath)
if err != nil {
return "", err
}
// 1. 获取预签名 URL
respBody, err := c.CreateWorkItemAttachment(slug, projectID, issueID, info.Name(), mimeType, info.Size())
if err != nil {
return "", err
}
var result struct {
UploadData struct {
URL string `json:"url"`
Fields map[string]string `json:"fields"`
} `json:"upload_data"`
AssetID string `json:"asset_id"`
}
json.Unmarshal(respBody, &result)
// 2. 上传到 S3
if err := c.UploadToS3(result.UploadData.URL, result.UploadData.Fields, filePath); err != nil {
return "", err
}
// 3. 确认
if err := c.ConfirmAttachmentUpload(slug, projectID, issueID, result.AssetID); err != nil {
return "", err
}
return result.AssetID, nil
}
9. 工作项关系 (Relations)
端点列表
| 方法 |
路径 |
说明 |
| GET |
.../work-items/{issue_id}/relations/ |
列出关系 |
| POST |
.../work-items/{issue_id}/relations/ |
创建关系 |
创建关系参数
| 字段 |
类型 |
必填 |
说明 |
relation_type |
string |
是 |
关系类型 |
issues |
array[uuid] |
是 |
关联工作项 ID 列表(至少 1 个) |
relation_type 可选值
| 值 |
说明 |
blocking |
当前工作项阻塞目标 |
blocked_by |
当前工作项被目标阻塞 |
duplicate |
重复 |
relates_to |
相关 |
start_before |
当前在目标之前开始 |
start_after |
当前在目标之后开始 |
finish_before |
当前在目标之前完成 |
finish_after |
当前在目标之后完成 |
列出关系响应
按类型分组的对象:
{
"blocking": [{"project_id": "...", "issue_id": "..."}],
"blocked_by": [...],
"duplicate": [...],
"relates_to": [...],
"start_after": [...],
"start_before": [...],
"finish_after": [...],
"finish_before": [...]
}
Go 示例
// ListWorkItemRelations 列出关系
func (c *V1Client) ListWorkItemRelations(slug, projectID, issueID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/relations/", slug, projectID, issueID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateWorkItemRelation 创建关系
func (c *V1Client) CreateWorkItemRelation(slug, projectID, issueID, relationType string, targetIDs []string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/work-items/%s/relations/", slug, projectID, issueID)
payload := map[string]interface{}{
"relation_type": relationType,
"issues": targetIDs,
}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
10. 工作项搜索 (Search)
端点
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/work-items/search/ |
搜索工作项 |
查询参数
| 参数 |
类型 |
必填 |
说明 |
search |
string |
是 |
搜索关键词(匹配名称、序列号、项目标识符) |
limit |
integer |
否 |
结果数量限制(默认 10) |
workspace_search |
string |
否 |
跨项目搜索:"true"/"false"(默认 "false") |
project_id |
uuid |
否 |
限定项目(仅 workspace_search=false 时生效) |
响应
{
"issues": [
{
"id": "uuid",
"name": "工作项名称",
"sequence_id": 42,
"project__identifier": "PROJ",
"project_id": "uuid",
"workspace__slug": "my-workspace"
}
]
}
Go 示例
// SearchWorkItems 搜索工作项
func (c *V1Client) SearchWorkItems(slug, query string, limit int, projectID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/work-items/search/?search=%s&limit=%d", slug, query, limit)
if projectID != "" {
path += "&project_id=" + projectID
}
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
11. 迭代 (Cycles)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/cycles/ |
列出迭代 |
| POST |
/api/v1/workspaces/{slug}/projects/{pid}/cycles/ |
创建迭代 |
| GET |
.../cycles/{id}/ |
获取详情 |
| PATCH |
.../cycles/{id}/ |
更新迭代 |
| DELETE |
.../cycles/{id}/ |
删除迭代 |
| GET |
.../cycles/{id}/cycle-issues/ |
列出迭代中的工作项 |
| POST |
.../cycles/{id}/cycle-issues/ |
添加工作项到迭代 |
| GET |
.../cycles/{id}/cycle-issues/{issue_id}/ |
获取迭代工作项 |
| DELETE |
.../cycles/{id}/cycle-issues/{issue_id}/ |
移除工作项 |
| POST |
.../cycles/{id}/transfer-issues/ |
转移工作项 |
| POST |
.../cycles/{id}/archive/ |
归档 |
| GET |
.../archived-cycles/ |
列出已归档 |
| DELETE |
.../archived-cycles/{id}/unarchive/ |
取消归档 |
创建迭代参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
名称(最大 255 字符) |
description |
string |
否 |
描述 |
start_date |
datetime |
否 |
开始时间(不可晚于 end_date) |
end_date |
datetime |
否 |
结束时间 |
owned_by |
uuid |
否 |
负责人(默认当前用户) |
timezone |
string |
否 |
时区 |
external_source |
string |
否 |
外部来源 |
external_id |
string |
否 |
外部 ID |
迭代响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
迭代 ID |
name |
string |
名称 |
description |
string |
描述 |
start_date |
datetime/null |
开始时间 |
end_date |
datetime/null |
结束时间 |
owned_by |
uuid |
负责人 |
total_issues |
integer |
工作项总数 |
completed_issues |
integer |
已完成数 |
started_issues |
integer |
进行中数 |
unstarted_issues |
integer |
未开始数 |
backlog_issues |
integer |
Backlog 数 |
cancelled_issues |
integer |
已取消数 |
total_estimates |
float |
总估算点 |
completed_estimates |
float |
已完成估算 |
started_estimates |
float |
进行中估算 |
archived_at |
datetime/null |
归档时间 |
添加工作项参数
| 字段 |
类型 |
必填 |
说明 |
issues |
array[uuid] |
是 |
工作项 ID 列表 |
转移工作项参数
| 字段 |
类型 |
必填 |
说明 |
new_cycle_id |
uuid |
是 |
目标迭代 ID |
Go 示例
// ListCycles 列出迭代
func (c *V1Client) ListCycles(slug, projectID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/", slug, projectID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateCycle 创建迭代
func (c *V1Client) CreateCycle(slug, projectID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/", slug, projectID)
body, status, err := c.jsonRequest("POST", path, data)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// GetCycle 获取迭代详情
func (c *V1Client) GetCycle(slug, projectID, cycleID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// UpdateCycle 更新迭代
func (c *V1Client) UpdateCycle(slug, projectID, cycleID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteCycle 删除迭代
func (c *V1Client) DeleteCycle(slug, projectID, cycleID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/%s/", slug, projectID, cycleID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// AddIssuesToCycle 添加工作项到迭代
func (c *V1Client) AddIssuesToCycle(slug, projectID, cycleID string, issueIDs []string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/%s/cycle-issues/", slug, projectID, cycleID)
payload := map[string]interface{}{"issues": issueIDs}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// RemoveIssueFromCycle 从迭代移除工作项
func (c *V1Client) RemoveIssueFromCycle(slug, projectID, cycleID, issueID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/%s/cycle-issues/%s/", slug, projectID, cycleID, issueID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// TransferCycleIssues 转移未完成工作项
func (c *V1Client) TransferCycleIssues(slug, projectID, cycleID, newCycleID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/%s/transfer-issues/", slug, projectID, cycleID)
payload := map[string]interface{}{"new_cycle_id": newCycleID}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// ArchiveCycle 归档迭代
func (c *V1Client) ArchiveCycle(slug, projectID, cycleID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/cycles/%s/archive/", slug, projectID, cycleID)
_, status, err := c.jsonRequest("POST", path, map[string]interface{}{})
if err != nil || status != 200 {
return fmt.Errorf("status: %d", status)
}
return nil
}
12. 模块 (Modules)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/modules/ |
列出模块 |
| POST |
/api/v1/workspaces/{slug}/projects/{pid}/modules/ |
创建模块 |
| GET |
.../modules/{id}/ |
获取详情 |
| PATCH |
.../modules/{id}/ |
更新模块 |
| DELETE |
.../modules/{id}/ |
删除模块 |
| GET |
.../modules/{id}/module-issues/ |
列出模块工作项 |
| POST |
.../modules/{id}/module-issues/ |
添加工作项 |
| DELETE |
.../modules/{id}/module-issues/{issue_id}/ |
移除工作项 |
| POST |
.../modules/{id}/archive/ |
归档 |
| GET |
.../archived-modules/ |
列出已归档 |
| DELETE |
.../archived-modules/{id}/unarchive/ |
取消归档 |
创建模块参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
名称(项目内唯一) |
description |
string |
否 |
描述 |
start_date |
date |
否 |
开始日期 (YYYY-MM-DD) |
target_date |
date |
否 |
目标日期 |
status |
string |
否 |
backlog/planned(默认)/in-progress/paused/completed/cancelled |
lead |
uuid |
否 |
负责人 |
members |
array[uuid] |
否 |
成员列表(须为项目成员) |
external_source |
string |
否 |
外部来源 |
external_id |
string |
否 |
外部 ID |
模块响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
模块 ID |
name |
string |
名称 |
status |
string |
状态 |
lead |
uuid/null |
负责人 |
members |
array[uuid] |
成员列表 |
total_issues |
integer |
工作项总数 |
completed_issues |
integer |
已完成数 |
started_issues |
integer |
进行中数 |
unstarted_issues |
integer |
未开始数 |
backlog_issues |
integer |
Backlog 数 |
cancelled_issues |
integer |
已取消数 |
start_date |
date/null |
开始日期 |
target_date |
date/null |
目标日期 |
添加工作项参数
| 字段 |
类型 |
必填 |
说明 |
issues |
array[uuid] |
是 |
工作项 ID 列表 |
Go 示例
// ListModules 列出模块
func (c *V1Client) ListModules(slug, projectID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/", slug, projectID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateModule 创建模块
func (c *V1Client) CreateModule(slug, projectID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/", slug, projectID)
body, status, err := c.jsonRequest("POST", path, data)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// GetModule 获取模块详情
func (c *V1Client) GetModule(slug, projectID, moduleID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// UpdateModule 更新模块
func (c *V1Client) UpdateModule(slug, projectID, moduleID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteModule 删除模块
func (c *V1Client) DeleteModule(slug, projectID, moduleID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/%s/", slug, projectID, moduleID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// AddIssuesToModule 添加工作项到模块
func (c *V1Client) AddIssuesToModule(slug, projectID, moduleID string, issueIDs []string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/%s/module-issues/", slug, projectID, moduleID)
payload := map[string]interface{}{"issues": issueIDs}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// RemoveIssueFromModule 从模块移除工作项
func (c *V1Client) RemoveIssueFromModule(slug, projectID, moduleID, issueID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/%s/module-issues/%s/", slug, projectID, moduleID, issueID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
// ArchiveModule 归档模块
func (c *V1Client) ArchiveModule(slug, projectID, moduleID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/modules/%s/archive/", slug, projectID, moduleID)
_, status, err := c.jsonRequest("POST", path, map[string]interface{}{})
if err != nil || status != 200 {
return fmt.Errorf("status: %d", status)
}
return nil
}
13. 状态 (States)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/states/ |
列出状态 |
| POST |
/api/v1/workspaces/{slug}/projects/{pid}/states/ |
创建状态 |
| GET |
.../states/{state_id}/ |
获取详情 |
| PATCH |
.../states/{state_id}/ |
更新状态 |
| DELETE |
.../states/{state_id}/ |
删除状态 |
创建状态参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
名称(项目内唯一) |
color |
string |
是 |
颜色值(如 #46A758) |
group |
string |
是 |
分组:backlog/unstarted/started/completed/cancelled(不可创建 triage) |
description |
string |
否 |
描述 |
sequence |
float |
否 |
排序(默认 65535) |
default |
boolean |
否 |
设为默认(true 时重置其他状态) |
external_source |
string |
否 |
外部来源 |
external_id |
string |
否 |
外部 ID |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
状态 ID |
name |
string |
名称 |
color |
string |
颜色 |
group |
string |
分组 |
sequence |
float |
排序 |
default |
boolean |
是否默认 |
slug |
string |
自动生成的 slug |
is_triage |
boolean |
是否 Triage 状态 |
Go 示例
// ListStates 列出状态
func (c *V1Client) ListStates(slug, projectID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/states/", slug, projectID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateState 创建状态
func (c *V1Client) CreateState(slug, projectID, name, color, group string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/states/", slug, projectID)
payload := map[string]interface{}{"name": name, "color": color, "group": group}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// UpdateState 更新状态
func (c *V1Client) UpdateState(slug, projectID, stateID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteState 删除状态
func (c *V1Client) DeleteState(slug, projectID, stateID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/states/%s/", slug, projectID, stateID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
14. 标签 (Labels)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/labels/ |
列出标签 |
| POST |
/api/v1/workspaces/{slug}/projects/{pid}/labels/ |
创建标签 |
| GET |
.../labels/{id}/ |
获取详情 |
| PATCH |
.../labels/{id}/ |
更新标签 |
| DELETE |
.../labels/{id}/ |
删除标签 |
创建标签参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
名称(项目内唯一) |
color |
string |
否 |
颜色值 |
description |
string |
否 |
描述 |
parent |
uuid |
否 |
父标签 ID |
sort_order |
float |
否 |
排序权重 |
external_source |
string |
否 |
外部来源 |
external_id |
string |
否 |
外部 ID |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
标签 ID |
name |
string |
名称 |
color |
string |
颜色 |
description |
string |
描述 |
parent |
uuid/null |
父标签 |
sort_order |
float |
排序 |
project |
uuid |
项目 ID |
created_at |
datetime |
创建时间 |
Go 示例
// ListLabels 列出标签
func (c *V1Client) ListLabels(slug, projectID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/labels/", slug, projectID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateLabel 创建标签
func (c *V1Client) CreateLabel(slug, projectID, name, color string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/labels/", slug, projectID)
payload := map[string]interface{}{"name": name, "color": color}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// UpdateLabel 更新标签
func (c *V1Client) UpdateLabel(slug, projectID, labelID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/labels/%s/", slug, projectID, labelID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteLabel 删除标签
func (c *V1Client) DeleteLabel(slug, projectID, labelID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/labels/%s/", slug, projectID, labelID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
15. 成员 (Members)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/members/ |
列出工作空间成员 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/members/ |
列出项目成员 |
| POST |
/api/v1/workspaces/{slug}/projects/{pid}/members/ |
添加项目成员 |
| GET |
.../members/{id}/ |
获取成员详情 |
| PATCH |
.../members/{id}/ |
更新角色 |
| DELETE |
.../members/{id}/ |
移除成员 |
角色值
| 值 |
名称 |
说明 |
| 20 |
Admin |
管理员 |
| 15 |
Member |
成员 |
| 5 |
Guest |
访客 |
添加项目成员参数
| 字段 |
类型 |
必填 |
说明 |
member |
uuid |
是 |
用户 ID(须为工作空间成员) |
role |
integer |
是 |
角色:20/15/5 |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
成员记录 ID |
member |
uuid |
用户 ID |
role |
integer |
角色值 |
Go 示例
// ListWorkspaceMembers 列出工作空间成员
func (c *V1Client) ListWorkspaceMembers(slug string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/members/", slug)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// ListProjectMembers 列出项目成员
func (c *V1Client) ListProjectMembers(slug, projectID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/members/", slug, projectID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// AddProjectMember 添加项目成员
func (c *V1Client) AddProjectMember(slug, projectID, memberID string, role int) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/members/", slug, projectID)
payload := map[string]interface{}{"member": memberID, "role": role}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// UpdateProjectMemberRole 更新成员角色
func (c *V1Client) UpdateProjectMemberRole(slug, projectID, memberRecordID string, role int) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberRecordID)
payload := map[string]interface{}{"role": role}
body, status, err := c.jsonRequest("PATCH", path, payload)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// RemoveProjectMember 移除项目成员
func (c *V1Client) RemoveProjectMember(slug, projectID, memberRecordID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/members/%s/", slug, projectID, memberRecordID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
16. 收件箱/Intake
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/projects/{pid}/intake-issues/ |
列出收件箱工作项 |
| POST |
/api/v1/workspaces/{slug}/projects/{pid}/intake-issues/ |
创建收件箱工作项 |
| GET |
.../intake-issues/{issue_id}/ |
获取详情 |
| PATCH |
.../intake-issues/{issue_id}/ |
更新 |
| DELETE |
.../intake-issues/{issue_id}/ |
删除 |
创建收件箱工作项参数
请求体结构:{"issue": {...}}
| 字段 |
类型 |
必填 |
说明 |
issue.name |
string |
是 |
名称 |
issue.description_html |
string |
否 |
HTML 描述 |
issue.description_json |
object |
否 |
JSON 描述(也接受 description 字段名) |
issue.priority |
string |
否 |
urgent/high/medium/low/none(默认 none) |
注意: API v1 的 Intake 接口仅接受 name、description、priority。
如需完整字段(assignees、labels 等),请使用 Web API(Session 认证)。
更新收件箱工作项参数
| 字段 |
类型 |
必填 |
说明 |
status |
integer |
否 |
-2=待处理, -1=拒绝, 0=暂缓, 1=接受, 2=重复 |
snoozed_till |
datetime |
否 |
暂缓截止时间 |
duplicate_to |
uuid |
否 |
重复目标 |
source |
string |
否 |
来源 |
source_email |
string |
否 |
来源邮箱 |
issue |
object |
否 |
更新工作项内容(name, description_html, priority) |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
Intake 记录 ID |
status |
integer |
状态 |
source |
string |
来源 |
snoozed_till |
datetime/null |
暂缓时间 |
duplicate_to |
uuid/null |
重复目标 |
issue |
uuid |
工作项 ID |
issue_detail |
object |
工作项详情(展开) |
inbox |
uuid |
收件箱 ID |
created_at |
datetime |
创建时间 |
Go 示例
// ListIntakeIssues 列出收件箱工作项
func (c *V1Client) ListIntakeIssues(slug, projectID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateIntakeIssue 创建收件箱工作项
func (c *V1Client) CreateIntakeIssue(slug, projectID, name, descHTML, priority string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/intake-issues/", slug, projectID)
payload := map[string]interface{}{
"issue": map[string]interface{}{
"name": name,
"description_html": descHTML,
"priority": priority,
},
}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// GetIntakeIssue 获取收件箱工作项详情
func (c *V1Client) GetIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// UpdateIntakeIssue 更新收件箱工作项
func (c *V1Client) UpdateIntakeIssue(slug, projectID, issueID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// AcceptIntakeIssue 接受收件箱工作项
func (c *V1Client) AcceptIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
return c.UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{"status": 1})
}
// RejectIntakeIssue 拒绝收件箱工作项
func (c *V1Client) RejectIntakeIssue(slug, projectID, issueID string) ([]byte, error) {
return c.UpdateIntakeIssue(slug, projectID, issueID, map[string]interface{}{"status": -1})
}
// DeleteIntakeIssue 删除收件箱工作项
func (c *V1Client) DeleteIntakeIssue(slug, projectID, issueID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/projects/%s/intake-issues/%s/", slug, projectID, issueID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
17. 资源 (Assets)
端点列表
| 方法 |
路径 |
说明 |
| POST |
/api/v1/assets/user-assets/ |
上传用户资源 |
| PATCH |
/api/v1/assets/user-assets/{asset_id}/ |
更新用户资源 |
| DELETE |
/api/v1/assets/user-assets/{asset_id}/ |
删除用户资源 |
| POST |
/api/v1/assets/user-assets/server/ |
服务端直传用户资源 |
| POST |
/api/v1/workspaces/{slug}/assets/ |
上传工作空间资源 |
| GET |
/api/v1/workspaces/{slug}/assets/{asset_id}/ |
获取资源 |
| PATCH |
/api/v1/workspaces/{slug}/assets/{asset_id}/ |
更新资源 |
上传用户资源参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
文件名 |
type |
string |
是 |
image/jpeg/image/png/image/webp/image/jpg/image/gif |
size |
integer |
是 |
字节数 |
entity_type |
string |
是 |
USER_AVATAR / USER_COVER |
上传工作空间资源参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
是 |
文件名 |
type |
string |
否 |
MIME 类型 |
size |
integer |
是 |
字节数 |
project_id |
uuid |
否 |
关联项目 |
external_id |
string |
否 |
外部 ID |
external_source |
string |
否 |
外部来源 |
资源上传响应
| 字段 |
类型 |
说明 |
upload_data.url |
string |
S3 上传 URL |
upload_data.fields |
object |
表单字段 |
asset_id |
uuid |
资源 ID |
asset_url |
string |
访问 URL |
Go 示例
// UploadUserAsset 上传用户资源
func (c *V1Client) UploadUserAsset(name, mimeType string, size int64, entityType string) ([]byte, error) {
payload := map[string]interface{}{
"name": name,
"type": mimeType,
"size": size,
"entity_type": entityType,
}
body, status, err := c.jsonRequest("POST", "/api/v1/assets/user-assets/", payload)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// UploadWorkspaceAsset 上传工作空间资源
func (c *V1Client) UploadWorkspaceAsset(slug string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/assets/", slug)
body, status, err := c.jsonRequest("POST", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
18. 邀请 (Invitations)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/invitations/ |
列出邀请 |
| POST |
/api/v1/workspaces/{slug}/invitations/ |
创建邀请 |
| GET |
.../invitations/{id}/ |
获取邀请详情 |
| PATCH |
.../invitations/{id}/ |
更新邀请 |
| DELETE |
.../invitations/{id}/ |
撤销邀请 |
创建邀请参数
| 字段 |
类型 |
必填 |
说明 |
email |
string |
是 |
邮箱(有效格式,同工作空间不可重复) |
role |
integer |
是 |
角色:20=管理员, 15=成员, 5=访客 |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
邀请 ID |
email |
string |
邮箱 |
role |
integer |
角色 |
accepted |
boolean |
是否已接受 |
responded_at |
datetime/null |
响应时间 |
created_at |
datetime |
创建时间 |
Go 示例
// ListInvitations 列出邀请
func (c *V1Client) ListInvitations(slug string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/invitations/", slug)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateInvitation 创建邀请
func (c *V1Client) CreateInvitation(slug, email string, role int) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/invitations/", slug)
payload := map[string]interface{}{"email": email, "role": role}
body, status, err := c.jsonRequest("POST", path, payload)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteInvitation 撤销邀请
func (c *V1Client) DeleteInvitation(slug, invitationID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/invitations/%s/", slug, invitationID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
19. 便签 (Stickies)
端点列表
| 方法 |
路径 |
说明 |
| GET |
/api/v1/workspaces/{slug}/stickies/ |
列出便签 |
| POST |
/api/v1/workspaces/{slug}/stickies/ |
创建便签 |
| GET |
.../stickies/{id}/ |
获取便签 |
| PATCH |
.../stickies/{id}/ |
更新便签 |
| DELETE |
.../stickies/{id}/ |
删除便签 |
创建便签参数
| 字段 |
类型 |
必填 |
说明 |
name |
string |
否 |
标题 |
description_html |
string |
否 |
HTML 内容 |
color |
string |
否 |
前景色 |
background_color |
string |
否 |
背景色 |
sort_order |
float |
否 |
排序权重 |
响应字段
| 字段 |
类型 |
说明 |
id |
uuid |
便签 ID |
name |
string/null |
标题 |
description_html |
string |
HTML 内容 |
color |
string/null |
前景色 |
background_color |
string/null |
背景色 |
sort_order |
float |
排序 |
workspace |
uuid |
工作空间 ID |
owner |
uuid |
所有者 |
created_at |
datetime |
创建时间 |
Go 示例
// ListStickies 列出便签
func (c *V1Client) ListStickies(slug string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/stickies/", slug)
body, status, err := c.jsonRequest("GET", path, nil)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d", status)
}
return body, nil
}
// CreateSticky 创建便签
func (c *V1Client) CreateSticky(slug string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/stickies/", slug)
body, status, err := c.jsonRequest("POST", path, data)
if err != nil || status != 201 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// UpdateSticky 更新便签
func (c *V1Client) UpdateSticky(slug, stickyID string, data map[string]interface{}) ([]byte, error) {
path := fmt.Sprintf("/api/v1/workspaces/%s/stickies/%s/", slug, stickyID)
body, status, err := c.jsonRequest("PATCH", path, data)
if err != nil || status != 200 {
return nil, fmt.Errorf("status: %d, body: %s", status, string(body))
}
return body, nil
}
// DeleteSticky 删除便签
func (c *V1Client) DeleteSticky(slug, stickyID string) error {
path := fmt.Sprintf("/api/v1/workspaces/%s/stickies/%s/", slug, stickyID)
_, status, err := c.jsonRequest("DELETE", path, nil)
if err != nil || status != 204 {
return fmt.Errorf("status: %d", status)
}
return nil
}
20. 附录
A. 完整使用示例
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
client := NewV1Client("http://localhost:8000", "your-api-key")
slug := "my-workspace"
projectID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
// 获取当前用户
me, _ := client.GetMe()
fmt.Println("当前用户:", string(me))
// 列出项目
projects, _ := client.ListProjects(slug)
fmt.Println("项目列表:", string(projects))
// 创建工作项
issue, _ := client.CreateWorkItem(slug, projectID, map[string]interface{}{
"name": "修复登录页面 Bug",
"description_html": "<p>用户反馈登录页面在手机端无法正常显示</p>",
"priority": "high",
"assignees": []string{},
"labels": []string{},
})
fmt.Println("创建工作项:", string(issue))
// 解析工作项 ID
var issueResp struct {
ID string `json:"id"`
}
json.Unmarshal(issue, &issueResp)
// 添加评论
comment, _ := client.CreateWorkItemComment(slug, projectID, issueResp.ID,
"<p>已确认复现,在 iOS Safari 上出现</p>")
fmt.Println("添加评论:", string(comment))
// 上传附件
assetID, _ := client.UploadAttachment(slug, projectID, issueResp.ID, "./screenshot.png", "image/png")
fmt.Println("附件上传成功:", assetID)
// 搜索工作项
results, _ := client.SearchWorkItems(slug, "登录", 5, projectID)
fmt.Println("搜索结果:", string(results))
}
B. 错误码参考
| 状态码 |
含义 |
处理方式 |
| 200 |
成功 |
正常解析 |
| 201 |
创建成功 |
正常解析 |
| 204 |
无内容(删除/确认成功) |
无需解析 Body |
| 302 |
重定向(附件下载) |
跟随重定向获取文件 |
| 400 |
参数错误 |
检查请求体字段 |
| 401 |
未认证(API Key 无效或缺失) |
检查 X-Api-Key |
| 403 |
权限不足 |
检查用户角色 |
| 404 |
资源不存在 |
检查路径参数 |
| 409 |
冲突(名称重复、external_id 重复) |
检查唯一性 |
C. expand 参数说明
工作项接口支持 expand 查询参数:
| 值 |
效果 |
assignees |
展开为完整用户对象(id, first_name, last_name, email, avatar_url, display_name) |
labels |
展开为完整标签对象(id, name, color) |
未展开时返回 UUID 数组。
示例:GET /api/v1/.../work-items/?expand=assignees,labels
D. 外部集成字段
多数资源支持 external_source + external_id:
| 用途 |
说明 |
| 导入标识 |
记录资源在外部系统(如 GitHub/Jira)中的 ID |
| Upsert |
PUT 接口通过这两个字段识别已有资源 |
| 唯一约束 |
同一 (project, external_source, external_id) 不可重复 |
E. 旧版路径兼容
| 旧路径 (仍可用) |
新路径 (推荐) |
.../projects/{pid}/issues/ |
.../projects/{pid}/work-items/ |
.../issues/{id}/issue-attachments/ |
.../work-items/{id}/attachments/ |
.../issues/search/ |
.../work-items/search/ |
.../issues/{proj}-{seq}/ |
.../work-items/{proj}-{seq}/ |
F. 所有端点快速参考
| 模块 |
列出 |
创建 |
详情 |
更新 |
删除 |
| 用户 |
- |
- |
GET /users/me/ |
- |
- |
| 项目 |
GET .../projects/ |
POST .../projects/ |
GET .../projects/{id}/ |
PATCH .../projects/{id}/ |
DELETE .../projects/{id}/ |
| 工作项 |
GET .../work-items/ |
POST .../work-items/ |
GET .../work-items/{id}/ |
PATCH .../work-items/{id}/ |
DELETE .../work-items/{id}/ |
| 链接 |
GET .../links/ |
POST .../links/ |
GET .../links/{id}/ |
PATCH .../links/{id}/ |
DELETE .../links/{id}/ |
| 评论 |
GET .../comments/ |
POST .../comments/ |
GET .../comments/{id}/ |
PATCH .../comments/{id}/ |
DELETE .../comments/{id}/ |
| 活动 |
GET .../activities/ |
- |
GET .../activities/{id}/ |
- |
- |
| 附件 |
GET .../attachments/ |
POST .../attachments/ |
GET .../attachments/{id}/ |
PATCH .../attachments/{id}/ |
DELETE .../attachments/{id}/ |
| 关系 |
GET .../relations/ |
POST .../relations/ |
- |
- |
- |
| 搜索 |
GET .../search/ |
- |
- |
- |
- |
| 迭代 |
GET .../cycles/ |
POST .../cycles/ |
GET .../cycles/{id}/ |
PATCH .../cycles/{id}/ |
DELETE .../cycles/{id}/ |
| 模块 |
GET .../modules/ |
POST .../modules/ |
GET .../modules/{id}/ |
PATCH .../modules/{id}/ |
DELETE .../modules/{id}/ |
| 状态 |
GET .../states/ |
POST .../states/ |
GET .../states/{id}/ |
PATCH .../states/{id}/ |
DELETE .../states/{id}/ |
| 标签 |
GET .../labels/ |
POST .../labels/ |
GET .../labels/{id}/ |
PATCH .../labels/{id}/ |
DELETE .../labels/{id}/ |
| 成员 |
GET .../members/ |
POST .../members/ |
GET .../members/{id}/ |
PATCH .../members/{id}/ |
DELETE .../members/{id}/ |
| 收件箱 |
GET .../intake-issues/ |
POST .../intake-issues/ |
GET .../intake-issues/{id}/ |
PATCH .../intake-issues/{id}/ |
DELETE .../intake-issues/{id}/ |
| 邀请 |
GET .../invitations/ |
POST .../invitations/ |
GET .../invitations/{id}/ |
PATCH .../invitations/{id}/ |
DELETE .../invitations/{id}/ |
| 便签 |
GET .../stickies/ |
POST .../stickies/ |
GET .../stickies/{id}/ |
PATCH .../stickies/{id}/ |
DELETE .../stickies/{id}/ |
G. Swagger 交互文档
若服务端启用了 ENABLE_DRF_SPECTACULAR:
| 文档 |
URL |
| OpenAPI Schema |
GET /api/schema/ |
| Swagger UI |
GET /api/schema/swagger-ui/ |
| ReDoc |
GET /api/schema/redoc/ |