Ga naar inhoud

Brand Partner API

Een kleine, voorspelbare REST API om mintbot-orders te maken, hun status op te vragen en lifecycle-events te ontvangen. JSON erin, JSON eruit. Bearer-token-authenticatie. Idempotente writes. Ondertekende webhooks.

Dashboard voor API-toegang Ga naar webhooks


In één oogopslag

Base URL https://mint.mintbot.ai/api/v1
Auth Authorization: Bearer mo_live_…
Idempotency Idempotency-Key: <uuid> bij elke POST behalve POST /dns/records
Content type application/json
Rate limit 120 req / 60 s per partner
Webhooks Ondertekend met HMAC-SHA256, tot 7 keer opnieuw geprobeerd

Authenticatie

Elk request stuurt een partner-API-sleutel in de Authorization header. Genereer of roteer de sleutel in het dashboard.

Authorization: Bearer mo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

Rotatie heeft geen respijtperiode

Het roteren van de sleutel trekt de vorige sleutel atomair in. Plan om de waarde in je config te vervangen voordat je op Rotate klikt.

Idempotency

Elke POST vereist een Idempotency-Key header — behalve POST /dns/records, dat op providerniveau van nature idempotent is en retries ook zonder deze header accepteert. Elke UUID per afzonderlijk request werkt.

  • We cachen de response 24 uur. Een retry met dezelfde sleutel speelt de oorspronkelijke response opnieuw af met Idempotent-Replay: true.
  • Hergebruik van dezelfde sleutel met een andere body retourneert 409 idempotency_key_mismatch.

Orders

Order aanmaken

POST /orders

Maakt een order aan en retourneert een Stripe Checkout URL. Zodra betaling is bevestigd, provisiont mintbot de agent en wordt het revenue-event voor het partneraandeel automatisch vastgelegd.

Request

{
  "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
Een van mo_test (Test 24h), mo_month (Standard), mo_month_plus (Performance).
duration_months
Kalendermaanden serverlooptijd. Moet 1, 3 of 12 zijn. mo_test is altijd één run van 24 uur, wat je ook meestuurt.
credit_usd
Optioneel, standaard 0. Een van 0, 5, 10, 20, 50. Vooraf betaald chattegoed (USD) dat met de order wordt meegeleverd. Het wordt bij het ordertotaal opgeteld en gaat naar mintbot — het hoort niet bij jouw marge.
language
Optioneel. Beïnvloedt de locale van Stripe Checkout en de taal van de welkomstmail.
external_id
Optioneel. Wordt teruggestuurd op elke webhook en orderresponse — gebruik dit om mintbot-orders te koppelen aan rijen in je eigen systeem.
success_url · cancel_url
Retour-URL's voor Stripe Checkout. {ORDER_ID} wordt server-side vervangen.
webhook_url
Optioneel. Override per order van de webhook-URL op partnerniveau.
product_name
Optioneel. Het regellabel dat de klant in Stripe Checkout ziet (max. 250 tekens). Standaard wordt een label TIER · N months gegenereerd.
price_usd
Optioneel. Afwijkende prijs in USD voor de hele order. Die mag niet onder je groothandelsminimum voor die tier en looptijd liggen — anders 400 validation_error.

Response — 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
}

Gebruik je de modus voor eigen prijzen en heb je deze tier niet geprijsd, dan bevat het antwoord ook "pricing_fallback": true.

Voorbeeld

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"]

Order ophalen

GET /orders/{id}

Haalt één order op via zijn mintbot-id. Retourneert dezelfde vorm als POST /orders.

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

Orders weergeven

GET /orders

Cursor-gepagineerde lijst, nieuwste eerst.

Query parameters

status
Optioneel. Filter op pending, completed of expired. Een betaalde order blijft tijdens de hele deploy completed — volg de voortgang van de deploy via de agent.*-webhooks.
cursor
Optioneel. Geef next_cursor van de vorige pagina door om verder te gaan.
limit
Optioneel. Paginagrootte, standaard 50, maximaal 200.

Response

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

next_cursor is null wanneer er geen pagina's meer zijn.


Order verlengen

POST /orders/{id}/renew

Verlengt een bestaande agent met nog eens duration_months. Alleen infra — er wordt geen nieuw chattegoed gebundeld. Retourneert een nieuwe order-id en een nieuwe Stripe Checkout URL.

Request

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

Abonnementen

Abonnement aanmaken

POST /subscriptions

Opent een Stripe Checkout in abonnementsmodus: de klant betaalt maandelijks voor een maandpakket en het abonnement wordt automatisch verlengd. Net als POST /orders vereist het een Idempotency-Key en een partneraccount met status live. De maandprijs komt uit jouw prijzen voor die tier.

