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_mismatchzurü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,3oder12sein.mo_testläuft unabhängig vom Wert immer genau 24 Stunden. credit_usd- Optional, Standard
0. Einer von0,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,completedoderexpired. Eine bezahlte Bestellung bleibt während des gesamten Deployscompleted— den Deploy-Fortschritt verfolgst du über dieagent.*-Webhooks. cursor- Optional. Übergib
next_cursorder vorherigen Seite, um fortzufahren. limit- Optional. Seitengröße, Standard
50, maximal200.
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. Auffalsesetzen, 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 nichtliveist; auflivesetzt dich nur das mintbot-Team. domain.modeown(du bringst deine eigene Apex-Domain mit) odermintbot(mintbot stellt eine*.mintbot.ai-Subdomain bereit). Beimintbotsind 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 vonsubdomain_prefix,subdomain_numberingundsubdomain_pad_width. Nutze es für eine „So sehen es deine Kunden“-Vorschau, ohne die Nummerierungsregeln nachzubauen. bot.modeown(vom Partner bereitgestellter Telegram-Bot),mintbot(mintbots Bot zum Testen) odernone(nur Panel, kein Telegram).template.modedefault(kein Agent-Customization-Repo — der Agent läuft auf der schlichten, ungebrandeten Basis) oderown(das Agent-Customization-Repo des Partners unterrepo_url). Beiownklont der eigene VPS des Agenten dieses Repo nach dem Standard-Deploy und führt desseninstall.sh/update.shaus, um deine Persona, dein Panel-Theme und zusätzliches Tooling auf die Basis aufzusetzen — die Zentrale führt das Repo nie selbst aus. Zeige mitrepo_urlauf 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
readyauftruesteht. 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). Andernfallsnull. section_status.*- Entspricht den Status-Pills der einzelnen Abschnitte im Dashboard.
statusistok,warnoderdefault(ein mintbot-Standard wird verwendet);issuenennt das Feld, das den Abschnitt blockiert (z. B.client_apex_domain,telegram_bot_token,dns_blocker), sonstnull.
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
exhaustedmarkiert 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/mintofficemit 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_limitedmit einemRetry-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.