Перейти к содержанию

Brand Partner API

Небольшой, предсказуемый REST API для создания заказов mintbot, опроса их статуса и получения событий жизненного цикла. JSON на входе, JSON на выходе. Авторизация bearer-token. Идемпотентные записи. Подписанные webhooks.

Панель доступа к API Перейти к webhooks


Кратко

Base URL https://mint.mintbot.ai/api/v1
Auth Authorization: Bearer mo_live_…
Идемпотентность 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

Список с курсорной пагинацией, сначала самые новые.

Query parameters

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 событий ledger.

Query parameters

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

Возвращает всю вашу несекретную конфигурацию партнёра одним ответом — выбранные режимы, apex-домен, DNS-провайдера, провайдера бота, репозиторий кастомизации, шаблон субдомена (с готовой меткой-превью), тарифы по уровням, метаданные API-ключа, метаданные webhook и тот же статус готовности / по секциям, который управляет баннером дашборда MintOffice.

Предназначен для интеграционных агентов, которым нужно узнать, как настроен партнёр, не парся дашборд и не опрашивая оператора. Типичный вызывающий — кодинг-агент, устанавливающий вашу white-label-сборку на свежем сервере: он может прочитать этот endpoint, ветвиться по readiness.status и сообщить оператору, какие именно секции ещё требуют внимания.

Секреты никогда не возвращаются

Всё, что партнёр указал как учётные данные — токен Telegram-бота, API-ключ Zone.ee, секрет подписи webhook, сам plaintext API-ключа — отображается только как булево *_set. Секрет webhook дополнительно показывает тот же 8-символьный display-префикс, который уже отображается в дашборде, так что вызывающий может подтвердить, какой секрет настроен, ни разу не увидев его значение.

Ответ

{
  "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, чтобы наложить вашу персону, тему панели и любой дополнительный инструментарий поверх базы — central никогда не выполняет репозиторий сам. Укажите в 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 (с кэшированием и липким fallback).

Тексты для отображения остаются вашими

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-записи

Интеграциям бренд-партнёров часто нужно создавать DNS-записи на apex-домене партнёра — направляя @, www или субдомены отдельных клиентов на нужный сервер. MintOffice предоставляет vendor-blind прокси, чтобы вы могли делать это ни разу не имея дела с API-ключом вышестоящего провайдера в собственной инфраструктуре. Учётные данные, сохранённые вами при онбординге (сегодня: zone.ee; cloudflare и route53 запланированы под той же формой), остаются зашифрованными в MintOffice; ваш вызов приходит с Bearer mo_live_…, а мы делаем вышестоящий вызов от вашего имени.

Область — это собственный настроенный apex партнёра (domain.client_apex_domain из GET /settings). Вы не можете трогать запись в зоне, которой не владеете.

Если ваш DNS-провайдер — Manual, прокси ничего не меняет: записи ни на что не влияют, а списки возвращаются пустыми.

Сегодня прокси поддерживает операции, которые использует сам deploy pipeline — A-записи (upsert / список / удаление по id). Поддержка CNAME / TXT / MX появится в той же форме, когда абстракция нижележащего провайдера их получит.

Idempotency-Key здесь не требуется

В отличие от endpoint'ов создания заказов, DNS-upsert'ы естественно идемпотентны на уровне провайдера (повтор с тем же (subdomain, ip) — это no-op, а устаревшие дубликаты вычищаются при каждом вызове). Вам не нужно отправлять заголовок Idempotency-Key — повторы всегда сходятся к одной A-записи на FQDN.

Создать или обновить A-запись (upsert)

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 у вышестоящего провайдера после вычистки дубликатов — поэтому успешный ответ ровно с одной записью — это сигнал happy-path, что ничего устаревшего не осталось.

Список записей

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 либо отсутствуют учётные данные настроенного провайдера. Исправьте в Настройки → Домен.
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 Для этого заказа запущен deploy pipeline.
agent.ready Deploy завершился успешно. 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 key неизвестен или был выведен из обращения ротацией.
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.

Rate limits

  • 120 requests / 60 s rolling window на партнёра. Burst и steady-state используют один и тот же bucket.
  • 60 requests / 60 s — отдельный bucket на IP для неаутентифицированных запросов и запросов с неудачной аутентификацией, чтобы brute-force bearer не расходовал партнёрскую квоту.
  • Лишние запросы возвращают 429 rate_limited с заголовком Retry-After (секунды ожидания).

Нужна помощь?

Эта документация написана для партнёров, которые реально пользуются API. Если чего-то не хватает, что-то непонятно или устарело, скажите об этом своему агенту mintbot — он передаст обратную связь, и мы обновим страницу.