跳转至

Brand Partner API

一个小巧、可预测的 REST API,用于创建 mintbot 订单、轮询订单状态,并接收生命周期事件。输入 JSON,输出 JSON。使用 Bearer token 认证,写入操作支持幂等性,webhook 会附带签名。

API 访问控制台 跳转到 webhooks


概览

基础 URL https://mint.mintbot.ai/api/v1
认证 Authorization: Bearer mo_live_…
幂等性 除 POST /dns/records 外,每个 POST 都使用 Idempotency-Key: <uuid>
内容类型 application/json
速率限制 每个合作伙伴 120 req / 60 s
Webhooks 使用 HMAC-SHA256 签名,最多重试 7 次

认证

每个请求都要在 Authorization header 中发送合作伙伴 API key。你可以在 控制台 生成或轮换 key。

Authorization: Bearer mo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

轮换没有宽限窗口

轮换 key 会以原子方式撤销前一个 key。请在点击 Rotate 之前,先计划好如何更新你的配置。

幂等性

每个 POST 都需要 Idempotency-Key header——POST /dns/records 除外,它在服务商层天然幂等,不带该 header 也接受重试。不同请求使用任意 UUID 即可。

  • 我们会缓存响应 24 小时。用同一 key 重试时,会通过 Idempotent-Replay: true 重放原始响应。
  • 使用同一 key 但提交不同 body时,会返回 409 idempotency_key_mismatch。

订单

创建订单

POST /orders

创建一个订单并返回 Stripe Checkout URL。付款确认后,mintbot 会预配智能体,并自动记录合作伙伴分成收入事件。

请求

{
  "tier": "mo_month",
  "duration_months": 1,
  "credit_usd": 10,
  "language": "en",
  "external_id": "your-side-id",
  "success_url": "https://your.app/thanks?id={ORDER_ID}",
  "cancel_url":  "https://your.app/cart",
  "webhook_url": "https://your.app/mintbot-webhook"
}
tier
mo_test(Test 24h)、mo_month(Standard)、mo_month_plus(Performance)之一。
duration_months
服务器使用期限,按自然月计算。必须是 1、3 或 12 之一。无论你发送什么,mo_test 始终是一次 24 小时的运行。
credit_usd
可选,默认 0。 取值为 0、5、10、20、50 之一。随订单附带的预付聊天额度(美元)。它会加到订单总额中并归 mintbot 所有——不计入你的利润。
language
可选。 影响 Stripe Checkout 区域设置和欢迎邮件语言。
external_id
可选。 会在每个 webhook 和订单响应中原样返回——可用它把 mintbot 订单关联到你自己系统中的记录。
success_url · cancel_url
Stripe Checkout 返回 URL。{ORDER_ID} 会在服务端替换。
webhook_url
可选。 针对此订单覆盖合作伙伴级 webhook URL。
product_name
可选。 客户在 Stripe Checkout 上看到的商品行名称(最多 250 个字符)。默认生成 TIER · N months 形式的名称。
price_usd
可选。 以美元覆盖整个订单的价格。不能低于该套餐和时长的批发底价——否则返回 400 validation_error。

响应 — 201 Created

{
  "id": 42,
  "tier": "mo_month",
  "duration_months": 1,
  "credit_usd": 10,
  "amount_cents": 1200,
  "currency": "usd",
  "status": "pending",
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_…",
  "panel_url": null,
  "expires_at": null,
  "language": "en",
  "external_id": "your-side-id",
  "created_at": "2026-05-16 09:58:00",
  "paid_at": null,
  "price_cents": 1200,
  "partner_cut_cents": 200
}

如果你处于自定义定价模式且尚未给这个套餐定价,响应中还会包含 "pricing_fallback": true。

示例

curl -X POST https://mint.mintbot.ai/api/v1/orders \
  -H "Authorization: Bearer $MINTBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "tier": "mo_month",
    "duration_months": 1,
    "credit_usd": 10,
    "success_url": "https://your.app/thanks?id={ORDER_ID}",
    "cancel_url":  "https://your.app/cart"
  }'
import os, uuid, requests

r = requests.post(
    "https://mint.mintbot.ai/api/v1/orders",
    headers={
        "Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "tier": "mo_month",
        "duration_months": 1,
        "credit_usd": 10,
        "success_url": "https://your.app/thanks?id={ORDER_ID}",
        "cancel_url":  "https://your.app/cart",
    },
    timeout=10,
)
r.raise_for_status()
order = r.json()
redirect_to = order["checkout_url"]

