写入 API

登记仓库与发布项目两个写入接口的用法。

两个写入接口都要带 key,都收 JSON,都可能因为要抓 GitHub 数据而比读接口慢 (/repos 最长 60 秒,/projects 最长 300 秒——大仓库要拉 README、翻译技能文档)。

「登记」与「发布」是两件事,也是两个 scope

接口scope写进哪张表公开站可见
POST /api/v1/reposrepos:writerepos、user_repos否
POST /api/v1/projectsprojects:writeprojects(含 README、技能文档)是

登记只是让雷达开始跟踪一个仓库;发布是把它放到公开页面上。自助签发拿不到 projects:write,需要发布就找管理员开。

请求体只有两种形态

两个写入端点的请求体形状相同,都只收一个地址——要么完整 URL,要么裸的 owner/repo。两者都可选地带回调参数。

不接受调用方带来的 GitHub 数据

请求体里没有 stars / topics / languages 这些字段,多一个字段整条拒绝 (invalid_body)。那份数据是调用方某一时刻的快照,落库等于让雷达的统计建立在 一个可能过期的副本上;服务端自己去取,多一次请求换来「库里那份和 GitHub 上一致」。

要登记又带分类信息的,走 POST /api/v1/projects——那里有 type,而这里没有。

形态一:完整 URL

curl -X POST "$CONSOLE_ORIGIN/api/v1/repos" \
  -H "Authorization: Bearer $CONSOLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://github.com/vercel/next.js"}'

url 接受 https://github.com/owner/repo、不带协议的 github.com/owner/repo、 owner/repo,末尾带 .git 或 / 也认。

形态二:裸 owner/repo

curl -X POST "$CONSOLE_ORIGIN/api/v1/repos" \
  -H "Authorization: Bearer $CONSOLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"repo":"vercel/next.js"}'

这种形态拒绝带协议的输入({"repo":"https://github.com/vercel/next.js"} 返回 invalid_url)——两种形态各有各的写法,混着写只会让一半的请求白跑。

回调参数

两个形态都接受 callbackUrl + callbackSecret,成对出现:给了地址没给密钥返回 invalid_body。

curl -X POST "$CONSOLE_ORIGIN/api/v1/repos" \
  -H "Authorization: Bearer $CONSOLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "repo": "vercel/next.js",
    "callbackUrl": "https://example.com/hooks/radar",
    "callbackSecret": "your-shared-secret"
  }'
字段类型说明
callbackUrl字符串收 POST 的地址,必须是 http(s)
callbackSecret字符串至少 16 字符,用于算签名

落库完成之后服务端同步发一次回调,等它返回或超时(30 秒)才响应你的请求。 同步是故意的:异步发出去的话,回调地址写错这件事永远不会被发现。

请求体:

{
  "eventId": "repo.registered.V1StGXR8Z5jd",
  "event": "repo.registered",
  "occurredAt": "2026-03-24T08:12:03.000Z",
  "fullName": "vercel/next.js",
  "repoId": "V1StGXR8Z5jd",
  "created": true
}

event 只有两个取值:登记是 repo.registered,发布是 repo.published(多带一个 projectId)。eventId 由事件名加 repoId 派生,不是随机值——同一个写请求 因为超时被重发时它不变,接收方按它去重就能认出这是同一件事,而不是两次独立的 登记。

签名头与订阅投递完全一致,用同一段代码算:

头内容
X-Webhook-Id同 body 里的 eventId,与订阅投递的 id 同一套语义
X-Webhook-TimestampUnix 秒
X-Webhook-Signaturesha256=<hex>,签名对象是 <timestamp>.<原始请求体>
X-Webhook-Event事件名

验签要按原始字节算:先解析 JSON 再重新序列化,字段顺序或空格变了,签名就对不上。 收到的时间戳与当前时间相差超过 300 秒就丢掉——那是重放。

回调失败不影响这次调用的结果

数据在回调之前就已经落库了,所以投递失败只进日志,不会把一次成功的写入报成失败。 你的重试只会登记同一个仓库第二次。回调是「可以开始读了」的信号,不是这次写入的 交付凭证——要确认写入结果,看响应体。

POST /api/v1/repos — 登记一个仓库

让它开始跟踪,不会发布任何东西。

这个端点不接受 type 字段

带 type 的请求会返回 400 type_not_accepted,而不是被忽略——静默忽略会让 调用方以为「我发布成功了」。要传 type 就调 POST /api/v1/projects。

响应

状态码什么时候
201新登记了一个仓库
200这个仓库早就在跟踪了(幂等)
{
  "ok": true,
  "repo": { "id": "V1StGXR8Z5jd", "full_name": "vercel/next.js", "stars": 132000 },
  "created": true,
  "projectCount": 0
}

created 区分「新建」与「刷新」;projectCount 是已经有多少个已发布的项目 指向这个仓库——登记端点自己不建 project,所以它非零只可能因为这个仓库先前被 策展过,这时你传的 type 会被拒绝而不是被采纳,看到这个数就知道了。

失败时是统一的错误形状(见调用、限流与错误):invalid_body (附 detail 指出第一个不合法的字段)、invalid_url、type_not_accepted、 insufficient_scope、rate_limited、github_unavailable。

POST /api/v1/projects — 发布一个项目

把一个仓库策展成 project,也就是放到公开站上。地址同样有两种写法,另加可选的 type(缺省 application)与那对回调参数。

curl -X POST "$CONSOLE_ORIGIN/api/v1/projects" \
  -H "Authorization: Bearer $CONSOLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"repo":"vercel/next.js","type":"application"}'

请求体:

字段类型默认值说明
url / repo字符串(二选一)—完整 URL,或裸的 owner/repo
type枚举(可选)applicationclient / server / application / skill / persona
callbackUrl / callbackSecret字符串(可选)—与登记端点相同,成对出现

响应

状态码status什么时候
201"created"新建了一个项目
200"existing"这个仓库早就发布过(幂等)
{
  "ok": true,
  "status": "created",
  "repo": { "full_name": "vercel/next.js" },
  "project": { "...": "整行 project 数据" },
  "readme": { "...": "解析后的 README" },
  "skills": { "count": 0, "translated": 0, "empty": false },
  "authorLinked": true,
  "delivered": null
}
  • project、readme、skills 是这次写进去的东西;skills 只有 type: "skill" 时才有意义。
  • delivered 只对 skill 有意义:true 表示技能已经推到下游站点、那边现在就能用; false 表示有技能没推成功(行已经进重试队列);null 表示这个部署没配下游, 不是失败。
  • 已经发布过的仓库再调一次不会建第二行,返回 existing。此时若 type 是 skill, 技能同步会重新跑一遍——调用方要的是「现在就能拿到技能」,不能只答一个 existing 就把人丢去等调度器。

失败:invalid_body、invalid_url、insufficient_scope(这把 key 没有 projects:write)、create_failed(502,可以重试)、github_unavailable。

写入之后会发生什么

仓库被雷达跟踪,下一轮周期任务开始采集它的 stargazer 到达时间;之后的 读取 API里才会出现它的档案与统计。榜单上出现还要更久—— 统计要先攒够一期。

发布的项目进入公开站的项目列表;分类(type)决定它落在哪个栏目。

每次调用都记一条审计:谁的 key、什么时间、改了什么。管理员可以在 /dashboard/api-keys 看到。

本页目录