API — Ingesta de eventos
La API de ingesta recibe eventos de aprendizaje desde Moodle (u otros orígenes) y los encola para procesamiento. Es la superficie que usa el plugin Moodle, pero puedes enviar eventos directamente si tienes un integrador propio.
Base URL
Sección titulada «Base URL»https://ingest.softsysanalytics.comAutenticación
Sección titulada «Autenticación»Todos los endpoints de ingesta (excepto /health) requieren la cabecera:
X-SSEA-Key: ssea_<32_hex_chars>Las claves las provisiona SoftSys Solutions por tenant. Ver Seguridad y privacidad para detalles sobre formato, expiración y revocación.
Endpoints
Sección titulada «Endpoints»POST /v1/events — Enviar un evento
Sección titulada «POST /v1/events — Enviar un evento»Envía un único evento. Útil para integradores con bajo volumen o para pruebas.
curl -X POST https://ingest.softsysanalytics.com/v1/events \ -H "X-SSEA-Key: ssea_<your_key>" \ -H "Content-Type: application/json" \ -d '{ "event_type": "quiz_submitted", "user_id": "moodle_user_42", "course_id": "moodle_course_7", "module_id": "moodle_cm_13", "timestamp": "2026-04-13T14:30:00Z", "metadata": { "attempt_number": 2, "score_percent": 84.5, "time_taken_seconds": 1200 } }'await fetch('https://ingest.softsysanalytics.com/v1/events', { method: 'POST', headers: { 'X-SSEA-Key': process.env.SSEA_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ event_type: 'quiz_submitted', user_id: 'moodle_user_42', course_id: 'moodle_course_7', timestamp: new Date().toISOString(), metadata: { score_percent: 84.5 }, }),});import os, requests
requests.post( 'https://ingest.softsysanalytics.com/v1/events', headers={ 'X-SSEA-Key': os.environ['SSEA_API_KEY'], 'Content-Type': 'application/json', }, json={ 'event_type': 'quiz_submitted', 'user_id': 'moodle_user_42', 'course_id': 'moodle_course_7', 'timestamp': '2026-04-13T14:30:00Z', 'metadata': {'score_percent': 84.5}, },)Respuesta exitosa — 202 Accepted
{ "ok": true, "data": { "queued": 1, "event_id": "<deterministic_hash>" }}El código 202 indica que el evento fue aceptado y encolado, pero aún no procesado. El dashboard puede tardar unos minutos en reflejar eventos recién encolados.
POST /v1/events/batch — Enviar un lote
Sección titulada «POST /v1/events/batch — Enviar un lote»Envía hasta 500 eventos en una sola request. Recomendado para integradores con alto volumen o para sincronizaciones iniciales.
curl -X POST https://ingest.softsysanalytics.com/v1/events/batch \ -H "X-SSEA-Key: ssea_<your_key>" \ -H "Content-Type: application/json" \ -d '{ "events": [ { "event_type": "course_viewed", "user_id": "u1", "course_id": "c1", "timestamp": "2026-04-13T10:00:00Z" }, { "event_type": "resource_viewed", "user_id": "u1", "course_id": "c1", "module_id": "m1", "timestamp": "2026-04-13T10:01:00Z" } ] }'Respuesta — 202 Accepted
{ "ok": true, "data": { "queued": 2, "skipped": 0 }}queued: eventos aceptados y encolados.skipped: eventos descartados por validación (formato incorrecto, tipos no soportados). No dispara error si al menos uno entra.
POST /v1/sync — Sincronización de entidades Moodle
Sección titulada «POST /v1/sync — Sincronización de entidades Moodle»Usado por el plugin Moodle para migrar datos históricos (usuarios, cursos, matrículas, actividades, calificaciones) al backend de SSEA. No es para telemetría de eventos — usa /v1/events para eso.
Este endpoint está pensado para el plugin Moodle y su formato de payload está acoplado al flujo del plugin. Si construyes un integrador propio, probablemente lo que quieras es enviar eventos normales por /v1/events.
GET /health — Health check
Sección titulada «GET /health — Health check»No requiere autenticación. Útil para monitoreo externo y pruebas de conectividad desde el servidor Moodle.
curl https://ingest.softsysanalytics.com/healthRespuesta — 200 OK
{ "ok": true, "service": "ssea-ingest"}Formato del payload de evento
Sección titulada «Formato del payload de evento»El payload sigue un núcleo común con campos opcionales según el tipo de evento. Ver Tipos de eventos para el catálogo completo.
{ "event_type": "string (uno de los 15 tipos soportados)", "user_id": "string (obligatorio — ID del usuario Moodle)", "course_id": "string (opcional — ID del curso Moodle)", "module_id": "string (opcional — ID del módulo del curso)", "timestamp": "string (ISO 8601 UTC, obligatorio)", "context": { "object_id": "string (opcional)", "context_level": "number (opcional)" }, "metadata": "object (opcional — campos específicos del evento)", "ip": "string (opcional — se hashea server-side antes de persistir)"}Notas:
event_type: debe ser uno del catálogo. Tipos desconocidos se descartan.timestamp: cuándo ocurrió el evento en el origen (no cuándo lo envías). Formato ISO 8601 UTC conZ(2026-04-13T14:30:00Z).ip: si la envías, el worker de ingesta calculaSHA-256(ip + salt)y descarta el valor crudo antes de encolar. Si prefieres no mandar IP, el plugin Moodle tiene una opciónno_ip.metadata: campos específicos enriquecidos por el plugin (p. ej.score_percentparaquiz_submitted). Ver catálogo.
Códigos de respuesta
Sección titulada «Códigos de respuesta»| HTTP | Significado | Acción del cliente |
|---|---|---|
202 Accepted | Evento(s) aceptados y encolados. | Continuar normalmente. |
400 Bad Request | JSON inválido o mal formado. | Revisar el body del request. No reintentar sin corregir. |
401 Unauthorized | Clave API faltante, inválida o revocada. | Verificar cabecera X-SSEA-Key. Si ya tenías claves válidas, contactar a soporte para confirmar estado. |
402 Payment Required | Cuota diaria del plan excedida. | Esperar al reset (medianoche UTC) o hacer upgrade de plan. Ver Planes y límites. |
422 Unprocessable Entity | Validación falló (p. ej. event_type obligatorio faltante, batch >500 eventos). | Corregir el payload. No reintentar sin corregir. |
429 Too Many Requests | Rate limit excedido (ver cabecera Retry-After). | Esperar el tiempo indicado y reintentar con backoff exponencial. |
503 Service Unavailable | Worker mal configurado (falta un secret crítico). | Muy raro — contactar a soporte. |
Formato de errores
Sección titulada «Formato de errores»Todas las respuestas de error siguen el formato:
{ "ok": false, "error": "Mensaje legible para humanos", "code": "CODE_CONSTANT"}Códigos de error comunes:
code | HTTP | Significado |
|---|---|---|
UNAUTHORIZED | 401 | API key inválida o expirada. |
INVALID_JSON | 400 | JSON mal formado. |
VALIDATION_ERROR | 422 | Campo obligatorio faltante o formato incorrecto. |
BATCH_TOO_LARGE | 422 | Lote con más de 500 eventos. |
PLAN_LIMIT_EXCEEDED | 402 | Cuota diaria del plan agotada. |
RATE_LIMITED | 429 | Demasiados requests en la ventana actual. |
Rate limiting
Sección titulada «Rate limiting»- Por defecto: 1 000 requests por minuto y por tenant.
- Planes Enterprise: hasta 10 000 req/60s, negociado por contrato.
- Las respuestas
429incluyen la cabeceraRetry-After: <segundos>.
La recomendación para clientes es implementar backoff exponencial: reintentar tras 1 s, 2 s, 4 s, 8 s… con un máximo razonable (p. ej. 5 reintentos). El plugin Moodle ya lo hace automáticamente.
Idempotencia y deduplicación
Sección titulada «Idempotencia y deduplicación»- Cada evento genera un ID determinístico basado en el hash de sus campos clave.
- Si reenvías el mismo evento (por ejemplo, por un timeout de red seguido de retry), el worker de procesamiento detecta el duplicado y no lo cuenta dos veces.
- Esto significa que puedes reintentar requests
202con seguridad: no duplicas datos.