获取订单

GET /orders/{id}

通过 mintbot id 获取单个订单。返回结构与 POST /orders 相同。

curl https://mint.mintbot.ai/api/v1/orders/42 \
  -H "Authorization: Bearer $MINTBOT_API_KEY"

列出订单

GET /orders

游标分页列表,最新的在前。

查询参数

status
可选。 按 pending、completed 或 expired 筛选。已付款的订单在整个部署过程中都保持 completed——请通过 agent.* webhook 跟踪部署进度。
cursor
可选。 传入上一页的 next_cursor 以继续。
limit
可选。 每页条数,默认 50,最大 200。

响应

{
  "items": [ /* OrderResponse … */ ],
  "next_cursor": "37"
}

当没有更多页面时,next_cursor 为 null。


续订订单

POST /orders/{id}/renew

将现有智能体延长 duration_months 个月。这里只续基础设施——不附带新的聊天额度。返回新的 order id 和新的 Stripe Checkout URL。

请求

{
  "duration_months": 1,
  "external_id": "your-side-id",
  "success_url": "https://your.app/thanks?id={ORDER_ID}",
  "cancel_url":  "https://your.app/account"
}

订阅

创建订阅

POST /subscriptions

以订阅模式打开 Stripe Checkout:客户为月度套餐按月付款,订阅自动续费。与 POST /orders 一样,它需要 Idempotency-Key,且合作伙伴账户必须处于 live 状态。月费来自你对该套餐的定价。

请求

{
  "tier": "mo_month",
  "language": "en",
  "success_url": "https://your.app/thanks",
  "cancel_url":  "https://your.app/cart",
  "customer_email": "customer@example.com",
  "external_id": "your-side-id"
}

customer_email、agent_id(要绑定订阅的你的现有智能体)、external_id 和 product_name 为可选字段。

响应 — 201 Created 包含 id、tier、currency、amount_cents、partner_cut_cents、status、checkout_url、current_period_end、cancel_at_period_end、agent_id、language、customer_email、external_id 和 created_at。

获取订阅

GET /subscriptions/{id}

返回与 POST /subscriptions 相同的结构。


收入

读取收入

GET /revenue

返回汇总数据以及最新 200 个账本事件。

查询参数

include_paid
可选,默认 true。 设为 false 时只查看尚未支付的事件。

响应

{
  "currency": "usd",
  "gross_cents": 12000,
  "partner_cut_cents": 2000,
  "mintbot_cut_cents": 10000,
  "unpaid_cents": 800,
  "events": [
    {
      "id": 7,
      "order_id": 42,
      "kind": "order_paid",
      "gross_cents": 1200,
      "partner_cut_cents": 200,
      "mintbot_cut_cents": 1000,
      "currency": "usd",
      "created_at": "2026-05-16 09:58:00",
      "payout_id": null,
      "payout_at": null
    }
  ]
}

合作伙伴资料

获取资料

GET /partner

返回你的合作伙伴资料以及未支付余额——适合在你自己的管理 UI 中展示收益,而无需自行存储。

响应

{
  "id": 12,
  "email": "you@kliendifirma.com",
  "pricing_currency": "usd",
  "balance_unpaid_cents": 800,
  "webhook_url": "https://your.app/mintbot-webhook",
  "api_key_prefix": "mo_live_a12b"
}

设置

获取设置

GET /settings

在一个 payload 中返回你完整的非机密合作伙伴配置——所选模式、apex 域名、DNS 服务商、机器人服务商、定制仓库、子域名模式(附带渲染好的预览标签)、各套餐定价、API key 元数据、webhook 元数据,以及驱动 MintOffice 控制台横幅的同一套就绪状态 / 分区状态。

专为需要了解合作伙伴是如何配置的、又不想抓取控制台或询问运营人员的集成智能体设计。典型的调用方是在新服务器上安装你的白标配置的编码智能体——它可以读取这个端点,根据 readiness.status 分支,并准确告诉运营人员哪些部分还需要处理。

绝不返回任何机密

合作伙伴作为凭据提供的任何内容——Telegram 机器人 token、Zone.ee API key、webhook 签名密钥、API key 明文本身——都只以 *_set 布尔值的形式出现。webhook 密钥还会额外返回控制台中已经显示的那个 8 字符显示前缀,让调用方无需看到值本身就能确认配置的是哪一个密钥。