Request

{
  "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 (een bestaande agent van jou waaraan het abonnement wordt gekoppeld), external_id en product_name zijn optioneel.

Response — 201 Created bevat 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 en created_at.

Abonnement ophalen

GET /subscriptions/{id}

Retourneert dezelfde structuur als POST /subscriptions.


Omzet

Omzet lezen

GET /revenue

Totalen plus de nieuwste 200 ledger-events.

Query parameters

include_paid
Optioneel, standaard true. Zet op false om alleen events te zien die nog niet zijn uitbetaald.

Response

{
  "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
    }
  ]
}

Partnerprofiel

Profiel ophalen

GET /partner

Geeft je partnerprofiel plus het onbetaalde saldo terug — handig om opbrengsten in je eigen admin UI te tonen zonder ze zelf op te slaan.

Response

{
  "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"
}

Instellingen

Instellingen ophalen

GET /settings

Retourneert je volledige niet-geheime partnerconfiguratie in één payload — gekozen modes, apex-domein, DNS-provider, bot-provider, customization repo, subdomeinpatroon (met een weergegeven preview-label), prijzen per tier, API-key-metadata, webhook-metadata en dezelfde readiness / per-sectie status die de banner van het MintOffice-dashboard aanstuurt.

Ontworpen voor integratie-agents die moeten ontdekken hoe de partner is opgezet zonder het dashboard te scrapen of de operator te bevragen. Een typische caller is een coding-agent die jouw white-label-setup op een verse server installeert — die kan deze endpoint lezen, vertakken op readiness.status en de operator precies vertellen welke secties nog aandacht nodig hebben.

Er wordt nooit een secret geretourneerd

Alles wat de partner als credential heeft opgegeven — Telegram-bottoken, Zone.ee API-key, webhook-ondertekeningssecret, de API-key in platte tekst zelf — verschijnt alleen als een *_set-boolean. Het webhook-secret toont daarnaast hetzelfde 8-tekens displayprefix dat al in het dashboard wordt getoond, zodat een caller kan bevestigen welk secret is geconfigureerd zonder ooit de waarde te zien.

Response

{
  "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
  }
}

Veldreferentie

status
Partnerlevenscyclus. Een van onboarding, live, suspended. De Brand Partner API weigert writes zolang de status niet live is; alleen het mintbot-team zet je op live.
domain.mode
own (je gebruikt je eigen apex-domein) of mintbot (mintbot levert een *.mintbot.ai-subdomein). Bij mintbot zijn de DNS-providervelden bewust leeg, ongeacht wat de partner eerder heeft ingevuld.
domain.subdomain_label_preview
Het weergegeven label dat de eerste klant-agent zou krijgen (agent1, 1, 001, …) gegeven de huidige subdomain_prefix, subdomain_numbering en subdomain_pad_width. Gebruik het voor een "dit is wat je klanten zullen zien"-preview zonder de nummeringsregels opnieuw te implementeren.
bot.mode
own (Telegram-bot van de partner), mintbot (de bot van mintbot tijdens het testen) of none (alleen paneel, geen Telegram).
template.mode
default (geen agent customization repo — de agent draait op de kale, merkloze basis) of own (de agent customization repo van de partner op repo_url). Bij own kloont de eigen VPS van de agent die repo na de standaarddeploy en draait diens install.sh / update.sh om jouw persona, paneelthema en eventuele extra tooling bovenop de basis toe te passen — central voert de repo zelf nooit uit. Wijs repo_url naar je GitHub-HTTPS agent customization repo wanneer je eigen branding wilt.
api.webhook_secret_prefix
De eerste 8 tekens van het webhook-ondertekeningssecret, alleen aanwezig wanneer een secret is geconfigureerd. Laat een caller onderscheiden welk secret actief is zonder de volledige waarde te zien. Het dashboard toont hetzelfde prefix.
pricing.allowed_credit_options
De credit_usd-tegoedopties die je je klanten aanbiedt (gekozen in Settings → Pricing).
readiness.missing_fields
Lijst met dashboard-veldnamen die nog vereist zijn vóór Go Live. Leeg wanneer ready true is. Stabiel genoeg om een "wat ontbreekt er nog"-UI in je eigen admin aan te sturen.
readiness.dns_blocker
Een leesbare reden wanneer de controle van de DNS-provider mislukt (bijv. partner has no zone.ee username configured). Anders null.
section_status.*
Weerspiegelt de statuslabels per sectie in het dashboard. status is ok, warn of default (er wordt een mintbot-standaard gebruikt); issue noemt het veld dat die sectie blokkeert (bijv. client_apex_domain, telegram_bot_token, dns_blocker), of null.

