AI 对接 Skill

把下面这段提示词发给你的 AI 编程助手,它会读取完整对接规范并完成接入。

一键复制提示词
rekitdev 支持接入登录、COS 存储、支付和服务号通知。请按照 skill 对接:https://rekitdev.com/skill.md
发给任意 AI 编程助手(Claude / Cursor / Copilot 等),即可开始对接。

Rekit · 开放平台对接(AI Agent 完整版)

本 skill 面向 其它 AI agent / 产品站开发:读完后应能写出可运行的对接代码,不要自建登录页、自配公众号网页授权、自发明鉴权。

公开直链(无需登录,含本文件 + reference):

text
https://rekitdev.com/skill.md

实现时优先遵循下文「最小实现」;字段级契约以文末「接口参考」为准(已拼入本文档)。

0. Agent 开工前必须拿到的材料

运营在 Rekit 后台配好站点后,应交给对接方(或你)至少:

材料示例用途
站点 slugweibo / rekitdev登录跳转 site=;与 Key 绑定
站点 API Keysk_...仅服务端 X-Api-Key
已开通权限email_notify / storage / wechat_notify / payments / cdn / captcha未开通 → HTTP 403
产品站对外 URL / 域名白名单https://wb.example.com登录回跳、支付 success/cancel 校验
基址(可默认)见下表环境变量

生产基址(默认):

变量谁用
NEXT_PUBLIC_SNGZS_AUTH_API_URLhttps://login.rekitdev.com浏览器跳转登录(须 NEXT_PUBLIC_
SNGZS_API_URLhttps://api.rekitdev.com服务端开放 API
NEXT_PUBLIC_SNGZS_CAPTCHA_API_URLhttps://api.rekitdev.com浏览器加载 captcha SDK / 获取 token
SNGZS_PAYMENTS_API_URLhttps://pay.rekitdev.com服务端支付
SNGZS_SITE_API_KEY站点 Key服务端;禁止进前端 bundle
NEXT_PUBLIC_BASE_URLhttps://你的站拼 callback / return_to

本地端口:login 9102,pay 9104,api 9106,admin API 8000

仅有 Key、没有 slug / 域名白名单 / 权限绑定:登录回不了 token,开放接口会 403/503。

1. 硬规则(违反即对接错误)

  1. 两套鉴权不要混 用户身份:Authorization: Bearer <access_token> → 只打 login.(如 /api/v1/me) 站点能力:X-Api-Key: <站点 Key> → 打 api. / pay.(邮件、图床开放预签名、公众号通知、支付)
  2. 登录只跳托管门户,产品站不自建注册/登录页,不自配公众号网页授权 AppID。
  3. 微信内 H5/login/mp;浏览器通用用 /login。不要用产品站域去做微信 OAuth。
  4. Key 永不进浏览器;开放能力请求放在服务端 / BFF。
  5. 图床:拿预签名后客户端 直传 COS;读图用返回的 url / static. CDN,文件不经产品站中转。
  6. 支付:优先 Checkout Session;success_url / cancel_url 的 host 必须在该站域名白名单内。

2. 子域名一览

子域用途产品站怎么用
login.托管登录 + 用户 JWT浏览器 302;Bearer
api.邮件 / 图床 / 公众号通知服务端 X-Api-Key
pay.支付 Checkout / 下单服务端 X-Api-Key
static.CDN 读图公开 URL

3. 登录对接(必做)

3.1 流程

text
用户点登录
  → 302 {LOGIN}/login?return_to={CALLBACK}&site={slug}&lang=zh|en
     (微信内 H5 → /login/mp,参数相同)
  → 用户在 login. 完成邮箱或微信
  → login. 302 → {CALLBACK}?login_code=...
  → 本站 callback 服务端 POST {LOGIN}/api/v1/auth/exchange 换 access_token
  → 写 HttpOnly Cookie,再 302 到业务 path(returnTo)
协议变更提醒login. 不再把签好的 JWT 明文放进跳转 URL(会留在浏览器历史 / 服务端访问日志 / Referer,且没有来源校验时任何人拿到别人的回跳链接就能把自己的 登录态嫁接到受害者浏览器上)。改为回传一次性 login_code(单次消费、90 秒内有效), 由 产品站服务端(不是浏览器)拿它换真正的 access_token。仍在用旧版 ?access_token=... 直读方式的接入方必须升级 callback 实现,否则登录会失败。

{CALLBACK} 推荐:https://你的站/api/auth/callback?returnTo=/accountreturnTo 为本站相对路径,须以 / 开头、禁止 //。)

site=<slug>要回传登录态时必填return_to 的 host 必须在该站「域名 / 对外 URL」白名单;否则登录成功但 不会 302 带回 login_code

3.2 跳转 URL 模板

text
# 浏览器 / 通用
{LOGIN}/login?return_to={encodeURIComponent(CALLBACK)}&site={slug}&lang=zh

# 微信内 H5(专用;非微信打开只提示「请在微信内打开」)
{LOGIN}/login/mp?return_to={encodeURIComponent(CALLBACK)}&site={slug}&lang=zh

# 微信内 H5 · 静默(snsapi_base,无授权页;推荐微信内首选)
{LOGIN}/login/mp?silent=1&return_to={encodeURIComponent(CALLBACK)}&site={slug}&lang=zh

检测微信内:/MicroMessenger/i.test(navigator.userAgent)