响应

{
  "id": 12,
  "email": "you@kliendifirma.com",
  "status": "live",
  "created_at": "2026-05-12 14:22:01",
  "onboarded_at": "2026-05-12 14:48:33",
  "activated_at": "2026-05-13 09:10:05",
  "last_login_at": "2026-05-20 00:51:18",
  "preferred_language": "en",
  "pricing_currency": "usd",
  "balance_unpaid_cents": 800,

  "domain": {
    "mode": "own",
    "client_apex_domain": "agent99.cc",
    "dns_provider": "zone_ee",
    "zone_ee_username": "you@kliendifirma.com",
    "zone_ee_api_key_set": true,
    "subdomain_prefix": "agent",
    "subdomain_numbering": "text",
    "subdomain_pad_width": 3,
    "subdomain_label_preview": "agent1"
  },

  "bot": {
    "mode": "own",
    "provider": "telegram",
    "telegram_bot_username": "agent99cc_bot",
    "telegram_bot_token_set": true
  },

  "template": {
    "mode": "default",
    "repo_url": null
  },

  "api": {
    "key_prefix": "mo_live_a12b",
    "key_created_at": "2026-05-13 09:11:42",
    "webhook_url": "https://your.app/mintbot-webhook",
    "webhook_secret_set": true,
    "webhook_secret_prefix": "ab12cd34"
  },

  "pricing": {
    "mode": "default",
    "currency": "usd",
    "tiers": {
      "mo_test":       { "price_cents": 100,  "partner_cut_cents": 0,   "updated_at": "2026-05-13 09:05:00" },
      "mo_month":      { "price_cents": 1200, "partner_cut_cents": 200, "updated_at": "2026-05-13 09:05:00" },
      "mo_month_plus": { "price_cents": 1800, "partner_cut_cents": 300, "updated_at": "2026-05-13 09:05:00" }
    },
    "allowed_credit_options": [10, 20, 50]
  },

  "readiness": {
    "ready": true,
    "missing_fields": [],
    "dns_blocker": null,
    "status": "live"
  },

  "section_status": {
    "domain":   { "status": "ok",      "mode": "own",     "issue": null, "dns_blocker": null },
    "bot":      { "status": "ok",      "mode": "own",     "issue": null, "dns_blocker": null },
    "template": { "status": "default", "mode": "default", "issue": null, "dns_blocker": null },
    "api":      { "status": "ok",      "mode": "active",  "issue": null, "dns_blocker": null },
    "all_ready": true
  }
}

字段说明

status
合作伙伴生命周期。取值为 onboarding、live、suspended 之一。除非状态为 live,Brand Partner API 会拒绝写入操作;只有 mintbot 团队可以把你切换到 live。
domain.mode
own(使用你自己的 apex 域名)或 mintbot(由 mintbot 提供 *.mintbot.ai 子域名)。为 mintbot 时,无论合作伙伴之前填写过什么,DNS 服务商相关字段都会刻意留空。
domain.subdomain_label_preview
在当前 subdomain_prefix、subdomain_numbering 和 subdomain_pad_width 下,第一个客户智能体将得到的渲染标签(agent1、1、001……)。可用于“你的客户将看到这样”的预览,无需自己重新实现编号规则。
bot.mode
own(合作伙伴自己的 Telegram 机器人)、mintbot(测试期间使用 mintbot 的机器人)或 none(仅面板,无 Telegram)。
template.mode
default(没有智能体定制仓库——智能体运行在不带品牌的基础版本上)或 own(合作伙伴位于 repo_url 的智能体定制仓库)。为 own 时,智能体自己的 VPS 会在标准部署之后克隆该仓库,并运行其中的 install.sh / update.sh,在基础之上应用你的人设、面板主题和任何额外工具——central 从不亲自执行该仓库。需要自定义品牌时,把 repo_url 指向你在 GitHub 上的 HTTPS 智能体定制仓库。
api.webhook_secret_prefix
webhook 签名密钥的前 8 个字符,仅在已配置密钥时出现。调用方无需看到完整值即可区分正在使用的是哪一个密钥。控制台显示相同的前缀。
pricing.allowed_credit_options
你向客户提供的 credit_usd 额度选项(在 Settings → Pricing 中选择)。
readiness.missing_fields
Go Live 之前仍需填写的控制台字段名列表。ready 为 true 时为空。足够稳定,可以用来在你自己的后台中驱动“还缺什么”界面。
readiness.dns_blocker
DNS 服务商检查失败时的可读原因(例如 partner has no zone.ee username configured)。否则为 null。
section_status.*
对应控制台中各分区的状态标签。status 为 ok、warn 或 default(正在使用 mintbot 默认值);issue 指出阻塞该分区的字段(例如 client_apex_domain、telegram_bot_token、dns_blocker),否则为 null。