Voorbeeld

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"])

Catalogus

De verkoopbare pakkettenlijst voor je storefront — de Brand Partner-pakketten (mo_test / mo_month / mo_month_plus) samengevoegd met jouw opgeloste prijzen, in jouw prijsvaluta. Render je plankaarten hieruit in plaats van tier-slugs en prijzen hard te coderen: ingetrokken pakketten verdwijnen vanzelf, en een prijs- of valutawijziging die je in het dashboard maakt, bereikt jouw storefront binnen het cachevenster — geen redeploy.

Dit is de endpoint die voorkomt dat een storefront de pakketten van gisteren adverteert. Het referentieportaal roept hem aan bij elke landing- / /buy- / /extend-render (gecachet, met een sticky fallback).

Displaytekst blijft van jou

display_name / description / featured zijn de canonieke mintbot- pakketmetadata. Wil je een pakket hernoemen of de omschrijving herschrijven voor je merk, override het dan in je eigen storefront (het referentie- portaal levert een PLAN_OVERRIDES-map precies hiervoor) — de catalogus is de bron van waarheid voor welke pakketten bestaan en wat ze kosten, niet voor je marketingtekst.

Catalogus ophalen

GET /api/v1/catalog

Geen parameters. Alleen Bearer-auth — er is geen activatiepoort, zodat een storefront de catalogus kan renderen voordat je partneraccount live is gezet.

Response

{
  "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 }
  ]
}
Veld Betekenis
currency Je prijsvaluta. Elke price_cents hieronder is hierin uitgedrukt.
packages[] Eén entry per publiek pakket, in aanbod-/weergavevolgorde.
tier De slug die je doorgeeft aan POST /orders / POST /subscriptions.
display_name / description / featured Canonieke displaytekst (override in je storefront als je wilt).
default_credit_usd De gebundelde LLM-tegoedhint van het pakket (informatief).
durations[] De setup-looptijden waarvoor dit pakket gekocht kan worden. months is de waarde die je als duration_months verstuurt; price_cents is wat de order in rekening brengt. Maandpakketten adverteren 1 / 3 / 12 maanden; mo_test adverteert één enkele looptijd van 24 uur.
subscription available is true voor pakketten die als maandelijkse auto-renew via POST /subscriptions gekocht kunnen worden; price_cents is het maandbedrag. mo_test is nooit abonnementsgeschikt.
credit_options[] Elke credit_usd-tegoedoptie geprijsd in jouw valuta (usd = tegoedbedrag, price_cents = wat het aan het ordertotaal toevoegt). Welke opties je aanbiedt, staat in pricing.allowed_credit_options van GET /settings.

Prijzen komen overeen met wat je in rekening wordt gebracht

Elke price_cents wordt berekend met dezelfde prijsresolver die de order-endpoint gebruikt, dus een kaartprijs is altijd gelijk aan het uiteindelijke Stripe Checkout-totaal voor die (tier, duration_months).

Voorbeeld

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-records

Brand-partner-integraties moeten vaak DNS-records provisionen op het apex-domein van de partner — @, www of subdomeinen per klant naar de juiste server laten wijzen. MintOffice biedt een vendor-blinde proxy zodat je dit kunt doen zonder ooit de API-key van de upstream-provider in je eigen infrastructuur te verwerken. De credentials die je tijdens onboarding hebt opgeslagen (vandaag: zone.ee; cloudflare en route53 staan op de roadmap achter dezelfde vorm) blijven versleuteld in MintOffice; jouw call komt binnen met Bearer mo_live_… en wij doen de upstream-call namens jou.

Scope is de eigen geconfigureerde apex van de partner (domain.client_apex_domain uit GET /settings). Je kunt geen record aanraken op een zone die je niet bezit.

Is je DNS-provider Manual, dan verandert de proxy niets: writes doen niets en lijsten komen leeg terug.

Vandaag ondersteunt de proxy de operaties die de deploy pipeline zelf gebruikt — A-records (upsert / list / delete op id). Ondersteuning voor CNAME / TXT / MX volgt in dezelfde vorm zodra de onderliggende provider-abstractie ze ondersteunt.

Idempotency-Key hier niet vereist

Anders dan bij order-creatie-endpoints zijn DNS-upserts van nature idempotent op de providerlaag (opnieuw uitvoeren met dezelfde (subdomain, ip) is een no-op en verouderde duplicaten worden bij elke call opgeruimd). Je hoeft de Idempotency-Key header niet te sturen — retries convergeren altijd naar één A-record per FQDN.