silent=1(静默登录):走 snsapi_base 网页授权,微信内不弹授权页——已在平台注册过的 用户(同开放平台主体、unionid 对齐者)零点击直接回跳 ?login_code=...;仅「平台全新」用户 由平台回调自动跳一次 snsapi_userinfo 补昵称头像后回跳。接入方只需带 silent=1,其余 (callback 收 login_codePOST /api/v1/auth/exchange 换 token)与非静默完全一致。仅微信内可用。 静默登录会把用户的公众号 openid 落到平台账号上,这正是内联 JSAPI 支付所依赖的 openid 来源(见 §8)

3.3 本站最小实现

  1. 登录入口:按上表 302(或 window.location.href)。
  2. GET /api/auth/callback(本站,服务端处理,不能是纯前端页面): 读 query:login_code(必填)、returnTo(相对路径) 服务端(不经浏览器)POST {LOGIN}/api/v1/auth/exchange,body {"code": login_code}

成功返回 {access_token, expires_in, user},失败(code 过期/已用过)返回 400,需引导用户重新登录

  • 写 Cookie(参考名 sngzs_user_token):HttpOnly; Path=/; SameSite=Lax;(HTTPS 加 Secure;可按主域设 Domain
  • Max-Age = max(60, expires_in)
  • 302 到 returnTo
  • 鉴权:服务端用 Cookie 里的 token 调

GET {LOGIN}/api/v1/me,头:Authorization: Bearer <token> 响应:{ "user": { id, nickname, avatar_url, background_url, email, has_password, has_wechat_web, has_mp, unionid } }没有 mp_openid 字段;只有 has_mp + unionid。发模板消息用的公众号 openid 见 §5。)

  1. 登出:清 Cookie。

参考实现(本仓库):rekitdev/src/lib/wechat-login.tsrekitdev/src/pages/api/auth/callback.tsrekitdev/src/lib/auth-session.tsrekitdev/src/lib/sngzs-client.ts

3.4 平台侧微信配置(对接方知悉,一般由运营做)

  • 开放平台「网站应用」授权回调域、公众号「网页授权域名」均填 login.你的域名(如 login.rekitdev.com),不是产品站域名。
  • 使用 /login/mp 时后台须启用「公众号网页授权」。
  • 小程序与公众号须同开放平台主体,才能用 unionid 对齐账号。

3.5 用户资料 / 账号绑定(Bearer)

方法路径说明
GET/api/v1/me当前用户
PATCH/api/v1/me{nickname?, avatar_url?, background_url?};URL 须 http(s)
POST/api/v1/me/bind-email/code{email}
POST/api/v1/me/bind-email{email, code, password};密码 ≥6;邮箱占用 409
POST/api/v1/me/unbind-email解绑前须已绑微信
POST/api/v1/me/password{current_password, new_password}
GET/api/v1/me/bind-wechat/web/start?return_to=返回 {state, authorize_url, redirect_uri}
POST/api/v1/me/unbind-wechat{channel:"web"};解绑前须有邮箱密码

完整表见 reference。

4. 开放 API 通用约定

http
X-Api-Key: <SNGZS_SITE_API_KEY>
Content-Type: application/json
HTTP含义
401缺 Key / Key 无效 / Bearer 无效
403站点停用或未开通该权限
400参数错误 / 业务拒绝(见 detail
409冲突:JSAPI payer_mp_openid_missing / jsapi_appid_mismatch,应回退扫码
422请求体校验失败
429超限:每站点每类通知 120 次/分钟
502上游失败(如 SMTP)
503平台或站点未绑定配置(SMTP / COS / 支付 / 微信等)

统一错误外壳: 所有对外错误响应体恒为 {"detail": {"code": "...", "message": "..."}}(平台网关层统一)。code 为稳定机器码(如 api_key_missingpermission_deniedsite_provider_unboundvalidation_error),可用于多语言映射;message 为兜底人读文案。接入方只需解析这一种形状。

权限 ID:storagecdnwechat_notifyemail_notifypaymentscaptcha

站点自检(对接第一步): GET {SNGZS_API_URL}/api/v1/site,只需 X-Api-Key,无权限门槛。返回各能力的 permission(权限位)与 ready(权限位 后台已绑配置,才是真正可调用);ready: falsemissing 精确说明缺什么——可直接转给运营当工单,不必逐个功能试出 503。存储另带 read_modepublic = 存 unsigned_url 永久链;signed = 带 CDN 鉴权、仅即传即用)与 cdn_auth_enabled(后台原始开关);支付另带 providers(可用渠道,与下单同一判定)与 webhook_configuredfalse = 到账不推送;后台配了 webhook_url 才有,且后配不补历史)。完整响应形状见文末接口参考。

5. Captcha(captcha)— api.

Rekit 托管登录页已内置 captcha。产品站如果自有表单也要防注册/登录/敏感动作,可复用同一套。

浏览器侧只使用公开站点 slug,不暴露 API Key:

html
<script src="https://api.rekitdev.com/rc/v1/rekit.min.js"></script>
<div id="captcha"></div>
<script>
  const rk = ReKitCaptcha.init({
    api: "https://api.rekitdev.com/rc/v1/s/你的站点slug"
  });
  rk.render(document.getElementById("captcha"), {
    onToken(token) {
      // 随业务表单一起提交给你的服务端
    }
  });
</script>

服务端校验 token(必须用 X-Api-Key,且 token 只能消费一次):