示例

curl https://mint.mintbot.ai/api/v1/settings \
  -H "Authorization: Bearer $MINTBOT_API_KEY"
import os, requests

r = requests.get(
    "https://mint.mintbot.ai/api/v1/settings",
    headers={"Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}"},
    timeout=10,
)
r.raise_for_status()
settings = r.json()

if not settings["readiness"]["ready"]:
    print("Still missing:", settings["readiness"]["missing_fields"])

目录

面向你的店面的可售套餐列表——Brand Partner 套餐(mo_test / mo_month / mo_month_plus)与你的最终定价合并,并以你的定价货币表示。用它来渲染套餐卡片,而不是硬编码套餐标识和价格:下架的套餐会自动消失,你在控制台中修改的价格或货币会在缓存窗口内同步到店面——无需重新部署。

正是这个端点让店面不会继续宣传过时的套餐。参考门户在每次渲染首页 / /buy / /extend 时都会调用它(带缓存和粘性回退)。

展示文案由你决定

display_name / description / featured 是 mintbot 的标准套餐元数据。如果你想为自己的品牌重命名套餐或改写简介,请在你自己的店面中覆盖(参考门户为此专门提供了 PLAN_OVERRIDES 映射)——目录是有哪些套餐、价格多少的权威来源,而不是你的营销文案的来源。

获取目录

GET /api/v1/catalog

没有参数。只需 Bearer 认证——没有激活门槛,因此在你的合作伙伴账户切换为 live 之前,店面就可以渲染目录。

响应

{
  "currency": "usd",
  "packages": [
    {
      "tier": "mo_test",
      "display_name": "Test 24h",
      "description": "2 vCPU · 4 GB RAM · 40 GB SSD · 24-hour evaluation. EU.",
      "featured": false,
      "default_credit_usd": 5,
      "durations": [
        { "months": 1, "label": "24 hours", "price_cents": 0 }
      ],
      "subscription": { "available": false, "price_cents": null }
    },
    {
      "tier": "mo_month",
      "display_name": "Standard",
      "description": "2 vCPU · 4 GB RAM · 40 GB SSD, monthly. EU.",
      "featured": false,
      "default_credit_usd": 10,
      "durations": [
        { "months": 1,  "label": "1 month",   "price_cents": 1500 },
        { "months": 3,  "label": "3 months",  "price_cents": 4500 },
        { "months": 12, "label": "12 months", "price_cents": 18000 }
      ],
      "subscription": { "available": true, "price_cents": 1500 }
    }
  ],
  "credit_options": [
    { "usd": 5,  "price_cents": 500 },
    { "usd": 10, "price_cents": 1000 }
  ]
}
字段 含义
currency 你的定价货币。下面所有的 price_cents 都以该货币计价。
packages[] 每个公开套餐一条,按提供 / 展示顺序排列。
tier 传给 POST /orders / POST /subscriptions 的套餐标识。
display_name / description / featured 标准展示文案(如有需要可在店面中覆盖)。
default_credit_usd 套餐附带的 LLM 额度提示(仅供参考)。
durations[] 该套餐可购买的开通时长。months 是你作为 duration_months 发送的值;price_cents 是订单将收取的金额。月度套餐提供 1 / 3 / 12 个月;mo_test 只提供一个 24 小时的时长。
subscription 对可以通过 POST /subscriptions 以按月自动续费方式购买的套餐,available 为 true;price_cents 为每月金额。mo_test 永远不支持订阅。
credit_options[] 以你的货币定价的每个 credit_usd 额度选项(usd = 额度金额,price_cents = 它在订单总额上增加的金额)。你提供哪些选项由 GET /settings 中的 pricing.allowed_credit_options 决定。

价格与实际收费一致

