API de Innobox
API HTTP para vender software con licencias de forma headless: integra la tienda, cobra en tu propio Stripe, entrega llaves de licencia y consulta tus métricas — todo desde tu backend o tu sitio. Pensada para promotores que quieren embeber o automatizar su tienda, y para desarrolladores que construyen apps sobre la plataforma.
URL base
https://eosxwcbciazxontwytie.supabase.co/functions/v1/
Todas las rutas de esta página son relativas a esa base. Ejemplo: POST /reseller-checkout = POST https://…/functions/v1/reseller-checkout.
Autenticación
Hay dos credenciales de promotor. Elige según cómo integres. Los endpoints de licencias y de analítica no requieren autenticación de promotor (ver sus secciones).
a) API key del promotor x-api-key
Clave permanente rk_live_… que obtienes (y rotas) desde tu panel de promotor. Ideal para integraciones servidor-a-servidor: no expira. Envíala en el header:
x-api-key: rk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Sirve para reseller-api (todas las acciones) y para canjear ventas en reseller-mint (POST). Guárdala solo en tu backend; nunca la expongas en el navegador.
b) Token de sesión Bearer
Token de corta vida (24 h) que obtienes con correo + contraseña vía reseller-auth. Pensado para un dashboard/UI de promotor. Envíalo como Bearer:
Authorization: Bearer <token>
Login del promotor. Verifica correo + contraseña y devuelve un token de sesión (Ed25519, válido 24 h). Tras 8 intentos fallidos bloquea la cuenta 15 minutos.
Body
| Campo | Tipo | Descripción | |
|---|---|---|---|
email | string | obligatorio | Correo del promotor. |
password | string | obligatorio | Contraseña del promotor. |
Respuesta
{ "token": "eyJ0eXAiOiJyZXNlbGxlciIsInJpZCI6Ii4uLiJ9.SIG..." }Ejemplo
curl -X POST https://eosxwcbciazxontwytie.supabase.co/functions/v1/reseller-auth \
-H "Content-Type: application/json" \
-d '{"email":"promotor@correo.com","password":"tu-contrasena"}'1 · Catálogo
Lee los productos publicados y el branding del vendedor. Solo lectura, público, sin auth. Con ?r=<reseller_id> devuelve la tienda del promotor (su marca y sus precios); sin él, la tienda matriz (Innobox).
Catálogo completo con ficha comercial (categoría, tagline, descripción, features, media, precios y planes). Es lo que alimenta el storefront white-label.
Query params
| Nombre | Tipo | Descripción | |
|---|---|---|---|
r | uuid | opcional | ID del promotor. Presente = tienda del promotor con sus precios; ausente = tienda matriz. |
category | string | opcional | Filtra por categoría exacta. |
q | string | opcional | Búsqueda libre sobre nombre / tagline / categoría / descripción. |
Respuesta (recortada)
{
"ok": true,
"seller": "matrix",
"branding": { "name": "Innobox", "logo": "", "accent": "" },
"pixels": { "meta_pixel_id": "", "ga4_measurement_id": "", "tiktok_pixel_id": "" },
"categories": ["Ventas y CRM"],
"products": [{
"slug": "leadops",
"name": "LeadOps",
"category": "Ventas y CRM",
"tagline": "Encuentra, califica y cierra…",
"description": "…",
"features": ["…"],
"media": { "logo": "", "screenshots": [], "download_url": "…" },
"price": { "amount": 350000, "currency": "MXN" },
"plans": [{ "key": "individual", "name": "Individual", "seats": 1, "amount": 350000, "currency": "MXN" }]
}]
}Con ?r= la respuesta añade seller:"reseller", reseller_id y delivery_mode; el promotor vende a precio único (un solo plan sintético).
Ejemplo
curl "https://eosxwcbciazxontwytie.supabase.co/functions/v1/store-catalog?r=RESELLER_ID"
Branding público + catálogo con los precios del promotor, filtrado al piso general y a la moneda vigente. Versión ligera pensada para pintar la vitrina antes de vender.
Query params
| Nombre | Tipo | Descripción | |
|---|---|---|---|
r | uuid | obligatorio | ID del promotor. |
Respuesta
{
"ok": true,
"name": "Acme Software",
"logo": "https://…/logo.png",
"accent": "#ff6b35",
"delivery_mode": "auto_email",
"configured": true,
"products": [
{ "slug": "leadops", "name": "LeadOps", "price": { "amount": 350000, "currency": "MXN" } }
]
}Ejemplo
curl "https://eosxwcbciazxontwytie.supabase.co/functions/v1/reseller-checkout?r=RESELLER_ID"
2 · Vender (headless)
Flujo del promotor en dos pasos: (a) crea una sesión de Checkout en tu Stripe y redirige al comprador; (b) tras el pago, canjea la sesión para mintear/renovar la licencia. El dinero nunca pasa por Innobox — se crea en tu cuenta con la llave secreta que registraste.
r)
Crea la sesión de Stripe Checkout en la cuenta del promotor con el precio que fijó (revalidado contra el piso general). Devuelve la URL a la que rediriges al comprador.
Body
| Campo | Tipo | Descripción | |
|---|---|---|---|
r | uuid | obligatorio | ID del promotor. |
product_slug | string | obligatorio | Slug del producto publicado (p. ej. leadops). |
buyer_email | string | opcional | Correo del comprador (prellena el Checkout y se usa para la entrega). |
renew_key | string | opcional | Si viene, es una renovación (+1 año) de esa clave existente en lugar de una venta nueva. |
utm | object | opcional | Atribución: utm_source/medium/campaign/content/term. Se propaga a la venta. |
La tienda del promotor es de precio único: reseller-checkout no recibe plan (los niveles Individual/Equipo/Agencia son concepto de la matriz).
Respuesta
{ "url": "https://checkout.stripe.com/c/pay/cs_live_..." }Ejemplo
curl -X POST https://eosxwcbciazxontwytie.supabase.co/functions/v1/reseller-checkout \
-H "Content-Type: application/json" \
-d '{"r":"RESELLER_ID","product_slug":"leadops","buyer_email":"cliente@correo.com"}'Entregar / canjear la licencia
Tras el pago, Stripe redirige a tu success_url con session_id. Dispones de dos caminos para materializar la licencia. Además, el webhook que reseller-api → stripe-setup creó en tu cuenta mintea automáticamente en segundo plano (idempotente), así que el canje manual nunca duplica.
Camino de la página pública de éxito: re-consulta la sesión con la sk del promotor, mintea si procede y responde según el delivery_mode. Si el pago aún no se confirma, devuelve pending.
Respuestas posibles
// venta, modo auto_email
{ "ok": true, "delivered": "auto_email", "key": "LO-XXXX-XXXX-XXXX-XXXX" }
// venta, modo self (no revela la clave al comprador)
{ "ok": true, "delivered": "self", "message": "El vendedor te enviará tu clave" }
// renovación
{ "ok": true, "renewal": true }
// pago aún en proceso
{ "ok": false, "pending": true, "error": "Pago en proceso" }Ejemplo
curl "https://eosxwcbciazxontwytie.supabase.co/functions/v1/reseller-mint?r=RESELLER_ID&session_id=cs_live_..."
Camino headless: autenticado con tu API key, siempre devuelve la clave para que tú la entregues (independiente del delivery_mode). Idempotente por sesión.
Body
| Campo | Tipo | Descripción | |
|---|---|---|---|
session_id | string | obligatorio | ID de la sesión de Checkout pagada. |
Respuesta
{ "ok": true, "key": "LO-XXXX-XXXX-XXXX-XXXX", "minted": true, "kind": "sale" }Ejemplo
curl -X POST "https://eosxwcbciazxontwytie.supabase.co/functions/v1/reseller-mint?r=RESELLER_ID" \
-H "x-api-key: rk_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"session_id":"cs_live_..."}'3 · Venta directa (matriz)
Venta directa de Innobox (nuestro Stripe, precio de lista). Soporta niveles/planes, cupones de lanzamiento y renovación. La entrega la hacen el webhook y la página de éxito de la matriz automáticamente.
Crea la sesión de Checkout en el Stripe de la matriz y devuelve la URL de pago.
Body
| Campo | Tipo | Descripción | |
|---|---|---|---|
product_slug | string | obligatorio | Slug del producto publicado. |
plan | string | opcional | Clave del nivel del producto (p. ej. individual, equipo). Sin él se usa el primer plan. Solo aplica a ventas nuevas. |
coupon | string | opcional | Código de cupón/promoción a pre-aplicar (solo venta nueva). Si no se pasa, el Checkout permite teclear código. |
renew_key | string | opcional | Renovación (+1 año) de esa clave en lugar de venta nueva. |
buyer_email | string | opcional | Correo del comprador (prellena el Checkout). |
utm | object | opcional | Atribución utm_*. |
test | boolean | opcional | Usa la llave de Stripe en modo prueba. |
Respuesta
{ "url": "https://checkout.stripe.com/c/pay/cs_live_..." }Ejemplo
curl -X POST https://eosxwcbciazxontwytie.supabase.co/functions/v1/store-checkout \
-H "Content-Type: application/json" \
-d '{"product_slug":"leadops","plan":"individual","buyer_email":"cliente@correo.com"}'4 · Gestionar (promotor)
Todo el autoservicio del promotor pasa por una función: reseller-api. Siempre es POST con body { "action": "…", … } y autenticación por x-api-key o Authorization: Bearer. Abajo, cada acción con su body y respuesta.
Ejemplo genérico
curl -X POST https://eosxwcbciazxontwytie.supabase.co/functions/v1/reseller-api \
-H "x-api-key: rk_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"action":"summary"}'summary — resumen del panel
Body: solo action. Devuelve datos del promotor, cupo, piso general y últimas ventas.
{
"ok": true,
"reseller": { "id":"…", "email":"…", "name":"…", "delivery_mode":"auto_email",
"stripe_configured": true, "test_mode": false, "api_key_prefix":"rk_live_abc",
"branding": {…}, "prices": {…} },
"quota": { "total": 100, "used": 12, "available": 88 },
"floor": { "amount": 150000, "currency": "MXN" },
"sales_count": 12,
"last_sales": [ { "id":"…", "kind":"sale", "buyer_email":"…", "amount":350000, "currency":"MXN", "licenses": { "key":"LO-…" } } ]
}sales — detalle de ventas
Body: { action, limit? } (limit 1–200, def. 50). Devuelve { ok, sales:[…] } con clave, estado y update_until de cada licencia.
metrics — funnel, ventas y campañas
Body: { action, from?, to? } (ISO; def. últimos 30 días).
{
"ok": true, "from": "…", "to": "…",
"funnel": { "view": 800, "click": 300, "begin_checkout": 120, "purchase": 40 },
"conversion_rate": 5.0,
"sales": 40, "revenue": 14000000,
"by_campaign": [ { "campaign":"…", "views":…, "clicks":…, "sales":…, "revenue":… } ],
"series": [ { "day":"2026-07-01", "views":…, "purchases":… } ]
}set-prices — fijar precios por producto
Body: { action, prices } donde prices es un objeto por slug: { amount, currency } (≥ piso, moneda exacta), o { hidden: true } para ocultarlo, o sin amount para volver a precio de lista. Devuelve { ok, prices }.
{"action":"set-prices","prices":{"leadops":{"amount":390000,"currency":"MXN"}}}set-branding — marca de la tienda
Body: { action, branding } (se hace merge). Campos: name, logo, accent. Devuelve { ok, branding }.
{"action":"set-branding","branding":{"name":"Acme Software","accent":"#0aa","logo":"https://…/logo.png"}}set-delivery — modo de entrega
Body: { action, mode } con mode = "auto_email" o "self". Devuelve { ok, delivery_mode }.
set-marketing — pixeles / conversiones
Body: { action, marketing } con IDs públicos (meta_pixel_id, ga4_measurement_id, tiktok_pixel_id) y tokens secretos (meta_capi_token, ga4_api_secret, se cifran; vacío = borrar). La respuesta nunca devuelve los tokens, solo flags meta_capi / ga4_secret.
batch-quote — cotizar cupo (sin efecto)
Body: { action, qty }. Devuelve el precio por volumen antes de comprar.
{ "ok": true, "min": 50, "qty": 100, "valid": true, "unit": 75000, "total": 7500000, "currency": "MXN", "tiers": [ { "min":50, "amount":80000 }, … ] }buy-batch — comprar cupo
Body: { action, qty, test? } (qty 50–500). Crea un Checkout en el Stripe de la matriz; el cupo se acredita solo al pagar. Devuelve { url }.
stripe-setup — conectar tu Stripe
Body: { action, sk, pk?, test? }. Valida tu llave secreta, crea el webhook en tu cuenta (apuntando a reseller-mint) y cifra sk + whsec. Devuelve { ok, account_id, test_mode }.
{"action":"stripe-setup","sk":"sk_live_xxx","pk":"pk_live_xxx"}stripe-status — salud de la conexión
Body: solo action. Devuelve { ok, configured, endpoint_status?, account_id }.
domain-add / domain-status / domain-remove — dominios propios
domain-add: { action, hostname, target? } (target def. store) → alta de Custom Hostname (Cloudflare for SaaS); si el alta automática no está habilitada devuelve { manual:true, instructions, cname_target }. domain-status: { action, id | hostname } → estado de DNS/SSL. domain-remove: { action, id } → { ok, removed:true }. (domains lista todos.)
rotate-api-key — nueva API key
Body: solo action. Genera una nueva rk_live_… (se muestra una vez) e invalida la anterior. Devuelve { ok, api_key, api_key_prefix }.
5 · Licencias (para apps)
Endpoints que consumen las apps de escritorio construidas sobre la plataforma: activan una licencia en una máquina y refrescan su estado. Devuelven un token firmado (Ed25519) que la app verifica offline. No requieren credenciales de promotor.
Valida la clave (o inicia una prueba), aplica el límite de asientos por máquina y devuelve el token firmado. La licencia de promotor se ata a su producto en la primera activación.
Body
| Campo | Tipo | Descripción | |
|---|---|---|---|
machine_id | string | obligatorio | Identificador estable de la máquina. |
key | string | condicional | Clave de licencia. Obligatoria salvo que se inicie una prueba. |
productSlug | string | opcional | Producto a activar (def. leadops). |
trial | boolean | opcional | Con true y sin key, inicia/recupera la prueba (14 días) anclada a la máquina. |
machine_name, os, app_version | string | opcional | Metadatos de la activación. |
Respuesta
{ "token": "<payloadB64>.<sigB64>" }Ejemplo
curl -X POST https://eosxwcbciazxontwytie.supabase.co/functions/v1/activate \
-H "Content-Type: application/json" \
-d '{"productSlug":"leadops","key":"LO-XXXX-XXXX-XXXX-XXXX","machine_id":"m-123","os":"win"}'Heartbeat: refresca el token con el estado actual de la licencia y actualiza last_seen. Si la licencia o la activación ya no valen, devuelve { revoked: true } para que la app se bloquee.
Body
| Campo | Tipo | Descripción | |
|---|---|---|---|
machine_id | string | obligatorio | La misma máquina de la activación. |
key | string | condicional | Clave, o bien manda token (se lee la clave de su payload). |
token | string | condicional | Token actual; alternativa a key. |
Respuesta
{ "token": "<nuevo token firmado>" } // o { "revoked": true }Ejemplo
curl -X POST https://eosxwcbciazxontwytie.supabase.co/functions/v1/validate \
-H "Content-Type: application/json" \
-d '{"key":"LO-XXXX-XXXX-XXXX-XXXX","machine_id":"m-123"}'6 · Analítica
Ingesta pública de eventos de funnel que emiten el storefront y las landings. Fire-and-forget: siempre responde 200 y nunca bloquea la página. Anti-PII: solo se guardan campos conocidos.
Registra eventos del embudo. Acepta un lote { events:[…] } (máx. 50) o un evento suelto. Los eventos inválidos y las claves desconocidas se descartan.
Campos de cada evento
| Campo | Tipo | Descripción | |
|---|---|---|---|
event | string | obligatorio | Uno de view, click, begin_checkout, purchase. |
product_slug | string | opcional | Slug del producto. |
session_id | string | opcional | ID de sesión anónima del navegador. |
seller_kind | string | opcional | "reseller" para atribuir al promotor (requiere reseller_id). |
reseller_id | uuid | opcional | ID del promotor (uuid válido); si falta, la venta es de la matriz. |
utm | object | opcional | Solo claves utm_*. |
hostname | string | opcional | Host de la página. |
Respuesta
{ "ok": true, "accepted": 2 }Ejemplo
curl -X POST https://eosxwcbciazxontwytie.supabase.co/functions/v1/track \
-H "Content-Type: application/json" \
-d '{"events":[{"event":"view","product_slug":"leadops","seller_kind":"reseller","reseller_id":"RESELLER_ID"}]}'