Brand Partner API¶
Une petite API REST prévisible pour créer des commandes mintbot, consulter leur statut et recevoir des événements de cycle de vie. JSON en entrée, JSON en sortie. Authentification par bearer token. Écritures idempotentes. Webhooks signés.
Dashboard d’accès API Aller aux webhooks
En bref¶
| Base URL | https://mint.mintbot.ai/api/v1 |
| Auth | `Authorization: Bearer *** |
| Idempotency | Idempotency-Key: <uuid> sur chaque POST |
| Content type | application/json |
| Rate limit | 120 req / 60 s par partenaire |
| Webhooks | Signés avec HMAC-SHA256, réessayés jusqu’à 7 fois |
Authentification¶
Chaque requête envoie une clé API partenaire dans l’en-tête Authorization. Génère ou fais tourner la clé dans le dashboard.
Authorization: Bearer mo_liv...xxxx
Content-Type: application/json
La rotation n’a pas de fenêtre de grâce
Faire tourner la clé révoque atomiquement la précédente. Prévois de remplacer la valeur dans ta config avant de cliquer sur Rotate.
Idempotence¶
Chaque POST requiert un en-tête Idempotency-Key. N’importe quel UUID convient pour chaque requête distincte.
- Nous mettons la réponse en cache pendant 24 heures. Une nouvelle tentative avec la même clé rejoue la réponse d’origine avec
Idempotent-Replay: true. - Réutiliser la même clé avec un corps différent renvoie
409 idempotency_key_mismatch.
Commandes¶
Créer une commande¶
POST /orders
Crée une commande et renvoie une URL Stripe Checkout. Une fois le paiement confirmé, mintbot provisionne l’agent et l’événement de revenu correspondant à la part partenaire est enregistré automatiquement.
Requête
{
"tier": "s1",
"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- Une des valeurs
trial,s1,s2,s4. duration_months- Durée de vie du serveur en mois calendaires. Doit être
1,3ou12. credit_usd- Optional. Crédit de chat préfinancé inclus avec la commande.
language- Optional. Influence la locale de Stripe Checkout et la langue de l’e-mail de bienvenue.
external_id- Optional. Renvoyé dans chaque webhook et chaque réponse de commande — utilise-le pour relier les commandes mintbot aux lignes de ton propre système.
success_url·cancel_url- URL de retour Stripe Checkout.
{ORDER_ID}est remplacé côté serveur. webhook_url- Optional. Remplacement, pour cette commande, de l’URL de webhook configurée au niveau partenaire.
Réponse — 201 Created
{
"id": 42,
"tier": "s1",
"duration_months": 1,
"credit_usd": 10,
"amount_cents": 1200,
"currency": "usd",
"status": "awaiting_payment",
"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
}
Exemple
curl -X POST https://mint.mintbot.ai/api/v1/orders \
-H "Authorization: Bearer *** \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"tier": "s1",
"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": "s1",
"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"]
Récupérer une commande¶
GET /orders/{id}
Récupère une commande unique à partir de son id mintbot. Renvoie la même forme que POST /orders.
curl https://mint.mintbot.ai/api/v1/orders/42 \
-H "Authorization: Bearer ***
Lister les commandes¶
GET /orders
Liste paginée par curseur, des plus récentes aux plus anciennes.
Paramètres de requête
status- Optional. Filtrer par
awaiting_payment,completed,deployed,deploy_failedouexpired. cursor- Optional. Passe
next_cursordepuis la page précédente pour continuer.
Réponse
{
"items": [ /* OrderResponse … */ ],
"next_cursor": "37"
}
next_cursor vaut null quand il n’y a plus de pages.
Renouveler une commande¶
POST /orders/{id}/renew
Prolonge un agent existant de duration_months supplémentaires. Infra uniquement — aucun nouveau crédit de chat n’est inclus. Renvoie un nouvel id de commande et une nouvelle URL Stripe Checkout.
Requête
{
"duration_months": 1,
"external_id": "your-side-id",
"success_url": "https://your.app/thanks?id={ORDER_ID}",
"cancel_url": "https://your.app/account"
}
Revenus¶
Lire les revenus¶
GET /revenue
Totaux plus les 200 derniers événements du ledger.
Paramètres de requête
include_paid- Optional, default
true. Mets àfalsepour voir uniquement les événements qui n’ont pas encore été payés.
Réponse
{
"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
}
]
}
Profil partenaire¶
Récupérer le profil¶
GET /partner
Renvoie ton profil partenaire ainsi que le solde impayé — pratique pour afficher les gains dans ta propre interface d’administration sans les stocker toi-même.
Réponse
{
"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"
}
Webhooks¶
Quand quelque chose arrive à une commande, nous envoyons par POST un événement JSON signé à ton URL de webhook configurée.
Types d’événements¶
| Événement | Quand il se déclenche |
|---|---|
order.created |
Session Stripe Checkout créée, en attente de paiement. |
order.paid |
Paiement confirmé par Stripe. Événement de revenu enregistré. |
order.cancelled |
Commande expirée ou explicitement annulée. |
agent.provisioning_started |
Pipeline de déploiement démarré pour cette commande. |
agent.ready |
Déploiement réussi. Le payload contient panel_url et expires_at. |
agent.failed |
Erreur dans le pipeline de déploiement. Consulte le champ error pour l’étape qui a cassé. |
agent.expired |
TTL de l’agent écoulé. Le partenaire peut renouveler via POST /orders/{id}/renew. |
Livraison et nouvelles tentatives¶
- Jusqu’à 7 tentatives selon un calendrier exponentiel :
0s, 30s, 2m, 10m, 1h, 6h, 24h. - Après la dernière tentative, la livraison est marquée
exhaustedet les nouvelles tentatives s’arrêtent. - Réponds avec un statut
2xxsous 10 secondes pour acquitter.
En-têtes de requête¶
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
Exemple de payload — order.paid¶
{
"id": 42,
"tier": "s1",
"duration_months": 1,
"credit_usd": 10,
"amount_cents": 1200,
"currency": "usd",
"status": "completed",
"external_id": "your-side-id",
"paid_at": "2026-05-16 10:02:14"
}
Vérifier la signature¶
La signature est HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Vérifie toujours le corps de requête brut — resérialiser le JSON déjà parsé cassera la comparaison.
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"),
);
}
Récepteurs idempotents
Utilise X-Mintbot-Event-Id comme clé de déduplication — les nouvelles tentatives réutilisent le même id, donc un INSERT … ON CONFLICT DO NOTHING au niveau ligne sur cette colonne garde ton handler sûr.
Erreurs¶
Chaque réponse d’erreur porte un code stable sur lequel tu peux brancher ton code. Le champ message est une indication lisible par un humain et peut changer entre les versions. request_id reprend l’en-tête de réponse X-Request-Id — inclus-le dans les tickets de support.
{
"error": {
"code": "invalid_api_key",
"message": "API key is unknown or revoked.",
"request_id": "f0c2d6c4-…"
}
}
| Code | Signification |
|---|---|
unauthenticated |
En-tête Authorization manquant ou mal formé. |
invalid_api_key |
La clé API est inconnue ou a été remplacée par rotation. |
rate_limited |
Limite de débit par partenaire ou par IP dépassée — voir Retry-After. |
missing_idempotency_key |
Requête POST sans Idempotency-Key. |
idempotency_key_mismatch |
Même clé réutilisée avec un corps de requête différent. |
validation_error |
Le corps de la requête a échoué à la validation du schéma. |
not_found |
La ressource n’existe pas ou n’appartient pas à ton partenaire. |
payment_gateway_error |
Stripe a rejeté la création de la session Checkout. |
Limites de débit¶
- 120 requêtes / fenêtre glissante de 60 s, par partenaire. Les bursts et le débit stable partagent le même bucket.
- 60 requêtes / 60 s dans un bucket séparé par IP pour les requêtes non authentifiées et les échecs d’authentification — cela empêche une attaque brute-force bearer de consommer le quota partenaire.
- Les requêtes en trop renvoient
429 rate_limitedavec un en-têteRetry-After(secondes d’attente).
Besoin d’aide ?¶
Cette documentation est écrite pour les partenaires qui utilisent réellement l’API. Si quelque chose manque, prête à confusion ou n’est plus à jour, signale-le à ton agent mintbot — il transmettra le retour et nous mettrons la page à jour.