每个 price_cents 都使用与订单端点相同的定价解析器计算,因此卡片上的价格始终等于该 (tier, duration_months) 最终的 Stripe Checkout 总额。

示例

curl -s https://mint.mintbot.ai/api/v1/catalog \
  -H "Authorization: Bearer $MINTBOT_API_KEY" | jq
import os, requests

r = requests.get(
    "https://mint.mintbot.ai/api/v1/catalog",
    headers={"Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}"},
    timeout=10,
)
r.raise_for_status()
catalog = r.json()

for pkg in catalog["packages"]:
    month1 = next(d for d in pkg["durations"] if d["months"] == 1)
    print(pkg["display_name"], month1["price_cents"], catalog["currency"])

DNS 记录

Brand Partner 集成经常需要在合作伙伴的 apex 域名上创建 DNS 记录——把 @、www 或每个客户的子域名指向正确的服务器。MintOffice 提供了一个与服务商无关的代理,让你无需在自己的基础设施中接触上游服务商的 API key 就能完成这些操作。你在入驻时保存的凭据(目前为 zone.ee;cloudflare 和 route53 已在路线图中,接口形式相同)在 MintOffice 中保持加密;你的调用使用 Bearer mo_live_…,由我们代你调用上游。

作用范围是合作伙伴自己配置的 apex(GET /settings 中的 domain.client_apex_domain)。你无法修改不属于你的 zone 中的记录。

如果你的 DNS 服务商是 Manual,代理不会做任何更改:写入操作不生效,列表返回为空。

目前代理支持部署流水线自身使用的操作——A 记录(upsert / 列出 / 按 id 删除)。等底层服务商抽象支持后,CNAME / TXT / MX 也会以相同的形式提供。

这里不需要 Idempotency-Key

与创建订单的端点不同,DNS upsert 在服务商层天然幂等(用相同的 (subdomain, ip) 重复执行不会产生变化,且每次调用都会清理过期的重复记录)。你不需要发送 Idempotency-Key header——重试最终总会收敛为每个 FQDN 一条 A 记录。

Upsert A 记录

POST /dns/records

幂等地把 <subdomain>.<your-apex> 指向一个 IPv4 地址。apex 本身请传 ""(或 "@")。

请求

{
  "subdomain": "www",
  "ip": "203.0.113.10"
}

subdomain 接受:

  • "" 或 "@" —— apex 本身(example.com)。
  • 单个 DNS 标签 —— "www"、"api" —— 对应 <label>.<apex>。
  • 以点分隔的子标签 —— "api.eu" —— 对应嵌套子域名(api.eu.example.com)。

ip 必须是点分十进制的 IPv4 地址。每个标签为 [a-z0-9_-],长度 1–63 个字符,不能以 - 开头或结尾。

响应 — 200

{
  "apex": "example.com",
  "provider": "zone_ee",
  "fqdn": "www.example.com",
  "records": [
    { "id": "9182734", "name": "www.example.com", "destination": "203.0.113.10" }
  ]
}

写入后返回的 records 列表始终反映上游服务商在清理重复记录之后对该 FQDN 的当前视图——因此,成功响应中恰好只有一条记录,就表示没有残留任何过期记录。

列出记录

GET /dns/records

返回已配置 apex 上的所有 A 记录。添加 ?name=<label-or-fqdn> 可筛选到单个 FQDN——值可以是单个标签("www")、带点的子域名("api.eu")或完整 FQDN("www.example.com")。

响应 — 200

{
  "apex": "example.com",
  "provider": "zone_ee",
  "items": [
    { "id": "9182734", "name": "example.com",     "destination": "203.0.113.10" },
    { "id": "9182735", "name": "www.example.com", "destination": "203.0.113.10" }
  ]
}

删除记录

DELETE /dns/records/{record_id}

record_id 是 GET /dns/records 返回的服务商标识(zone.ee 使用整数 id,在 API 边界处转为字符串)。删除已不存在的记录会被视为成功——该端点在客户端看来是幂等的。

响应 — 200

{ "deleted": true, "id": "9182734" }

错误