Een A-record upserten

POST /dns/records

Laat <subdomain>.<your-apex> idempotent naar een IPv4-adres wijzen. Geef "" (of "@") door voor de apex zelf.

Request

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

subdomain accepteert:

  • "" of "@" — de apex zelf (example.com).
  • Eén DNS-label — "www", "api" — voor <label>.<apex>.
  • Gepunte sublabels — "api.eu" — voor geneste subdomeinen (api.eu.example.com).

ip moet een gepunt IPv4-adres zijn. Elk label is [a-z0-9_-], 1–63 tekens, geen leidende/afsluitende -.

Response — 200

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

De records-lijst na de write weerspiegelt altijd de actuele kijk van de upstream-provider op de FQDN na een duplicatensweep — een geslaagde response met precies één record is dus het happy-path-signaal dat er niets verouderds overblijft.

Records weergeven

GET /dns/records

Retourneert elk A-record op de geconfigureerde apex. Voeg ?name=<label-or-fqdn> toe om te filteren op één FQDN — de waarde mag een kaal label ("www"), een gepunt subdomein ("api.eu") of de volledige FQDN ("www.example.com") zijn.

Response — 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" }
  ]
}

Een record verwijderen

DELETE /dns/records/{record_id}

record_id is de vendor-identifier die door GET /dns/records wordt geretourneerd (zone.ee geeft integer-id's uit, gestringificeerd op de API-grens). Het verwijderen van een record dat niet meer bestaat wordt als een succes behandeld — de endpoint is idempotent aan de clientzijde.

Response — 200

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

Fouten

HTTP error.code Oorzaak
401 unauthenticated / invalid_api_key Ontbrekend / fout Bearer-token.
422 validation_error Fout subdomein of niet-IPv4-bestemming.
422 dns_not_configured Partner heeft geen client_apex_domain of de credentials van de geconfigureerde provider ontbreken. Fix in Settings → Domain.
422 dns_provider_unsupported De DNS-provider van de partner is nog niet aangesloten op de MintOffice-proxy (bijv. cloudflare-placeholder). Schakel voorlopig over naar zone_ee.
502 dns_provider_error Upstream-provider gaf een 4xx/5xx terug, of het transport mislukte. De fout-message bevat de upstream-status en een korte, secret-geredigeerde uittreksel van de upstream-body. Veilig om opnieuw te proberen.

Voorbeeld

# 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

Wanneer er iets met een order gebeurt, sturen we met POST een ondertekend JSON-event naar je geconfigureerde webhook-URL.

Webhooks zijn optioneel

De Webhook URL en het Webhook signing secret onder Settings → API access zijn volledig optioneel — laat ze tijdens de onboarding leeg als je nog geen ontvanger hebt. Er worden pas events afgeleverd zodra beide zijn ingesteld — met een URL maar zonder signing secret wordt er niets verstuurd. Het dashboard en de rest van de API werken prima zonder webhooks; je kunt in plaats daarvan GET /api/v1/orders/{id} pollen om de levenscyclus te volgen. Voeg de URL toe (en genereer het secret) zodra je er klaar voor bent; opnieuw activeren is niet nodig.

Eventtypen

Event Wanneer het wordt verzonden
order.created Stripe Checkout session aangemaakt, wacht op betaling.
order.paid Stripe heeft betaling bevestigd. Revenue-event vastgelegd.
order.cancelled Order is verlopen of expliciet geannuleerd.
agent.provisioning_started Deploy pipeline voor deze order gestart.
agent.ready Deploy geslaagd. Payload bevat panel_url en expires_at.
agent.failed De deploy-pipeline gaf een fout. error_step noemt de stap die misging; error_message is een korte samenvatting.
agent.expired Agent-TTL is verlopen. De partner kan verlengen via POST /orders/{id}/renew.
agent.resumed Een eerder verlopen agent is binnen de respijtperiode verlengd en weer live. Tegenhanger van agent.expired bij herstel.
subscription.created · subscription.activated · subscription.updated · subscription.cancelled Levenscyclus van een abonnement uit POST /subscriptions.
partner.activated · partner.deactivated Het mintbot-team heeft je account geactiveerd of van live gehaald (suspended in de payload geeft aan of het is opgeschort of teruggezet naar onboarding).

Bezorging en retries

  • Tot 7 pogingen volgens een exponentieel schema: 0s, 30s, 2m, 10m, 1h, 6h, 24h.
  • Na de laatste poging wordt de bezorging gemarkeerd als exhausted en stopt het opnieuw proberen.
  • Reageer binnen 10 seconden met een 2xx status om te bevestigen.

Request 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

Voorbeeldpayload — 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
}

