跳转到内容

API 规范

完整的 REST 端点参考。关于教程和示例,请参阅 API 指南

凡是一个项目成员能在 Web UI 中做的事,这里都有——SPA 消费的正是这同一套 API。需要所有者角色的操作标注为 (manager);其余的只需项目成员资格(或者,对于标注为 (viewer) 的读取,任何访问级别即可)。下面的表格列出了服务器挂载的每一个路由组;那些仅用一行概括的,在实时的 openapi.json 中有完整描述。

https://eastagiletracker.com/api/v1

https://api.eastagiletracker.com/api/v1 提供完全相同的 API。所有请求和响应都是 JSON,少数接受 multipart 的文件上传端点除外。

有两组端点位于上一层,挂在 /api 而非 /api/v1 之下:认证界面(/api/auth/*)和公共表单(/api/contact/api/feedback)。它们的 /api/v1/… 写法会返回 404

每个经过认证的请求都通过以下之一发送一个凭据:

  • X-TrackerToken: <key>
  • Authorization: Bearer <key>

用户密钥以 ea_user_ 开头,智能体密钥以 ea_agent_ 开头,MCP 访问令牌以 ea_mcp_ 开头。见 API 指南 → 三种凭据

无需认证的端点:/openapi.json/docs/api/auth/* 端点,以及参考数据查询(/story_types/story_states/effort_scales/priority_scales)。/meta需要认证的——任何有效密钥都行,但它不限于项目范围(一个绑定到项目的智能体密钥也能访问它)。

四个级别门控着项目范围内的端点:

LevelWho passesTypical operations
public viewer任何人,在可见性为公开的项目上看板的读取:故事、迭代、搜索、故事和 Epic 活动(行为者详情已脱敏)
viewerviewer、member、manager读取(列出/获取故事、搜索、指标、导出格式列表)
membermember、manager所有工作项写入(故事、任务、评论……)、事件流
manager仅 manager项目设置、成员管理、智能体密钥、删除、导入、导出下载、备份、审计日志

智能体持有与成员相同的角色——viewermembermanager——上限为铸造该密钥的成员的角色。非成员在私有项目路径上会收到 404 unfound_resource(而非 403),因此项目 ID 无法被枚举。

MethodPathDescription
GET/openapi.json实时的 OpenAPI 3 规范,含请求体。无需认证。
GET/docsSwagger UI。无需认证。
GET/meta调用者身份(auth.kind/key_id/agent_id/project_id)+ 故事类型流转图。需要认证(任何有效密钥;不限于项目范围)。先调用它。
GET/api/health · /api/config存活探测,以及该部署的公开配置(单组织模式、已启用的可选功能、实例名称)。无需认证,位于 /v1 之外。

会话端点,除另有说明外均无需认证。由 SPA 驱动;脚本通常改用 API 密钥。

MethodPathDescription
POST/auth/register注册一个新账户——受 reCAPTCHA 保护;账户随后需通过 SMS 验证
POST/auth/sms/challenge · /auth/sms/status · /auth/sms/bypass发送 / 检查注册 SMS 验证码(bypass 由运维方门控)
GET/auth/config该部署提供哪些登录方式
POST/auth/login用电子邮件 + 密码登录;返回一个会话 JWT,或一个 TOTP 挑战
POST/auth/login/totp用验证器验证码或恢复码完成登录
POST/auth/passkey/login/start · /auth/passkey/login/finish无密码的 WebAuthn 登录
GET/auth/oauth/github/start · /auth/oauth/github/callback · /auth/oauth/google/start · /auth/oauth/google/callback · POST /auth/oauth/exchange用 GitHub 或 Google 进行 OAuth 登录
POST/auth/refresh · /auth/refresh/revoke轮换刷新令牌 / 撤销它
POST/auth/logout登出(撤销刷新令牌)
POST/auth/forgot-password · /auth/reset-password请求一封重置邮件 / 使用重置令牌
POST/auth/accept-invite/lookup · /auth/accept-invite把一个邀请令牌解析为电子邮件 / 接受项目邀请(认证之后)

这些作用于调用者本人,只需一个有效密钥(无需项目角色)。

MethodPathDescription
GET/me当前用户资料
PUT/me更新资料
DELETE/me删除账户——当你是某个组织、或某个仍有其他成员的项目的唯一所有者时会被拒绝
GET/me/deletion-impact删除账户会移除什么,以及什么会阻止删除
PUT/me/password更改密码
PUT/me/settings更新设置(主题、通知偏好)
POST/me/avatar上传头像(multipart)
POST/me/api-token/regenerate轮换你的 API 令牌——使已有的会话/密钥失效
GET/me/api_keys · POST /me/api_keys · DELETE /me/api_keys/{id}管理用户(ea_user_)API 密钥
GET/me/totp · POST /me/totp/setup · /me/totp/verify · /me/totp/disable双因素(TOTP)注册;verify 只返回一次恢复码
GET/me/passkeys · POST /me/passkeys/register/start · /me/passkeys/register/finish · DELETE /me/passkeys/{credential_id}通行密钥的注册与移除
GET/me/oauth_grants · DELETE /me/oauth_grants/{grant_id}已连接的应用——你授权过的 MCP 客户端和 OAuth 应用
GET/me/activity你在所有项目中的活动
GET/me/stories在该令牌可触达的每个项目中,由你负责、请求或关注的故事——role=owned|requested|followingstate=cursor= / limit=(最大 200)
GET/me/mentions · POST /me/mentions/{mention_id}/ack@提及收件箱(unacked=true 用于过滤)与确认——也并入下文的通知信息流
GET/me/data-export你的数据的 GDPR 自导出
GET/me/consent · POST /me/consent读取 / 记录同意({ consent_type, granted }
GET/legal/pending · POST /legal/accept待处理的点击同意文档 / 记录接受
GET / PUT/agent/me智能体密钥自身的身份与资料,可由智能体读取和编辑(/me 在智能体侧的对应物)
POST/api/contact · /api/feedback · /api/feedback/with-screenshot联系 + 应用内反馈。位于 /v1 之外;按 IP 限速

创建/估算故事时使用的种子查询。ID 稳定。

MethodPathDescription
GET/story_typesfeature、bug、chore、release(+ allow_points
GET/story_statesunstarted … accepted、rejected
GET/effort_scales可用的估算尺度
GET/effort_scales/{scale_id}/values某个尺度中的点数值
GET/priority_scales · /priority_scales/{scale_id}/values优先级尺度及其取值(故事上的 priority_id 在此解析)

仅限托管服务——自托管安装以单组织模式运行,不会挂载这些端点(组织列表除外)。角色为组织角色:owner、admin、member。

MethodPathDescription
GET / POST/organizations列出你的组织 / 创建一个
GET / PUT / DELETE/organizations/{oid}读取、重命名(名称 + slug;owner 或 admin)、删除
GET / POST/organizations/{oid}/memberships · PUT / DELETE …/memberships/{member_id}成员与邀请;邀请带有角色上限(绝不高于调用者自己的;owner 角色从不通过邀请授予)
POST/organizations/{oid}/memberships/bulk-role · …/memberships/bulk-remove一次更改最多 200 名成员的角色或将其移除。全部或全不:会移除最后一个 owner 或让项目失去所有者的批次会被整体拒绝;指定 reassign_confirmed 后,改由你成为这些项目的所有者
DELETE/organizations/{oid}/invitations/{invitation_id}撤销一个待处理的邀请
POST/organizations/{oid}/transfer-ownership把 owner 角色移交给另一名成员
PUT/organizations/{oid}/memberships/{member_id}/anonymization在整个组织范围内遮蔽某成员的姓名 / 邮箱 / 头像
GET/organization-invitations/{token} · POST …/{token}/accept解析 / 接受一封邮件中的组织邀请
POST/organizations/{oid}/export · GET / POST …/export/jobs · GET …/export/jobs/{job_id} · …/export/jobs/{job_id}/download仅限 owner 的组织导出:一个包含 SQL 转储和所有附件的 zip,以作业方式运行
MethodPathDescription
GET/projects列出你的项目(limit ≤ 200)
POST/projects创建一个项目
GET/projects/{id}获取项目详情 (viewer)
PUT/projects/{id}更新项目设置 (manager)
DELETE/projects/{id}删除一个项目 (manager)
POST/projects/{id}/pin在你的项目列表上置顶 / 取消置顶该项目
POST/projects/{id}/transfer-organization把项目移到另一个组织 (manager)
POST/projects/{id}/slack/test向项目的 Slack 信息流发送一条测试消息 (manager)
GET / POST/projects/{id}/showcase_claim_eligibility · /projects/{id}/showcase_claim · /projects/{id}/showcase_seed公开展示项目:检查你是否能认领一个、认领它、为它填充种子数据
GET/projects/{id}/audit-log审计日志读取——项目历史,以及通过 surface= 的按故事 / 按 Epic 活动;访问权限因 surface 而异,见下文
GET/projects/{id}/events以游标分页的事件流 (member)——见 Events

审计日志查询参数:event_type=(单个类型或逗号分隔列表)、limit=(≤ 1000)、before=(keyset 游标,ISO-8601 格式的 created_at)、surface=project_historystory_activitiesepic_activities)、target_id=(故事/Epic 的 id——当 surface=story_activitiesepic_activities 时必填)。访问权限:未过滤日志与 surface=project_history(manager)story_activities / epic_activities 项目任意成员均可读取,公共项目上也可匿名读取,行为者的 PII 会被脱敏。

MethodPathDescription
GET/projects/{id}/memberships列出成员 (viewer)
POST/projects/{id}/memberships按电子邮件邀请一名成员 (manager)
PUT/projects/{id}/memberships/{mid}更新角色 (manager)
DELETE/projects/{id}/memberships/{mid}移除一名成员 (manager)
GET/projects/{id}/addable-members · POST /projects/{id}/members/add-existing尚未加入项目的组织成员 / 无需邮件邀请直接添加一名 (manager)
POST/projects/{id}/members/join组织的 owner 或 admin 以 manager 身份加入其组织内的某个项目,或把自己提升为 manager(项目列表上的把我设为所有者操作)
PUT/projects/{id}/members/{mid}/anonymization在此项目上遮蔽某成员的姓名 / 邮箱 / 头像 (manager)
GET / POST/projects/{id}/agent_keys列出 / 铸造智能体密钥——manager,或项目的创建者角色策略所允许的角色
DELETE/projects/{id}/agent_keys/{kid}撤销一个智能体密钥
GET/projects/{id}/agent_keys/onboarding入门套件:面向常见智能体客户端的提示词和配置文件
GET/projects/{id}/agents · GET / PUT /projects/{id}/agents/{aid}项目的智能体及其资料(名称、首字母、描述、颜色)
POST/projects/{id}/agents/{aid}/rotate-key · /projects/{id}/agents/{aid}/avatar轮换某个智能体的密钥(保留身份与历史) / 上传其头像

所有故事写入都需要 member 角色。

MethodPathDescription
GET/projects/{id}/stories列出故事(可分页、可筛选) (viewer)
POST/projects/{id}/stories创建一个故事
GET/projects/{id}/stories/{sid}获取一个故事 (viewer)
PUT/projects/{id}/stories/{sid}更新一个故事
DELETE/projects/{id}/stories/{sid}删除一个故事
POST/projects/{id}/stories/{sid}/transitions带校验地更改状态
POST/projects/{id}/stories/{sid}/reject · /projects/{id}/stories/{sid}/restart拒绝一个已交付的故事 / 把一个被拒绝的故事放回 started(对 /transitions 而言 rejected 是终态)
POST/projects/{id}/stories/{sid}/archive · /projects/{id}/stories/{sid}/unarchive归档 / 取消归档一个故事
POST/projects/{id}/stories/bulk_transition一次流转多个故事(1–100)
POST/projects/{id}/stories/bulk-archive · bulk-delete · bulk-duplicate · bulk-move归档、删除、复制或移动(到某个面板 / 位置)多个故事
POST/projects/{id}/stories/{sid}/duplicate复制一个故事
GET / POST / DELETE/projects/{id}/stories/{sid}/epics · …/epics/{eid}该故事的 Epic 归属
GET/short-links/{code} · /story-references把一个 /s/<code> 短链接解析为其故事 / 把最多 100 个故事引用(#id、URL)解析为调用者可读的故事

故事列表查询参数:archived=exclude 为默认值 / include / only——三态归档过滤器;取代已弃用的 include_archived=true,后者现为 archived=include 的别名)、include_done=true(纳入冻结在过往迭代上的 Done 面板故事,默认排除)。分页(cursor= / limit= / offset=)与稀疏字段集(fields=)遵循下文“分页”与“字段投影”两节。

CreatePOST …/stories):{ "name" (required), "story_type": "feature|bug|chore|release", "description"?, "estimate"?, "current_state"?, "icebox"?, "labels"? }estimate 是尺度值的标签,以字符串形式给出("3""13");JSON 数字会被拒绝。labels 接受 ["auth"][{ "name": "auth" }];未知的标签会被创建。默认值:story_type=featurecurrent_state=unstarted

UpdatePUT …/stories/{sid}):相同的字段,全部可选,外加 "position"(float)、"force_state_change"(bool)和 "expected_updated_at"(RFC 3339——若故事在你读取之后发生了变化,保存描述会被以 409 stale_write 拒绝)。故事写入还会依据故事的 ETag 检查 If-Match;不匹配则为 412 precondition_failed

TransitionPOST …/transitions):{ "to": "<state>" }。字段是 to。返回 { story_id, state }。非法的移动 → 422 invalid_transition,附带 details: { from, to, allowed }

Bulk transitionPOST …/bulk_transition):{ "story_ids": [int,…] (1–100), "to": "<state>" }。每个故事都被独立裁决;返回 { results: [ { id, status: "ok" } | { id, status: "failed", error } ] }

全部为 member。其中大多数的 List/GET 为 (viewer)

MethodPathBody / notes
GET / POST/projects/{id}/stories/{sid}/tasks · PUT/DELETE …/tasks/{tid}{ description (or task_desc), complete?, task_order? }
GET / POST/projects/{id}/stories/{sid}/comments · PUT/DELETE …/comments/{cid}{ text (or comment_text) }{ comment_emoji }。GET 接受 fields=(允许列表:comment_idstory_comment_idstory_idcomment_textcomment_emojimembercreated),外加 cursor= / limit=(≤ 200)/ order=asc|desc
GET / POST/projects/{id}/stories/{sid}/blockers · PUT/DELETE …/blockers/{bid}{ blocker_desc, resolved? }
GET / POST/projects/{id}/stories/{sid}/links · PUT/DELETE …/links/{lid}{ url, link_type?, title? }——link_typerelates_toduplicatesblocksis_blocked_bypull_requestbranchother;GitHub 的 /pull//tree/ URL 会被自动判定类型
GET / POST/projects/{id}/stories/{sid}/reviews · PUT/DELETE …/reviews/{rid}创建:{ reviewer_id? / reviewer_agent_id?, comment? }——两者都省略则指派你自己。更新:{ status, comment? }
GET / POST/projects/{id}/stories/{sid}/owners · DELETE …/owners/{mid} · DELETE …/owners/agents/{aid}{ member_id? / agent_id? }——两者都省略则添加调用者本人
GET / POST/projects/{id}/stories/{sid}/followers · DELETE …/followers/{mid} · DELETE …/followers/agents/{aid}{ member_id? / agent_id? }
GET / POST/projects/{id}/stories/{sid}/labels · DELETE …/labels/{lid}{ name }
GET / POST/projects/{id}/stories/{sid}/attachments (+ /json) · DELETE …/attachments/{aid}multipart 上传——视频 ≤ 200 MB,PDF / Word / Excel ≤ 25 MB,图片 / CSV / 文本 ≤ 10 MB;list 为 (viewer)
GET / POST/projects/{id}/stories/{sid}/link-attachments · DELETE …/link-attachments/{laid}链接附件——与文件附件放在一起、而非作为代码链接保存的外部 URL
GET/attachments/{token} · /api/avatars/{token}以令牌寻址读取附件或头像——即 API 返回的那些 URL;无需 X-TrackerToken

与故事形态相同,只是没有状态机。写入需 member,读取为 (viewer)

MethodPathDescription
GET / POST/projects/{id}/epics · GET / PUT / DELETE …/epics/{eid}Epic 带有名称、Markdown 描述,以及一个把其故事连接起来的底层标签
GET / POST / PUT / DELETE…/epics/{eid}/comments · …/comments/{cid}Epic 评论
GET / POST / DELETE…/epics/{eid}/owners · …/followers(+ /agents/{aid} 变体)负责人与关注者,成员或智能体——Epic 的负责人会传播到其故事
GET / POST / DELETE…/epics/{eid}/attachments(+ /json)· …/link-attachments附件,上限与故事相同
GET/projects/{id}/analytics/epics · …/analytics/epics/{eid}按 Epic 的进度:燃起、吞吐量、健康度、预测 (viewer)

写入需 member,读取为 (viewer)

MethodPathDescription
GET / POST/projects/{id}/labels列出 / 创建一个标签
PUT / DELETE/projects/{id}/labels/{lid}更新 / 删除一个标签
POST/projects/{id}/labels/{lid}/archive归档(软隐藏)一个标签

读取对任何项目角色开放,在公开项目上还可匿名读取。

MethodPathDescription
GET/projects/{id}/iterations列出迭代(每页 ≤ 500;带有 ETag,被截断时还带有 X-Tracker-Pagination-* 续页请求头)
GET/projects/{id}/iterations/{itid}单个迭代
GET/projects/{id}/iterations/first-preview第一个迭代将获得的日期,显示在播种确认框中
POST/projects/{id}/iterations创建一个手动迭代 (member)
DELETE/projects/{id}/iterations/{itid}删除一个迭代 (manager)
PUT/projects/{id}/iterations/{itid}/velocity覆盖单个迭代的速率而不改变项目策略 (manager)
GET/projects/{id}/iterations/{itid}/done-stories某个已关闭迭代中已接受的故事,可分页
MethodPathDescription
GET/projects/{id}/search?q=…强大的搜索——全文 + 分面 / 日期范围 / 人员限定符(GitHub 风格的 DSL);返回 { results, total, limit, offset }queryq 的别名;limit=(默认 50,最大 1000)/ offset= 用于分页;sort=relevance(默认)、createdcreated_ascstateupdated 排序。(viewer)——见指南
GET/projects/{id}/metrics/{velocity,burndown,story-types,contributors}Metrics 页面的数据序列 (viewer);Epic 指标在上文的 /analytics/epics 之下
GET/projects/{id}/backlog/groupingBacklog 投影出的迭代分组 (viewer)
GET / PUT/projects/{id}/preferences你针对此项目的看板偏好——任何项目角色,仅限你自己的那一行
MethodPathDescription
GET/projects/{id}/events以游标分页的事件流 (member)——viewer 会得到 403

查询参数:since=<event_id>types=story.created,story.transitioned,comment.added,…limit=(≤ 500)、cursor=。响应包含 next_cursor。把你看到的最后一个 event_id 作为 since 传入以继续。

应用内统一的通知信息流:一等公民的通知行(评审请求、story 动态、邀请等)与 @提及收件箱合并为一条按最新排序的流。信息流 id 带有来源前缀(nt-… / sc-… / ec-…)。成员会话和 ea_user_* 密钥读取各自的成员侧行;ea_agent_* 密钥读取其代理侧行。

MethodPathDescription
GET/me/notifications你的通知信息流。过滤器:unread=truesince_id=kind=(mentions / reviews / stories / invitations);用 cursor= / limit= 分页
GET/me/notifications/unread-count未读总数 — { unread_count, by_category: { mentions, reviews, stories, invitations } }
POST/me/notifications/read-all全部标记为已读;返回最新的计数
POST/me/notifications/{id}/ack将单个条目标记为已读(幂等)
POST/me/notifications/{id}/accept从信息流中接受项目 / 组织邀请(仅限成员令牌)
POST/me/notifications/{id}/decline拒绝项目 / 组织邀请(仅限成员令牌)
GET/me/notifications/resolve-invite?token=…将邮件中的邀请令牌解析为你的通知 id — { "id": "nt-…" }{ "id": null }
GET/me/notifications/stream实时推送 — Server-Sent Events(text/event-stream);见下文

stream 端点不是 JSON 端点,因此不在 OpenAPI 规范中:它保持连接打开,每当有新内容到达时发送一个不带载荷的帧({"type":"notification","kind":…}),提示客户端重新拉取信息流。连接在服务端 45 分钟后关闭 — 请重新连接并重新认证。仅限成员会话和 ea_user_* 密钥;ea_agent_* 密钥会收到 403

MethodPathDescription
POST/projects/{id}/import文件来源:source=pivotaljiraasanagitlabshortcuttrellolinearplaneplane_jsoneat。Multipart file=。同步——以结果计数作答。
POST/projects/{id}/import/jsonJSON 请求体;source=github 无需文件——ownerrepo、可选的 token,以及可选启用的开关 include_pull_requests / include_milestones / include_releases / include_dependencies;文件来源则发送 file_base64异步:返回 202 { import_id, status }。服务器通过 GitHub 的 GraphQL API 抓取,而该 API 拒绝匿名调用方,因此总会有一个令牌抵达 GitHub——你自己的,或该部署的共享令牌。参见指南
GET/projects/{id}/imports/{import_id}轮询一个作业:status 依次经过 pending → fetching → writing → done | failed,抓取期间带有 progress_current / progress_total,到达 done 时带有结果计数

每个项目同一时间只运行一个导入;在一个导入进行中再次 POST 会得到 409 import_already_runningdry_run: true(JSON 体或 multipart 的 dry_run=true)可预览任意来源:解析、解析映射、去重,产生相同的 { imported, skipped, errors, unmatched } 计数,然后回滚——什么都不会写入。上限:体积 10 MiB,以及基于文件的来源单次导入 5,000 个故事(超出任一者 → 400,不写入任何内容)。GitHub 来源没有上限——它分块提交,而不是放在单个事务里。重新导入按来源 id 幂等——已导入的行会被跳过,而非重复。

MethodPathDescription
GET/projects/{id}/export/formats已注册的格式:{ id, name, content_type, drops, includes_archived }。任何项目角色。
GET/projects/{id}/export/{format}下载其一 (manager)。互通:eat(全保真)、jirapivotalshortcuttrelloasanagitlablinearplaneplane_json;文档:pdfdocx
GET/projects/{id}/export/attachments所有附件打包为一个可浏览的 zip(文件保留原始名称;带 JSON + CSV 清单) (manager)

文档导出(pdfdocx)接受额外的查询参数:page_size=letter 为默认值 / a4 / legal / folio)、from= / to=(故事窗口边界——RFC 3339 或纯 YYYY-MM-DD;当故事的 createdcompleted_at 落在其中时即在范围内)、include_icebox= / include_backlog=(默认均为 false,因此可分享的导出只显示已排期 / 进行中的工作)。CSV 互通格式会忽略这些参数。

MethodPathDescription
GET / POST/projects/{id}/backups · GET …/backups/{snapshot_id} · …/backups/health列出快照、立即创建一个、读取一个,以及保留期健康状况摘要
GET / POST/projects/{id}/restores · POST …/restores/partial · GET …/restores/{restore_id}恢复整个快照,或从某个快照恢复选定的表,并轮询恢复进度

这些 POST 位于敏感速率限制层级(见下文)。

East Agile Tracker 是面向 MCP 客户端的 OAuth 2.1 提供方。客户端在 /.well-known/oauth-authorization-server/.well-known/oauth-protected-resource/mcp 发现它,把你送到 /oauth/authorize(同意页面),在 /oauth/token 兑换授权码,然后用得到的 ea_mcp_* 令牌在 /mcp 上使用 MCP。授权可在 /me/oauth_grants 列出和撤销。提供方端点有自己的速率限制层级。

wss://eastagiletracker.com/ws/control?token=<session JWT>

用于交互式 UI 远程控制({ "action": "get_state", "id": "req-1" })。令牌是浏览器会话 JWT——API 密钥会在升级之前就被以 401 拒绝。它不是一个数据通道——所有读/写都走 REST。仅限单实例;不会在副本间扇出。

写入端点(POSTPUTDELETE)接受一个 Idempotency-Key 请求头。相同密钥 + 相同请求体会重放缓存的响应(24 小时窗口);相同密钥 + 一个不同的请求体会返回 409 idempotency_conflict。该键的作用域限于发送它的凭据。不适用于 GET/HEAD/OPTIONS/openapi.json/docs/api/auth/*,或 /attachments 路径上的 multipart 上传。未能给出领域答案就中止的响应从不被缓存——401403404429 以及所有 5xx——因此在这些之后重试会抵达处理器;400409412422 是领域给出的答案,会像成功响应一样被重放。

列表端点接受 cursor=<opaque>limit=<n>。设置后,响应为 { "items": [...], "next_cursor": "<str|null>" };把 next_cursor 传回以翻页。limit 的上限按端点而异:故事、评论和项目为 200;事件为 500;搜索和审计日志为 1000。

一个不得不截断响应的普通列表(无 cursor/limit)会通过响应头说明这一点——X-Tracker-Pagination-TruncatedX-Tracker-Pagination-LimitX-Tracker-Pagination-OffsetX-Tracker-Pagination-Next-Offset;把最后一个作为 offset= 传回即可获取下一页。没有总数响应头。

列表端点接受 fields=(逗号分隔),以仅返回特定字段。story_id 始终被包含;一个未知的字段名会返回 400 validation_failed,并在 details.fields 中给出出错的名称。

GET /projects/123/stories?fields=story_id,name,current_state,owners

每个 JSON 错误都有 codeerror;有些会加上 details

{ "code": "invalid_transition",
"error": "Cannot move story from `unstarted` to `accepted`",
"details": { "from": "unstarted", "to": "accepted", "allowed": ["started"] } }
StatuscodeWhen
400invalid_parameter输入有误;消息在 error 中,无 details(大多数校验:空白/长度/null 字节/电子邮件)
400validation_failed结构化的输入错误;details.fields 是一个由出错字段名组成的数组
401unauthenticated令牌缺失/无效
403unauthorized_operation已认证但角色不足
404unfound_resource未找到——也会返回给非成员
409conflict资源冲突(例如重复)
409idempotency_conflictIdempotency-Key 被以一个不同的请求体重用
409stale_write · import_already_running故事自你的 expected_updated_at 之后已发生变化 · 已有一个导入在进行中
412precondition_failedIf-Match 与资源当前的 ETag 不匹配;details 携带 expectedcurrent
413request_too_large请求体超出该路由的大小限制
422invalid_transition非法的状态移动;details 携带 { from, to, allowed }
429rate_limited此 IP 在受限速路由上的请求过多;带 Retry-After 请求头
500internal_error服务端故障——通用消息;可安全重试
503not_configured该部署缺少此路由所需的集成(SMS、对象存储……)

details.fields 是一个由字段名组成的 JSON 数组(例如 ["to"]),有时还带有额外的键,比如 max。没有 field→message 的映射。

{ "code": "validation_failed", "error": "unknown field(s): foo", "details": { "fields": ["foo"] } }

按客户端 IP,仅限少数路由;其余经过认证的 API 流量不做速率限制。默认值(每对数值为持续速率与突发量,可由运维方调整):

  • Auth——/api/auth/*:0.5 req/s,突发 20。
  • OAuth provider——/oauth/*:1 req/s,突发 60。
  • Public——/api/contact:0.2 req/s,突发 10。
  • Feedback——/api/feedback:三层叠加——每 15 秒一次提交、每小时 10 次、每天 36 次。
  • Avatars——无需认证的头像重定向:20 req/s,突发 200。
  • Sensitive——备份与恢复的 POST:约 0.002 req/s,突发 5。

超出限制会返回 429,附带一个 Retry-After 请求头和标准的 JSON 错误信封,code: "rate_limited"