http
POST /api/v1/captcha/verify
X-Api-Key: <SNGZS_SITE_API_KEY>
Content-Type: application/json

{ "token": "<browser token>" }

成功:{"ok":true,"site":"你的站点slug","sub":"rekit-pass|rekit-verify","score":0.9,"expires_at":...}。 失败或重放:400 captcha_invalid。站点未开权限:403 permission_denied

6. 公众号通知(wechat_notify)— api.

基址: {SNGZS_API_URL} 鉴权: X-Api-Key + 权限 wechat_notify 收件人身份: 统一用 unionid(接入方共享同一开放平台主体,只拿得到 unionid;公众号 openid 只有公众号自己能拿)。平台内部用 members 表映射到投递 openid。 限流: 每站点 120 次/分钟/类,超限 429 rate_limited路径: /api/v1/notify/*

5.0 前置:拿到 unionid + 用户须关注

通知与查关注以 unionid唯一收件人身份。能用 unionid 的前提是: 你站点的公众号、你的小程序/网站应用,绑定在同一个微信开放平台账号下 (同一用户在各应用里才会拿到同一 unionid)。这是接入前一次性配置—— 小程序/网站应用侧由你自己绑(开放平台「管理中心」),平台公众号侧由运营绑; 没绑齐,unionid 拿不到或对不上,查关注/通知对该用户永久不可用且报错与 "未关注"不可区分(只能靠平台后台告警发现)。

三条渠道拿到 unionid(按你的接入形态选一条):

接入形态怎么拿 unionid
小程序自有登录(主画像)小程序 wx.login() → 你的服务端 jscode2session → 响应直接含 unionid(前提:小程序已绑开放平台)
网站自有微信登录你的网站应用 OAuth(snsapi_login)→ access_token + openidsns/userinfo 响应含 unionid
全托管 Rekit 登录登录后 GET /api/v1/me(或 auth/exchange 响应)的 user.unionid,平台已替你落库

关注是投递前提: 模板消息只能发给已关注该站点绑定服务号的用户。

  • 用户该关注哪个号:运营在后台给你站点绑定的那套公众号——向运营要公众号名称/二维码(平台不托管关注页;/wechat/subscribe/{app_id} 是一次性订阅消息授权页,与关注无关)。
  • 用户关注后平台回调自动落库 unionid→openid,随后 subscribe-status 即可查到(通常秒级)。
  • 产品站推荐形态:发通知前 subscribe-status 预检;subscribed !== true 时在通知设置/订单页展示该服务号的关注入口(公众号资料卡/二维码),关注后再发。
  • 用户明明已关注却始终 subscribed:false / wechat_member_not_found:几乎必然是公众号未绑开放平台(平台拿不到 unionid),联系运营核查绑定。

5.1 查是否关注

http
GET /api/v1/notify/subscribe-status?unionid=oYYYY

Query 传 unionid

成功:

json
{
  "ok": true,
  "openid": "公众号openid",
  "subscribed": true,
  "nickname": "昵称或null",
  "unionid": "unionid或null"
}

关键约束:

  • 平台用 members 表把 unionid → 公众号 openid(用户须曾关注过,并由公众号事件/回填写入)。
  • 库中无该 unionid(从未关注)→ 返回 {"subscribed": false}(不再报错),据此判断即可。
  • 实时结果以微信 user/infosubscribe==1 为准,不是只读本地缓存。

推荐用法(产品站):

  1. 直接用登录用户的 unionid 查。
  2. subscribed !== true 时不要发模板消息(发也发不出去)。

5.2 发「工单状态」模板消息

http
POST /api/v1/notify/work-order-status
Content-Type: application/json

{
  "unionid": "oYYYY",
  "project_name": "你的产品名",
  "status": "账号过期",
  "customer_name": "用户昵称",
  "time": "2026-07-17 18:00",
  "url": "https://你的站/notify?scene=expired",
  "client_msg_id": "expired:123"
}
字段必填说明
unionid接入方共享 unionid;平台内部映射到投递 openid
project_name≤100
status≤100;映射到 short_thing 时微信侧常 ≤5 字
customer_name≤100
timeYYYY-MM-DD HH:MM 或带秒;不传用服务器当前时间
url点击跳转
client_msg_id防重

成功:{ "ok": true, "msgid": "..." } 收件人 unionid 从未关注公众号:400 wechat_member_not_found(发送前用 5.1 预检)

模板字段映射由运营在公众号配置里设置;产品站只传上述语义字段。

7. 邮件(email_notify)— api.

http
POST /api/v1/notify/email
{
  "to": "a@b.com",
  "subject": "主题",
  "html": "<p>正文</p>"
}

成功:{ "ok": true } 站点须在后台「站点接入」绑定 SMTP;未绑定 → 503 site_email_unbound。 注册验证码由 login 服务自己发信,不要走本接口。

8. 图床(storage)— api.

7.1 站点开放预签名(Api-Key,不计入用户配额)

http
POST /api/v1/storage/open/presign
{
  "filename": "a.webp",
  "content_type": "image/webp",
  "content_length": 12345,
  "subdir": "products/thumbs"
}
  • content_type 仅:image/jpeg | image/png | image/webp | image/gif
  • content_length必填(字节,与 PUT body 长度一致);缺失 → 400 content_length_required(不传则预签名 URL 不带长度约束,客户端可 PUT 任意大小对象)。用户 Bearer 预签名同样必填
  • 单文件 ≤ 5MB
  • 响应:{ key, put_url, url, unsigned_url, expires_in }(秒,通常 300)
  • 客户端对 put_urlHTTP PUT,Header:Content-Type 与申请时一致;若预签名绑定了长度则 body 长度必须等于 content_length
  • 读图用 url(可能带 CDN 鉴权)或 unsigned_url

7.2 登录用户资料图(Bearer,计入用户配额 ≤15MB)

http
POST /api/v1/storage/presign
Authorization: Bearer <access_token>
{ "filename":"a.webp", "content_type":"image/webp", "content_length":12345, "subdir":"avatars" }

subdiravatars / backgrounds 时,换绑后由 PATCH /api/v1/me 删除旧对象。

9. 支付 Checkout(payments)— pay.

http
POST /api/v1/payments/open/checkout/sessions
{
  "mode": "payment",
  "success_url": "https://你的站/checkout/success?session_id={CHECKOUT_SESSION_ID}",
  "cancel_url": "https://你的站/pricing",
  "user_id": 123,
  "external_customer_id": "站点自管客户标识(与 user_id 二选一)",
  "client_reference_id": "本地订单号",
  "customer_email": "a@b.com",
  "metadata": { "plan_type": "personal-monthly" },
  "line_items": [{
    "quantity": 1,
    "price_data": {
      "currency": "cny",
      "unit_amount": 4900,
      "product_data": {
        "name": "个人版 - 月付",
        "description": "有效期1年,不自动续费",
        "unit_label": "/ 月",
        "features": ["权益1", "权益2"]
      }
    }
  }]
}
  • 付款方双轨:user_id(login. /me 的平台用户 id)与 external_customer_id(站点自管客户标识)二选一;未接 Rekit 登录用后者即可
  • 目前 仅支持恰好 1 条 line_itemsunit_amount 单位为
  • 响应含 idurlstatuspayment_statusamount_total
  • 浏览器 302 到 url(如 https://pay.rekitdev.com/c/pay/cs_...
  • 回站后履约:
http
GET /api/v1/payments/open/checkout/sessions/{id}

仅当 payment_status === "paid" 时履约。可手动过期:POST .../sessions/{id}/expire

底层直连(兼容,一般不必用):

http
POST /api/v1/payments/open/orders
{ "provider": "wechat", "amount_cents": 100, "subject": "标题", "external_customer_id": "cust_123" }

providerwechat | alipay。付款方 user_idexternal_customer_id 二选一(都没传 → 422)。 站点须绑定对应支付配置。响应含 order_id / out_trade_no / code_url(微信 NATIVE 扫码)/ pay_url(支付宝)。

查订单状态(站点 Key,webhook 之外的轮询兜底):

http
GET /api/v1/payments/open/orders/{order_id}

返回 statuspending | paid | ...)等;仅当 paid 时履约。

8.2 微信内联 JSAPI(产品站自己页面直接拉起付款,不跳转、不扫码)

微信内(MicroMessenger)希望在产品站自己的页面点一下直接弹微信付款,用这个。前提:站点 微信支付配置的 appid 必须等于平台公众号 appid(同一服务号),运营在后台绑定即可。

JSAPI 需 Rekit 微信身份:付款人 openid 由平台按 user_idUser.mp_openid 解析(产品站不接触 openid),前置该用户先完成 Rekit 微信登录(§3 的 silent=1 即可)。external_customer_id 的外部客户 拿不到公众号 openid,JSAPI 不可用,请走 NATIVE 扫码 / 支付宝 / §8 的 Checkout Session。

http
POST /api/v1/payments/open/orders
X-Api-Key: <站点 Key>
{ "provider": "wechat", "jsapi": true, "amount_cents": 100, "subject": "标题", "user_id": 123 }

成功响应含 prepay_params,产品站在自己页面拉起:

js
// 必须在微信内置浏览器中;prepay_params 为服务端返回的对象
WeixinJSBridge.invoke('getBrandWCPayRequest', order.prepay_params, function (res) {
  if (res.err_msg === 'get_brand_wcpay_request:ok') {
    // 用户已支付:以服务端为准履约——等 webhook order.paid,或轮询 GET /open/orders/{id}
  } else {
    // 取消或失败,提示重试
  }
});
  • 到账仍以服务端为准:靠 webhook order.paid(§8.1)+ GET /open/orders/{order_id} 兜底,

不要只信前端 getBrandWCPayRequest:ok

  • 409 {code:"payer_mp_openid_missing"}:该用户无公众号 openid(如仅邮箱登录)→ 引导其先做

微信登录,或回退 NATIVE 扫码 / 跳转 §8 的 Checkout Session。

  • 409 {code:"jsapi_appid_mismatch"}:站点支付 appid≠公众号 appid,运营需在后台改配;此前回退扫码。
  • 不传 jsapi → 维持 NATIVE 扫码(返回 code_url);alipay 忽略该字段。

8.1 Webhook —— 到账主动推送(推荐主力,轮询作兜底)

配置:后台「站点接入」→ 站点详情填 Webhook URL(https)、复制 Webhook Secret(站点详情随时可查看复制)。留空则不推送、退回轮询。

到账时 Rekit 向该地址 POST 一个签名事件(门铃);产品站验签后即发货,不必守着轮询。轮询 GET .../sessions/{id} 仍保留作兜底(保险锁)——万一 webhook 漏收,靠它补齐。两条腿都用,不丢单。

请求头:

说明
X-Rekit-Event-Id事件唯一 id(evt_...),用于去重
X-Rekit-Event-Typeorder.paidping 为后台测试事件)
X-Rekit-TimestampUnix 秒,用于防重放
X-Rekit-Signaturesha256=<hmac>,见下方验签

Body(order.paid):

json
{ "id":"evt_ab12…", "type":"order.paid", "created":1710000000,
  "data":{ "order_id":123, "out_trade_no":"…", "provider":"wechat",
           "amount_cents":4900, "currency":"cny", "user_id":456, "external_customer_id":null, "site_id":1,
           "checkout_session_id":"cs_…", "client_reference_id":"你的本地单号",
           "metadata":{…}, "paid_at":"2026-07-18T09:00:00+00:00" } }

验签hex( HMAC_SHA256(webhook_secret, "<X-Rekit-Timestamp>.<原始 body>") ) 应等于 X-Rekit-Signature 去掉 sha256= 前缀。务必用原始请求体字节验签,勿先 JSON 反序列化再拼回。

js
// Node.js / Express(express.raw 拿原始 body)
import crypto from "crypto";
app.post("/api/rekit/webhook", express.raw({ type: "*/*" }), (req, res) => {
  const ts = req.get("X-Rekit-Timestamp") || "";
  const sig = (req.get("X-Rekit-Signature") || "").replace(/^sha256=/, "");
  const expect = crypto.createHmac("sha256", process.env.REKIT_WEBHOOK_SECRET)
                       .update(`${ts}.${req.body.toString("utf8")}`).digest("hex");
  if (sig.length !== expect.length ||
      !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expect))) {
    return res.status(400).end();            // 验签失败
  }
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.status(400).end(); // 防重放
  const evt = JSON.parse(req.body.toString("utf8"));
  // 幂等:按 evt.id 或 data.order_id 去重后再发货
  fulfillOnce(evt);
  res.status(200).end();                      // 必须回 2xx,否则会重试
});