HTTP error.code 原因
401 unauthenticated / invalid_api_key 缺少 Bearer token 或 token 无效。
422 validation_error 子域名无效,或目标不是 IPv4 地址。
422 dns_not_configured 合作伙伴没有 client_apex_domain,或缺少所配置服务商的凭据。请在 Settings → Domain 中修复。
422 dns_provider_unsupported 合作伙伴的 DNS 服务商尚未接入 MintOffice 代理(例如 cloudflare 占位项)。目前请切换为 zone_ee。
502 dns_provider_error 上游服务商返回了 4xx/5xx,或传输失败。错误 message 中包含上游状态码和一段已去除机密的上游响应摘录。可以安全重试。

示例

# Point apex + www at your server's IP.
curl https://mint.mintbot.ai/api/v1/dns/records \
  -H "Authorization: Bearer $MINTBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subdomain": "",    "ip": "203.0.113.10"}'

curl https://mint.mintbot.ai/api/v1/dns/records \
  -H "Authorization: Bearer $MINTBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subdomain": "www", "ip": "203.0.113.10"}'

# Verify.
curl "https://mint.mintbot.ai/api/v1/dns/records" \
  -H "Authorization: Bearer $MINTBOT_API_KEY"
import os, requests

URL = "https://mint.mintbot.ai/api/v1/dns/records"
H = {"Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}"}

for sub in ("", "www"):
    r = requests.post(URL, headers=H, json={"subdomain": sub, "ip": "203.0.113.10"})
    r.raise_for_status()
    print(sub or "@", r.json()["records"])

Webhooks

当订单发生变化时,我们会向你配置的 webhook URL POST 一个已签名的 JSON 事件。

Webhooks 是可选的

Settings → API access 中的 Webhook URL 和 Webhook signing secret 完全可选——如果你还没有接收端,入驻时可以先留空。只有两者都设置后才会开始投递——只有 URL 而没有签名密钥时,不会发送任何事件。控制台和 API 的其余部分在没有 webhook 的情况下也能正常工作;你可以改为轮询 GET /api/v1/orders/{id} 来跟踪生命周期。准备好后再添加 URL(并生成密钥)即可,无需重新激活。

事件类型

Event 触发时机
order.created Stripe Checkout session 已创建,等待付款。
order.paid Stripe 已确认付款。收入事件已记录。
order.cancelled 订单超时或被明确取消。
agent.provisioning_started 此订单的部署流水线已启动。
agent.ready 部署成功。Payload 包含 panel_url 和 expires_at。
agent.failed 部署流水线出错。error_step 指出出错的步骤;error_message 是简短说明。
agent.expired 智能体 TTL 已到期。合作伙伴可通过 POST /orders/{id}/renew 续订。
agent.resumed 已过期的智能体在宽限期内完成续订,重新上线。是 agent.expired 对应的恢复事件。
subscription.created · subscription.activated · subscription.updated · subscription.cancelled 通过 POST /subscriptions 创建的订阅的生命周期。
partner.activated · partner.deactivated mintbot 团队激活了你的账户,或将其移出 live(payload 中的 suspended 表明账户是被暂停还是退回到入驻阶段)。

投递与重试

  • 按指数计划最多尝试 7 次:0s, 30s, 2m, 10m, 1h, 6h, 24h。
  • 最后一次尝试后,投递会标记为 exhausted 并停止重试。
  • 在 10 秒 内返回 2xx 状态以确认接收。

请求 headers

Content-Type: application/json
User-Agent: mintbot-webhook/1.0
X-Mintbot-Signature: t=<unix_ts>,v1=<hex_hmac_sha256>
X-Mintbot-Event-Id: evt_42_order.paid_1747371234567
X-Mintbot-Event-Type: order.paid

示例 payload — order.paid

{
  "id": 42,
  "tier": "mo_month",
  "duration_months": 1,
  "currency": "usd",
  "amount_cents": 1200,
  "status": "completed",
  "external_id": "your-side-id",
  "created_at": "2026-05-16 09:58:00",
  "paid_at": "2026-05-16 10:02:14",
  "expires_at": null
}

验证签名

签名为 HMAC-SHA256(secret, "{timestamp}.{raw_body}")。务必验证 raw request body——重新序列化解析后的 JSON 会导致比较失败。

import hmac, hashlib, time

def verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
    try:
        ts_part, v1_part = header.split(",", 1)
        ts  = int(ts_part.split("=", 1)[1])
        sig = v1_part.split("=", 1)[1]
    except Exception:
        return False
    if abs(int(time.time()) - ts) > tolerance:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{ts}.".encode() + body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, sig)
const crypto = require("crypto");

