Zum Inhalt

Brand Partner API

Eine kleine, berechenbare REST-API zum Anlegen von mintbot-Bestellungen, zum Abfragen ihres Status und zum Empfangen von Lifecycle-Ereignissen. JSON rein, JSON raus. Bearer-Token-Authentifizierung. Idempotente Schreibvorgänge. Signierte Webhooks.

Dashboard für API-Zugriff Zu den Webhooks springen


Auf einen Blick

Base-URL https://mint.mintbot.ai/api/v1
Auth Authorization: Bearer mo_live_…
Idempotenz Idempotency-Key: <uuid> bei jedem POST außer POST /dns/records
Content-Type application/json
Rate-Limit 120 Anfragen / 60 s pro Partner
Webhooks Signiert mit HMAC-SHA256, bis zu 7 Zustellversuche

Authentifizierung

Jede Anfrage sendet einen Partner-API-Key im Authorization-Header. Erzeuge oder rotiere den Key im Dashboard.

Authorization: Bearer mo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

Rotation ohne Übergangsfrist

Beim Rotieren wird der bisherige Key atomar widerrufen. Plane, den Wert in deiner Konfiguration zu tauschen, bevor du auf Rotate klickst.

Idempotenz

Jeder POST braucht einen Idempotency-Key-Header — außer POST /dns/records, das auf Provider-Ebene von Natur aus idempotent ist und Wiederholungen auch ohne Header akzeptiert. Jede UUID pro eigenständiger Anfrage funktioniert.

  • Wir cachen die Antwort 24 Stunden lang. Eine Wiederholung mit demselben Key liefert die ursprüngliche Antwort erneut, mit Idempotent-Replay: true.
  • Wird derselbe Key mit einem anderen Body wiederverwendet, kommt 409 idempotency_key_mismatch zurück.

Bestellungen

Bestellung anlegen

POST /orders

Legt eine Bestellung an und gibt eine Stripe-Checkout-URL zurück. Sobald die Zahlung bestätigt ist, provisioniert mintbot den Agenten, und das Umsatzereignis für den Partneranteil wird automatisch verbucht.

Anfrage

