Brand Partner API¶
Una REST API pequeña y predecible para crear pedidos de mintbot, consultar su estado y recibir eventos de ciclo de vida. JSON entra, JSON sale. Autenticación con bearer token. Escrituras idempotentes. Webhooks firmados.
Panel de acceso a la API Saltar a Webhooks
De un vistazo¶
| URL base | https://mint.mintbot.ai/api/v1 |
| Autenticación | `Authorization: Bearer *** |
| Idempotencia | Idempotency-Key: <uuid> en cada POST |
| Tipo de contenido | application/json |
| Rate limit | 120 req / 60 s por partner |
| Webhooks | Firmados con HMAC-SHA256, reintentados hasta 7 veces |
Autenticación¶
Cada solicitud envía una clave de API de partner en el header Authorization. Genera o rota la clave en el dashboard.
Authorization: Bearer mo_liv...xxxx
Content-Type: application/json
La rotación no tiene ventana de gracia
Rotar la clave revoca la anterior de forma atómica. Planifica cambiar el valor en tu configuración antes de hacer clic en Rotate.
Idempotencia¶
Cada POST requiere un header Idempotency-Key. Sirve cualquier UUID por cada solicitud distinta.
- Guardamos la respuesta en caché durante 24 horas. Un reintento con la misma clave reproduce la respuesta original con
Idempotent-Replay: true. - Reutilizar la misma clave con un body diferente devuelve
409 idempotency_key_mismatch.
Pedidos¶
Crear pedido¶
POST /orders
Crea un pedido y devuelve una URL de Stripe Checkout. Cuando se confirma el pago, mintbot provisiona el agente y el evento de ingresos correspondiente a la parte del partner se registra automáticamente.
Solicitud
{
"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- Uno de
trial,s1,s2,s4. duration_months- Meses calendario de vida útil del servidor. Debe ser uno de
1,3o12. credit_usd- Opcional. Crédito de chat prefinanciado incluido con el pedido.
language- Opcional. Afecta al locale de Stripe Checkout y al idioma del email de bienvenida.
external_id- Opcional. Se devuelve en cada webhook y respuesta de pedido: úsalo para vincular pedidos de mintbot con filas de tu propio sistema.
success_url·cancel_url- URLs de retorno de Stripe Checkout.
{ORDER_ID}se sustituye en el servidor. webhook_url- Opcional. Sustitución por pedido de la URL de webhook configurada a nivel de partner.
Respuesta — 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
}
Ejemplo
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"]
Obtener pedido¶
GET /orders/{id}
Obtiene un solo pedido por su id de mintbot. Devuelve la misma forma que POST /orders.
curl https://mint.mintbot.ai/api/v1/orders/42 \
-H "Authorization: Bearer ***
Listar pedidos¶
GET /orders
Lista paginada con cursor, de más reciente a más antiguo.
Parámetros de consulta
status- Opcional. Filtra por
awaiting_payment,completed,deployed,deploy_failedoexpired. cursor- Opcional. Pasa el
next_cursorde la página anterior para continuar.
Respuesta
{
"items": [ /* OrderResponse … */ ],
"next_cursor": "37"
}
next_cursor es null cuando no hay más páginas.
Renovar pedido¶
POST /orders/{id}/renew
Extiende un agente existente durante otros duration_months. Solo infraestructura: no se incluye crédito de chat nuevo. Devuelve un nuevo id de pedido y una URL nueva de Stripe Checkout.
Solicitud
{
"duration_months": 1,
"external_id": "your-side-id",
"success_url": "https://your.app/thanks?id={ORDER_ID}",
"cancel_url": "https://your.app/account"
}
Ingresos¶
Leer ingresos¶
GET /revenue
Totales más los 200 eventos de ledger más recientes.
Parámetros de consulta
include_paid- Opcional, por defecto
true. Ponlo enfalsepara ver solo eventos que aún no se han pagado.
Respuesta
{
"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
}
]
}
Perfil del partner¶
Obtener perfil¶
GET /partner
Devuelve tu perfil de partner más el saldo no pagado: útil para mostrar ingresos dentro de tu propio panel de administración sin almacenarlos tú.
Respuesta
{
"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¶
Cuando le ocurre algo a un pedido, hacemos POST de un evento JSON firmado a tu URL de webhook configurada.
Tipos de evento¶
| Evento | Cuándo se dispara |
|---|---|
order.created |
Sesión de Stripe Checkout creada, esperando pago. |
order.paid |
Stripe confirmó el pago. Evento de ingresos registrado. |
order.cancelled |
El pedido agotó el tiempo o fue cancelado explícitamente. |
agent.provisioning_started |
El pipeline de deploy empezó para este pedido. |
agent.ready |
El deploy tuvo éxito. El payload contiene panel_url y expires_at. |
agent.failed |
El pipeline de deploy falló. Revisa el campo error para ver el paso que se rompió. |
agent.expired |
El TTL del agente terminó. El partner puede renovar vía POST /orders/{id}/renew. |
Entrega y reintentos¶
- Hasta 7 intentos con un calendario exponencial:
0s, 30s, 2m, 10m, 1h, 6h, 24h. - Tras el último intento, la entrega se marca como
exhaustedy deja de reintentarse. - Responde con un estado
2xxen un plazo de 10 segundos para confirmar la recepción.
Headers de solicitud¶
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
Payload de ejemplo — 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"
}
Verificar la firma¶
La firma es HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Verifica siempre el cuerpo raw de la solicitud: volver a serializar el JSON parseado romperá la comparación.
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"),
);
}
Receptores idempotentes
Usa X-Mintbot-Event-Id como clave de deduplicación: los reintentos reutilizan el mismo id, así que un INSERT … ON CONFLICT DO NOTHING a nivel de fila sobre esa columna mantiene seguro tu handler.
Errores¶
Cada respuesta de error lleva un code estable sobre el que puedes ramificar programáticamente. El campo message es una pista legible para humanos y puede cambiar entre versiones. El request_id replica el header de respuesta X-Request-Id; inclúyelo en tickets de soporte.
{
"error": {
"code": "invalid_api_key",
"message": "API key is unknown or revoked.",
"request_id": "f0c2d6c4-…"
}
}
| Código | Significado |
|---|---|
unauthenticated |
Header Authorization ausente o mal formado. |
invalid_api_key |
La API key es desconocida o fue rotada. |
rate_limited |
Se superó el rate limit por partner o por IP; consulta Retry-After. |
missing_idempotency_key |
Solicitud POST sin Idempotency-Key. |
idempotency_key_mismatch |
Misma clave reutilizada con un body de solicitud diferente. |
validation_error |
El body de la solicitud no pasó la validación del esquema. |
not_found |
El recurso no existe o no pertenece a tu partner. |
payment_gateway_error |
Stripe rechazó la creación de la sesión de Checkout. |
Rate limits¶
- 120 solicitudes / ventana móvil de 60 s, por partner. Ráfagas y estado estable comparten el mismo bucket.
- 60 solicitudes / 60 s, bucket separado por IP para solicitudes no autenticadas y con autenticación fallida: evita que un ataque de fuerza bruta al bearer consuma la cuota del partner.
- Las solicitudes excedentes devuelven
429 rate_limitedcon un headerRetry-After(segundos que debes esperar).
¿Necesitas ayuda?¶
Esta documentación está escrita para los partners que realmente usan la API. Si algo falta, resulta confuso o está desactualizado, coméntaselo a tu agente de mintbot: nos reenviará el feedback y actualizaremos la página.