Brand Partner API¶
一个小巧、可预测的 REST API,用于创建 mintbot 订单、轮询订单状态,并接收生命周期事件。输入 JSON,输出 JSON。使用 Bearer token 认证,写入操作支持幂等性,webhook 会附带签名。
概览¶
| 基础 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"
}
tiermo_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.modeown(使用你自己的 apex 域名)或mintbot(由 mintbot 提供*.mintbot.ai子域名)。为mintbot时,无论合作伙伴之前填写过什么,DNS 服务商相关字段都会刻意留空。domain.subdomain_label_preview- 在当前
subdomain_prefix、subdomain_numbering和subdomain_pad_width下,第一个客户智能体将得到的渲染标签(agent1、1、001……)。可用于“你的客户将看到这样”的预览,无需自己重新实现编号规则。 bot.modeown(合作伙伴自己的 Telegram 机器人)、mintbot(测试期间使用 mintbot 的机器人)或none(仅面板,无 Telegram)。template.modedefault(没有智能体定制仓库——智能体运行在不带品牌的基础版本上)或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-Afterheader(需要退避的秒数)。
需要帮助?¶
这些文档是写给真正使用 API 的合作伙伴看的。如果有内容缺失、令人困惑或过时,请告诉你的 mintbot 智能体——它会转发反馈,我们会更新页面。