{
  "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
Einer von mo_test (Test 24h), mo_month (Standard), mo_month_plus (Performance).
duration_months
Kalendermonate Serverlaufzeit. Muss 1, 3 oder 12 sein. mo_test läuft unabhängig vom Wert immer genau 24 Stunden.
credit_usd
Optional, Standard 0. Einer von 0, 5, 10, 20, 50. Vorab aufgeladenes Chat-Guthaben (USD), das mit der Bestellung gebündelt wird. Es kommt zum Bestellbetrag hinzu und geht an mintbot — es ist nicht Teil deiner Marge.
language
Optional. Bestimmt die Sprache des Stripe-Checkouts und der Willkommens-E-Mail.
external_id
Optional. Wird in jedem Webhook und jeder Bestellantwort zurückgegeben — damit verknüpfst du mintbot-Bestellungen mit Datensätzen in deinem eigenen System.
success_url · cancel_url
Rücksprung-URLs für den Stripe-Checkout. {ORDER_ID} wird serverseitig ersetzt.
webhook_url
Optional. Überschreibt für diese Bestellung die auf Partnerebene hinterlegte Webhook-URL.
product_name
Optional. Die Positionsbezeichnung, die der Kunde im Stripe Checkout sieht (max. 250 Zeichen). Standard ist ein generiertes Label im Format TIER · N months.
price_usd
Optional. Preis-Override in USD für die gesamte Bestellung. Er darf deinen Großhandels-Mindestpreis für diesen Tier und diese Laufzeit nicht unterschreiten — sonst 400 validation_error.

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

Wenn du im benutzerdefinierten Preismodus bist und für diesen Tier keinen Preis gesetzt hast, enthält die Antwort zusätzlich "pricing_fallback": true.

Beispiel

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

Bestellung abrufen

GET /orders/{id}

Ruft eine einzelne Bestellung anhand ihrer mintbot-ID ab. Liefert dieselbe Struktur wie POST /orders.

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

Bestellungen auflisten

GET /orders

Cursor-paginierte Liste, neueste zuerst.

Query-Parameter

status
Optional. Filtert nach pending, completed oder expired. Eine bezahlte Bestellung bleibt während des gesamten Deploys completed — den Deploy-Fortschritt verfolgst du über die agent.*-Webhooks.
cursor
Optional. Übergib next_cursor der vorherigen Seite, um fortzufahren.
limit
Optional. Seitengröße, Standard 50, maximal 200.

Antwort

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

next_cursor ist null, wenn es keine weiteren Seiten gibt.


Bestellung verlängern

POST /orders/{id}/renew

Verlängert einen bestehenden Agenten um weitere duration_months. Nur Infrastruktur — es wird kein neues Chat-Guthaben gebündelt. Liefert eine neue Bestell-ID und eine frische Stripe-Checkout-URL.

Anfrage

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

Abonnements

Abonnement anlegen

POST /subscriptions

Öffnet einen Stripe Checkout im Abo-Modus: Der Kunde zahlt monatlich für ein Monatspaket, und das Abo verlängert sich automatisch. Wie POST /orders braucht es einen Idempotency-Key und ein Partnerkonto im Status live. Der Monatspreis kommt aus deiner Preisgestaltung für diesen 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 (ein bestehender Agent von dir, an den das Abo gebunden wird), external_id und product_name sind optional.

Antwort — 201 Created enthält 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 und created_at.

Abonnement abrufen

GET /subscriptions/{id}

Liefert dieselbe Struktur wie POST /subscriptions.


Umsatz

Umsatz abrufen

GET /revenue

Summen plus die letzten 200 Ledger-Ereignisse.

Query-Parameter

include_paid
Optional, Standard true. Auf false setzen, um nur Ereignisse zu sehen, die noch nicht ausgezahlt wurden.

Antwort

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

Partnerprofil

Profil abrufen

GET /partner

Liefert dein Partnerprofil plus den nicht ausgezahlten Saldo — praktisch, um Einnahmen in deiner eigenen Admin-Oberfläche anzuzeigen, ohne sie selbst zu speichern.

Antwort

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

Einstellungen

Einstellungen abrufen

GET /settings

Liefert deine vollständige nicht geheime Partnerkonfiguration in einer einzigen Antwort — gewählte Modi, Apex-Domain, DNS-Provider, Bot-Provider, Customization-Repo, Subdomain-Muster (mit gerendertem Vorschau-Label), Preise pro Tier, API-Key-Metadaten, Webhook-Metadaten sowie denselben Readiness- und Abschnittsstatus, der das Banner im MintOffice-Dashboard steuert.

Gedacht für Integrations-Agenten, die herausfinden müssen, wie der Partner eingerichtet ist, ohne das Dashboard zu scrapen oder den Betreiber zu befragen. Ein typischer Aufrufer ist ein Coding-Agent, der dein White-Label-Setup auf einem frischen Server installiert — er kann diesen Endpoint lesen, anhand von readiness.status verzweigen und dem Betreiber genau sagen, welche Abschnitte noch Aufmerksamkeit brauchen.

Es wird nie ein Geheimnis zurückgegeben

Alles, was der Partner als Zugangsdaten hinterlegt hat — Telegram-Bot-Token, Zone.ee-API-Key, Webhook-Signaturgeheimnis, der API-Key selbst im Klartext —, erscheint nur als *_set-Boolean. Das Webhook-Geheimnis gibt zusätzlich dasselbe 8-stellige Anzeigepräfix preis, das schon im Dashboard zu sehen ist, sodass ein Aufrufer bestätigen kann, welches Geheimnis konfiguriert ist, ohne je den Wert zu sehen.

Antwort

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

Feldreferenz

status
Partner-Lifecycle. Einer von onboarding, live, suspended. Die Brand Partner API lehnt Schreibvorgänge ab, solange der Status nicht live ist; auf live setzt dich nur das mintbot-Team.
domain.mode
own (du bringst deine eigene Apex-Domain mit) oder mintbot (mintbot stellt eine *.mintbot.ai-Subdomain bereit). Bei mintbot sind die DNS-Provider-Felder bewusst leer — unabhängig davon, was der Partner zuvor eingegeben hat.
domain.subdomain_label_preview
Das gerenderte Label, das der erste Kundenagent erhalten würde (agent1, 1, 001, …), berechnet aus den aktuellen Werten von subdomain_prefix, subdomain_numbering und subdomain_pad_width. Nutze es für eine „So sehen es deine Kunden“-Vorschau, ohne die Nummerierungsregeln nachzubauen.
bot.mode
own (vom Partner bereitgestellter Telegram-Bot), mintbot (mintbots Bot zum Testen) oder none (nur Panel, kein Telegram).
template.mode
default (kein Agent-Customization-Repo — der Agent läuft auf der schlichten, ungebrandeten Basis) oder own (das Agent-Customization-Repo des Partners unter repo_url). Bei own klont der eigene VPS des Agenten dieses Repo nach dem Standard-Deploy und führt dessen install.sh / update.sh aus, um deine Persona, dein Panel-Theme und zusätzliches Tooling auf die Basis aufzusetzen — die Zentrale führt das Repo nie selbst aus. Zeige mit repo_url auf dein Agent-Customization-Repo (GitHub, HTTPS), wenn du eigenes Branding willst.
api.webhook_secret_prefix
Die ersten 8 Zeichen des Webhook-Signaturgeheimnisses, nur vorhanden, wenn ein Geheimnis konfiguriert ist. Damit kann ein Aufrufer unterscheiden, welches Geheimnis hinterlegt ist, ohne den vollständigen Wert zu sehen. Das Dashboard zeigt dasselbe Präfix.
pricing.allowed_credit_options
Die credit_usd-Pakete, die du deinen Kunden anbietest (festgelegt unter Settings → Pricing).
readiness.missing_fields
Liste der Dashboard-Feldnamen, die vor dem Go-Live noch fehlen. Leer, wenn ready auf true steht. Stabil genug, um eine „Was fehlt noch?“-Ansicht in deiner eigenen Admin-Oberfläche zu speisen.
readiness.dns_blocker
Ein menschenlesbarer Grund, wenn die Prüfung des DNS-Providers fehlschlägt (z. B. partner has no zone.ee username configured). Andernfalls null.
section_status.*
Entspricht den Status-Pills der einzelnen Abschnitte im Dashboard. status ist ok, warn oder default (ein mintbot-Standard wird verwendet); issue nennt das Feld, das den Abschnitt blockiert (z. B. client_apex_domain, telegram_bot_token, dns_blocker), sonst null.

Beispiel

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

Katalog

Die Liste der verkaufbaren Pakete für deinen Storefront — die Brand-Partner-Pakete (mo_test / mo_month / mo_month_plus) verknüpft mit deinen aufgelösten Preisen in deiner Preiswährung. Baue deine Plan-Karten aus dieser Liste, statt Tier-Slugs und Preise fest zu verdrahten: Ausgemusterte Pakete verschwinden von selbst, und eine Preis- oder Währungsänderung im Dashboard erreicht deinen Storefront innerhalb seines Cache-Fensters — ohne Redeploy.

Dieser Endpoint verhindert, dass ein Storefront die Pakete von gestern bewirbt. Das Referenz-Portal ruft ihn bei jedem Rendern von Landing-Page, /buy und /extend auf (gecacht, mit einem beständigen Fallback).

Die Anzeigetexte bleiben deine Sache

display_name / description / featured sind die kanonischen Paket-Metadaten von mintbot. Wenn du ein Paket umbenennen oder seinen Beschreibungstext an deine Marke anpassen willst, überschreibe ihn in deinem eigenen Storefront (das Referenz-Portal bringt genau dafür eine PLAN_OVERRIDES-Map mit) — der Katalog ist die maßgebliche Quelle dafür, welche Pakete es gibt und was sie kosten, nicht für deine Marketingtexte.

Katalog abrufen

GET /api/v1/catalog

Keine Parameter. Nur Bearer-Auth — es gibt keine Aktivierungssperre, ein Storefront kann den Katalog also rendern, bevor dein Partneraccount auf live geschaltet ist.

Antwort

{
  "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 }
  ]
}
Feld Bedeutung
currency Deine Preiswährung. Jeder price_cents-Wert unten ist darin angegeben.
packages[] Ein Eintrag pro öffentlichem Paket, in Angebots-/Anzeigereihenfolge.
tier Der Slug, den du an POST /orders / POST /subscriptions übergibst.
display_name / description / featured Kanonische Anzeigetexte (kannst du in deinem Storefront überschreiben).
default_credit_usd Hinweis auf das im Paket gebündelte LLM-Guthaben (informativ).
durations[] Die Laufzeiten, für die dieses Paket gekauft werden kann. months ist der Wert, den du als duration_months sendest; price_cents ist, was die Bestellung berechnet. Monatspakete bieten 1 / 3 / 12 Monate an; mo_test bietet eine einzelne 24-Stunden-Laufzeit.
subscription available ist true bei Paketen, die über POST /subscriptions als monatlich automatisch verlängerndes Abo gekauft werden können; price_cents ist der Monatsbetrag. mo_test ist nie abonnierbar.
credit_options[] Jedes credit_usd-Paket mit Preis in deiner Währung (usd = Guthabenbetrag, price_cents = Aufschlag auf den Bestellbetrag). Welche Pakete du anbietest, steht in pricing.allowed_credit_options von GET /settings.