重试与幂等:非 2xx / 超时 → 按退避重试(30s→2m→10m→30m→2h→6h→12h,约 8 次后置 failed,后台可「重发」)。同一笔到账只推一次事件,但重试可能让同一 event_id 到达多次——产品站务必按 event_idorder_id 幂等发货(至少一次语义)。

联调:后台站点详情「发送测试事件」会推一条 type:"ping",可用来验证地址连通 + 验签逻辑。

10. 多租户边界

  • 用户账号是 平台级 SSO(邮箱/微信全局唯一;JWT 不含 site_id)。
  • 开放能力按 站点 Key + 权限 隔离;支付订单带 site_id
  • 登录回跳 / Checkout URL 按站点域名白名单校验。
  • 微信模板消息使用该站点绑定的公众号配置取 access_token。

11. 运营后台一次性配置(对接方 checklist)

  1. 「站点接入」:建站 → 填域名/对外 URL → 复制 API Key → 开权限 → 绑定邮件/存储/支付/公众号(按需)。
  2. 「邮件通知」:SMTP → 站点选用。
  3. 「对象存储」:COS;public_base = CDN(如 https://static.你的域名);直传域名勿填 CDN。
  4. 支付:商户配置 → 站点绑定;(推荐)填 Webhook URL + 复制 Webhook Secret,让到账主动推送产品站(§8.1)。
  5. 登录:微信回调域 = login.;系统设置 secret_key / auth_public_base_url 等各服务共用。

12. 给其它 AI 的提示词

text
请先完整读取对接 skill(含文末接口参考),再按其中流程实现产品站对接,不要自建登录或公众号网页授权:
https://rekitdev.com/skill.md

我将提供:站点 slug、API Key、已开通权限、产品站 BASE_URL。
请实现:登录跳转 + callback 写 Cookie、/me、以及我开通权限对应的开放 API。

13. 实现检查清单

  • 对接第一步调 GET /api/v1/site 自检;ready: false 的项按 missing 找运营补齐
  • 登录只跳 login./login/login/mp,带 site + return_to
  • callback 收 login_code,服务端 POST /api/v1/auth/exchangeaccess_token / expires_in,再写 HttpOnly Cookie(不要在浏览器端/前端代码里直接读 URL 上的 token)
  • 用户 API 用 Bearer;开放 API 用服务端 X-Api-Key
  • Key 未进前端;权限已开
  • 查关注 / 发模板:路径与字段按 §5;对外传 unionid(推荐),无需公众号 openid
  • 发模板前用 subscribe-status 预检;unionid 从未关注 → subscribed:false / 发模板 400 wechat_member_not_found
  • 支付付款方:接了登录传 user_id,否则传 external_customer_id(二选一)
  • 邮件/存储/支付站点已绑定配置
  • 图床直传对象存储;支付用 Checkout 且 success/cancel host 在白名单
  • (推荐)配 Webhook:验签 X-Rekit-Signature、按 event_id/order_id 幂等发货、回 2xx;轮询作兜底
  • 微信授权回调域均为 login. 域名

接口参考(完整契约)

产品站 / AI agent 按需查阅。鉴权:用户接口用 Authorization: Bearer;带权限 ID 的接口用 X-Api-Key

基址:

  • login. → https://login.rekitdev.com(或 NEXT_PUBLIC_SNGZS_AUTH_API_URL
  • api. → https://api.rekitdev.com(或 SNGZS_API_URL
  • pay. → https://pay.rekitdev.com(或 SNGZS_PAYMENTS_API_URL

错误体统一形态:{"detail": {"code": "...", "message": "..."}}(平台在网关层统一外壳,接入方只需解析这一种)。

login. — 浏览器门户(非 JSON)

方法路径Query说明
GET/loginreturn_to(回跳 URL)、site(slug)、lang=zh|en托管登录(邮箱/微信门户)
GET/login/mp同上 + silent=1(可选)微信内公众号 H5 登录;非微信仅提示。silent=1→snsapi_base 静默(老用户零点击,新用户自动一次 userinfo 补资料),回传同样是 login_code
GET/login/wechat同上门户混入口(微信内→公众号,否则→PC 扫码);产品站 H5 请用 /login/mp
GET/register同上托管注册(回传同样是 login_code
GET/forgot-passwordreturn_to托管找回密码

登录成功后(site 有效且 return_to host 在白名单):

text
302 → {return_to}?login_code={一次性码}

return_to 可自带 query(如 .../callback?returnTo=%2Fmember),平台仅追加 login_code。产品站服务端再用 POST /api/v1/auth/exchange {code: login_code}access_token + user(一次性、90s 有效)——原始 token 不出现在浏览器 URL 里。

login. — JSON API

邮箱认证(一般由托管页使用;产品站通常不必直调)

方法路径Body成功要点
POST/api/v1/auth/email-code{email, purpose:"register"|"reset"}发验证码
POST/api/v1/auth/register{email, code, password}access_tokenexpires_inuser
POST/api/v1/auth/login{email, password}同上
POST/api/v1/auth/reset-password{email, code, password}重置密码

密码长度 ≥ 6。

配置与微信登录(唯一入口:托管门户)

方法路径说明
GET/api/v1/app-config微信登录/支付是否启用
GET/login?return_to=&site=托管登录门户(PC 扫码 / 邮箱);登录后 302 回 return_to?login_code=…
GET/login/mp?return_to=&site=&silent=1微信内 H5 公众号登录;silent=1 走 snsapi_base 静默(无授权页)
POST/api/v1/auth/exchange{code}(回跳带回的 login_code)→ access_token + user(一次性换票)

平台回调 /api/v1/auth/wechat/{web,mp,mp/silent}/callback 由门户内部使用,产品站不直接调用

唯一路子: 浏览器 /login,微信内 H5 /login/mp?silent=1,回跳拿 login_codePOST /api/v1/auth/exchange 换 token。不要在产品站自拼微信授权 URL,也不存在 state/authorize-url 这类底层入口。

当前用户(Bearer)

GET /api/v1/me

json
{
  "user": {
    "id": 1,
    "nickname": "昵称",
    "avatar_url": "https://...",
    "background_url": "https://...",
    "email": "a@b.com",
    "has_password": true,
    "has_wechat_web": false,
    "has_mp": true,
    "unionid": "oUnion..."
  }
}

注意:响应 不含 mp_openid;仅有 has_mp。公众号 openid 请用 api. subscribe-status 解析并自行缓存。

PATCH /api/v1/me

Body(均可选,至少一项有意义):

json
{ "nickname": "...", "avatar_url": "https://...", "background_url": "https://..." }
  • avatar_url / background_urlhttp://https://,或空串清空
  • nickname 非空
  • 换绑资料图时平台会删除旧 COS 对象(若属于本平台存储)

邮箱绑定 / 密码 / 微信

方法路径Body / Query说明
POST/api/v1/me/bind-email/code{email}返回 {expires_in}
POST/api/v1/me/bind-email{email, code, password}邮箱占用 409
POST/api/v1/me/unbind-email须已绑微信
POST/api/v1/me/password{current_password, new_password}须已设密码
GET/api/v1/me/bind-wechat/web/startreturn_to={state, authorize_url, redirect_uri}
POST/api/v1/me/unbind-wechat{channel:"web"}须已有邮箱密码

api. — 站点自检

权限: 无(只需 X-Api-Key 有效、站点启用)

GET /api/v1/site

返回本站点身份与各能力就绪状态——对接第一步先调它。permission = 权限位;ready = 权限位 后台已绑定可用配置;missing = 未就绪的精确原因(可直接作为给运营的工单)。

json
{
  "site": {"slug": "yoursite", "name": "YourSite", "domain": "your.site.com"},
  "capabilities": {
    "storage": {
      "permission": true,
      "ready": true,
      "missing": null,
      "read_mode": "public",
      "cdn_auth_enabled": false,
      "public_base": "https://static.example.com"
    },
    "wechat_notify": {
      "permission": true,
      "ready": false,
      "missing": "未绑定公众号配置,或所绑公众号已停用,请联系运营在后台「站点接入」绑定"
    },
    "email_notify": {"permission": true, "ready": true, "missing": null},
    "captcha": {"permission": true, "ready": true, "missing": null},
    "payments": {
      "permission": true,
      "ready": true,
      "missing": null,
      "providers": ["wechat", "alipay"],
      "webhook_configured": true
    }
  },
  "docs": "https://rekitdev.com/skill.md"
}
字段说明
capabilities.*.ready权限位 + 后台已绑配置且启用,才是真正可调用
storage.read_modepublicunsigned_url 是永久链;signedurl 带 CDN 鉴权(默认 3600s 过期、无续期端点,仅即传即用)
storage.cdn_auth_enabled后台原始开关,与 read_mode 可能不一致(开了鉴权但站点无 cdn 权限时,生效仍是 public
payments.providers可用渠道(绑定 + 启用 + 配置完整,与下单运行时同一判定)
payments.webhook_configuredfalse = 到账不推送(后台配 webhook_url 后才有;后配不补历史,靠轮询兜底)

api. — 邮件

api. — Captcha

权限: captcha 浏览器协议: /rc/v1/s/{site_slug}(SDK 自动调用 handshake / assess / challenge / verify) SDK: {SNGZS_API_URL}/rc/v1/rekit.min.js

POST /api/v1/captcha/verify

服务端用站点 Key 校验浏览器拿到的 token。默认 consume:true,同一 token 只能成功一次。

json
{ "token": "...", "action": "可选", "consume": true }

成功:

json
{ "ok": true, "site": "yoursite", "sub": "rekit-pass", "score": 0.92, "expires_at": 1710000000 }

失败:400 captcha_invalid;未开权限:403 permission_denied

权限: email_notify 头: X-Api-Key 路径: /api/v1/notify/*

POST /api/v1/notify/email

json
{ "to": "a@b.com", "subject": "主题", "html": "<p>正文</p>" }
字段约束
to3–254
subject1–200
html非空

成功:{"ok": true} 未绑定 SMTP:503 site_email_unbound SMTP 失败:502

api. — 公众号通知

权限: wechat_notify 头: X-Api-Key 收件人身份: 统一用 unionid(接入方共享同一开放平台主体,只拿得到 unionid;公众号 openid 只有公众号自己拿得到)。平台内部用 members 表映射到投递 openid。 限流: 每站点 120 次/分钟/类,超限 429 rate_limited路径: /api/v1/notify/*

GET /api/v1/notify/subscribe-status

Query:

参数说明
unionid平台用 members 表解析到公众号 openid

成功:

json
{
  "ok": true,
  "openid": "oMp...",
  "subscribed": true,
  "nickname": "昵称",
  "unionid": "oUnion..."
}

失败 / 边界:

情况HTTP / 返回
未传 unionid422
unionid 从未关注(members 无记录)200 {"subscribed": false}(不再报错)
微信 API 错误400
Key / 权限401 / 403

subscribed:微信 cgi-bin/user/infosubscribe == 1(实时)。

POST /api/v1/notify/work-order-status

json
{
  "unionid": "oUnion...",
  "project_name": "你的产品名",
  "status": "账号过期",
  "customer_name": "用户昵称",
  "time": "2026-07-17 18:00",
  "url": "https://站/notify?scene=expired",
  "client_msg_id": "可选防重ID"
}
字段必填约束
unionid接入方共享 unionid;平台内部映射到投递 openid
project_name≤100
status≤100;short_thing 模板常限制约 5 字
customer_name≤100
timeYYYY-MM-DD HH:MM / HH:MM:SS / YYYY/MM/DD HH:MM
url点击跳转
client_msg_id防重

成功:{"ok": true, "msgid": "..."} time 格式非法:400 invalid_time_format 收件人 unionid 从未关注公众号:400 wechat_member_not_found(先用 subscribe-status 预检)

api. — 图床

GET /api/v1/storage/config(可无 Key)

json
{
  "enabled": true,
  "public_base": "https://static.example.com",
  "cdn_auth_enabled": false,
  "path_prefix": "...",
  "max_single_upload_bytes": 5242880,
  "max_user_storage_bytes": 15728640
}

POST /api/v1/storage/open/presign — 权限 storage + Api-Key

请求:

json
{
  "filename": "a.webp",
  "content_type": "image/webp",
  "content_length": 12345,
  "subdir": "products/thumbs"
}
字段必填说明
filename默认 upload.webp
content_type默认 image/webp;仅 jpeg/png/webp/gif
content_length字节;与 PUT body 一致;缺失 → 400 content_length_required;校验 ≤5MB
subdir≤128;落在 {path_prefix}/{site_slug}/

响应:

json
{
  "key": "prefix/slug/subdir/uuid.webp",
  "put_url": "https://cos.../...",
  "url": "https://static.../...",
  "unsigned_url": "https://static.../...",
  "expires_in": 300
}

站点未绑定存储:503 site_storage_unbound

客户端:PUT put_urlContent-Type 与申请一致。

POST /api/v1/storage/presign — Bearer 用户上传

同上 body(content_length 同样必填);计入用户配额(总 ≤15MB)。 响应额外含 quota: {used_bytes, max_bytes, max_single_bytes}

GET /api/v1/storage/quota — Bearer

json
{ "used_bytes": 0, "max_bytes": 15728640, "max_single_bytes": 5242880 }

pay. — 支付

权限: payments(开放接口) 头: X-Api-Key(开放接口)

POST /api/v1/payments/open/checkout/sessions

请求:

json
{
  "mode": "payment",
  "success_url": "https://站/success?session_id={CHECKOUT_SESSION_ID}",
  "cancel_url": "https://站/pricing",
  "user_id": 123,
  "external_customer_id": "站点自管客户标识(与 user_id 二选一)",
  "client_reference_id": "local-order-1",
  "customer_email": "a@b.com",
  "metadata": { "plan_type": "monthly" },
  "line_items": [
    {
      "quantity": 1,
      "price_data": {
        "currency": "cny",
        "unit_amount": 4900,
        "product_data": {
          "name": "商品名",
          "description": "描述",
          "unit_label": "/ 月",
          "features": ["特性"],
          "validity": "",
          "activate_note": "",
          "renewal_note": ""
        }
      }
    }
  ]
}

约束:

  • mode 目前仅 "payment"
  • line_items 恰好 1 条unit_amount > 0,单位
  • 付款方双轨:user_id(Rekit 登录用户)与 external_customer_id(站点自管客户标识)二选一;未接 Rekit 登录用后者即可
  • success_url / cancel_url host 须在站点白名单
  • metadata 值为 string 字典

响应要点(Stripe 风格):

json
{
  "id": "cs_...",
  "object": "checkout.session",
  "url": "https://pay.../c/pay/cs_...",
  "status": "open",
  "payment_status": "unpaid",
  "mode": "payment",
  "amount_total": 4900,
  "currency": "cny",
  "success_url": "...",
  "cancel_url": "...",
  "client_reference_id": "...",
  "customer_email": "...",
  "metadata": {},
  "line_items": [],
  "expires_at": 1710000000,
  "created": 1710000000
}

浏览器跳转 url。履约前再查 Session。

GET /api/v1/payments/open/checkout/sessions/{id}

同站点 Key 可查。payment_status === "paid" 时履约。

POST /api/v1/payments/open/checkout/sessions/{id}/expire

手动过期未支付 Session。

托管页(浏览器,非 JSON)

路径说明
GET /c/pay/{session_id}单页收银台:确认订单 / 选支付方式 / 内联二维码 / 就地切换(/wait 旧链接 302 回本页)

Webhook —— 到账主动推送(order.paid

站点配 webhook_url(后台「站点接入」)后,到账时 Rekit POST 签名事件到该地址。头 X-Rekit-Event-Id / X-Rekit-Event-Type / X-Rekit-Timestamp / X-Rekit-Signature: sha256=<hmac>;签名 = HMAC_SHA256(webhook_secret, "<timestamp>.<raw_body>") hex。Body dataorder_id / out_trade_no / provider / amount_cents / currency / user_id / external_customer_id / site_id / checkout_session_id / client_reference_id / metadata / paid_at。非 2xx 退避重试(~8 次),按 event_id/order_id 幂等发货。详见 SKILL §8.1(含验签示例)。轮询接口仍作兜底。

POST /api/v1/payments/open/orders(兼容直连)

json
{
  "provider": "wechat",
  "amount_cents": 100,
  "subject": "订单标题",
  "user_id": 123,
  "external_customer_id": "站点自管客户标识(与 user_id 二选一)"
}

providerwechat | alipay。付款方 user_idexternal_customer_id 二选一(都没传 → 422)。 传了 user_id 但用户不存在:404 user_not_registered(用 external_customer_id 则不涉及)。未绑定渠道:503。 响应含 order_id / out_trade_no / code_url(微信 NATIVE)/ pay_url(支付宝)。

微信内联 JSAPI:加 "jsapi": true(provider=wechat)→ 响应含 prepay_params{appId,timeStamp, nonceStr,package,signType,paySign}),产品站在自己页面 WeixinJSBridge.invoke('getBrandWCPayRequest', prepay_params, cb) 拉起。付款人 openid 只能来自 Rekit 微信登录(平台按 user_idUser.mp_openid 解析,见 §3 silent=1;该用户须已通过 Rekit 微信登录)。开放下单 body 没有 payer_openid 字段;用 external_customer_id 的外部客户拿不到公众号 openid, JSAPI 不可用,请走 NATIVE 扫码 / 支付宝 / 托管收银台。错误:409 payer_mp_openid_missing409 jsapi_appid_mismatch(支付 appid≠公众号 appid)→ 均应回退扫码。

GET /api/v1/payments/open/orders/{order_id}(站点 Key,轮询兜底)

site_id 隔离取单 → 返回 statuspending|paid|...)等;仅 paid 履约。webhook order.paid 主推之外的兜底腿。订单不存在或跨站:404 order_not_found

POST /api/v1/payments/open/orders/{order_id}/cancel(站点 Key)

取消 pending 订单(与下单/查单同一条开放路)。按 site_id 隔离,防跨站。订单不存在或跨站:404 order_not_found

下单 / 查单 / 取消对外只有 /api/v1/payments/open/* 一套(站点 Key)。无 Bearer 版订单接口。

支付结果异步通知路径(平台内部验签,产品站一般不对接): /api/v1/payments/notify/{provider}/{config_id}

权限目录

id标签服务
storage图床上传api
cdnCDN 鉴权(影响返回的 url 是否签名)api
wechat_notify公众号查关注 / 模板消息api
email_notify邮件通知api
payments支付pay
captcha人机验证api
Cookie用途
sngzs_user_token产品站 HttpOnly 存 access_token(参考站约定名)
sngzs_auth_return可选,暂存登录后相对路径

说明:login. 服务自身也可能 Set-Cookie 名为 user_token;产品站应以 callback 写入本站 Cookie 为准,不要依赖跨域读 login. 的 Cookie。

Callback query:login_code(一次性消费、90s 有效)、returnTo(相对路径)。站点服务端用 login_codePOST /api/v1/auth/exchange 换取 access_token(旧 ?access_token= 直回协议已废止)。

参考代码:rekitdev/src/lib/auth-session.tsrekitdev/src/pages/api/auth/callback.tsrekitdev/src/lib/sngzs-client.ts