Docs/API y webhooks/Cursos personalizados

Cursos personalizados

Un curso personalizado es una secuencia curada y orientada al estudiante de temas y escenarios extraídos del catálogo de contenido de SecureCodingHub. Una vez creado, un curso personalizado se puede asignar igual que un tema o categoría integrados. Requiere custom-courses:read o custom-courses:write según el verbo.

Endpoints

MétodoRutaScopePropósito
GET/custom-coursescustom-courses:readListar todos los cursos personalizados de la organización.
GET/custom-courses/{'{'}id{'}'}custom-courses:readRecuperar un único curso personalizado con sus elementos ordenados.
POST/custom-coursescustom-courses:writeCrear un nuevo curso personalizado.
PATCH/custom-courses/{'{'}id{'}'}custom-courses:writeActualizar metadatos y/o reemplazar la lista de elementos.
DELETE/custom-courses/{'{'}id{'}'}custom-courses:writeDesactivar de forma suave un curso personalizado.

Todas las rutas son relativas a https://api.limeplate.com/api/public/v1.

El modelo de elemento

Cada curso personalizado contiene una lista ordenada de items. Cada elemento es una referencia a un tema o escenario del catálogo: consulta Catálogo de contenido para los identificadores.

CampoTipoSignificado
itemTypestringBien "topic" (un tema de práctica como sql-injection) o "scenario" (un escenario en modo aprendizaje).
itemIdstringEl id del tema o escenario en el catálogo.
orderIndexintegerOrden de visualización en base cero en la UI del estudiante. Los valores menores aparecen primero.

En PATCH, omite el campo items para dejar los elementos intactos, o envía una lista de reemplazo completa para sobrescribir. No hay actualización parcial de elementos: reenvía la secuencia completa en el orden en que quieras que quede guardada.

Listar cursos personalizados

Devuelve una entrada por cada curso personalizado activo en la organización, ordenadas por actualización más reciente.

GET /api/public/v1/custom-courses
Authorization: Bearer scs_live_…

Respuesta

[
  {
    "id": "8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02",
    "name": "Backend Onboarding — Q2",
    "description": "Required reading for new backend hires.",
    "icon": "shield",
    "color": "#3b82f6",
    "itemCount": 6,
    "usageCount": 12,
    "createdByUserId": "1a2b3c4d-5e6f-7081-9203-4d5e6f708192",
    "createdByName": "Jane Smith",
    "createdAt": "2026-04-10T09:22:14Z",
    "updatedAt": "2026-05-18T15:01:47Z"
  }
]

Ejemplo

curl -sS \
  -H "Authorization: Bearer $SCH_API_KEY" \
  https://api.limeplate.com/api/public/v1/custom-courses

Obtener un curso personalizado

Devuelve el curso con sus elementos ordenados expandidos. resolvedTitle es el título legible del tema o escenario subyacente en el momento de la lectura, cómodo para renderizar sin una segunda consulta al catálogo.

GET /api/public/v1/custom-courses/{customCourseId}
Authorization: Bearer scs_live_…

Respuesta

{
  "id": "8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02",
  "name": "Backend Onboarding — Q2",
  "description": "Required reading for new backend hires.",
  "icon": "shield",
  "color": "#3b82f6",
  "items": [
    {
      "id": "c1d2e3f4-5060-7081-9203-4d5e6f708192",
      "itemType": "topic",
      "itemId": "sql-injection",
      "resolvedTitle": "SQL Injection",
      "orderIndex": 0
    },
    {
      "id": "d2e3f405-6070-8192-0304-5e6f70819203",
      "itemType": "scenario",
      "itemId": "auth-bypass-walkthrough",
      "resolvedTitle": "Auth Bypass Walkthrough",
      "orderIndex": 1
    },
    {
      "id": "e3f40506-7081-9203-0405-6f7081920304",
      "itemType": "topic",
      "itemId": "xss",
      "resolvedTitle": "Cross-Site Scripting",
      "orderIndex": 2
    }
  ],
  "usageCount": 12,
  "createdByUserId": "1a2b3c4d-5e6f-7081-9203-4d5e6f708192",
  "createdByName": "Jane Smith",
  "createdAt": "2026-04-10T09:22:14Z",
  "updatedAt": "2026-05-18T15:01:47Z"
}