function verify(secret, rawBody, header, toleranceSec = 300) {
  const [tsPart, v1Part] = header.split(",");
  const ts  = Number(tsPart.split("=")[1]);
  const sig = v1Part.split("=")[1];
  if (Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.`)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(sig, "hex"),
  );
}

幂等接收方

使用 X-Mintbot-Event-Id 作为去重 key——重试会复用同一个 id,因此在该列上执行行级 INSERT … ON CONFLICT DO NOTHING 可以保证你的处理程序安全。

参考门户

一个可运行的端到端参考实现位于 mintbot-ai/partner-portal-example——FastAPI + SQLite,已 Docker 化,附带一个名为 ExampleAI 的示例品牌,供你 fork 后替换成自己的。架构全景——店面、本 API、部署 worker 和定制仓库如何协同——见 MintOffice — 技术说明。该门户实现了:

  • 首页 / 套餐选择 / 感谢 / 取消页面,
  • POST /buy → POST /api/v1/orders → 跳转到 Stripe,
  • 带有上文 HMAC 校验的 POST /webhooks/mintoffice,
  • 一个使用 HTTP Basic 认证的 /admin 事件浏览器,方便你快速查看投递情况。

fork 它,在 .env 中设置 PARTNER_BRAND、MINTOFFICE_API_KEY 和 MINTOFFICE_WEBHOOK_SECRET,运行 docker compose up,然后把你的合作伙伴账户的 webhook URL 指向 https://<your-host>/webhooks/mintoffice。完整步骤见 MintOffice → 参考门户。


错误

每个错误响应都带有稳定的 code,便于你在程序中分支处理。message field 是给人看的提示,可能会在不同版本之间变化。request_id 会回显 X-Request-Id response header——提交支持工单时请附上它。

{
  "error": {
    "code": "invalid_api_key",
    "message": "API key is unknown or revoked.",
    "request_id": "f0c2d6c4-…"
  }
}
Code 含义
unauthenticated 缺少 Authorization header 或格式错误。
invalid_api_key API key 未知或已被轮换失效。
rate_limited 超出每个合作伙伴或每 IP 的速率限制——请查看 Retry-After。
missing_idempotency_key POST request 缺少 Idempotency-Key。
idempotency_key_mismatch 同一 key 被用于不同的 request body。
validation_error Request body 未通过 schema validation。
not_found 资源不存在,或不属于你的合作伙伴账号。
partner_not_live HTTP 403 —— 在账户不是 live 状态时执行写入(POST /orders、/orders/{id}/renew、/subscriptions)。body 中包含 status,处于入驻阶段的账户还会包含 missing_fields + dns_blocker。
conflict HTTP 409 —— 使用相同 Idempotency-Key 的请求仍在处理中。请在 Retry-After 之后重试。
unpriced_tier HTTP 409 —— 该套餐没有可收费的价格。
internal_error 意外的服务器端错误。联系支持时请附上 request_id。
payment_gateway_error Stripe 拒绝创建 Checkout session。
dns_not_configured 对没有 apex 或没有 DNS 凭据的合作伙伴调用 POST /dns/records。请在 Settings → Domain 中修复。
dns_provider_unsupported 合作伙伴的 dns_provider 值尚未接入 MintOffice 代理。
dns_provider_error 上游 DNS 服务商返回错误——消息中包含已去除机密的摘录。
quota_exceeded HTTP 429,仅限 DEV 环境(mint.mintbot.dev)——LIVE 没有按合作伙伴的上限。合作伙伴已用完所有部署名额(默认 10 个,支付 Brand Partner 开通费后为 20 个)。如需提高上限,请联系 mintbot 支持。由 POST /orders 和 POST /orders/{id}/renew 返回。body 中包含 deploys_used + deploy_quota。

速率限制

  • 每个合作伙伴 120 请求 / 60 s 滚动窗口。突发和稳定流量共享同一个 bucket。
  • 60 请求 / 60 s 的独立每 IP bucket,用于未认证和认证失败请求——避免 bearer 暴力破解消耗合作伙伴配额。
  • 超额请求会返回 429 rate_limited,并带有 Retry-After header(需要退避的秒数)。

需要帮助?

这些文档是写给真正使用 API 的合作伙伴看的。如果有内容缺失、令人困惑或过时,请告诉你的 mintbot 智能体——它会转发反馈,我们会更新页面。