Die Preise entsprechen dem, was berechnet wird

Jeder price_cents-Wert wird mit demselben Preis-Resolver berechnet, den auch der Bestell-Endpoint nutzt. Ein Kartenpreis entspricht also immer dem späteren Gesamtbetrag im Stripe-Checkout für dieses (tier, duration_months).

Beispiel

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-Einträge

Brand-Partner-Integrationen müssen oft DNS-Einträge auf der Apex-Domain des Partners anlegen — @, www oder Subdomains pro Kunde auf den richtigen Server zeigen lassen. MintOffice stellt dafür einen anbieterneutralen Proxy bereit, sodass du das erledigen kannst, ohne je den API-Key des Upstream-Providers in deiner eigenen Infrastruktur zu handhaben. Die Zugangsdaten, die du beim Onboarding gespeichert hast (heute: zone.ee; cloudflare und route53 sind mit derselben Struktur geplant), bleiben verschlüsselt in MintOffice; dein Aufruf kommt mit Bearer mo_live_… herein, und wir führen den Upstream-Aufruf in deinem Namen aus.

Der Geltungsbereich ist die konfigurierte Apex-Domain des Partners (domain.client_apex_domain aus GET /settings). Einträge in einer Zone, die dir nicht gehört, kannst du nicht anfassen.

