PJT AI REST API
用于将 PJT AI 数据与外部系统集成的标准 REST API。所有请求与响应均为 JSON,Base URL 为 https://api.pjt.ai/api/external/v1。
- 认证 — X-API-Key 请求头(账号 API 密钥,pjt_ 前缀)
- 密钥范围 — read(只读,默认)/ write。POST·PUT·DELETE 需要 write 范围的密钥 — 否则返回 403 SCOPE_FORBIDDEN
- 请求限制 — 每密钥 60 次/分钟 + 每月 10,000 次(Enterprise 可协商)
- 响应码 — 2xx 成功、4xx 客户端错误、5xx 服务器错误
- 日期 — 所有时间戳均为 ISO 8601(UTC)
认证
每个请求都需要 X-API-Key: <API_KEY> 请求头。密钥在 账号设置 > API 密钥 中签发(仅在创建时显示一次)。
/me我的 API Key(连接测试)
返回 API Key 的有效性与能力(权限范围)。因为是 GET,无需 write 权限——只读 Key 也能查看自身能力。Key 缺失/无效/过期返回 401,这本身就是连接测试结果。用响应中的 canProvision/canWrite/canRead 提前判断能力(例如 canProvision=false 时在预配调用前提示「此 Key 不可用」→避免误报绿灯)。与基础设施 liveness(服务器是否存活)无关。
message 字段按请求语言(?lang 或 Accept-Language)以 8 种语言返回。
UNAUTHORIZEDX-API-Key 缺失·无效·过期——这个 401 本身就是「未连接」信号(200 表示已连接)。{
"timestamp": "2026-07-14T09:00:00Z",
"message": "API key is missing, invalid, or expired."
}/provisioning/workspaces仅 Enterprise工作区预配(幂等)
使用 provisioning 权限范围的 API Key 一次性预配租户与工作区。提供 tenantId 时使用该租户(调用 Key 的账号须为 OWNER/ADMIN);否则用 tenantExternalRef 在账号命名空间内幂等查找/创建(两者都无则 400)。workspaceExternalRef 为必填幂等键——重复请求返回已有资源(200)而非新建(201),通过 createdTenant/createdWorkspace 区分。权限层级 provisioning ⊃ write(无需单独 write)。⚠️ businessType 目前不影响结果(仅审计);defaultLocale/accentHue/template 为弱校验(请发送准确取值·未知 template 被忽略);响应 tenant.myRole 可能为 null——不要用于鉴权。
此端点仅在 Enterprise 计划中可用。签发和使用预配权限的 Key 包含在 Enterprise 合约中。
| 名称 | 类型 | 说明 |
|---|---|---|
| tenantId | integer | 已有租户 ID(Long, 可选)。提供时忽略 tenant·tenantExternalRef。调用 Key 的账号须为该租户的 OWNER/ADMIN |
| tenantExternalRef | string | 新租户幂等键(≤100, 当无 tenantId 时使用)。相同(账号, 值)复用已有租户 |
| tenant.name | string | 新租户名称(≤100, 省略时使用 workspace.name) |
| tenant.slug | string | 新租户 slug(≤50, 全局唯一·未指定时自动生成) |
| tenant.description | string | 新租户描述(≤500) |
| workspaceExternalRefREQUIRED | string | 工作区幂等键(≤100, 必填)。相同(tenantId, 值)复用已有工作区 |
| workspace.nameREQUIRED | string | 工作区名称(≤100, 必填) |
| workspace.slug | string | 工作区 slug(≤50, 租户内唯一·未指定时由名称派生) |
| workspace.description | string | 工作区描述(≤500) |
| workspace.defaultLocale | string | 默认语言 — ko|en|ja|zh|zh-TW|es|vi|th(不校验·请发送准确值) |
| workspace.accentHue | integer | 强调色相(0~360, 不校验) |
| workspace.template | string | 预设 — BLANK|DEV|AGENCY|OPS(未知值静默忽略) |
| workspace.businessType | string | 行业类型(前瞻性)——仅审计·不反映到所建工作区 |
message 字段按请求语言(?lang 或 Accept-Language)以 8 种语言返回。
SCOPE_FORBIDDEN使用只读 Key(无 write 权限)尝试写操作——被网关过滤器拦截{
"error": "SCOPE_FORBIDDEN",
"message": "This API key is read-only. A 'write' scope is required for this operation."
}FORBIDDEN缺少 provisioning 权限的 Key——此端点必须 provisioning(仅 write 不够){
"timestamp": "2026-07-14T09:00:00Z",
"message": "The 'provisioning' scope is required."
}FORBIDDEN指定已有 tenantId 时,调用账号不是该租户的 OWNER/ADMIN(或不是成员){
"timestamp": "2026-07-14T09:00:00Z",
"message": "OWNER or ADMIN role on the tenant is required."
}BAD_REQUESTtenantId 与 tenantExternalRef 都未提供{
"timestamp": "2026-07-14T09:00:00Z",
"message": "One of tenantId or tenantExternalRef is required."
}VALIDATION_ERROR校验失败(必填/长度等)——errors[] 内为字段级详情{
"timestamp": "2026-07-14T09:00:00Z",
"code": "VALIDATION_ERROR",
"message": "workspaceExternalRef: must not be blank",
"errors": [
{ "field": "workspaceExternalRef", "code": "NotBlank", "message": "must not be blank" }
]
}UNAUTHORIZEDX-API-Key 缺失·无效·过期(External 通用){
"timestamp": "2026-07-14T09:00:00Z",
"message": "API key is missing, invalid, or expired."
}/tenants我的租户
返回 API Key 账号所属的全部租户(租户直接成员 + 仅通过工作区可达的组织=MEMBER)。因为是 GET,具备 read 权限即可。请依据每项的 myRole(OWNER|ADMIN|MEMBER)判定权限——创建工作区(POST /workspaces)仅在 myRole ∈ [OWNER, ADMIN] 的租户上可行,因此 SI 客户端从此列表中挑选 OWNER/ADMIN 项的 id 作为 tenantId 使用。
| 名称 | 类型 | 说明 |
|---|---|---|
| id | Long | 作为 POST /workspaces 的 tenantId 使用的租户 ID |
| myRole | String (enum) | OWNER | ADMIN | MEMBER(未加入则为 null)。仅可在您为 OWNER/ADMIN 的租户中创建工作区(POST /workspaces) |
| status | String (enum) | 租户状态 — ACTIVE | ARCHIVED | DELETE(Tenant.Status) |
| slug | String | 组织 slug(用于路由) |
| mfaSetupRequired | boolean | 若组织强制启用 2FA 且尚未设置则为 true |
| slugSet | boolean | 用户是否手动设置了 slug(自动生成的 org-xxxx 为 false) |
message 字段按请求语言(?lang 或 Accept-Language)以 8 种语言返回。
UNAUTHORIZEDX-API-Key 缺失·无效·过期(External 通用){
"timestamp": "2026-07-14T20:00:00Z",
"message": "API key is missing, invalid, or expired."
}/tenants/{tenantId}单个租户
以与列表相同的 shape 返回单个租户。若不属于该租户则访问校验失败(403);不存在的租户返回 400 tenant.not_found。具备 read 权限即可。
| 名称 | 类型 | 说明 |
|---|---|---|
| tenantIdREQUIRED | integer | 要查询的租户 ID(Long, 必填) |
message 字段按请求语言(?lang 或 Accept-Language)以 8 种语言返回。
FORBIDDEN调用账号不是该租户的成员(tenant 访问校验失败){
"timestamp": "2026-07-14T20:00:00Z",
"message": "테넌트 멤버가 아닙니다"
}BAD_REQUEST不存在的租户(tenant.not_found){
"timestamp": "2026-07-14T20:00:00Z",
"message": "테넌트를 찾을 수 없습니다"
}UNAUTHORIZEDX-API-Key 缺失·无效·过期(External 通用){
"timestamp": "2026-07-14T20:00:00Z",
"message": "API key is missing, invalid, or expired."
}/workspaces创建工作区(已有租户)
与预配分离的轻量路径——不创建租户,仅在已有租户(tenantId)下创建工作区。具备 write 权限即可(无需 provisioning·只读 Key 返回 403 SCOPE_FORBIDDEN)。调用 Key 的账号须为该租户的 OWNER/ADMIN。传入 externalRef 则幂等——以相同(tenantId, externalRef)重复请求时返回已有工作区(200)而非新建(201);省略则每次调用都新建(为安全重试建议传入)。所建为团队工作区(非个人)。⚠️ defaultLocale/accentHue/template 为弱校验(取值原样通过·未知 template 被忽略)。若需自动创建租户,请使用预配(POST /provisioning/workspaces)。
| 名称 | 类型 | 说明 |
|---|---|---|
| tenantIdREQUIRED | integer | 在此租户下创建(Long, 必填)。调用 Key 的账号须为该租户的 OWNER/ADMIN |
| externalRef | string | 幂等键(≤100, 可选)。以相同(tenantId, 值)重复请求时返回已有工作区。省略则每次调用都新建 |
| nameREQUIRED | string | 工作区名称(≤100, 必填) |
| slug | string | 工作区 slug(≤50, 租户内唯一·未指定时由名称派生) |
| description | string | 工作区描述(≤500) |
| defaultLocale | string | 默认语言 — ko|en|ja|zh|zh-TW|es|vi|th(不校验直接通过·请发送准确值) |
| accentHue | integer | 强调色相(0~360, 不校验直接通过) |
| template | string | 预设 — BLANK|DEV|AGENCY|OPS(未知值静默忽略) |
message 字段按请求语言(?lang 或 Accept-Language)以 8 种语言返回。
SCOPE_FORBIDDEN使用只读 Key(无 write 权限)尝试写操作——被网关过滤器拦截{
"error": "SCOPE_FORBIDDEN",
"message": "This API key is read-only. A 'write' scope is required for this operation."
}FORBIDDEN调用账号不是 tenantId 租户的 OWNER/ADMIN(tenant.admin_required){
"timestamp": "2026-07-14T20:00:00Z",
"message": "관리자 권한이 필요합니다"
}FORBIDDEN调用账号不是该租户的成员(含不存在的租户,tenant.not_member){
"timestamp": "2026-07-14T20:00:00Z",
"message": "테넌트 멤버가 아닙니다"
}VALIDATION_ERROR校验失败(缺少 tenantId·name 等)——errors[] 内为字段级详情{
"timestamp": "2026-07-14T20:00:00Z",
"code": "VALIDATION_ERROR",
"message": "tenantId: must not be null",
"errors": [
{ "field": "tenantId", "code": "NotNull", "message": "must not be null" }
]
}UNAUTHORIZEDX-API-Key 缺失·无效·过期(External 通用){
"timestamp": "2026-07-14T20:00:00Z",
"message": "API key is missing, invalid, or expired."
}/workspaces/slug/available检查工作区 slug 是否重复
检查工作区 slug 在租户内是否可用(未被占用)。由于 slug 按 (tenantId, slug) 唯一,因此与创建工作区一样需要 tenantId。slug 会按照保存规则同样进行归一化(去除首尾空格、转小写)后再比较,空值或重复返回 available:false。授权使用与创建工作区相同的门控——调用方必须是该租户的 OWNER/ADMIN(requireTenantAdmin),非成员和非管理员返回 403,且不暴露租户是否存在。由于是 GET,read 权限范围即可。请在创建工作区(POST /workspaces)之前用此 API 预先检查 slug 可用性。
| 名称 | 类型 | 说明 |
|---|---|---|
| tenantIdREQUIRED | integer | 在此租户范围内做重复检查(Long,必填 query)。slug 的唯一性范围。调用方必须是此租户的 OWNER/ADMIN |
| slugREQUIRED | string | 要检查的工作区 slug(query,必填)。归一化(trim、小写)后比较 |
| 名称 | 类型 | 说明 |
|---|---|---|
| available | boolean | true=可用(未被占用),false=已被占用或为空 slug |
message 字段按请求语言(?lang 或 Accept-Language)以 8 种语言返回。
FORBIDDEN调用方不是 tenantId 租户的 OWNER/ADMIN(含非成员)——被 requireTenantAdmin 阻止(不暴露租户是否存在){
"timestamp": "2026-07-14T20:00:00Z",
"message": "관리자 권한이 필요합니다"
}UNAUTHORIZEDX-API-Key 缺失、无效或已过期(External 通用){
"timestamp": "2026-07-14T20:00:00Z",
"message": "API key is missing, invalid, or expired."
}/workspaces工作区列表
返回 API 密钥账号可访问的工作区列表。
/workspaces/{workspaceId}查询工作区
查询单个工作区。
| 名称 | 类型 | 说明 |
|---|---|---|
| workspaceIdREQUIRED | integer | 工作区 ID |
/projects项目列表(按租户与工作区)
分页返回指定租户与工作区的项目列表。tenantId 与 workspaceId 为必填,可用 status 进一步过滤。
| 名称 | 类型 | 说明 |
|---|---|---|
| tenantIdREQUIRED | integer | 租户 ID(必填) |
| workspaceIdREQUIRED | integer | 工作区 ID(必填) |
| status | enum | 项目状态精确匹配(PLANNING、ESTIMATING、WAITING、IN_PROGRESS、ON_HOLD、COMPLETED、CANCELLED)。指定时也包含已取消/已归档。 |
| page | integer | 页码(从 0 开始) |
| size | integer | 每页大小(默认 20,上限 100) |
message 字段按请求语言(?lang 或 Accept-Language)以 8 种语言返回。
BAD_REQUEST工作区不存在或不属于该租户(跨租户以 not_found 处理,不暴露是否存在)。{
"timestamp": "2026-07-14T09:00:00Z",
"message": "워크스페이스를 찾을 수 없습니다"
}BAD_REQUESTstatus 传入了不支持的值。{
"timestamp": "2026-07-14T09:00:00Z",
"message": "유효하지 않은 프로젝트 상태 값입니다"
}FORBIDDEN无该工作区访问权限(非 ACTIVE 成员且非已接受的客户/合作方 — 无租户成员身份回退)。{
"timestamp": "2026-07-14T09:00:00Z",
"message": "워크스페이스 접근 권한이 없습니다"
}UNAUTHORIZEDX-API-Key 缺失/无效/过期。{
"timestamp": "2026-07-14T09:00:00Z",
"message": "API key is missing, invalid, or expired."
}/workspaces/{workspaceId}/projects项目列表
返回工作区的活跃项目列表(不含已取消/已归档)。
| 名称 | 类型 | 说明 |
|---|---|---|
| workspaceIdREQUIRED | integer | 工作区 ID |
/projects创建项目(幂等)
创建项目。传入 externalRef 时为幂等操作 — 使用相同 (workspaceId, externalRef) 重复请求时不会新建,而是以 200 返回既有项目(首次创建为 201)。
| 名称 | 类型 | 说明 |
|---|---|---|
| workspaceIdREQUIRED | integer | 工作区 ID |
| nameREQUIRED | string | 项目名称 |
| externalRef | string | 幂等键 — 使用相同值重复请求时不会新建,而是以 200 返回既有项目 |
| code | string | 项目代码(任务键前缀)。省略时根据名称自动生成 |
| description | string | 描述 |
| clientIds | array | 客户 ID 数组 |
| managerId | integer | 负责经理的账号 ID |
| startDate | date | 开始日期 — ISO 8601 (YYYY-MM-DD) |
| endDate | date | 结束日期 — ISO 8601 (YYYY-MM-DD) |
| budget | integer | 预算 |
/projects/{projectId}项目详情 / 进度
返回项目详情,包含状态、进度(%)、计划/实际日程和最后修改时间。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
/projects/{projectId}更新项目
仅更新传入的字段(部分更新)。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
| 名称 | 类型 | 说明 |
|---|---|---|
| name | string | 项目名称 |
| status | enum | 状态 — PLANNING·ESTIMATING·WAITING·IN_PROGRESS·ON_HOLD·COMPLETED·CANCELLED |
| progressRate | integer | 进度(%) 0~100 |
| startDate | date | 开始日期 — ISO 8601 (YYYY-MM-DD) |
| endDate | date | 结束日期 — ISO 8601 (YYYY-MM-DD) |
/projects/{projectId}删除项目
删除项目。成功时返回 204 No Content(无响应体)。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
/projects/{projectId}/timeline时间线(里程碑)
项目里程碑列表 — 名称、状态(PLANNED/IN_PROGRESS/COMPLETED)、截止日期、完成日期和进度。按 sortOrder 排序返回。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
/projects/{projectId}/activities活动记录
按最新顺序返回项目的任务变更事件 — 类型、消息、操作者和时间。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
| 名称 | 类型 | 说明 |
|---|---|---|
| page | integer | 页码(从 0 开始,默认 0) |
| size | integer | 每页数量(默认 50,最大 200) |
/projects/{projectId}/files文件列表
合并返回项目内任务和评论的附件,按最新排序。fileUrl 为不过期的静态 URL。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
| 名称 | 类型 | 说明 |
|---|---|---|
| limit | integer | 最大数量(默认 100,最大 500) |
/projects/{projectId}/tasks任务列表
返回项目的任务列表。需要分页时请使用 /tasks/paged。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
| 名称 | 类型 | 说明 |
|---|---|---|
| sortBy | string | 排序字段(默认 createdAt) |
/tasks创建任务
创建任务。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | integer | 项目 ID(数字) |
| titleREQUIRED | string | 标题 |
| description | string | 描述 |
| assigneeId | integer | 负责人账号 ID |
| status | enum | 状态 — PENDING·TODO·IN_PROGRESS·IN_REVIEW·BLOCKED·COMPLETED·CANCELLED |
| priority | enum | 优先级 — URGENT·HIGH·MEDIUM·LOW |
| dueDate | date | 截止日期 — ISO 8601 |
| milestoneId | integer | 要关联的里程碑 ID |
/tasks/{taskId}查询任务
查询单个任务。
| 名称 | 类型 | 说明 |
|---|---|---|
| taskIdREQUIRED | integer | 任务 ID |
/tasks/{taskId}更新任务
仅更新传入的字段。
| 名称 | 类型 | 说明 |
|---|---|---|
| taskIdREQUIRED | integer | 任务 ID |
| 名称 | 类型 | 说明 |
|---|---|---|
| title | string | 标题 |
| status | enum | 状态 — PENDING·TODO·IN_PROGRESS·IN_REVIEW·BLOCKED·COMPLETED·CANCELLED |
| priority | enum | 优先级 — URGENT·HIGH·MEDIUM·LOW |
| dueDate | date | 截止日期 — ISO 8601 |
/tasks/{taskId}删除任务
删除任务。成功时返回 204 No Content。
| 名称 | 类型 | 说明 |
|---|---|---|
| taskIdREQUIRED | integer | 任务 ID |
/tasks/assigned我的任务
返回分配给 API 密钥账号的任务列表。
/projects/{projectId}/documents文档列表
返回项目的文档列表。需要分页时请使用 /documents/paged。
| 名称 | 类型 | 说明 |
|---|---|---|
| projectIdREQUIRED | string | 项目 ID — 数字 ID 或以 p_ 开头的 publicId |
/documents/{documentId}查询文档
查询单个文档(含正文)。
| 名称 | 类型 | 说明 |
|---|---|---|
| documentIdREQUIRED | integer | 文档 ID |
/documents创建文档
创建文档。
| 名称 | 类型 | 说明 |
|---|---|---|
| workspaceIdREQUIRED | integer | 工作区 ID |
| titleREQUIRED | string | 标题 |
| documentTypeREQUIRED | enum | 文档类型(如 REQUIREMENT, MEETING_NOTE) |
| projectId | integer | 项目 ID(数字) |
| content | string | 文档正文 |
| visibility | enum | 可见范围 — PUBLIC·TEAM·PRIVATE |
/documents/{documentId}更新文档
仅更新传入的字段。
| 名称 | 类型 | 说明 |
|---|---|---|
| documentIdREQUIRED | integer | 文档 ID |
| 名称 | 类型 | 说明 |
|---|---|---|
| title | string | 标题 |
| content | string | 文档正文 |
| visibility | enum | 可见范围 — PUBLIC·TEAM·PRIVATE |
/documents/{documentId}删除文档
删除文档。成功时返回 204 No Content。
| 名称 | 类型 | 说明 |
|---|---|---|
| documentIdREQUIRED | integer | 文档 ID |
错误码
所有错误响应都包含 error.code 和 error.message。
| 代码 | 名称 | 说明 | 处理 |
|---|---|---|---|
| 400 | Bad Request | 请求体无效。 | 校验请求 body |
| 401 | Unauthorized | API 密钥错误或缺失。 | 重新确认 API 密钥 |
| 403 | Forbidden | 没有访问该资源的权限。 使用只读密钥发送写入请求时会返回 SCOPE_FORBIDDEN。 | 检查角色/作用域 |
| 404 | Not Found | 未找到请求的资源。 | 重新确认 ID |
| 429 | Rate Limited | 已超过请求限制。 | 参考 Retry-After 头并应用退避 |
| 500 | Server Error | 服务器处理时发生错误。 | 5 分钟后重试,查看 status.pjt.ai |