Integración de partners con Ivolution Analytics
Los partners de Ivolution pueden leer los atletas, las fichas y los resultados de las instituciones que los autoricen, y mostrar sus tableros dentro de su propio producto.
Antes de empezar
Esta API es de solo lectura. Expone los datos que una institución produjo en Ivolution y autorizó a compartir.
Todas las rutas cuelgan de /api/v1/partner/. La institución no va en la ruta: se indica con el parámetro de consulta institution_id en cada llamada de datos. Todas las respuestas, con éxito o con error, llegan dentro del mismo sobre: status, data, message, errors y meta.
La referencia completa, generada desde la especificación OpenAPI, está en /api/v1/partner/docs y permite probar cada operación desde el navegador.
Fuera de alcance:
El modelo en cuatro palabras
Todo el sistema se explica con cuatro términos. Conviene leerlos antes que cualquier endpoint.
| Término | Qué es |
|---|---|
| Partner | La organización con acuerdo comercial vigente. Es una entidad del sistema, no una persona ni una institución. |
| Credencial | Lo que el partner presenta para autenticarse. Es del partner, no de una integración: la misma credencial sirve para todas. Indica quién llama. |
| Integración | El vínculo que una institución autoriza con un partner. Define qué puede leerse, y es la unidad sobre la que se auditan los accesos. Sin una integración vigente, no hay dato. |
| Código de vinculación | El código de un solo uso que la institución obtiene al autorizar y entrega al partner. Se canjea una vez y vence a las 48 h. No es una credencial y no da acceso por sí mismo. |
Una credencial válida no alcanza para leer nada. Autenticación y alcance son cosas distintas: la credencial identifica al partner, la integración determina qué instituciones lo autorizaron.
Autenticación
Cada llamada lleva la credencial de partner en el header Authorization: Bearer. La credencial tiene la forma <prefijo>.<secreto>, y el prefijo indica el entorno: una credencial de producción empieza con ivo_live_ y es rechazada en cualquier otro entorno, igual que la de prueba en producción.
Para rotarla, Ivolution emite una credencial nueva y las dos funcionan a la vez hasta que el sistema del partner termina de cambiar; recién entonces se revoca la anterior, sin cortar el servicio.
La credencial también define los alcances: athletes para atletas y fichas, results para resultados y dashboards para el catálogo y las URLs de tableros. Si falta el que exige la ruta, la respuesta es 403 insufficient_scope.
curl "https://app.analytics-api.ivolution.com.ar/api/v1/partner/institutions" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY" \
-H "Accept: application/json"{
"status": "success",
"data": [
{
"institution_id": 42,
"name": "Club Deportivo Ejemplo",
"authorized_at": "2026-03-12T14:02:00Z"
}
],
"message": null,
"errors": null,
"meta": {
"pagination": null,
"cursor": { "next_cursor": null, "has_more": false }
}
}El listado muestra solo las instituciones con integración activa. No depende de la suscripción: una institución listada puede responder subscription_required en la siguiente llamada de datos.
Vinculación paso a paso
La vinculación convierte un acuerdo comercial en permiso técnico sobre una institución concreta. Son cuatro pasos, y solo dos corresponden al partner.
Autoriza desde Analytics
Un administrador de la institución entra a Configuraciones → Integraciones y autoriza al partner. El sistema genera entonces un código de vinculación y lo muestra una sola vez.
Entrega el código al partner
Por el canal que ambas partes acuerden. El código vence a las 48 h si no se canjea, y no da acceso a nada sin la credencial del partner.
Canjea el código
Una sola llamada a POST /api/v1/partner/link, con la credencial de partner. El código se consume en el canje: un segundo intento responde 409 link_code_invalid.
Queda habilitado para leer
La integración pasa a active, la respuesta trae el institution_id y la institución aparece en el listado. Recién entonces responden los recursos de datos.
curl -X POST "https://app.analytics-api.ivolution.com.ar/api/v1/partner/link" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{"code": "EjemploDeCodigoDeVinculacion-NoEsReal_00000"}'{
"status": "success",
"data": {
"institution_id": 42,
"name": "Club Deportivo Ejemplo",
"authorized_at": "2026-03-12T14:02:00Z"
},
"message": null,
"errors": null,
"meta": null
}El código es una cadena de 43 caracteres. Cualquier falla del canje —un código desconocido, emitido para otro partner, ya canjeado o vencido— responde el mismo 409 link_code_invalid, sin distinguir cuál.
El canje es irreversible. Conviene guardar el institution_id que devuelve: es el parámetro de consulta de todas las llamadas de datos. Si el código se pierde antes del canje, la institución debe revocar la integración y autorizar de nuevo.
Estados de una integración
Solo uno de los cuatro habilita la lectura. Los otros tres devuelven error, y el sistema del partner debe distinguirlos para mostrar un mensaje útil.
| Estado | Qué significa | ¿Lee? |
|---|---|---|
pending | La institución autorizó, pero el código no se canjeó todavía. | No: 409 integration_pending |
active | Canjeado. Es el único estado que da acceso. | Sí |
expired | El código venció sin canjearse. Terminal. | No: 409 integration_expired |
revoked | La institución cortó el acceso. Terminal. | No: 403 integration_revoked |
expired y revoked son terminales, pero no bloquean una autorización nueva: la institución puede volver a autorizar cuando quiera, y eso genera otro código. Sin ninguna integración con esa institución, la respuesta es 404 integration_not_found.
Para comprobar una integración sin leer datos está GET /api/v1/partner/integration, con el mismo institution_id: responde 200 solo si la integración está activa y la institución tiene una suscripción o una prueba vigente.
Atletas y match por correo
El listado devuelve los atletas de la institución, activos o no, ordenados por id. Cada atleta trae id, active, name, last_name, email, sex y birthdate; los tres últimos pueden llegar en null.
curl "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes?institution_id=42" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY"{
"status": "success",
"data": [
{
"id": 4821,
"active": true,
"email": "lucia.ejemplo@example.com",
"name": "Lucía",
"last_name": "Ejemplo",
"sex": {
"id": 2,
"names": { "es": "Femenino", "en": "Female", "pt": "Feminino" }
},
"birthdate": "2004-05-14"
}
],
"message": null,
"errors": null,
"meta": {
"pagination": null,
"cursor": { "next_cursor": "eyJpZCI6NDJ9", "has_more": true }
}
}GET /api/v1/partner/athletes/{athlete_id} devuelve los mismos campos más current_sheet: la ficha más reciente, o null si el atleta no tiene ninguna. GET /api/v1/partner/athletes/{athlete_id}/sheets devuelve todas sus fichas, de la más antigua a la más reciente y sin paginar, para ver cómo cambiaron el peso o la posición a lo largo del tiempo.
Cada ficha trae id, sheet_date, height (cm), weight (kg) y, como elementos de catálogo, sport, position, athlete_type, physical_condition, dominant_arm y dominant_leg. Un elemento de catálogo es un id con sus names en es, en y pt, o null si no hay dato. Una primera ficha puede llegar con sheet_date en 2025-01-01: es la fecha fija que se asignó al alta, no un error.
Solo se ven los atletas de la institución indicada en institution_id: un athlete_id de otra institución, uno eliminado o uno inexistente responden igual, 404 athlete_not_found.
Correlación con los atletas del partner
El cruce con los atletas del sistema del partner se hace por correo. El listado no filtra por correo: para eso está la búsqueda, que recibe el correo en el cuerpo y devuelve una página con la misma forma que el listado.
curl -X POST "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes/search?institution_id=42" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "lucia.ejemplo@example.com"}'La coincidencia no distingue mayúsculas ni acentos, y los espacios al principio y al final del correo buscado se recortan. Sin coincidencias, la respuesta es 200 con una página vacía, no un error; un correo vacío responde 422.
El correo va siempre en el cuerpo, nunca en la URL. Así no queda en un historial del navegador, en un log de acceso ni en una URL compartida.
Notas sobre el match
- Un correo puede devolver más de un atleta. El correo no es único dentro de una institución: varios atletas pueden compartirlo. En ese caso se desempata con
sexybirthdateantes de guardar elid. - Un 404 sobre un `id` ya guardado (
athlete_not_found) significa que ese atleta dejó de estar disponible en la institución: hay que rehacer el match por correo, no reintentar el mismoid. - En los catálogos, la llave es el `id`. El deporte, la posición y los demás elementos de catálogo se cruzan con los resultados del atleta por su
id, no por el nombre traducido: eliddesportcorresponde asport_iden los resultados, y el deposition, aposition_id.
Resultados
Los resultados siguen el mismo contrato que el export de la plataforma: cada fila es un intento de un ejercicio, con una columna plana por campo y los mismos nombres del export, como heightJump, maxForce o exerciseDate, más exercise_key y exercise_side.
Quedan fuera los datos personales del export —correo, documento, fecha de nacimiento, edad y país—, los identificadores internos y el comentario libre del ejercicio. Solo se devuelven datos de Ivolution Trainer, cualquiera sea la fuente que la institución tenga elegida: no es un parámetro solicitable.
curl "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes/4821/results?institution_id=42&from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY"{
"status": "success",
"data": [
{
"institution": "Club Deportivo Ejemplo",
"nameAthlete": "Lucía Ejemplo",
"test": "CMJ",
"exercise_key": "cmj",
"exercise_side": null,
"exerciseTypeName": "Countermovement Jump",
"exerciseDate": "2026-09-03T14:20:00Z",
"repetitionNumber": 1,
"heightJump": 32.5,
"maxForce": 1450.3
}
],
"message": null,
"errors": null,
"meta": {
"pagination": null,
"cursor": { "next_cursor": "eyJpZCI6NDJ9", "has_more": true }
}
}Filtros opcionales: evaluation_type, el protocolo (por ejemplo balance_jumps_with_cea; uno desconocido responde 422); test, el exercise_key exacto del ejercicio; y from y to, fechas inclusivas según el día calendario de Argentina. Un atleta inactivo devuelve una lista vacía.
Las columnas varían según el protocolo. Cualquier campo puede llegar en null: una fila solo trae con valor las columnas que produce su protocolo. Conviene leerlas por nombre y tolerar null, sin asumir un conjunto fijo.
Notas de uso
- Solo salen datos de Ivolution Trainer. Una institución que mide solo con Valkyria Trainer muestra sus atletas y sus fichas, pero no tiene resultados, y sus tableros no muestran datos.
- Los nombres de campo son los del export, con dos equivalencias con el recurso atleta:
idAthletees eliddel atleta, ygenderes susex, como texto; elidde ese dato llega engender_id. - Para filtrar y agrupar se usan claves estables: el protocolo (
evaluation_type) yexercise_key+exercise_side. Las etiquetas en español, comotestoTipo_Evaluacion, pueden cambiar, y el nombre del ejercicio incluye el lado. - `from` y `to` cortan por el día argentino (
America/Argentina/Buenos_Aires), yexerciseDatellega en UTC, conZ. Una evaluación tomada el 18 de junio a las 21:22 en Argentina llega como2026-06-19T00:22:00Zy entra conto=2026-06-18. - Una evaluación borrada deja de venir, sin aviso. En esta versión no hay lápidas ni un listado de lo borrado: para que deje de mostrarse, lo ya guardado se actualiza volviendo a leer ese rango de fechas.
- Cada consulta tarda varios segundos. Filtrar por
evaluation_typela hace bastante más rápida, porque sin ese filtro se consultan todos los protocolos. Conviene guardar lo ya leído en vez de pedirlo cada vez que se abre una ficha.
Paginación
El listado y la búsqueda de atletas, los resultados y el listado de instituciones se paginan por cursor. La página siguiente se pide pasando meta.cursor.next_cursor en el parámetro cursor, con los mismos filtros; cuando has_more llega en false, la colección terminó.
limit vale 10 por defecto y como máximo 100: un valor mayor se recorta. Las fichas y el catálogo de tableros no se paginan.
# El cursor es opaco: no debe construirse a mano ni reutilizarse con otros filtros.
# Se guarda el valor tal como llegó y se devuelve igual.
curl "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes?institution_id=42&cursor=eyJpZCI6NDJ9&limit=100" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY"Un cursor inválido, vencido o usado con otros filtros responde 400 invalid_cursor. En ese caso se vuelve a pedir la primera página.
Catálogo de tableros
Además de los datos crudos, los tableros de Analytics pueden mostrarse dentro del producto del partner. El catálogo devuelve, completo y sin paginar, los tableros con alcance de atleta listos para embeber.
curl "https://app.analytics-api.ivolution.com.ar/api/v1/partner/dashboards?institution_id=42" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY"{
"status": "success",
"data": [
{
"id": 7,
"names": { "es": "Resumen físico", "en": "Physical summary", "pt": "Resumo físico" },
"analysis_type": null,
"equipment": "platform"
}
],
"message": null,
"errors": null,
"meta": null
}names trae el nombre en los tres idiomas, y analysis_type es un elemento de catálogo o null.
Los `id` de tablero son de cada entorno. Se toman del catálogo en el entorno de prueba y en producción; no deben fijarse en el código.
Notas de uso
- Solo hay tableros por atleta, con los mismos datos de Ivolution Trainer que los resultados: la URL firmada fija la fuente en Ivolution Trainer, igual que la institución y el atleta.
- El equipamiento llega como clave estable, no como nombre traducido:
platform,dynamometer,encoder,inertialophoto_cell, onullsi el tablero no tiene equipamiento asociado. - Si el servicio de tableros no responde, la respuesta es 503
dashboards_unavailable, siempre conRetry-After: se espera ese tiempo y se reintenta. El catálogo nunca llega parcial, y no debe guardarse uno vacío en su lugar.
El flujo del iframe
Un tablero no se embebe con una URL fija. Se solicita una URL firmada para un atleta concreto, se coloca en un iframe, y esa URL vence a los diez minutos.
Solicitud de la URL
Con el id del tablero y el del atleta. El servidor verifica que el atleta pertenezca a la institución (si no, 404 athlete_not_found), que esté activo (si no, 409 athlete_inactive) y que el tablero esté en el catálogo (si no, 404 dashboard_not_available).
Render en el iframe
La URL se usa tal cual. Institución, atleta y fuente vienen bloqueados en la firma: ningún filtro del tablero puede modificarse desde el cliente.
Renovación al vencer
No hay auto-refresh. Si la vista permanece abierta más de diez minutos, se solicita otra URL y se reemplaza el src.
curl -X POST "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes/4821/dashboards/7/embed-url?institution_id=42" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{"locale": "es"}'{
"status": "success",
"data": {
"url": "<URL firmada: se coloca tal cual en el src del iframe>",
"expires_at": "2026-09-03T14:30:00Z"
},
"message": null,
"errors": null,
"meta": null
}El cuerpo es opcional: locale acepta es, en, pt o una variante regional como pt-BR, y cualquier otro valor cae a es.
El tablero no permite descargas. La URL se firma con las descargas desactivadas, y la respuesta llega con Cache-Control: no-store: cada URL se pide en el momento de mostrarla.
De la vinculación al tablero
El recorrido entero, en el orden en que lo hace el sistema del partner: canjear el código, encontrar al atleta, leer sus resultados y mostrar su tablero. Usa los mismos datos de ejemplo que el resto de la guía.
# 1. Canjear el código de vinculación, una sola vez.
# Se guarda data.institution_id (42): es el institution_id de todo lo que sigue.
curl -X POST "https://app.analytics-api.ivolution.com.ar/api/v1/partner/link" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{"code": "EjemploDeCodigoDeVinculacion-NoEsReal_00000"}'
# 2. Encontrar al atleta por correo. Si llega más de uno, se desempata con sex y birthdate.
# Se guarda su id (4821).
curl -X POST "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes/search?institution_id=42" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "lucia.ejemplo@example.com"}'
# 3. Leer sus resultados de un protocolo en un mes, página por página con meta.cursor.
curl "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes/4821/results?institution_id=42&evaluation_type=balance_jumps_with_cea&from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY"
# 4. Elegir un tablero del catálogo de este entorno: su id (7) no se fija en el código.
curl "https://app.analytics-api.ivolution.com.ar/api/v1/partner/dashboards?institution_id=42" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY"
# 5. Al abrir la vista, pedir la URL firmada del tablero de ese atleta.
curl -X POST "https://app.analytics-api.ivolution.com.ar/api/v1/partner/athletes/4821/dashboards/7/embed-url?institution_id=42" \
-H "Authorization: Bearer $IVOLUTION_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{"locale": "es"}'<!-- src: el data.url que devolvió el paso 5, tal cual. Vence a los diez minutos. -->
<iframe
src="URL_FIRMADA_DEL_PASO_5"
title="Tablero de Lucía Ejemplo"
width="100%"
height="600"
></iframe>Lo que se guarda es el institution_id, el id del atleta y los resultados ya leídos. La URL del tablero no se guarda: se pide cada vez que se abre la vista, y otra vez si sigue abierta pasados los diez minutos.
Errores y límites
Todos los errores tienen la misma forma: status en error, un message legible y la lista errors, cuyo code es lo que debe leer el sistema del partner. Varios de estos códigos no son fallas técnicas: son estados del negocio que el sistema del partner debe saber comunicar.
{
"status": "error",
"data": null,
"message": "Institution has no active trial or subscription.",
"errors": [
{
"code": "subscription_required",
"message": "Institution has no active trial or subscription.",
"field": null
}
],
"meta": null
}| Código | HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
invalid_cursor | 400 | El cursor es inválido, venció o se usó con otros filtros. | Volver a pedir la primera página. |
invalid_credential | 401 | La credencial falta o no funciona. | Revisar la credencial en uso y su entorno. |
insufficient_scope | 403 | La credencial no tiene el alcance que exige la ruta. | Consultar a Ivolution por el alcance. |
integration_revoked | 403 | La institución terminó la integración a propósito. | No reintentar: hace falta una autorización nueva. |
subscription_required | 403 | La integración existe, pero la institución no tiene suscripción ni prueba vigente. | Notificar a la institución. Se resuelve de su lado. |
athlete_not_found | 404 | El atleta no existe, fue eliminado o es de otra institución. | Rehacer el match por correo; no reintentar el mismo athlete_id. |
dashboard_not_available | 404 | El tablero no está en el catálogo. | Volver a leer el catálogo de ese entorno. |
integration_not_found | 404 | No hay integración con esa institución. | Solicitar una autorización nueva. |
athlete_inactive | 409 | El atleta está inactivo: no se firma su tablero. | No mostrar el tablero de ese atleta. |
integration_expired | 409 | El código de vinculación venció sin canjearse. | La institución genera un código nuevo. |
integration_pending | 409 | La institución autorizó, pero el código no se canjeó todavía. | Canjear el código. |
link_code_invalid | 409 | El código no existe, es de otro partner, ya se canjeó o venció. | Pedir un código nuevo a la institución. |
validation_error | 422 | La solicitud no pasó la validación. | Corregir la solicitud. |
rate_limited | 429 | Se superó el límite de llamadas. | Esperar lo que indica Retry-After y reintentar. |
dashboards_unavailable | 503 | El catálogo o el tablero no pueden confirmarse ahora. | Reintentar después de Retry-After. |
results_unavailable | 503 | Los resultados no están disponibles por el momento. | Reintentar más tarde, después de Retry-After si llega. |
Límites de uso
Los límites se cuentan por credencial, en tres cupos independientes. Al superar uno, la respuesta es 429 rate_limited con Retry-After.
| Cupo | Límite |
|---|---|
| Lecturas: todas las rutas salvo las dos siguientes | 100 por minuto |
Canje del código de vinculación (/link) | 10 por minuto |
URL firmada de tablero (/embed-url) | 30 por minuto |
Entorno de prueba
El entorno de prueba tiene una institución demo con atletas y fichas sintéticos. Ningún dato de una persona real circula por ese entorno, lo que permite desarrollar sin acuerdos de confidencialidad de por medio.
No hay alta self-service: la credencial de prueba, el institution_id de la institución demo y la dirección del entorno los entrega Ivolution con el alta del partner. La integración con la institución demo ya llega activa, y a partir de ahí las rutas, las respuestas y los errores son los mismos que en producción.
Cada credencial funciona en un solo entorno. La de prueba no sirve en producción ni la de producción en prueba: el prefijo de la credencial indica a cuál pertenece.
Responsabilidades, atribución y purga
Los datos pertenecen a la institución que los compartió, no al partner ni a Ivolution. La integración supone asumir tres obligaciones:
- Custodia. Los datos leídos quedan bajo responsabilidad del partner, con el mismo cuidado que los propios.
- Atribución. Se solicita indicar que los datos de rendimiento provienen de Ivolution allí donde se muestren.
- Purga a pedido. Si una institución revoca la integración y solicita el borrado de lo leído, el partner debe poder hacerlo.
Correlación por correo. Una misma persona puede ser atleta de dos instituciones que autorizaron al partner: aparece en el listado de cada una, con un id distinto, y sus resultados se leen por separado, cada uno con el institution_id de su institución. Reconocerla por el correo es una correlación aceptada. La API, en cambio, no cruza datos: cada lectura devuelve solo lo que produjo la institución indicada.
La revocación no borra nada del lado del partner. Cuando una institución revoca, el acceso se corta en la siguiente llamada, pero los datos ya obtenidos siguen en el sistema del partner: borrarlos a pedido queda de su lado.