API de gestión
Además de consultar socios y registrar movimientos (API pública v1), /api/v1 permite configurar el programa sin entrar en el panel: programas y niveles, campañas, catálogo de recompensas, lotes de cupones y resultados de sorteos.
En los ejemplos, $BASE es la dirección de tu plataforma y $KEY tu clave API (o un token OAuth2).
Autenticación y ámbitos
Cada llamada lleva Authorization: Bearer mk_.... La clave pertenece a una cuenta de cliente y todo se acota a esa cuenta: un recurso de otra cuenta responde 404, nunca 403.
Estos ámbitos son explícitos: una clave sin ámbitos (las antiguas, con acceso completo) no los recibe, y hay que concederlos a la clave expresamente.
El selector Ámbitos de Administración › Claves API muestra otra lista (documents:read, credits:write, webhooks:write…) y no permite escribir valores libres. Para conceder los ámbitos de esta página, usa la API de administración con una sesión de administrador que tenga el permiso Claves API: POST /api/services/app/ApiKey/UpdateApiKey (o CreateApiKey al crear una nueva) con el campo scopes como texto separado por comas, por ejemplo "read,programs:read,programs:write". UpdateApiKey recibe la clave completa (id, name, isActive, límites…), no solo scopes.
| Ámbito | Permite |
|---|---|
programs:read | Leer programas y niveles |
programs:write | Crear, modificar y borrar programas y niveles |
campaigns:read | Leer campañas, sus campos personalizados y sus reglas de participación |
campaigns:write | Crear, modificar y borrar campañas; activate, pause; campos y reglas |
rewards:read | Leer el catálogo de recompensas |
rewards:write | Crear, reemplazar y borrar recompensas |
coupons:read | Listar cupones y sus códigos |
coupons:manage | Emitir lotes e invalidar cupones |
raffles:read | Leer sorteos realizados y sus ganadores |
read | Leer el esquema de atributos de socio (además de lo de la API pública) |
Los ámbitos se comparan tal cual: campaigns:write no incluye campaigns:read, y coupons:write (canje en caja) no permite emitir cupones. Pide a la clave solo lo que necesita.
Cada bloque exige además que la función esté incluida en el plan de la cuenta (programa de puntos, niveles, campañas, recompensas, cupones; y Generador de cupones en bloque para emitir lotes). Si no lo está, la respuesta es 403 con code: feature_disabled.
Convenciones comunes
Paginación. Los listados devuelven { "items": [...], "nextCursor": "..." }. Envía nextCursor como cursor para pedir la página siguiente; es null en la última. limit va de 1 a 200 (50 por defecto; sorteos: 1 a 100, 20 por defecto). Un cursor manipulado da 400 con code: invalid_cursor.
Errores. El cuerpo trae siempre un campo code estable; programa tu lógica contra code, no contra el texto. Los códigos no se renombran, pero pueden añadirse nuevos. Los límites por hora y el 429 son los de la clave.
Idempotencia. POST, PATCH y DELETE aceptan Idempotency-Key (ver API pública). Es obligatoria al emitir lotes de cupones (400 con code: idempotency_key_required si falta).
Enumerados. Programas, niveles y campañas usan texto en minúsculas con guion bajo (points, instant_win). Recompensas y cupones usan el nombre tal cual (Physical, PercentageDiscount). Trata cualquier valor desconocido con tolerancia: pueden añadirse.
Cambios parciales (PATCH). Solo cambian las propiedades que envías. Un null explícito borra los campos que admiten vacío; en el resto da 400 validation_failed.
Programas
Una cuenta tiene como máximo un programa activo. Función del plan: programa de puntos.
| Método y ruta | Ámbito | Qué hace |
|---|---|---|
GET /api/v1/programs?status=&cursor=&limit= | programs:read | Lista (por id) |
GET /api/v1/programs/{id} | programs:read | Un programa |
POST /api/v1/programs | programs:write | Crea (201) |
PATCH /api/v1/programs/{id} | programs:write | Modificación parcial |
DELETE /api/v1/programs/{id} | programs:write | Borra (204) |
Valores de type: points, cashback, stamps, product_cashback. Valores de status: draft, active, paused, archived. Campos principales: name, description, pointsCurrencyName, pointsToMoneyRate (valor en dinero de un punto, de 0 a 1), pointsExpirationDays, pendingPeriodDays, qualifyingPointsWindowMonths, tierGracePeriodDays, cashbackPercentage y modules (points, cashback, stamps, evidence, games, giftCards, courses, challenges, cada uno true/false).
curl -s -X POST "$BASE/api/v1/programs" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Programa de puntos","type":"points","pointsCurrencyName":"puntos","pointsToMoneyRate":0.01}'
Las validaciones son las mismas que en el panel (por ejemplo, el periodo pendiente debe ser menor que la caducidad de puntos). Errores propios:
| Código | code | Cuándo |
|---|---|---|
409 | program_already_exists | La cuenta ya tiene un programa |
409 | program_in_use | Al borrar: aún tiene niveles o socios |
404 | program_not_found | No existe en tu cuenta |
422 | quota_exceeded | Supera el límite de programas de tu plan |
Niveles
Función del plan: niveles.
| Método y ruta | Ámbito |
|---|---|
GET /api/v1/tiers?programId=&cursor=&limit= | programs:read |
GET /api/v1/tiers/{id} | programs:read |
POST /api/v1/tiers | programs:write |
PATCH /api/v1/tiers/{id} | programs:write |
DELETE /api/v1/tiers/{id} | programs:write |
Para crear, programId y name son obligatorios. Campos: level, criteriaType (points_accumulated, total_spent, transaction_count), minPoints, maintenancePoints, pointsMultiplier, color, iconUrl, isDefault. Un nivel no puede cambiar de programa.
curl -s -X POST "$BASE/api/v1/tiers" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"programId":7,"name":"Oro","level":3,"minPoints":1000,"maintenancePoints":800,"pointsMultiplier":1.5}'
Borrar un nivel que aún tienen socios da 409 con code: tier_in_use.
Campañas
Gestionas la carcasa de la campaña. La configuración propia de cada tipo (juegos, premios, momentos ganadores, constructor de landing) sigue en el panel. Función del plan: campañas.
| Método y ruta | Ámbito | Notas |
|---|---|---|
GET /api/v1/campaigns?status=&type= | campaigns:read | Las más recientes primero |
GET /api/v1/campaigns/{id} | campaigns:read | |
POST /api/v1/campaigns | campaigns:write | 201; siempre nace en draft |
PATCH /api/v1/campaigns/{id} | campaigns:write | type y loyaltyProgramId no cambian |
DELETE /api/v1/campaigns/{id} | campaigns:write | 204; solo borradores |
POST /api/v1/campaigns/{id}/activate | campaigns:write | Pasa a active (o scheduled si aún no ha empezado) |
POST /api/v1/campaigns/{id}/pause | campaigns:write | Desde active o scheduled |
GET / POST /api/v1/campaigns/{id}/custom-fields | read / write | Datos que pide la participación |
PUT / DELETE /api/v1/campaigns/{id}/custom-fields/{fieldId} | campaigns:write | |
GET / PUT /api/v1/campaigns/{id}/participation-rules | read / write | Política de participación (policyJson, con versión) |
type: game, points_multiplier, bonus_points, challenge, cashback, instant_win, pin_code, ugc_contest, sports_prediction, raffle. status: draft, scheduled, active, paused, completed, cancelled, expired_deserted.
Para crear: name (hasta 256 caracteres), type, startDate y endDate (la fin, posterior al inicio). Opcionales: slug, loyaltyProgramId, brandId, imageUrl, textos de landing, pointsMultiplier (0,01 a 100), bonusPoints, cashbackPercentage (0 a 100), maxTotalEntries, maxPrizesPerParticipant, minAgeYears y budget. En PATCH, un texto vacío borra el texto y 0 borra un límite o importe.
curl -s -X POST "$BASE/api/v1/campaigns" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: crear-verano-2027" \
-H "Content-Type: application/json" \
-d '{"name":"Bonus de verano","type":"bonus_points","startDate":"2027-06-01","endDate":"2027-08-31","bonusPoints":100}'
curl -s -X POST "$BASE/api/v1/campaigns/17/activate" -H "Authorization: Bearer $KEY"
| Código | code | Cuándo |
|---|---|---|
400 | invalid_request | Datos fuera de rango o faltan obligatorios |
403 | feature_disabled | Campañas, o cashback si el tipo/porcentaje lo exige, no incluidos en el plan |
404 | campaign_not_found | No existe en tu cuenta |
409 | campaign_not_draft | Borrar una campaña que no es borrador (pausa en su lugar) |
409 | invalid_status_transition | Activar o pausar desde un estado que no lo permite |
409 | budget_paused | Pausada por presupuesto: súbelo y reactívala desde el panel |
409 | end_date_frozen | Cambiar la fecha de fin de un sorteo ya realizado, o de una campaña cerrada |
409 | slug_taken | El slug ya está en uso |
422 | cannot_go_live | activate rechazado (ventana terminada, reglas inválidas, instant-win sin momentos); el mensaje dice por qué |
422 | loyalty_program_not_found, brand_not_found | La referencia no es de tu cuenta |
422 | quota_exceeded | Supera el límite de campañas de tu plan |
Recompensas
Catálogo canjeable por puntos. Función del plan: recompensas.
| Método y ruta | Ámbito |
|---|---|
GET /api/v1/rewards?programId=&campaignId=&status=&category= | rewards:read |
GET /api/v1/rewards/{id} | rewards:read |
POST /api/v1/rewards | rewards:write |
PUT /api/v1/rewards/{id} | rewards:write |
DELETE /api/v1/rewards/{id} | rewards:write |
rewardType: Physical, Digital, Experience, Discount, Cashback. fulfillmentType: Digital, Ship, InStore, External. status al escribir: Available o Disabled; OutOfStock lo calcula la plataforma según el stock.
curl -s -X POST "$BASE/api/v1/rewards" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"programId":5,"name":"Café gratis","rewardType":"Physical","pointsCost":200,"stock":500,"fulfillmentType":"InStore"}'
programId,nameyrewardTypeson obligatorios.imageUrldebe ser una URLhttp(s)absoluta.PUTreemplaza los campos: lo que omitas toma su valor por defecto.stocksolo se lee al crear; después se mueve con los canjes y con el ajuste de stock del panel.- La entrega por tarjeta regalo y los ajustes mixtos de puntos más dinero solo se editan en el panel.
DELETEes un borrado lógico (204): los canjes pasados conservan la referencia.- Errores:
reward_not_found,program_not_found,campaign_not_found(404),feature_disabled(403) yvalidation_failed(400).
Cupones
Para canjear en caja usa los endpoints de la API pública. Aquí se gestionan los cupones que existen.
| Método y ruta | Ámbito |
|---|---|
GET /api/v1/coupons?programId=&campaignId=&batchId=&status=&memberId=&code= | coupons:read |
GET /api/v1/coupons/{id} | coupons:read |
POST /api/v1/coupons/batches | coupons:manage |
POST /api/v1/coupons/{id}/invalidate | coupons:manage |
POST /api/v1/coupons/batches/{batchId}/invalidate | coupons:manage |
Estados: Active, Used, Expired, Cancelled. Tipos (couponType): FixedDiscount, PercentageDiscount, FreeProduct, FreeShipping, Custom.
Emitir un lote
curl -s -X POST "$BASE/api/v1/coupons/batches" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"programId":5,"count":500,"couponType":"PercentageDiscount","discountValue":10,"prefix":"VERANO","codeLength":10,"maxUses":1,"validUntil":"2027-09-30"}'
# 201 {"batchId":"3F9A1C7E0B42","issued":500,"coupons":[{"id":812,"code":"VERANO-K7Q2M9XH4T"}, ...]}
- Hasta 1 000 cupones por lote. Los códigos son aleatorios y únicos.
codeFormat:Alphanumeric(por defecto),NumericoAlpha.codeLengthde 6 a 20 (10 por defecto). Si es demasiado corto paracount, se rechaza para que los códigos no sean fáciles de adivinar.prefix: hasta 10 letras o dígitos.maxUsesvacío = usos ilimitados;1para códigos de un solo uso.maxUsesPerMember,pointsCost,minTierLevel,validFromyvalidUntilson opcionales.- Los cupones de un lote no salen en el portal del socio salvo que envíes
isListed: true. - Un reintento con la misma
Idempotency-Keyy el mismo cuerpo devuelve el mismo lote. - Errores:
409 code_space_exhausted(no hay códigos libres suficientes: alargacodeLengtho cambia el prefijo),409 conflict(choque con otra petición simultánea: reintenta),404program_not_found/campaign_not_found,403 feature_disabled.
Invalidar
Un cupón Active pasa a Cancelled y ya no se puede canjear. Repetir la llamada no hace daño (invalidated: 0). Un cupón ya Used o Expired da 409 con code: coupon_not_active. La invalidación de un lote responde { "invalidated": N }; un lote desconocido da 404 con code: batch_not_found.
Sorteos
GET /api/v1/campaigns/{id}/raffles (ámbito raffles:read) devuelve los sorteos ya realizados de la campaña, del más antiguo al más reciente, cada uno con sus ganadores y suplentes. No depende de que la página pública de ganadores esté activada.
curl -s "$BASE/api/v1/campaigns/17/raffles?limit=20" -H "Authorization: Bearer $KEY"
Cada sorteo incluye drawId, drawnAt, scope, storeId, slotKey, los recuentos pedidos y sorteados, eligiblePoolSize y las huellas de auditoría del acta (poolHashSha256, outputHashSha256, seedSha256; nunca la semilla). Cada ganador trae role, position, status, prizeId, notified y solo tus propias referencias (externalParticipantRef, memberExternalId, claimId) más el nombre y el correo enmascarados (maskedName, maskedEmail). No hay datos de contacto. Una campaña sin sorteos devuelve items vacío.
Errores: 404 campaign_not_found, 403 feature_disabled (campañas), 400 invalid_cursor / invalid_request.
Esquema de atributos de socio
GET /api/v1/member-attributes (ámbito read) devuelve los atributos personalizados que tu cuenta ha definido para los socios, ordenados por displayOrder, para que sepas qué claves y tipos admite el objeto attributes de un socio.
curl -s "$BASE/api/v1/member-attributes" -H "Authorization: Bearer $KEY"
Cada elemento trae key, displayName, description, dataType, isRequired, allowedValues, displayOrder, isActive y creationTime. dataType es numérico: 1 texto, 2 número, 3 booleano, 4 fecha (yyyy-MM-dd) y 5 selección (el valor debe estar en allowedValues). Solo se devuelven los atributos activos. Las definiciones se gestionan en el panel, no por esta API; key y dataType no cambian una vez creada la definición.
Siguientes pasos
- OAuth2: tokens de corta duración en lugar de enviar la clave.
- Ámbitos de administración: llamar al resto del panel por API.
- Eventos de webhook: enterarte de participaciones, sorteos y cupones.
- Modo pruebas: probar sin afectar a socios reales.
- Colección Postman