Integración de partners
Documentación operativa del canal de distribución para el integrador externo: cómo se contratan asientos, cómo se dan de alta los alumnos y qué datos devuelve la plataforma. Es material técnico para quien firma un contrato de partner, no una oferta comercial.
Qué da el canal
Un contrato con IAcademy define dos cosas: una quota de asientos (max_active_seats, el número máximo de alumnos activos a la vez) y un tier de acceso (pro o premium, nunca mezclas). El partner da de alta alumnos por API; el alumno entra con su propio login y el asiento se activa. El partner pinta su propuesta con el catálogo que le corresponde por tier y recibe reporting solo de agregados.
- Facturación por asiento mensual (variante opcional
flat_fee, según contrato). - Vigencia: cada asiento comparte el ciclo mensual del contrato. Si el contrato no se renueva, los asientos caducan con él: no se reembolsa lo consumido, decisión de producto.
- Sin intermediación del alumno: nunca se emite un código de compra ni una invitación; el acceso es siempre del alumno, dentro de su propia cuenta.
Cómo se entra: la clave de API
Todas las rutas viven bajo https://api.ia-academy.io/api/partner/v1/ y exigen la cabecera X-Api-Key en cada llamada, también en las de lectura. La clave se entrega una sola vez con la firma del contrato y no se puede volver a consultar: en base de datos solo queda el hash (SHA-256). Si la clave se pierde, la solución es dar de alta un partner nuevo, no recuperar la anterior. Una clave suspendida y una clave inexistente responden igual: la plataforma no enumera partners.
La API: seis endpoints
El contrato técnico es la especificación OpenAPI; es la fuente que prevalece si este documento y ella difieren. Consúltala en la Referencia completa de la API. Los errores van como {detail} legible o {code, message} (caso de quota).
1. GET /catalog: catálogo con visibilidad por tier
Cada curso y unidad del catálogo, con el campo tier_required que dice qué tier mínimo abre la unidad. Con eso el partner compara el tier de su contrato y pinta su árbol con la visibilidad real. No hay URLs de media ni contenido de lecciones: eso vive tras la sesión del alumno en app.ia-academy.io.
curl -sS "https://api.ia-academy.io/api/partner/v1/catalog" \
-H "X-Api-Key: <tu-clave>"
2. POST /learners: crear asientos (bulk, idempotente)
De 1 a 500 alumnos por llamada. Cada alumno se identifica por partner_learner_id (el identificador tuyo; el de IAcademy se queda en la base de datos aquí) y, si lo conoces, por email, que pre-vincula y hace que el claim del alumno no espere a su primera sesión. La operación es idempotente: repetir el mismo partner_learner_id re-declara el asiento; reactivated: true revive uno revocado antes. La quota es dura: si el lote no cabe entero, falla entero con 409 quota_exceeded, sin alta parcial.
curl -sS -X POST "https://api.ia-academy.io/api/partner/v1/learners" \
-H "X-Api-Key: <tu-clave>" \
-H "Content-Type: application/json" \
-d '{"learners":[{"partner_learner_id":"ws-12345","email":"[email protected]"}]}'
3. GET /learners: listar asientos
Todos los asientos del contrato, de más antiguo a más nuevo. Paginación con limit (1-500, por defecto 100) y offset.
curl -sS "https://api.ia-academy.io/api/partner/v1/learners?limit=100&offset=0" \
-H "X-Api-Key: <tu-clave>"
4. GET /learners/{partner_learner_id}: estado de un asiento
Respuestas: active, revoked (con revoked_reason, el motivo que el partner escribió al revocar) o expired (el contrato acabó). Un id que nunca se dió de alta devuelve 404: el estado no se adivina.
curl -sS "https://api.ia-academy.io/api/partner/v1/learners/ws-12345" \
-H "X-Api-Key: <tu-clave>"
5. DELETE /learners/{partner_learner_id}: liberar un asiento
Sirve para reutilizar el hueco cuando alguien deja la empresa cliente o hay que corregir un pedido: el asiento se libera y se puede dar de alta a un alumno distinto. El cuerpo lleva un campo reason obligatorio (3-500 caracteres), que queda en la auditoría. Idempotente: revocar lo ya revocado responde igual. Sin reembolsos: liberar el asiento es el único movimiento; es una decisión de producto.
curl -sS -X DELETE "https://api.ia-academy.io/api/partner/v1/learners/ws-12345" \
-H "X-Api-Key: <tu-clave>" \
-H "Content-Type: application/json" \
-d '{"reason":"left the company"}'
6. GET /reporting: agregados L0
Solo agregados, sin dato personal de ningún alumno: seats_contracted, seats_active, seats_activate_rate (activos / contratados; la cifra que dice si el canal funciona), learners_login, quizzes_passed, certificates.
curl -sS "https://api.ia-academy.io/api/partner/v1/reporting" \
-H "X-Api-Key: <tu-clave>"
Ninguna ruta de partner cambia el contrato ni la clave: eso lo gestiona IAcademy desde su admin (el alta devuelve la clave en claro una vez; la suspensión expira los asientos en vivo). Requiere sesión admin de IAcademy, no la clave del partner.
Qué hace el alumno: el claim de asiento
El alumno de empresa nunca recibe un código de compra:
- El partner graba el asiento por API (con
partner_learner_id, y con el email si lo quiere pre-vincular). - El alumno entra con su propio login (OIDC) en
https://app.ia-academy.ioen su propia cuenta; si el partner ya le dió el email en el alta, la cuenta se casa en ese primer login. - La app llama internamente a
POST /api/auth/claim-seatcon la sesión del alumno: eso convierte esa sesión en acceso al curso. Idempotente: un segundo claim responde igual. - Un
409de asiento de partner puede venir de asiento revocado o de contrato caducado: lo decide el partner, no el alumno. El claim también tiene rate limit: 10 intentos por 10 minutos por cuenta.
El alumno acepta además la política de privacidad de IAcademy en su alta; el texto legal ya recoge que su empresa o proveedor accede a progreso agregado.
Errores y rate limit
| HTTP | Cuándo ocurre | Ejemplo de cuerpo |
|---|---|---|
| 401 | Falta la cabecera X-Api-Key, clave inválida o partner suspendido: misma respuesta en los tres casos, para no enumerar partners. | {"detail":"Clave de partner invalida"} |
| 409 | El lote de POST /learners no cabe entero en la quota del contrato; no se da de alta nada parcialmente. | {"detail":{"code":"quota_exceeded","message":"..."}} |
| 404 | El partner_learner_id consultado no existe en ese contrato. | {"detail":"Ese alumno no existe en el contrato"} |
| 422 | Entrada que no valida contra el OpenAPI: email demasiado largo, límite fuera de rango, campo que falta. | {"detail":[...]} |
| 429 | Rate limit por partner en cada ruta, lectura o escritura (también en el claim del alumno: 10 intentos / 10 min por cuenta). | {"detail":"Demasiadas peticiones; espera un minuto"} |
La quota contratada solo se consulta (y se respeta) en el alta de asientos: cuando un lote no cabe, devolvemos 409 con el detalle de la quota. Prevenirlo: GET /learners + contar los active.
Datos del alumno: niveles L0 / L1 / L2
El principio de partida: el partner es responsable de sus alumnos; a IAcademy le llegan como una lista que vosotros gestionáis. Tres niveles de visibilidad:
| Nivel | Qué devuelve | Cuándo |
|---|---|---|
L0 (defecto; es lo que da GET /reporting hoy) | Agregados: nº de asientos activos, tasa de activación, medias de score, nº de certificados. | Fase 1: canal en producción |
| L1 (opt-in del partner) | Por alumno: partner_learner_id, estado active/revoked/expired, módulos completados, score del último quiz, última actividad, credentials.public_id verificable. | Fase 2: dashboard que el partner pinta a su empresa cliente |
| L2 (futuro) | Email del alumno (como identidad que el partner ya posee, no como dato personal que IAcademy devuelve). | Solo bajo contrato explícito |
Lo que jamás se devuelve al partner: el nombre real del alumno, su dirección IP, el histórico de preguntas y respuestas, ni las conversaciones con el tutor (ni las notas del Tutor+). Ni como reporting en ningún nivel, ni bajo opt-in.
Siguiente paso
Para empezar
Si tu organización quiere ser partner de distribución, escríbenos y cerramos el contrato (tiers, quota y facturación). Con la firma se entrega la clave de API, una sola vez y en texto plano.