Ist dein DNS-Provider Manual, ändert der Proxy nichts: Schreibvorgänge sind No-ops, und Listen kommen leer zurück.

Derzeit unterstützt der Proxy die Operationen, die auch die Deploy-Pipeline selbst nutzt — A-Einträge (Upsert / Auflisten / Löschen per ID). CNAME- / TXT- / MX-Unterstützung folgt nach demselben Muster, sobald die zugrunde liegende Provider-Abstraktion sie bekommt.

Idempotency-Key hier nicht nötig

Anders als die Endpoints zum Anlegen von Bestellungen sind DNS-Upserts auf Provider-Ebene von Natur aus idempotent (ein erneuter Aufruf mit demselben (subdomain, ip) ist ein No-op, und veraltete Duplikate werden bei jedem Aufruf entfernt). Du musst den Idempotency-Key-Header nicht senden — Wiederholungen laufen immer auf genau einen A-Eintrag pro FQDN hinaus.

A-Eintrag anlegen oder aktualisieren

POST /dns/records

Lässt <subdomain>.<your-apex> idempotent auf eine IPv4-Adresse zeigen. Übergib "" (oder "@") für die Apex-Domain selbst.

Anfrage

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

subdomain akzeptiert:

  • "" oder "@" — die Apex-Domain selbst (example.com).
  • Ein einzelnes DNS-Label — "www", "api" — für <label>.<apex>.
  • Labels mit Punkt — "api.eu" — für verschachtelte Subdomains (api.eu.example.com).

ip muss eine IPv4-Adresse in Punktschreibweise sein. Jedes Label besteht aus [a-z0-9_-], ist 1–63 Zeichen lang und beginnt oder endet nicht mit -.

Antwort — 200

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

Die records-Liste nach dem Schreiben spiegelt immer die aktuelle Sicht des Upstream-Providers auf den FQDN nach einem Duplikat-Sweep wider — eine erfolgreiche Antwort mit genau einem Eintrag ist also das Signal, dass nichts Veraltetes übrig ist.

Einträge auflisten

GET /dns/records

Liefert jeden A-Eintrag auf der konfigurierten Apex-Domain. Hänge ?name=<label-or-fqdn> an, um auf einen FQDN zu filtern — der Wert kann ein bloßes Label ("www"), eine Subdomain mit Punkt ("api.eu") oder der vollständige FQDN ("www.example.com") sein.

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

Eintrag löschen

DELETE /dns/records/{record_id}

record_id ist die Provider-Kennung, die GET /dns/records zurückgibt (zone.ee vergibt Integer-IDs, die an der API-Grenze in Strings umgewandelt werden). Das Löschen eines Eintrags, der nicht mehr existiert, gilt als Erfolg — der Endpoint ist clientseitig idempotent.

