API Reference — Vital4Female
Documentación técnica de todos los endpoints REST expuestos por la aplicación (routes/api.php), pensada para publicarse en la documentación oficial del sistema y servir de referencia a integraciones externas (Vue.js, mobile, ElevenLabs Agent, etc.).
Para el flujo detallado de registro de estudiantes (incluyendo integración con PayPro, colas y ejemplos Vue), ver
student-registration-api.md. Este documento resume ese mismo endpoint y añade el resto de la API.
Convenciones generales
Base URL
https://admin.vital4female.nl/api
Headers por defecto
Content-Type: application/json
Accept: application/json
Formato de respuesta
La mayoría de los endpoints heredan de BaseController y devuelven un envelope consistente:
// Éxito
{
"success": true,
"data": { ... },
"message": "Descripción legible"
}
// Error
{
"success": false,
"message": "Descripción del error",
"data": { "campo": ["mensaje de validación"] }
}
Un pequeño número de endpoints "legacy" (course_grid, questions, agent/*, surveyanswers, checkdiscounts) no usan este envelope y devuelven JSON plano — se indica explícitamente en cada sección.
Autenticación
La API usa Laravel Sanctum (tokens personales tipo Bearer). Los endpoints protegidos requieren:
Authorization: Bearer {token}
El token se obtiene vía POST /api/login (ver más abajo) y solo se emite a usuarios con rol super-admin, admin o api-user.
Un grupo separado de endpoints (/api/agent/*) usa un esquema de autenticación distinto basado en API key por header (ver sección Agente ElevenLabs).
Índice de endpoints
| Método | Endpoint | Auth | Descripción |
|---|---|---|---|
| POST | /login |
Pública | Login y emisión de token Sanctum |
| GET | /user |
Sanctum | Usuario autenticado actual |
| GET/POST/PUT/DELETE | /coaches /coaches/{id} |
Sanctum | CRUD de coaches (parcial) |
| GET/POST/PUT/DELETE | /students /students/{id} |
Sanctum (index/show)¹ | Registro y consulta de estudiantes |
| PATCH | /membership_change |
Sanctum | Cambia la membresía de un estudiante |
| GET/POST/PUT/DELETE | /categories /categories/{id} |
Pública | Catálogo de categorías (solo index) |
| GET/POST/PUT/DELETE | /category/{category}/subcategories |
Pública | Subcategorías por categoría (solo index) |
| GET/POST/PUT/DELETE | /courses /courses/{id} |
Pública | Catálogo de cursos (solo index) |
| GET | /coursegrid |
Pública | Cursos + categorías + subcategorías + membresías activas |
| GET/POST/PUT/DELETE | /lessons /lessons/{id} |
Pública | Catálogo de lecciones (solo index) |
| GET/POST/PUT/DELETE | /levels /levels/{id} |
Pública | CRUD completo de niveles |
| GET/POST/PUT/DELETE | /statuses /statuses/{id} |
Pública | CRUD completo de estados |
| GET | /memberships |
Pública | Membresías activas |
| GET | /checkdiscounts/{code} |
Pública | Valida un código de descuento |
| GET/POST/PUT/DELETE | /courses/{course}/lessons /courses/{course}/lessons/{id} |
Pública | Lecciones dentro de un curso (solo index) |
| PUT | /addLessonToCourse/{id} |
Pública | Asocia una lección a un curso |
| GET | /deleteLessonInCourse/{id} |
Pública | Desasocia una lección de un curso |
| POST | /surveyanswers |
Pública² | Guarda respuestas de una encuesta |
| GET | /surveyanswers/{id} |
Pública | Respuestas de un usuario |
| GET | /questions |
Pública | Preguntas de una encuesta (?survey_id=) |
| GET | /questions/{id} |
Pública | ⚠️ Ruta registrada, controlador sin método show() (ver notas) |
| GET | /questions/index/{id} |
Pública | Preguntas de la encuesta {id} |
| POST | /agent/rag |
API key (x-agent-key) |
Búsqueda semántica (RAG) para el agente ElevenLabs |
| GET | /agent/student/{id} |
API key (x-agent-key) |
Perfil del estudiante para el agente |
| POST | /agent/interaction |
API key (x-agent-key) |
Guarda una interacción del agente |
¹ POST /students es pública (registro de nuevos estudiantes); index/show/update/destroy requieren Sanctum.
² No exige token, pero usa auth()->id() internamente — ver advertencia en su sección.
Autenticación
POST /api/login
Autentica un usuario administrativo y emite un token Sanctum.
Rate limiting: 5 intentos por minuto, por combinación email + IP.
Request body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
email |
string | ✅ | Email del usuario |
password |
string | ✅ | Contraseña |
{
"email": "admin@vital4female.nl",
"password": "secret"
}
Respuestas
200 — Login exitoso
{
"success": true,
"data": {
"token": "1|AbCdEfGhIjKlMnOpQrStUvWxYz",
"username": "admin"
},
"message": "User logged in successfully."
}
401 — Credenciales inválidas
{
"success": false,
"message": "Unauthorised.",
"data": { "error": "Invalid credentials" }
}
403 — Usuario sin acceso API
Solo los roles super-admin, admin y api-user pueden generar tokens; cualquier otro rol recibe:
{
"success": false,
"message": "Forbidden.",
"data": { "error": "This user has no API access" }
}
422 — Rate limit excedido
Laravel responde con el mensaje de throttle estándar de auth.throttle (vía ValidationException).
Endpoints protegidos (Sanctum)
Requieren header Authorization: Bearer {token} obtenido en /login.
GET /api/user
Devuelve el modelo User autenticado (según el token enviado).
{ "id": 1, "username": "admin", "email": "admin@vital4female.nl", "...": "..." }
Coaches — /api/coaches
Registrado como apiResource, pero el controlador solo implementa index y show; store, update y destroy no están implementados (llamarlos produce un error de método no definido).
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /coaches |
Lista todos los coaches |
| GET | /coaches/{id} |
Detalle de un coach (404 si no existe) |
{ "success": true, "data": [ { "id": 1, "coach_social": {...}, "coach_schedule": {...} } ], "message": "Coaches list" }
Students — /api/students
| Método | Endpoint | Auth | Descripción |
|---|---|---|---|
| POST | /students |
Pública (fuera del grupo Sanctum) | Registro de un nuevo estudiante — ver student-registration-api.md |
| GET | /students |
Sanctum | Lista todos los estudiantes |
| GET | /students/{id} |
Sanctum | Detalle de un estudiante |
update y destroy están declarados por apiResource pero no implementados en el controlador.
Resumen de POST /students
POST /api/students
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
firstname |
string | ✅ | Máx. 100 caracteres |
lastname |
string | ✅ | Máx. 100 caracteres |
email |
string | ✅ | Único en users.email |
product_id |
integer | ✅ | Debe existir en products.id |
membership |
string | ✅ | Nombre de la membresía |
payment_method |
string | ✅ | ideal, ideal-qr, etc. |
birthday |
date | ❌ | YYYY-MM-DD |
discount_code |
string | ❌ | Código de descuento |
Respuesta 201:
{
"success": true,
"data": {
"payment_url": "...",
"payment_hash": "...",
"order_id": 42,
"user_id": 88
},
"message": "Student registered successfully."
}
Ver el documento dedicado para el detalle completo de errores (422, 429, 500) y el flujo de pago con PayPro.
PATCH /api/membership_change
Cambia la membresía activa de un estudiante, actualiza su disponibilidad de coaching y envía notificación por email + notificación interna.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
user_id |
integer | ✅ | ID del usuario/estudiante |
membership_id |
integer | ✅ | Nuevo ID de membresía |
membership_name |
string | ✅ | Nombre de la nueva membresía |
Efectos secundarios:
- Actualiza
students.membership_idystudents.student_membership. - Actualiza
availabilities.minutes_availablesegúnmembership_coaching_time. - Envía el correo
MembershipUpgradeal usuario. - Dispara la notificación
MembershipChanged.
{ "success": true, "data": "Changed", "message": "Membership changed" }
⚠️ No valida que
user_idomembership_idexistan antes de operar sobre ellos (firstOrFailsobreStudent, peroMembership::findsinorFail); unmembership_idinexistente puede provocar un error 500 al acceder amembership_coaching_timedenull.
Catálogo público
Estos endpoints no requieren autenticación — se usan desde los componentes Vue del sitio público y del checkout.
Categorías y subcategorías
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /api/categories |
Lista todas las categorías (category_tags como array) |
| GET | /api/category/{category}/subcategories |
Subcategorías de la categoría indicada |
Solo index está implementado en ambos controladores, aunque las rutas se registran como apiResource completo.
{
"success": true,
"data": [
{
"id": 1,
"category_name": "Fitness",
"category_tags": ["cardio", "fuerza"]
}
],
"message": "Category list"
}
Cursos
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /api/courses |
Lista todos los cursos (sin filtrar por estado) |
| GET | /api/coursegrid |
Cursos activos (status_id = 1) + catálogos relacionados |
GET /api/coursegrid es el endpoint usado por la grilla pública de cursos. No usa el envelope success/data/message:
{
"courses": [ { "id": 5, "course_title": "...", "category": {...}, "subcategory": {...} } ],
"categories": [ ... ],
"subcategories": [ ... ],
"memberships": [ ... ]
}
Lecciones
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /api/lessons |
Lista todas las lecciones (solo index implementado) |
| GET | /api/courses/{course}/lessons |
Lecciones de un curso específico (solo index) |
| PUT | /api/addLessonToCourse/{id} |
Reasigna una lección (id) al curso indicado en course_id |
| GET | /api/deleteLessonInCourse/{id} |
Desasocia la lección {id} de su curso (pone course_id = 0) |
// PUT /api/addLessonToCourse/12 body: { "course_id": 7 }
{
"success": true,
"data": { "id": 12, "course_id": 7, "...": "..." },
"message": "lessonincourse updated"
}
⚠️
deleteLessonInCourseusaGET, noDELETE— es una convención heredada del sistema, no un error de este documento. Además, no elimina la lección, solo la desvincula del curso (course_id = 0).
Niveles — /api/levels
CRUD completo y funcional:
| Método | Endpoint | Body | Descripción |
|---|---|---|---|
| GET | /levels |
— | Lista todos |
| POST | /levels |
{ "level_name": "..." } |
Crea (level_name requerido) |
| GET | /levels/{id} |
— | Detalle (404 si no existe) |
| PUT/PATCH | /levels/{id} |
{ "level_name": "..." } |
Actualiza |
| DELETE | /levels/{id} |
— | Elimina |
Estados — /api/statuses
Mismo patrón CRUD que levels, con el campo status_name:
| Método | Endpoint | Body |
|---|---|---|
| GET | /statuses |
— |
| POST | /statuses |
{ "status_name": "..." } |
| GET | /statuses/{id} |
— |
| PUT/PATCH | /statuses/{id} |
{ "status_name": "..." } |
| DELETE | /statuses/{id} |
— |
Membresías — /api/memberships
GET /api/memberships
Devuelve solo las membresías con isActive = true, ordenadas por membership_order.
{
"success": true,
"data": [{ "id": 1, "membership_slug": "founder", "membership_order": 1 }],
"message": "Membership list"
}
Descuentos
GET /api/checkdiscounts/{code}
Valida un código de descuento antes del checkout. No usa el envelope estándar.
| Respuesta | Código HTTP | Body |
|---|---|---|
| Válido | 200 | { "valid": true, "message": "Discount code is valid", "amount": 40 } |
| No encontrado | 404 | { "valid": false, "message": "Discount not found" } |
Agotado (uses_count >= max_uses) |
400 | { "valid": false, "message": "Discount code has been used up" } |
amount es el porcentaje de descuento (discounts.percentage), redondeado a entero.
Encuestas y preguntas
POST /api/surveyanswers
Guarda las respuestas de un usuario a una encuesta.
| Campo | Tipo | Descripción |
|---|---|---|
surveyId |
integer | ID de la encuesta |
answers |
object | Mapa { question_id: answer_value } |
{ "surveyId": 3, "answers": { "10": "opcion_a", "11": "opcion_b" } }
// 200
{ "message": "Successfully saved responses" }
⚠️ El controlador usa
auth()->id()para asociar las respuestas al usuario, pero la ruta no está dentro del grupoauth:sanctum. Si se llama sin sesión/token activo,user_idse guarda comonull. Si se integra desde un cliente externo, considerar exigir autenticación o enviar eluser_idexplícitamente.
GET /api/surveyanswers/{id}
Devuelve todas las respuestas guardadas para el usuario {id}.
[
{
"id": 1,
"user_id": 5,
"survey_id": 3,
"question_id": 10,
"value": "opcion_a"
}
]
Preguntas — /api/questions
| Método | Endpoint | Descripción |
|---|---|---|
| GET | /api/questions?survey_id={id} |
Preguntas de una encuesta, con title/description de la encuesta (join) — devuelve un array plano, sin envelope |
| GET | /api/questions/{id} |
⚠️ Registrado por Route::resource(...)->only(['index','show']), pero QuestionController no define show() — invocarlo produce un error del framework. Usar questions/index/{id} en su lugar |
| GET | /api/questions/index/{id} |
Alias explícito equivalente a index filtrando por survey_id = {id} |
Los métodos store, update y destroy existen en el controlador pero no están registrados en routes/api.php (no son accesibles vía API pública actualmente).
Agente ElevenLabs
Grupo de endpoints bajo el prefijo /api/agent, protegidos por el middleware agent.key (no usa Sanctum).
Autenticación
x-agent-key: {AGENT_API_KEY}
Configurado en config/services.php → services.agent.key (variable de entorno correspondiente). Una key ausente o incorrecta responde:
// 401
{ "error": "Unauthorized" }
POST /api/agent/rag
Búsqueda semántica (RAG) sobre la base de conocimiento (knowledge_base, pgvector) usando embeddings de OpenAI (text-embedding-3-small). Pensado para ser invocado por el agente conversacional de ElevenLabs antes de generar una respuesta.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
query |
string | ✅ | Máx. 1000 caracteres — texto a buscar |
user_id |
integer | ❌ | Si se envía, filtra resultados por la membresía del estudiante (exists:users,id) |
limit |
integer | ❌ | 1–10, por defecto 5 |
{ "query": "¿Cómo cancelo mi membresía?", "user_id": 88, "limit": 3 }
Respuesta (sin envelope):
{
"query": "¿Cómo cancelo mi membresía?",
"context": [
{
"source_type": "faq",
"source_id": 12,
"content": "Para cancelar tu membresía...",
"similarity": 0.8734,
"metadata": { "membership": "founder" }
}
]
}
Requiere
OPENAI_API_KEYconfigurada (services.openai.key) y la extensiónpgvectoren PostgreSQL.
GET /api/agent/student/{id}
Perfil resumido de un estudiante, usado por el agente para personalizar sus respuestas.
{
"user_id": 88,
"name": "Anna Jansen",
"language": "nl",
"status": "active",
"membership": "founder",
"courses": [{ "id": 5, "course_title": "...", "course_description": "..." }]
}
courses solo incluye cursos activos (status_id = 1) cuyo campo JSON course_membership contiene el slug de la membresía del estudiante. Si el usuario no existe, responde 404 (findOrFail).
POST /api/agent/interaction
Registra una interacción del agente (para historial/analítica) en la tabla agent_interactions.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
session_id |
string | ✅ | ID de sesión de la conversación |
query |
string | ✅ | Pregunta del usuario |
response |
string | ✅ | Respuesta generada por el agente |
user_id |
integer | ❌ | exists:users,id |
context |
array | ❌ | Contexto RAG usado para generar la respuesta |
// 200
{ "status": "ok" }
Notas y limitaciones conocidas
- Rutas
apiResourceparcialmente implementadas:categories,category.subcategories,courses,lessons,coachesycourses.lessonsse registran como recursos REST completos, pero sus controladores solo implementanindex(yshowencoaches). Invocarstore/update/destroysobre estos recursos falla con un error de método no definido en el controlador. GET /api/questions/{id}no funciona — falta el métodoshow()enQuestionController. UsarGET /api/questions/index/{id}como alternativa ya soportada.- Endpoints sin envelope estándar:
coursegrid,checkdiscounts,surveyanswers,questionsyagent/*devuelven JSON plano en lugar de{ success, data, message }. Tenerlo en cuenta al escribir clientes genéricos. deleteLessonInCourseusaGETen vez deDELETE, y no elimina la lección, solo la desvincula del curso.surveyanswersno está protegida por Sanctum pese a depender deauth()->id()internamente.- Los endpoints marcados como "pública" en el índice están disponibles sin token porque alimentan componentes Vue del sitio público (landing, checkout, grilla de cursos); no exponen datos sensibles de estudiantes individuales salvo
agent/student/{id}, que sí está protegido por API key.
Referencias relacionadas
student-registration-api.md— flujo completo de registro + pago (PayPro), ejemplos Vue.js, colas y webhook.routes/api.php— fuente de verdad de las rutas registradas.app/Http/Controllers/API/V1/— controladores de la API versión 1.app/Http/Middleware/AgentKeyMiddleware.php— validación dex-agent-key.