De handtekening verifiëren

De handtekening is HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Verifieer altijd de raw request body — het opnieuw serialiseren van je geparste JSON breekt de vergelijking.

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"),
  );
}

Idempotente ontvangers

Gebruik X-Mintbot-Event-Id als je dedup-sleutel — retries hergebruiken dezelfde id, dus een rij-niveau INSERT … ON CONFLICT DO NOTHING op die kolom houdt je handler veilig.

Referentieportaal

Een uitvoerbare end-to-end-referentie staat op mintbot-ai/partner-portal-example — FastAPI + SQLite, in Docker, met een voorbeeldmerk ExampleAI dat je forkt en vervangt door je eigen merk. Het architectuuroverzicht — hoe de storefront, deze API, de deploy-worker en de customization repo samenhangen — staat op MintOffice — Technisch. Het portaal bevat:

  • de landingspagina, pakketkeuze, bedankpagina en annuleringspagina,
  • POST /buy → POST /api/v1/orders → doorverwijzing naar Stripe,
  • POST /webhooks/mintoffice met de HMAC-verificatie van hierboven,
  • een /admin-eventbrowser achter HTTP Basic-auth, zodat je afleveringen snel kunt bekijken.

Fork het, zet PARTNER_BRAND, MINTOFFICE_API_KEY en MINTOFFICE_WEBHOOK_SECRET in .env, start docker compose up en wijs de webhook-URL van je partneraccount naar https://<your-host>/webhooks/mintoffice. De volledige uitleg staat in MintOffice → Referentieportaal.


Fouten

Elke foutresponse bevat een stabiele code waarop je programmatisch kunt vertakken. Het veld message is een menselijk leesbare hint en kan tussen releases veranderen. De request_id spiegelt de X-Request-Id response header — vermeld die in supporttickets.

{
  "error": {
    "code": "invalid_api_key",
    "message": "API key is unknown or revoked.",
    "request_id": "f0c2d6c4-…"
  }
}
Code Betekenis
unauthenticated Ontbrekende of verkeerd gevormde Authorization header.
invalid_api_key API key is onbekend of is weggeroteerd.
rate_limited Rate limit per partner of per IP overschreden — zie Retry-After.
missing_idempotency_key POST request zonder Idempotency-Key.
idempotency_key_mismatch Dezelfde sleutel opnieuw gebruikt met een andere request body.
validation_error Request body is niet door schemavalidatie gekomen.
not_found Resource bestaat niet of hoort niet bij je partner.
partner_not_live HTTP 403 — een write (POST /orders, /orders/{id}/renew, /subscriptions) terwijl je account niet live is. De body bevat status, en voor accounts in onboarding ook missing_fields + dns_blocker.
conflict HTTP 409 — een request met dezelfde Idempotency-Key wordt nog verwerkt. Probeer het opnieuw na Retry-After.
unpriced_tier HTTP 409 — voor de tier is geen prijs om te factureren.
internal_error Onverwachte fout aan serverzijde. Vermeld de request_id als je contact opneemt met support.
payment_gateway_error Stripe heeft het aanmaken van de Checkout session geweigerd.
dns_not_configured POST /dns/records voor een partner zonder apex of zonder DNS-credentials. Los dit op in Settings → Domain.
dns_provider_unsupported De dns_provider-waarde van de partner is nog niet aangesloten op de MintOffice-proxy.
dns_provider_error De upstream DNS-provider gaf een fout — de message bevat een geschoond fragment.
quota_exceeded HTTP 429, alleen in de DEV-omgeving (mint.mintbot.dev) — LIVE heeft geen limiet per partner. De partner heeft alle deploy-slots gebruikt (standaard 10, 20 na de Brand Partner-setupfee). Neem contact op met mintbot-support om de limiet te verhogen. Geretourneerd door POST /orders en POST /orders/{id}/renew. De body bevat deploys_used + deploy_quota.

Rate limits

  • 120 requests / 60 s rolling window, per partner. Burst en steady-state delen dezelfde bucket.
  • 60 requests / 60 s aparte bucket per IP voor niet-geauthenticeerde en mislukte-auth requests — voorkomt dat bearer brute-force de partnerquota opsoupeert.
  • Overtollige requests retourneren 429 rate_limited met een Retry-After header (seconden om te wachten).

Hulp nodig?

Deze documentatie is geschreven voor de partners die de API echt gebruiken. Als er iets ontbreekt, verwarrend of verouderd is, meld het dan aan je mintbot-agent — die stuurt de feedback door en wij werken de pagina bij.