Ejemplo

curl -sS \
  -H "Authorization: Bearer $SCH_API_KEY" \
  https://api.limeplate.com/api/public/v1/custom-courses/8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02

Crear un curso personalizado

Crea un curso y devuelve la vista de detalle. La organización de la API key es propietaria del nuevo curso; el usuario que emitió la clave queda registrado como creador.

POST /api/public/v1/custom-courses
Authorization: Bearer scs_live_…
Content-Type: application/json

Cuerpo de la solicitud

{
  "name": "Backend Onboarding — Q2",
  "description": "Required reading for new backend hires.",
  "icon": "shield",
  "color": "#3b82f6",
  "items": [
    { "itemType": "topic",    "itemId": "sql-injection",          "orderIndex": 0 },
    { "itemType": "scenario", "itemId": "auth-bypass-walkthrough", "orderIndex": 1 },
    { "itemType": "topic",    "itemId": "xss",                    "orderIndex": 2 }
  ]
}

name es obligatorio y debe ser único dentro de la organización. description, icon y color son opcionales: color admite cualquier cadena hexadecimal CSS. items puede estar vacío en la creación; puedes rellenarlo después vía PATCH.

Respuesta

Misma forma que Obtener un curso personalizado.

Ejemplo

curl -sS -X POST \
  -H "Authorization: Bearer $SCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Backend Onboarding — Q2",
    "description": "Required reading for new backend hires.",
    "icon": "shield",
    "color": "#3b82f6",
    "items": [
      { "itemType": "topic", "itemId": "sql-injection", "orderIndex": 0 },
      { "itemType": "topic", "itemId": "xss",           "orderIndex": 1 }
    ]
  }' \
  https://api.limeplate.com/api/public/v1/custom-courses

Actualizar un curso personalizado

Todos los campos de la solicitud son opcionales. Pasa solo los campos que quieras cambiar. Enviar items reemplaza toda la lista de elementos: para reordenar, reenvía cada elemento con nuevos valores de orderIndex.

PATCH /api/public/v1/custom-courses/{customCourseId}
Authorization: Bearer scs_live_…
Content-Type: application/json

Cuerpo de la solicitud

{
  "name": "Backend Onboarding — Q3",
  "items": [
    { "itemType": "topic",    "itemId": "sql-injection", "orderIndex": 0 },
    { "itemType": "topic",    "itemId": "ssrf",          "orderIndex": 1 },
    { "itemType": "scenario", "itemId": "jwt-tampering", "orderIndex": 2 }
  ]
}

Respuesta

El objeto de detalle actualizado.

Ejemplo

curl -sS -X PATCH \
  -H "Authorization: Bearer $SCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Backend Onboarding — Q3" }' \
  https://api.limeplate.com/api/public/v1/custom-courses/8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02

Eliminar un curso personalizado

Desactiva el curso de forma suave. Deja de aparecer en GET /custom-courses y en las UIs de estudiante. Las asignaciones existentes que referenciaban el curso siguen registrando progreso histórico, pero ya no se entregan a nuevos destinatarios.

DELETE /api/public/v1/custom-courses/{customCourseId}
Authorization: Bearer scs_live_…

Respuesta

{ "message": "Custom course deactivated" }

Ejemplo

curl -sS -X DELETE \
  -H "Authorization: Bearer $SCH_API_KEY" \
  https://api.limeplate.com/api/public/v1/custom-courses/8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02

Asignar un curso personalizado

Un curso personalizado no se entrega a los estudiantes hasta que se asigna. Usa Asignaciones con targetType: "custom-course" y targetId apuntando al id del curso personalizado:

curl -sS -X POST \
  -H "Authorization: Bearer $SCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "assigneeType": "user",
    "assigneeId": "1a2b3c4d-5e6f-7081-9203-4d5e6f708192",
    "contentArea": "practice",
    "targetType": "custom-course",
    "targetId": "8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02",
    "deadline": "2026-06-15T00:00:00Z",
    "isMandatory": true
  }' \
  https://api.limeplate.com/api/public/v1/assignments

El estudiante ve los elementos en el orden de orderIndex. Los temas de práctica y los escenarios de aprendizaje se rastrean bajo sus respectivos endpoints de progreso: consulta Progreso.