Brand Partner API¶
Et lille, forudsigeligt REST API til at oprette mintbot-ordrer, hente deres status og modtage lifecycle events. JSON ind, JSON ud. Bearer-token auth. Idempotente writes. Signerede webhooks.
API access dashboard Gå til webhooks
Kort overblik¶
| Base URL | https://mint.mintbot.ai/api/v1 |
| Auth | Authorization: Bearer mo_live_… |
| Idempotency | Idempotency-Key: <uuid> på hver POST undtagen POST /dns/records |
| Content type | application/json |
| Rate limit | 120 req / 60 s pr. partner |
| Webhooks | Signeret med HMAC-SHA256, forsøges igen op til 7 gange |
Autentificering¶
Hver request sender en partner API key i Authorization headeren. Generér eller rotér key'en i dashboardet.
Authorization: Bearer mo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Rotation har intet grace window
Når du roterer key'en, tilbagekaldes den tidligere atomisk. Planlæg at udskifte værdien i din config, før du klikker Rotate.
Idempotency¶
Hver POST kræver en Idempotency-Key header — undtagen POST /dns/records, som er naturligt idempotent i udbyderlaget og accepterer genforsøg uden den. En hvilken som helst UUID pr. unik request virker.
- Vi cacher response i 24 timer. Et retry med samme key afspiller det oprindelige response med
Idempotent-Replay: true. - Genbrug af samme key med en anden body returnerer
409 idempotency_key_mismatch.
Ordrer¶
Opret order¶
POST /orders
Opretter en order og returnerer en Stripe Checkout URL. Når betalingen er bekræftet, provisionerer mintbot agenten, og revenue event for partner-andelen registreres automatisk.
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- En af
mo_test(Test 24h),mo_month(Standard),mo_month_plus(Performance). duration_months- Kalendermåneder for serverens levetid. Skal være en af
1,3eller12.mo_tester altid én kørsel på 24 timer, uanset hvad du sender. credit_usd- Valgfri, standard
0. En af0,5,10,20,50. Forudbetalt chatkredit (USD), der følger med ordren. Den lægges oven i ordretotalen og går til mintbot — den er ikke en del af din margin. language- Valgfri. Påvirker Stripe Checkout locale og sproget i welcome-email.
external_id- Valgfri. Sendes tilbage i hver webhook og order response — brug den til at knytte mintbot-ordrer til rækker i dit eget system.
success_url·cancel_url- Stripe Checkout return URLs.
{ORDER_ID}erstattes server-side. webhook_url- Valgfri. Per-order override af webhook URL på partnerniveau.
product_name- Valgfri. Den linjeetiket, kunden ser i Stripe Checkout (maks. 250 tegn). Standard er en genereret etiket af typen
TIER · N months. price_usd- Valgfri. Tilsidesat pris i USD for hele ordren. Den kan ikke gå under dit engrosgulv for det niveau og den periode — ellers
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
}
Hvis du bruger tilpassede priser og ikke har prissat dette niveau, indeholder svaret også "pricing_fallback": true.
Eksempel
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"]
Hent order¶
GET /orders/{id}
Henter en enkelt order via dens mintbot id. Returnerer samme form som POST /orders.
curl https://mint.mintbot.ai/api/v1/orders/42 \
-H "Authorization: Bearer $MINTBOT_API_KEY"
List ordrer¶
GET /orders
Cursor-pagineret liste, nyeste først.
Query-parametre
status- Valgfri. Filtrér efter
pending,completedellerexpired. En betalt order forblivercompletedunder hele sit deploy — følg deployets fremdrift viaagent.*-webhooks. cursor- Valgfri. Send
next_cursorfra den forrige side for at fortsætte. limit- Valgfri. Sidestørrelse, standard
50, maks.200.
Response
{
"items": [ /* OrderResponse … */ ],
"next_cursor": "37"
}
next_cursor er null, når der ikke er flere sider.
Forny order¶
POST /orders/{id}/renew
Forlænger en eksisterende agent med yderligere duration_months. Kun infra — ingen ny chat credit følger med. Returnerer et nyt order id og en ny 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"
}
Abonnementer¶
Opret abonnement¶
POST /subscriptions
Åbner en Stripe Checkout i abonnementstilstand: kunden betaler hver måned for en månedspakke, og abonnementet fornyes automatisk. Ligesom POST /orders kræver det en Idempotency-Key og en partnerkonto med status live. Månedsprisen kommer fra dine priser for det niveau.
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 (en eksisterende agent hos dig, som abonnementet skal knyttes til), external_id og product_name er valgfri.
Response — 201 Created indeholder 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 og created_at.
Hent abonnement¶
GET /subscriptions/{id}
Returnerer samme form som POST /subscriptions.
Indtægter¶
Læs indtægter¶
GET /revenue
Totaler plus de seneste 200 ledger events.
Query-parametre
include_paid- Valgfri, default
true. Sæt tilfalsefor kun at se events, der endnu ikke er udbetalt.
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
}
]
}
Partnerprofil¶
Hent profil¶
GET /partner
Sender din partnerprofil tilbage plus den ubetalte saldo — praktisk til at vise indtjening i dit eget admin UI uden selv at gemme den.
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"
}
Indstillinger¶
Hent indstillinger¶
GET /settings
Returnerer hele din ikke-hemmelige partnerkonfiguration i én payload — valgte tilstande, apex-domæne, DNS-udbyder, botudbyder, tilpasningsrepo, subdomænemønster (med en renderet eksempeletiket), priser pr. niveau, metadata for API-nøglen og webhooken samt den samme parathed og sektionsstatus, som styrer banneret i MintOffice-dashboardet.
Lavet til integrationsagenter, der skal finde ud af, hvordan partneren er sat op, uden at skrabe dashboardet eller spørge operatøren. En typisk kalder er en kodeagent, der installerer dit white label-setup på en ny server — den kan læse dette endpoint, forgrene på readiness.status og fortælle operatøren præcis, hvilke sektioner der stadig mangler.
Hemmeligheder returneres aldrig
Alt, hvad partneren har angivet som legitimationsoplysning — Telegram-bottoken, Zone.ee-API-nøglen, webhookens signeringshemmelighed, selve API-nøglen i klartekst — vises kun som en *_set-boolean. For webhookhemmeligheden vises desuden det samme 8-tegns visningspræfiks, som allerede står i dashboardet, så kalderen kan bekræfte, hvilken hemmelighed der er konfigureret, uden nogensinde at se værdien.
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
}
}
Feltreference¶
status- Partnerens livscyklus. En af
onboarding,live,suspended. Brand Partner API afviser writes, medmindre status erlive; kun mintbot-teamet kan flytte dig tillive. domain.modeown(du bruger dit eget apex-domæne) ellermintbot(mintbot leverer et*.mintbot.ai-subdomæne). Vedmintboter DNS-udbyderfelterne bevidst tomme, uanset hvad partneren tidligere har indtastet.domain.subdomain_label_preview- Den renderede etiket, som den første kundeagent ville få (
agent1,1,001, …) med de nuværendesubdomain_prefix,subdomain_numberingogsubdomain_pad_width. Brug den til en ”sådan ser dine kunder det”-forhåndsvisning uden selv at genimplementere nummereringsreglerne. bot.modeown(partnerens egen Telegram-bot),mintbot(mintbots bot bruges under test) ellernone(kun panel, ingen Telegram).template.modedefault(intet tilpasningsrepo til agenten — agenten kører på den rene base uden brand) ellerown(partnerens tilpasningsrepo til agenten pårepo_url). Vedownkloner agentens egen VPS repoet efter standard-deployet og kører detsinstall.sh/update.shfor at lægge din persona, dit paneltema og eventuelt ekstra værktøj oven på basen — central kører aldrig selv repoet. Pegrepo_urlpå dit tilpasningsrepo på GitHub (HTTPS), når du vil have dit eget brand.api.webhook_secret_prefix- De første 8 tegn af webhookens signeringshemmelighed; kun til stede, når en hemmelighed er konfigureret. Lader kalderen skelne, hvilken hemmelighed der bruges, uden at se hele værdien. Dashboardet viser det samme præfiks.
pricing.allowed_credit_options- De
credit_usd-muligheder, du tilbyder dine kunder (vælges under Settings → Pricing). readiness.missing_fields- Liste over dashboardfelter, der stadig kræves før Go Live. Tom, når
readyertrue. Stabil nok til at drive en ”hvad mangler stadig”-visning i din egen admin. readiness.dns_blocker- En læsbar årsag, når tjekket af DNS-udbyderen fejler (f.eks.
partner has no zone.ee username configured). Ellersnull. section_status.*- Spejler dashboardets statusmærker pr. sektion.
statuserok,warnellerdefault(en mintbot-standard er i brug);issueangiver det felt, der blokerer sektionen (f.eks.client_apex_domain,telegram_bot_token,dns_blocker), ellernull.
Eksempel
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¶
Listen over pakker, der kan sælges, til din butik — Brand Partner-pakkerne (mo_test / mo_month / mo_month_plus) kombineret med dine fastlagte priser, i din prisvaluta. Byg dine pakkekort ud fra den i stedet for at hardkode niveau-slugs og priser: udfasede pakker forsvinder af sig selv, og en pris- eller valutaændring, du laver i dashboardet, når din butik inden for cachevinduet — uden nyt deploy.
Det er dette endpoint, der forhindrer en butik i at reklamere for gårsdagens pakker. Referenceportalen kalder det ved hver rendering af forsiden, /buy og /extend (cachet, med en vedvarende fallback).
Visningsteksterne er dine
display_name / description / featured er mintbots kanoniske pakkemetadata. Vil du omdøbe en pakke eller omskrive beskrivelsen til dit brand, så overskriv den i din egen butik (referenceportalen har et PLAN_OVERRIDES-map netop til det) — kataloget er sandhedskilden for, hvilke pakker der findes, og hvad de koster, ikke for din markedsføringstekst.
Hent katalog¶
GET /api/v1/catalog
Ingen parametre. Kun Bearer-auth — der er ingen aktiveringsspærre, så en butik kan vise kataloget, før din partnerkonto er sat live.
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 }
]
}
| Felt | Betydning |
|---|---|
currency |
Din prisvaluta. Alle price_cents nedenfor er angivet i den. |
packages[] |
Én post pr. offentlig pakke, i tilbuds-/visningsrækkefølge. |
tier |
Den slug, du sender til POST /orders / POST /subscriptions. |
display_name / description / featured |
Kanoniske visningstekster (overskriv dem i din butik, hvis du vil). |
default_credit_usd |
Pakkens vejledende inkluderede LLM-kredit (til orientering). |
durations[] |
De opsætningsperioder, pakken kan købes for. months er værdien, du sender som duration_months; price_cents er det, ordren opkræver. Månedspakker tilbyder 1 / 3 / 12 måneder; mo_test tilbyder en enkelt periode på 24 timer. |
subscription |
available er true for pakker, der kan købes som månedlig automatisk fornyelse via POST /subscriptions; price_cents er månedsbeløbet. mo_test kan aldrig købes som abonnement. |
credit_options[] |
Hver credit_usd-mulighed prissat i din valuta (usd = kreditbeløb, price_cents = hvad den lægger oven i ordretotalen). Hvilke muligheder du tilbyder, står i pricing.allowed_credit_options i GET /settings. |
Priserne svarer til det, der faktisk opkræves
Hver price_cents beregnes med den samme prislogik, som order-endpointet bruger, så en kortpris altid er lig med den endelige Stripe Checkout-total for den pågældende (tier, duration_months).
Eksempel¶
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-integrationer skal ofte oprette DNS-records på partnerens apex-domæne — for at pege @, www eller subdomæner pr. kunde på den rigtige server. MintOffice tilbyder en leverandøruafhængig proxy, så du kan gøre det uden nogensinde at håndtere den bagvedliggende udbyders API-nøgle i din egen infrastruktur. De legitimationsoplysninger, du gemte under onboarding (i dag zone.ee; cloudflare og route53 er på køreplanen med samme struktur), forbliver krypterede i MintOffice; dit kald kommer ind med Bearer mo_live_…, og vi foretager kaldet til udbyderen på dine vegne.
Omfanget er partnerens egen konfigurerede apex (domain.client_apex_domain fra GET /settings). Du kan ikke røre en record i en zone, du ikke ejer.
Hvis din DNS-udbyder er Manual, ændrer proxyen intet: writes gør ingenting, og lister kommer tomme tilbage.
I dag understøtter proxyen de operationer, som deploy-pipelinen selv bruger — A-records (upsert / list / slet via id). Understøttelse af CNAME / TXT / MX følger med samme struktur, når den underliggende udbyderabstraktion får dem.
Idempotency-Key er ikke nødvendig her
I modsætning til endpoints, der opretter ordrer, er DNS-upserts naturligt idempotente i udbyderlaget (at køre igen med samme (subdomain, ip) ændrer intet, og forældede dubletter ryddes op ved hvert kald). Du behøver ikke sende headeren Idempotency-Key — genforsøg ender altid med én A-record pr. FQDN.
Opret eller opdater en A-record¶
POST /dns/records
Peger idempotent <subdomain>.<your-apex> på en IPv4-adresse. Send "" (eller "@") for selve apex-domænet.
Request
{
"subdomain": "www",
"ip": "203.0.113.10"
}
subdomain accepterer:
""eller"@"— selve apex-domænet (example.com).- En enkelt DNS-label —
"www","api"— til<label>.<apex>. - Punktumseparerede underlabels —
"api.eu"— til indlejrede subdomæner (api.eu.example.com).
ip skal være en punktumsepareret IPv4-adresse. Hver label er [a-z0-9_-], 1–63 tegn, uden - først eller sidst.
Response — 200
{
"apex": "example.com",
"provider": "zone_ee",
"fqdn": "www.example.com",
"records": [
{ "id": "9182734", "name": "www.example.com", "destination": "203.0.113.10" }
]
}
Listen records efter skrivningen afspejler altid udbyderens aktuelle billede af FQDN’en efter oprydning af dubletter — så et vellykket svar med præcis én record er tegnet på, at intet forældet er tilbage.
List records¶
GET /dns/records
Returnerer alle A-records på den konfigurerede apex. Tilføj ?name=<label-or-fqdn> for at filtrere til én FQDN — værdien kan være en enkelt label ("www"), et punktumsepareret subdomæne ("api.eu") eller hele FQDN’en ("www.example.com").
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" }
]
}
Slet en record¶
DELETE /dns/records/{record_id}
record_id er udbyderens id, som GET /dns/records returnerer (zone.ee bruger heltals-id’er, der laves om til strenge ved API-grænsen). Sletning af en record, der ikke længere findes, regnes som en succes — endpointet er idempotent set fra klientens side.
Response — 200
{ "deleted": true, "id": "9182734" }
Fejl¶
| HTTP | error.code |
Årsag |
|---|---|---|
| 401 | unauthenticated / invalid_api_key |
Manglende eller ugyldigt Bearer-token. |
| 422 | validation_error |
Ugyldigt subdomæne eller en destination, der ikke er IPv4. |
| 422 | dns_not_configured |
Partneren har intet client_apex_domain, eller den konfigurerede udbyders legitimationsoplysninger mangler. Ret det under Settings → Domain. |
| 422 | dns_provider_unsupported |
Partnerens DNS-udbyder er endnu ikke koblet på MintOffice-proxyen (f.eks. pladsholderen for cloudflare). Skift til zone_ee indtil videre. |
| 502 | dns_provider_error |
Udbyderen returnerede 4xx/5xx, eller forbindelsen fejlede. Fejlens message indeholder udbyderens status og et kort uddrag af svaret, renset for hemmeligheder. Sikkert at prøve igen. |
Eksempel¶
# 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¶
Når der sker noget med en order, POSTer vi en signeret JSON event til din konfigurerede webhook URL.
Webhooks er valgfri
Webhook URL og Webhook signing secret under Settings → API access er helt valgfri — lad dem stå tomme under onboarding, hvis du ikke har en modtager endnu. Der leveres først hændelser, når begge er sat — med en URL, men uden signeringshemmelighed, sendes ingen hændelser. Dashboardet og resten af API’et fungerer fint uden webhooks; du kan i stedet polle GET /api/v1/orders/{id} for at følge livscyklussen. Tilføj URL’en (og generér hemmeligheden), når du er klar — ingen genaktivering nødvendig.
Hændelsestyper¶
| Event | Hvornår den udløses |
|---|---|
order.created |
Stripe Checkout session oprettet, afventer betaling. |
order.paid |
Stripe bekræftede betaling. Revenue event registreret. |
order.cancelled |
Order timed out eller blev eksplicit annulleret. |
agent.provisioning_started |
Deploy pipeline startet for denne order. |
agent.ready |
Deploy lykkedes. Payload indeholder panel_url og expires_at. |
agent.failed |
Deploy-pipelinen fejlede. error_step angiver det trin, der gik i stykker; error_message er et kort resumé. |
agent.expired |
Agent TTL udløb. Partneren kan forny via POST /orders/{id}/renew. |
agent.resumed |
En tidligere udløbet agent blev fornyet inden for sin frist og er live igen. Genoprettelses-modstykket til agent.expired. |
subscription.created · subscription.activated · subscription.updated · subscription.cancelled |
Livscyklussen for et abonnement oprettet med POST /subscriptions. |
partner.activated · partner.deactivated |
mintbot-teamet aktiverede din konto eller tog den af live (suspended i payloaden fortæller, om den blev suspenderet eller sat tilbage til onboarding). |
Levering og genforsøg¶
- Op til 7 forsøg efter en eksponentiel plan:
0s, 30s, 2m, 10m, 1h, 6h, 24h. - Efter det sidste forsøg markeres leveringen som
exhausted, og retries stopper. - Svar med en
2xxstatus inden for 10 sekunder for at kvittere.
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
Sample 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
}
Verificering af signaturen¶
Signaturen er HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Verificér altid den rå request body — hvis du serialiserer din parsede JSON igen, ødelægger det sammenligningen.
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 receivers
Brug X-Mintbot-Event-Id som din dedup key — retries genbruger samme id, så en row-level INSERT … ON CONFLICT DO NOTHING på den kolonne holder din handler sikker.
Referenceportal¶
En kørbar end-to-end-reference ligger på mintbot-ai/partner-portal-example — FastAPI + SQLite i Docker, med et eksempelbrand kaldet ExampleAI, som du forker og skifter ud. Arkitekturbilledet — hvordan butikken, dette API, deploy-workeren og tilpasningsrepoet hænger sammen — findes på MintOffice — Teknisk. Portalen indeholder:
- forside, pakkevalg, takkeside og annulleringsside,
POST /buy→POST /api/v1/orders→ omdirigering til Stripe,POST /webhooks/mintofficemed HMAC-verifikationen ovenfor,- en hændelsesbrowser på
/adminbag HTTP Basic-auth, så du hurtigt kan kigge leveringerne igennem.
Fork den, sæt PARTNER_BRAND, MINTOFFICE_API_KEY og MINTOFFICE_WEBHOOK_SECRET i .env, kør docker compose up, og peg din partnerkontos webhook-URL på https://<your-host>/webhooks/mintoffice. Hele gennemgangen står i MintOffice → Referenceportal.
Fejl¶
Hvert error response har en stabil code, som du kan branch'e på programmatisk. message feltet er et menneskelæsbart hint og kan ændre sig mellem releases. request_id gentager X-Request-Id response headeren — medtag det i support tickets.
{
"error": {
"code": "invalid_api_key",
"message": "API key is unknown or revoked.",
"request_id": "f0c2d6c4-…"
}
}
| Code | Betydning |
|---|---|
unauthenticated |
Manglende eller forkert formet Authorization header. |
invalid_api_key |
API key er ukendt eller er roteret ud. |
rate_limited |
Rate limit pr. partner eller pr. IP overskredet — se Retry-After. |
missing_idempotency_key |
POST request uden Idempotency-Key. |
idempotency_key_mismatch |
Samme key genbrugt med en anden request body. |
validation_error |
Request body bestod ikke schema validation. |
not_found |
Ressource findes ikke eller tilhører ikke din partner. |
partner_not_live |
HTTP 403 — en write (POST /orders, /orders/{id}/renew, /subscriptions), mens din konto ikke er live. Body indeholder status og, for konti i onboarding, missing_fields + dns_blocker. |
conflict |
HTTP 409 — en request med samme Idempotency-Key er stadig under behandling. Prøv igen efter Retry-After. |
unpriced_tier |
HTTP 409 — niveauet har ingen pris at opkræve. |
internal_error |
Uventet fejl på serversiden. Medtag request_id, når du kontakter support. |
payment_gateway_error |
Stripe afviste oprettelsen af Checkout session. |
dns_not_configured |
POST /dns/records mod en partner uden apex eller uden DNS-legitimationsoplysninger. Ret det under Settings → Domain. |
dns_provider_unsupported |
Partnerens dns_provider-værdi er endnu ikke koblet på MintOffice-proxyen. |
dns_provider_error |
DNS-udbyderen returnerede en fejl — beskeden indeholder et uddrag renset for hemmeligheder. |
quota_exceeded |
HTTP 429, kun i DEV-miljøet (mint.mintbot.dev) — LIVE har ingen grænse pr. partner. Partneren har brugt alle deploy-pladser (10 som standard, 20 efter Brand Partner-opstartsgebyret). Kontakt mintbots support for at hæve grænsen. Returneres af POST /orders og POST /orders/{id}/renew. Body indeholder deploys_used + deploy_quota. |
Rate limits¶
- 120 requests / 60 s rolling window, pr. partner. Burst og steady-state deler samme bucket.
- 60 requests / 60 s separat per-IP bucket for unauthenticated og failed-auth requests — forhindrer bearer brute-force i at æde af partnerens kvote.
- Overskydende requests returnerer
429 rate_limitedmed enRetry-Afterheader (sekunder at vente).
Brug for hjælp?¶
Denne dokumentation er skrevet til de partnere, der faktisk bruger API'et. Hvis noget mangler, er forvirrende eller forældet, så nævn det for din mintbot agent — de videresender feedbacken, og vi opdaterer siden.