Integra AVEX en tus flujos
Documentación mínima para integradores. Bearer tokens scoped por cliente, webhooks firmados HMAC-SHA256 y tiers de rate-limit publicados — los mismos números que corren en producción.
Bearer token por cliente
Cada API key de AVEX está limitada al cliente que la solicita. Envíala en el header Authorization como Bearer token y AVEX scopea automáticamente cada operación a los recursos de ese cliente. El ejemplo crea o refleja un pase de estudiante vía POST /api/v1/pass/upsert.
curl -X POST https://www.avex.com.co/api/v1/pass/upsert \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Content-Type: application/json" \
-d '{
"institutionId": "11111111-1111-4111-8111-111111111111",
"externalId": "EST-12345",
"data": {
"uniqueIdentifier": "1098765432",
"careerId": "ING-SIS",
"name": "Ana María Gómez",
"email": "[email protected]",
"semester": 6,
"enrollmentYear": 2023,
"studentStatus": "Active",
"academicCalendarLink": null,
"photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
}
}'// Node 22+ / TypeScript — fetch nativo.
const res = await fetch("https://www.avex.com.co/api/v1/pass/upsert", {
method: "POST",
headers: {
Authorization: "Bearer avex_k1_<tu_clave>",
"Content-Type": "application/json",
},
body: JSON.stringify({
institutionId: "11111111-1111-4111-8111-111111111111",
externalId: "EST-12345",
data: {
uniqueIdentifier: "1098765432",
careerId: "ING-SIS",
name: "Ana María Gómez",
email: "[email protected]",
semester: 6,
enrollmentYear: 2023,
studentStatus: "Active",
academicCalendarLink: null,
photoUrl: "https://cdn.example.edu.co/fotos/est-12345.jpg",
},
}),
});
if (!res.ok) {
throw new Error(`AVEX respondió ${res.status}`);
}
const data = await res.json();Los 6 endpoints v1
AVEX es un canal, no un CRM: tu sistema empuja los datos ya computados y AVEX los refleja en el pase de Apple y Google Wallet. Las 6 rutas v1 comparten la misma autenticación — Bearer API key scoped por cliente — y cada una exige el scope de su vertical. Reemplaza avex_k1_<tu_clave> por la API key real que generas en tu panel.
Referencia completa del API
Explora el contrato OpenAPI interactivo — esquemas de request/response, códigos de error y los headers de cada ruta pública.
Abrir referencia OpenAPIEducation — pases de estudiante
scope: passes:updateRefleja el pase del estudiante desde tu SIS. El upsert crea en el primer push y refleja los campos mutables en los siguientes; event empuja el cambio de estado; revoke retira el pase.
curl -X POST https://www.avex.com.co/api/v1/pass/upsert \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000001" \
-H "Content-Type: application/json" \
-d '{
"institutionId": "11111111-1111-4111-8111-111111111111",
"externalId": "EST-12345",
"data": {
"uniqueIdentifier": "1098765432",
"careerId": "ING-SIS",
"name": "Ana María Gómez",
"email": "[email protected]",
"semester": 6,
"enrollmentYear": 2023,
"studentStatus": "Active",
"academicCalendarLink": null,
"photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
}
}'// Node 22+ / TypeScript — fetch nativo, sin dependencias externas.
const res = await fetch("https://www.avex.com.co/api/v1/pass/upsert", {
method: "POST",
headers: {
Authorization: "Bearer avex_k1_<tu_clave>",
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"institutionId": "11111111-1111-4111-8111-111111111111",
"externalId": "EST-12345",
"data": {
"uniqueIdentifier": "1098765432",
"careerId": "ING-SIS",
"name": "Ana María Gómez",
"email": "[email protected]",
"semester": 6,
"enrollmentYear": 2023,
"studentStatus": "Active",
"academicCalendarLink": null,
"photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
}
}),
});
// 201 en la primera creación, 200 cuando reflejas un pase ya existente.
if (!res.ok) {
throw new Error(`AVEX respondió ${res.status}`);
}curl -X POST https://www.avex.com.co/api/v1/pass/EST-12345/event \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Content-Type: application/json" \
-d '{
"institutionId": "11111111-1111-4111-8111-111111111111",
"type": "status.changed",
"data": {
"studentStatus": "Graduated"
}
}'curl -X POST https://www.avex.com.co/api/v1/pass/EST-12345/revoke \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Content-Type: application/json" \
-d '{
"institutionId": "11111111-1111-4111-8111-111111111111",
"reason": "Retiro voluntario del programa"
}'Loyalty — miembros y saldos
scope: loyalty:writeRefleja al miembro y su saldo desde tu POS/CRM. AVEX nunca calcula puntos: el upsert mantiene los datos de contacto y el snapshot empuja el balance ya computado más el nivel resuelto.
curl -X POST https://www.avex.com.co/api/v1/loyalty/member/upsert \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000002" \
-H "Content-Type: application/json" \
-d '{
"programId": "22222222-2222-4222-8222-222222222222",
"externalId": "SOCIO-789",
"data": {
"name": "Carlos Restrepo",
"email": "[email protected]",
"phone": "+573001234567",
"accountNumber": "ACC-0099"
}
}'curl -X POST https://www.avex.com.co/api/v1/loyalty/member/SOCIO-789/snapshot \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Content-Type: application/json" \
-d '{
"balance": 4500,
"tierId": null
}'Real estate — cuotas
scope: installments:writeRefleja el avance de la cuota desde tu sistema de cobranza. El snapshot empuja el acumulado pagado, el saldo restante y el estado — AVEX los refleja verbatim en el pase.
curl -X POST https://www.avex.com.co/api/v1/installment/CUOTA-456/snapshot \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Content-Type: application/json" \
-d '{
"accumulatedPaid": 1500000,
"remainingBalance": 8500000,
"status": "Active"
}'Sandbox de validación (dry-run)
Antes de empujar datos reales, valida tu integración con el header X-AVEX-Dry-Run: true. Corre toda la autenticación y validación, devuelve un 200 con la previsualización del efecto y no persiste nada — ningún pase llega a tus usuarios finales.
# Sandbox de validación: agrega X-AVEX-Dry-Run: true a cualquier
# endpoint de ingesta. AVEX corre auth + validación + el efecto teórico
# y responde 200 con la previsualización — sin crear pases, sin push,
# sin email. Quita el header para ejecutar de verdad.
curl -X POST https://www.avex.com.co/api/v1/installment/CUOTA-456/snapshot \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "X-AVEX-Dry-Run: true" \
-H "Content-Type: application/json" \
-d '{"accumulatedPaid":1500000,"remainingBalance":8500000,"status":"Active"}'Firmados con HMAC-SHA256
AVEX firma cada webhook con HMAC-SHA256 sobre el body crudo. Verifica la firma con timingSafeEqual antes de leer el payload — nunca confíes en el header sin validar.
# Ejemplo de llamada que AVEX envía a tu webhook.
# El body se firma con HMAC-SHA256 sobre el secreto compartido.
curl -X POST https://tu-servidor.com/webhooks/avex \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$AVEX_WEBHOOK_SECRET" -r | cut -d' ' -f1)" \
-d "$BODY"import crypto from "node:crypto";
/**
* Verifica la firma HMAC-SHA256 que AVEX envía en X-Webhook-Signature.
* Usamos crypto.timingSafeEqual para evitar ataques de timing.
*/
export function verifyAvexSignature(rawBody: string, headerValue: string, secret: string): boolean {
if (!headerValue) return false;
// AVEX puede enviar "sha256=<hex>" o el hex crudo. Toleramos ambos.
const hex = headerValue.includes("=") ? headerValue.split("=").pop()! : headerValue;
if (!/^[0-9a-fA-F]+$/.test(hex)) return false;
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const expectedBuf = Buffer.from(expected, "hex");
const receivedBuf = Buffer.from(hex, "hex");
if (expectedBuf.length !== receivedBuf.length) return false;
return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}El contrato de entrada completo en un solo mapa
Todo endpoint al que tu sistema llama hacia AVEX, en una sola tabla, con su mecanismo de autenticación por fila y el enlace al panel donde vive la credencial. Tres familias de auth: API key (Bearer avex_k1_* con scope por vertical) para la ingesta v1, HMAC-SHA256 global para los webhooks de pago, y HMAC con un secreto entrante por cliente para sus variantes. AVEX no reconstruye el self-serve aquí: cada fila enlaza al panel que ya generas en Configuración.
| Endpoint | Método | Autenticación | Credencial |
|---|---|---|---|
| Ingesta v1 — API key Bearer avex_k1_* + scope por vertical | |||
| /api/v1/pass/upsert | POST | API key · scope passes:update | Rotar API key |
| /api/v1/pass/{externalId}/event | POST | API key · scope passes:update | Rotar API key |
| /api/v1/pass/{externalId}/revoke | POST | API key · scope passes:update | Rotar API key |
| /api/v1/loyalty/member/upsert | POST | API key · scope loyalty:write | Rotar API key |
| /api/v1/loyalty/member/{id}/snapshot | POST | API key · scope loyalty:write | Rotar API key |
| /api/v1/installment/{id}/snapshot | POST | API key · scope installments:write | Rotar API key |
| /api/v1/verify/{institutionId}/{uniqueIdentifier}/{careerCode} | GET | API key · scope passes:read | Rotar API key |
| Webhooks de pago — HMAC-SHA256 X-Webhook-Signature, secreto global | |||
| /api/payments/utility-webhook | POST | HMAC global | Variable de entorno |
| /api/webhooks/installment-payment | POST | HMAC global | Variable de entorno |
| /api/webhooks/payment-confirmation | POST | HMAC global | Variable de entorno |
| Webhooks de pago por cliente — HMAC-SHA256, un secreto entrante por cliente | |||
| /api/payments/utility-webhook/{clientSlug} | POST | HMAC secreto por cliente | Rotar secreto entrante |
| /api/webhooks/installment-payment/{clientSlug} | POST | HMAC secreto por cliente | Rotar secreto entrante |
| /api/webhooks/payment-confirmation/{clientSlug} | POST | HMAC secreto por cliente | Rotar secreto entrante |
| Webhook de Resend — HMAC Svix, gestionado por la plataforma | |||
| /api/webhooks/resend | POST | HMAC Svix (svix-signature) | Gestionado por plataforma |
Las filas con secreto auto-gestionado enlazan al self-serve ya desplegado: la rotación de API keys vive en Configuración → API Keys, y el secreto entrante por cliente (uno por cliente, cubre las tres URLs por-cliente) en Configuración → Webhooks. Los webhooks de secreto global usan una variable de entorno gestionada en el despliegue, y el webhook de Resend lo administra la plataforma. Esta tabla es documentación: la gestión de cada credencial ocurre solo en su panel enlazado.
Tres patrones que ya están en producción
Cada diagrama refleja un flujo que clientes integran hoy. Las cajas en azul AVEX son código nuestro; las cajas neutras son tu sistema o un proveedor externo.
Una llamada autenticada con Bearer crea N pases y los entrega en paralelo a Apple Wallet y Google Wallet.
La pasarela firma el body con HMAC, AVEX verifica con timingSafeEqual y delega al servicio de install del vertical para activar el pase.
AVEX firma un token corto con HMAC, lo entrega por email o SMS y la ruta pública /install/[token] valida firma y expiry antes de emitir el pase.
Tiers públicos sincronizados con producción
Los números de esta tabla se importan en caliente del runtime — un cambio en producción rompe CI hasta que la página se actualice. Cada respuesta 429 incluye el header Retry-After con los segundos restantes.
| Tier | Límite | Ventana | Alcance | Endpoint ejemplo |
|---|---|---|---|---|
| userUsuario autenticado | 100 req | 1 min | Por sesión | GET /api/institution |
| publicEndpoints públicos (lectura) | 30 req | 1 min | Por IP | GET /api/v1/verify/... |
| public_strictEndpoints públicos (escritura) | 10 req | 1 min | Por IP | POST /api/contact |
| strictEndpoints críticos | 5 req | 1 min | Por IP o usuario | POST /api/auth/login |
| api_integrationIntegraciones por API key | 300 req | 1 min | Por API key | POST /api/utility-bills |
Los límites se aplican por IP en endpoints públicos y por API key en endpoints autenticados. Si tu integración requiere un tier más alto, escríbenos al correo del final de la página.
Una sola shape para toda la API
Todos los endpoints públicos responden con el mismo shape JSON. El campo code es estable y machine-readable; error es el mensaje en el idioma del request; details es opcional y depende del código.
{
"error": "Límite de solicitudes excedido",
"code": "RATE_LIMITED",
"details": {
"retryAfterSeconds": 12
}
}Códigos comunes
- HTTP 401UNAUTHORIZED
Falta Bearer, API key inválida, expirada o revocada.
- HTTP 429RATE_LIMITED
El caller excedió el tier aplicable. La respuesta incluye Retry-After con los segundos restantes.
- HTTP 400VALIDATION_ERROR
El body no cumple con la validación Zod del endpoint. details enumera cada campo con su mensaje.
- HTTP 404NOT_FOUND
El recurso no existe o no pertenece al cliente scoped por la API key.
Reintenta sin duplicar efectos
Envía Idempotency-Key en POST y PATCH para que tus reintentos sean seguros aunque la red falle a medio camino. AVEX cachea la respuesta original y la devuelve byte por byte si repites la misma clave.
curl -X POST https://www.avex.com.co/api/v1/pass/upsert \
-H "Authorization: Bearer avex_k1_<tu_clave>" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000001" \
-H "Content-Type: application/json" \
-d '{
"institutionId": "11111111-1111-4111-8111-111111111111",
"externalId": "EST-12345",
"data": {
"uniqueIdentifier": "1098765432",
"careerId": "ING-SIS",
"name": "Ana María Gómez",
"email": "[email protected]",
"semester": 6,
"enrollmentYear": 2023,
"studentStatus": "Active",
"academicCalendarLink": null,
"photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
}
}'Usa un UUID v4
Genera la clave con un UUID v4 único por operación lógica. La misma clave vuelta a enviar dentro de la ventana devuelve la respuesta original sin ejecutar el efecto dos veces.
Ámbito por API key
El espacio de claves es por API key, no global. Tu Idempotency-Key no puede colisionar con la de otro cliente.
Aplica a POST y PATCH
GET y DELETE son idempotentes por definición — el header se ignora en esos métodos para no inducir falsa seguridad.
¿Listo para integrar?
Estos son los tres pasos que normalmente siguen los equipos técnicos cuando han terminado de leer esta documentación.
- Escribir a [email protected]
Solicita tu API key
Escríbenos con el caso de uso y el cliente al que va asociada. Respondemos en horario laboral colombiano.
- Ver /status
Estado del servicio
Disponibilidad de la API pública, los webhooks y la entrega a Apple y Google Wallet en tiempo real.
- Leer la política
Política de datos
Cómo tratamos los datos de tus usuarios finales y qué garantías de seguridad cumplimos antes de procesar el primer pase.