Antwort — 200

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

Fehler

HTTP error.code Ursache
401 unauthenticated / invalid_api_key Fehlendes / ungültiges Bearer-Token.
422 validation_error Ungültige Subdomain oder Ziel ist keine IPv4-Adresse.
422 dns_not_configured Der Partner hat keine client_apex_domain, oder die Zugangsdaten des konfigurierten Providers fehlen. Behebst du unter Settings → Domain.
422 dns_provider_unsupported Der DNS-Provider des Partners ist noch nicht an den MintOffice-Proxy angebunden (z. B. der cloudflare-Platzhalter). Wechsle vorerst zu zone_ee.
502 dns_provider_error Der Upstream-Provider hat einen 4xx/5xx zurückgegeben, oder die Verbindung ist fehlgeschlagen. Die message des Fehlers enthält den Upstream-Status und einen kurzen, um Geheimnisse bereinigten Auszug aus dem Upstream-Body. Wiederholung ist unbedenklich.

Beispiel

# 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

Wenn mit einer Bestellung etwas passiert, senden wir per POST ein signiertes JSON-Ereignis an deine konfigurierte Webhook-URL.

Webhooks sind optional

Webhook URL und Webhook signing secret unter Settings → API access sind komplett optional — lass sie beim Onboarding leer, wenn du noch keinen Empfänger hast. Das Dashboard und der Rest der API funktionieren auch ohne Webhooks; du kannst stattdessen GET /api/v1/orders/{id} abfragen, um den Lifecycle zu verfolgen. Trage die URL ein (und erzeuge das Geheimnis), sobald du so weit bist — eine erneute Aktivierung ist nicht nötig. Zugestellt wird erst, wenn beides gesetzt ist — mit URL, aber ohne Signaturgeheimnis werden keine Ereignisse gesendet.

Ereignistypen

Ereignis Wann es ausgelöst wird
order.created Stripe-Checkout-Session angelegt, Zahlung ausstehend.
order.paid Stripe hat die Zahlung bestätigt. Umsatzereignis verbucht.
order.cancelled Bestellung ist abgelaufen oder wurde ausdrücklich storniert.
agent.provisioning_started Deploy-Pipeline für diese Bestellung gestartet.
agent.ready Deploy erfolgreich. Payload enthält panel_url und expires_at.
agent.failed Deploy-Pipeline fehlgeschlagen. error_step nennt den fehlgeschlagenen Schritt, error_message ist eine kurze Zusammenfassung.
agent.expired Die Laufzeit (TTL) des Agenten ist abgelaufen. Der Partner kann über POST /orders/{id}/renew verlängern.
agent.resumed Ein zuvor abgelaufener Agent wurde innerhalb seiner Karenzfrist verlängert und läuft wieder. Gegenstück zu agent.expired.
subscription.created · subscription.activated · subscription.updated · subscription.cancelled Lebenszyklus eines über POST /subscriptions angelegten Abos.
partner.activated · partner.deactivated Das mintbot-Team hat dein Konto aktiviert oder aus live genommen (suspended in der Payload zeigt, ob es gesperrt oder auf Onboarding zurückgesetzt wurde).

Zustellung & Wiederholungen

  • Bis zu 7 Versuche nach exponentiellem Zeitplan: 0s, 30s, 2m, 10m, 1h, 6h, 24h.
  • Nach dem letzten Versuch wird die Zustellung als exhausted markiert und nicht weiter wiederholt.
  • Antworte innerhalb von 10 Sekunden mit einem 2xx-Status, um den Empfang zu bestätigen.

Request-Header

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

Beispiel-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
}

Signatur prüfen

Die Signatur ist HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Prüfe immer den rohen Request-Body — wenn du dein geparstes JSON neu serialisierst, schlägt der Vergleich fehl.

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 Empfänger

Nutze X-Mintbot-Event-Id als Dedup-Schlüssel — Wiederholungen verwenden dieselbe ID, sodass ein zeilenweises INSERT … ON CONFLICT DO NOTHING auf dieser Spalte deinen Handler absichert.

Referenz-Portal

