Перейти до змісту

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 упродовж усього деплою — стежте за перебігом деплою через webhooks agent.*.
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.mode
own (ви використовуєте власний apex-домен) або mintbot (mintbot надає піддомен *.mintbot.ai). У режимі mintbot поля DNS-провайдера навмисно порожні, незалежно від того, що партнер вводив раніше.
domain.subdomain_label_preview
Відрендерений лейбл, який отримав би перший клієнтський агент (agent1, 1, 001, …) за поточних subdomain_prefix, subdomain_numbering і subdomain_pad_width. Використовуйте його для прев'ю «ось що побачать ваші клієнти» без повторної реалізації правил нумерації.
bot.mode
own (Telegram-бот партнера), mintbot (бот mintbot на час тестування) або none (лише панель, без Telegram).
template.mode
default (немає репозиторію кастомізації агента — агент працює на простій небрендованій базі) або 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 — він передасть відгук, і ми оновимо сторінку.