Brand Partner API¶
Невеликий і передбачуваний REST API для створення замовлень mintbot, перевірки їхнього статусу та отримання подій життєвого циклу. JSON на вході, JSON на виході. Автентифікація bearer-токеном. Ідемпотентні записи. Підписані webhooks.
Панель доступу до API Перейти до webhooks
Коротко¶
| Base URL | https://mint.mintbot.ai/api/v1 |
| Auth | Authorization: Bearer mo_live_… |
| Idempotency | Idempotency-Key: <uuid> для кожного POST, крім POST /dns/records |
| Content type | application/json |
| Rate limit | 120 req / 60 s на партнера |
| Webhooks | Підписуються HMAC-SHA256, повторюються до 7 разів |
Автентифікація¶
Кожен запит надсилає API-ключ партнера в заголовку Authorization. Створіть або ротуйте ключ у панелі керування.
Authorization: Bearer mo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Ротація не має пільгового вікна
Ротація ключа атомарно відкликає попередній. Заплануйте заміну значення у своїй конфігурації до натискання Rotate.
Ідемпотентність¶
Кожен POST потребує заголовка Idempotency-Key — крім POST /dns/records, який природно ідемпотентний на рівні провайдера й приймає повторні спроби без нього. Підійде будь-який UUID для кожного окремого запиту.
- Ми кешуємо відповідь протягом 24 годин. Повторна спроба з тим самим ключем відтворює початкову відповідь із
Idempotent-Replay: true. - Повторне використання того самого ключа з іншим тілом повертає
409 idempotency_key_mismatch.
Замовлення¶
Створити замовлення¶
POST /orders
Створює замовлення та повертає URL Stripe Checkout. Коли платіж підтверджено, 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. Передоплачений кредит для чату (USD), що входить до замовлення. Він додається до суми замовлення й іде mintbot — до вашої маржі він не входить. language- Необов’язково. Впливає на локаль Stripe Checkout і мову вітального email.
external_id- Необов’язково. Повертається в кожному webhook і відповіді замовлення — використовуйте його, щоб пов’язувати замовлення mintbot із рядками у власній системі.
success_url·cancel_url- URL повернення Stripe Checkout.
{ORDER_ID}підставляється на боці сервера. webhook_url- Необов’язково. Перевизначення URL webhook рівня партнера для конкретного замовлення.
product_name- Необов’язково. Назва позиції, яку клієнт бачить у Stripe Checkout (до 250 символів). За замовчуванням генерується назва на кшталт
TIER · N months. price_usd- Необов’язково. Перевизначення ціни всього замовлення в 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упродовж усього деплою — стежте за перебігом деплою через webhooksagent.*. cursor- Необов’язково. Передайте
next_cursorз попередньої сторінки, щоб продовжити. limit- Необов’язково. Розмір сторінки, за замовчуванням
50, максимум200.
Відповідь
{
"items": [ /* OrderResponse … */ ],
"next_cursor": "37"
}
next_cursor дорівнює null, коли сторінок більше немає.
Продовжити замовлення¶
POST /orders/{id}/renew
Подовжує чинного агента ще на duration_months. Лише інфраструктура — новий кредит для чату не додається. Повертає новий id замовлення та новий URL Stripe Checkout.
Запит
{
"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
Повертає ваш профіль партнера плюс невиплачений баланс — зручно, щоб показувати заробіток у власній адмін-панелі без самостійного зберігання цих даних.
Відповідь
{
"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-провайдера, bot-провайдера, репозиторій кастомізації, шаблон субдомену (з відрендереним прев'ю-лейблом), ціноутворення для кожного тарифу, метадані API-ключа, метадані webhook і той самий статус готовності / статус кожної секції, який керує банером панелі MintOffice.
Розроблено для інтеграційних агентів, яким потрібно з'ясувати, як налаштований партнер, без скрейпінгу панелі чи опитування оператора. Типовий виклик — це кодувальний агент, що встановлює ваш white-label-набір на свіжому сервері — він може прочитати цей endpoint, розгалужити логіку за readiness.status і точно сказати оператору, які секції ще потребують уваги.
Жоден секрет ніколи не повертається
Усе, що партнер надав як обліковий дані — токен Telegram-бота, API-ключ Zone.ee, секрет підпису webhook, сам відкритий текст API-ключа — з'являється лише як булеве *_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. Brand Partner API відхиляє записи, доки статус неlive; перевести вас уliveможе лише команда mintbot. domain.modeown(ви використовуєте власний apex-домен) абоmintbot(mintbot надає піддомен*.mintbot.ai). У режиміmintbotполя DNS-провайдера навмисно порожні, незалежно від того, що партнер вводив раніше.domain.subdomain_label_preview- Відрендерений лейбл, який отримав би перший клієнтський агент (
agent1,1,001, …) за поточнихsubdomain_prefix,subdomain_numberingіsubdomain_pad_width. Використовуйте його для прев'ю «ось що побачать ваші клієнти» без повторної реалізації правил нумерації. bot.modeown(Telegram-бот партнера),mintbot(бот mintbot на час тестування) абоnone(лише панель, без Telegram).template.modedefault(немає репозиторію кастомізації агента — агент працює на простій небрендованій базі) абоown(партнерський agent customization repo заrepo_url). Колиown, власний VPS агента клонує цей репозиторій після стандартного деплою і запускає йогоinstall.sh/update.sh, щоб застосувати вашу персону, тему панелі та будь-яке додаткове оснащення поверх бази — центр ніколи не виконує сам репозиторій. Спрямуйтеrepo_urlна свій GitHub HTTPS agent customization repo, коли хочете кастомний брендинг.api.webhook_secret_prefix- Перші 8 символів секрету підпису webhook, присутні лише коли секрет налаштовано. Дозволяє виклику розрізнити, який секрет на місці, не бачачи повного значення. Панель показує той самий префікс.
pricing.allowed_credit_options- Варіанти
credit_usd, які ви пропонуєте своїм клієнтам (обираються в Settings → Pricing). readiness.missing_fields- Список назв полів панелі, які ще потрібні перед Go Live. Порожній, коли
readyдорівнюєtrue. Достатньо стабільний, щоб керувати UI «що ще відсутнє» у вашій власній адмінці. 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), поєднані з вашими розрахованими цінами, у
вашій валюті ціноутворення. Формуйте картки тарифів із цього, а не
жорстко прописуйте слаги тарифів і ціни: знятті з продажу пакети зникають самі
по собі, а зміна ціни чи валюти, яку ви робите в панелі керування, доходить до
вашої вітрини в межах її вікна кешування — без передеплою.
Це endpoint, який не дає вітрині рекламувати вчорашні
пакети. Референсний портал викликає його під час кожного рендеру лендингу / /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 |
available дорівнює true для пакетів, які можна купити як щомісячне автопродовження через POST /subscriptions; price_cents — це щомісячна сума. mo_test ніколи не підтримує підписку. |
credit_options[] |
Кожен варіант credit_usd із ціною у вашій валюті (usd — сума кредиту, price_cents — скільки він додає до суми замовлення). Які варіанти ви пропонуєте, визначає pricing.allowed_credit_options у GET /settings. |
Ціни відповідають тому, що вам нарахують
Кожен price_cents обчислюється тим самим резолвером цін, який
використовує endpoint замовлень, тож ціна картки завжди дорівнює підсумковій сумі Stripe
Checkout для цього (tier, duration_months).
Приклад¶
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-партнерів часто потребують створення DNS-записів на apex-домені партнера — спрямовуючи @, www або субдомени окремих клієнтів на потрібний сервер. MintOffice надає vendor-blind проксі, тож ви можете робити це, жодного разу не маючи справи з API-ключем вищестоящого провайдера у власній інфраструктурі. Облікові дані, які ви зберегли під час онбордингу (сьогодні: zone.ee; cloudflare і route53 заплановані за тією самою формою), залишаються зашифрованими в MintOffice; ваш виклик надходить із Bearer mo_live_…, а ми робимо вищестоящий виклик від вашого імені.
Областю дії є власний налаштований apex партнера (domain.client_apex_domain з GET /settings). Ви не можете чіпати запис у зоні, якою не володієте.
Якщо ваш DNS-провайдер — Manual, проксі нічого не змінює: записи ні на що не впливають, а списки повертаються порожніми.
Сьогодні проксі підтримує операції, які використовує сам pipeline розгортання — A-записи (upsert / список / видалення за id). Підтримка CNAME / TXT / MX з'явиться за тією самою формою, коли абстракція вищестоящого провайдера їх отримає.
Idempotency-Key тут не потрібен
На відміну від endpoint'ів створення замовлень, DNS-апсерти природно ідемпотентні на рівні провайдера (повторний запуск із тими самими (subdomain, ip) нічого не змінює, а застарілі дублікати вичищаються під час кожного виклику). Вам не потрібно надсилати заголовок Idempotency-Key — повторні спроби завжди сходяться до єдиного A-запису на FQDN.
Зробити 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
Повертає кожен A-запис на налаштованому apex. Додайте ?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). Видалення запису, який уже не існує, вважається успіхом — endpoint ідемпотентний з боку клієнта.
Відповідь — 200
{ "deleted": true, "id": "9182734" }
Помилки¶
| HTTP | error.code |
Причина |
|---|---|---|
| 401 | unauthenticated / invalid_api_key |
Відсутній / некоректний Bearer-токен. |
| 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¶
Коли із замовленням щось відбувається, ми надсилаємо POST із підписаною JSON-подією на налаштований вами URL webhook.
Webhooks необов’язкові
Webhook URL і Webhook signing secret у Settings → API access повністю необов’язкові — залиште їх порожніми під час онбордингу, якщо у вас ще немає приймача. Доставка починається лише тоді, коли задано обидва — якщо є URL, але немає секрету підпису, події не надсилаються. Панель керування й решта API чудово працюють без webhooks; натомість можна опитувати GET /api/v1/orders/{id}, щоб стежити за життєвим циклом. Додайте URL (і згенеруйте секрет), коли будете готові, — повторна активація не потрібна.
Типи подій¶
| Event | Коли спрацьовує |
|---|---|
order.created |
Створено сесію Stripe Checkout, очікується оплата. |
order.paid |
Stripe підтвердив оплату. Подію доходу записано. |
order.cancelled |
Замовлення вичерпало час очікування або було явно скасоване. |
agent.provisioning_started |
Для цього замовлення запущено pipeline розгортання. |
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 (suspended у payload показує, чи його призупинено, чи повернуто на онбординг). |
Доставка та повторні спроби¶
- До 7 спроб за експоненційним розкладом:
0s, 30s, 2m, 10m, 1h, 6h, 24h. - Після останньої спроби доставка позначається як
exhaustedі повтори зупиняються. - Відповідайте статусом
2xxпротягом 10 секунд, щоб підтвердити отримання.
Заголовки запиту¶
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}"). Завжди перевіряйте сире тіло запиту — повторна серіалізація розібраного 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 як ключ дедуплікації — повторні спроби використовують той самий id, тому рядковий INSERT … ON CONFLICT DO NOTHING для цієї колонки захищає ваш обробник.
Референсний портал¶
Робочий end-to-end приклад є в mintbot-ai/partner-portal-example — FastAPI + SQLite у Docker, із прикладом бренду ExampleAI, який ви форкаєте й замінюєте своїм. Архітектурна картина — як поєднуються вітрина, цей API, воркер деплою та репозиторій кастомізації — є на сторінці MintOffice — Технічне. Портал реалізує:
- головну сторінку, вибір тарифу, сторінки подяки та скасування,
POST /buy→POST /api/v1/orders→ редирект у Stripe,POST /webhooks/mintofficeз HMAC-перевіркою, описаною вище,- браузер подій
/adminпід HTTP Basic-автентифікацією, щоб швидко переглядати доставки.
Форкніть його, задайте PARTNER_BRAND, MINTOFFICE_API_KEY і MINTOFFICE_WEBHOOK_SECRET у .env, запустіть docker compose up і вкажіть webhook URL вашого партнерського акаунта як https://<your-host>/webhooks/mintoffice. Повний покроковий опис — у MintOffice → Референсний портал.
Помилки¶
Кожна відповідь із помилкою містить стабільний code, за яким можна програмно розгалужувати логіку. Поле message — підказка для людини, і воно може змінюватися між релізами. request_id повторює заголовок відповіді X-Request-Id — додавайте його до звернень у підтримку.
{
"error": {
"code": "invalid_api_key",
"message": "API key is unknown or revoked.",
"request_id": "f0c2d6c4-…"
}
}
| Code | Значення |
|---|---|
unauthenticated |
Відсутній або некоректно сформований заголовок Authorization. |
invalid_api_key |
API-ключ невідомий або був виведений з обігу ротацією. |
rate_limited |
Перевищено ліміт запитів на партнера або IP — див. Retry-After. |
missing_idempotency_key |
Запит POST без Idempotency-Key. |
idempotency_key_mismatch |
Той самий ключ повторно використано з іншим тілом запиту. |
validation_error |
Тіло запиту не пройшло валідацію схеми. |
not_found |
Ресурс не існує або не належить вашому партнеру. |
partner_not_live |
HTTP 403 — запис (POST /orders, /orders/{id}/renew, /subscriptions), поки ваш акаунт не в статусі live. Тіло містить 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. |
dns_not_configured |
POST /dns/records для партнера без apex-домену або без облікових даних DNS. Виправте в Settings → Domain. |
dns_provider_unsupported |
Значення dns_provider партнера ще не підключене до проксі MintOffice. |
dns_provider_error |
Висхідний DNS-провайдер повернув помилку — повідомлення містить очищений від секретів фрагмент. |
quota_exceeded |
HTTP 429, лише в середовищі DEV (mint.mintbot.dev) — у LIVE ліміту на партнера немає. Партнер вичерпав усі слоти деплою (за замовчуванням 10, після оплати Brand Partner setup fee — 20). Зверніться до підтримки mintbot, щоб підняти ліміт. Повертається POST /orders і POST /orders/{id}/renew. Тіло містить deploys_used + deploy_quota. |
Ліміти запитів¶
- 120 запитів / 60 s ковзного вікна, на партнера. Burst і steady-state використовують один кошик.
- 60 запитів / 60 s в окремому кошику на IP для неавтентифікованих запитів і запитів із невдалою автентифікацією — це не дає brute-force bearer-токена витрачати квоту партнера.
- Надлишкові запити повертають
429 rate_limitedіз заголовкомRetry-After(секунди очікування перед повтором).
Потрібна допомога?¶
Ця документація написана для партнерів, які справді користуються API. Якщо щось відсутнє, незрозуміле або застаріле, згадайте це своєму агенту mintbot — він передасть відгук, і ми оновимо сторінку.