Eine lauffähige End-to-End-Referenz liegt unter mintbot-ai/partner-portal-example — FastAPI + SQLite, dockerisiert, mit einer Beispielmarke namens ExampleAI, die du forkst und austauschst. Das Architekturbild — wie Storefront, diese API, der Deploy-Worker und das Customization-Repo zusammenspielen — findest du unter MintOffice — Technisch. Das Portal implementiert:

  • die Seiten für Landing, Planauswahl, Danke und Abbruch,
  • POST /buy → POST /api/v1/orders → Stripe-Weiterleitung,
  • POST /webhooks/mintoffice mit dem oben gezeigten HMAC-Prüfer,
  • einen /admin-Ereignisbrowser hinter HTTP-Basic-Auth, mit dem du Zustellungen im Blick behältst.

Forke es, setze PARTNER_BRAND, MINTOFFICE_API_KEY und MINTOFFICE_WEBHOOK_SECRET in .env, starte docker compose up und zeige mit der Webhook-URL deines Partnereintrags auf https://<your-host>/webhooks/mintoffice. Die vollständige Anleitung steht unter MintOffice → Referenz-Portal.


Fehler

Jede Fehlerantwort enthält einen stabilen code, nach dem du programmatisch verzweigen kannst. Das Feld message ist ein menschenlesbarer Hinweis und kann sich zwischen Releases ändern. request_id entspricht dem Response-Header X-Request-Id — gib ihn in Support-Tickets an.

{
  "error": {
    "code": "invalid_api_key",
    "message": "API key is unknown or revoked.",
    "request_id": "f0c2d6c4-…"
  }
}
Code Bedeutung
unauthenticated Authorization-Header fehlt oder ist fehlerhaft.
invalid_api_key API-Key ist unbekannt oder wurde per Rotation ersetzt.
rate_limited Rate-Limit pro Partner oder pro IP überschritten — siehe Retry-After.
missing_idempotency_key POST-Anfrage ohne Idempotency-Key.
idempotency_key_mismatch Derselbe Key wurde mit einem anderen Request-Body wiederverwendet.
validation_error Der Request-Body hat die Schema-Validierung nicht bestanden.
not_found Die Ressource existiert nicht oder gehört nicht zu deinem Partner.
partner_not_live HTTP 403 — ein Schreibvorgang (POST /orders, /orders/{id}/renew, /subscriptions), während dein Konto nicht live ist. Der Body enthält status und bei Konten im Onboarding missing_fields + dns_blocker.
conflict HTTP 409 — eine Anfrage mit demselben Idempotency-Key wird noch verarbeitet. Nach Retry-After erneut versuchen.
unpriced_tier HTTP 409 — für den Tier gibt es keinen abrechenbaren Preis.
internal_error Unerwarteter serverseitiger Fehler. Gib die request_id an, wenn du dich an den Support wendest.
payment_gateway_error Stripe hat das Anlegen der Checkout-Session abgelehnt.
dns_not_configured POST /dns/records bei einem Partner ohne Apex-Domain / ohne DNS-Zugangsdaten. Behebst du unter Settings → Domain.
dns_provider_unsupported Der dns_provider-Wert des Partners ist noch nicht an den MintOffice-Proxy angebunden.
dns_provider_error Der Upstream-DNS-Provider hat einen Fehler zurückgegeben — die Nachricht enthält einen bereinigten Auszug.
quota_exceeded HTTP 429, nur in der DEV-Umgebung (mint.mintbot.dev) — LIVE hat kein Limit pro Partner. Der Partner hat alle Deploy-Slots verbraucht (standardmäßig 10, nach der Brand-Partner-Einrichtungsgebühr 20). Wende dich an den mintbot-Support, um das Limit zu erhöhen. Wird von POST /orders und POST /orders/{id}/renew zurückgegeben. Der Body enthält deploys_used + deploy_quota.

Rate-Limits

  • 120 Anfragen / 60 s im gleitenden Fenster, pro Partner. Bursts und Dauerlast teilen sich denselben Bucket.
  • 60 Anfragen / 60 s in einem separaten Bucket pro IP für nicht authentifizierte und fehlgeschlagene Auth-Anfragen — damit ein Bearer-Brute-Force nicht das Partnerkontingent aufbraucht.
  • Überzählige Anfragen erhalten 429 rate_limited mit einem Retry-After-Header (Wartezeit in Sekunden).

Brauchst du Hilfe?

Diese Dokumentation ist für Partner geschrieben, die die API tatsächlich nutzen. Wenn etwas fehlt, unklar oder veraltet ist, sag es deinem mintbot-Agenten — er leitet das Feedback weiter, und wir aktualisieren die Seite.