DesarrolladoresAPI v1 · OpenAPI 3.1

Tu escuela, conectada.

Todo lo que necesitas para conectar Be School con tus sistemas: desde tu primera petición hasta una inscripción completa y eventos en tiempo real.

Empezar a conectarTexto para LLMOpenAPI

La API en cifras

Operaciones

31

Cada una con su ejemplo

Eventos

15

Webhooks firmados

Lenguajes de ejemplo

3

cURL, JavaScript y Python

Dirección base: https://sch.begraffic.com/api/v1

Documentación completa

La API REST v1 conecta tu escuela con una web de inscripción, una app, un ERP, informes o automatizaciones. Puedes leer el catálogo y los datos académicos disponibles, dar de alta estudiantes, matricular con justificación, solicitar admisiones y recibir eventos.

1. Necesitas una escuela activa y un plan que incluya integraciones (desde Academia). Como propietario o administrador, abre Panel → Ajustes → API y claves (/panel/ajustes/api). Crea una clave con los permisos mínimos necesarios y guárdala: se muestra una sola vez.

2. Configura BESCHOOL_API_KEY como secreto en tu servidor. La base de los ejemplos es https://sch.begraffic.com/api/v1. Usa la dirección base indicada en el panel de tu escuela; no añadas el slug de la escuela a /api/v1. La escuela se obtiene de la clave, no de un parámetro school_id.

3. Ejecuta GET /me para comprobar la conexión. Verifica el id de escuela y los permisos antes de automatizar escrituras. El prefijo bs_live_ no implica un entorno de pruebas: una escritura con una clave real cambia datos reales. Para ensayar usa una escuela separada y sus propias claves.

OpenAPI 3.1 está disponible sin autenticación en /api/v1/openapi.json. Puedes importarlo en Postman, Insomnia o un visor compatible. Esta referencia incluye todas sus operaciones; los ejemplos contienen datos ficticios y los listados pueden estar vacíos.

# Define BESCHOOL_API_KEY en el entorno seguro de tu servidor.
curl 'https://sch.begraffic.com/api/v1/me' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"

Cada solicitud protegida requiere Authorization: Bearer <clave>. No uses cookies del panel, tokens de Firebase ni claves en parámetros URL. Las claves pertenecen a una sola escuela y no pueden consultar otra: un recurso ajeno responde 404.

Mantén la clave en el servidor o en el almacén de credenciales de tu herramienta de automatización. Tu navegador llama a tu propio backend y ese backend llama a Be School. No pongas claves en variables NEXT_PUBLIC_, bundles, repositorios, capturas ni conversaciones con LLM.

Escuela: Nombre, moneda, zona horaria y dirección pública. Permisos: school:read.

Catálogo: Rutas con sus cursos, temarios, clases (ficha), periodos, precios y programas. Permisos: catalog:read.

Contenido de clases: Lecturas, vídeos, audios, documentos y materiales de las clases, también borradores. Acceso de integración sin las restricciones de una matrícula. Permisos: content:read.

Estudiantes: La lista y la ficha básica (nombre, correo, estado). Permisos: students:read, students:write. Escritura: Dar de alta e invitar.

Matrículas: Quién está matriculado en qué periodo y con qué justificación. Permisos: enrollments:read, enrollments:write. Escritura: Matricular con beca, pago fuera de línea o gratis.

Pedidos y cobros: Importes, estados y reembolsos. Permisos: orders:read.

Certificados: Los emitidos y revocados, con su código de verificación. Permisos: certificates:read.

Progreso: Las clases que ha completado cada estudiante. Permisos: progress:read.

Admisiones: Solicitudes de admisión a los programas y su respuesta. Permisos: admissions:read, admissions:write. Escritura: Enviar una solicitud en nombre de un estudiante.

Webhooks (Zapier, Make): Suscribir una URL a un evento y darla de baja (REST Hooks). Permisos: webhooks:write. Escritura: Suscribir y dar de baja.

Escribir incluye leer el mismo recurso. GET /me acepta cualquier clave válida; si otra operación necesita un permiso ausente devuelve 403 insufficient_scope. Las claves antiguas read/write conservan sus permisos anteriores; no incluyen content:read automáticamente. Habilita explícitamente «Contenido de clases» para leer cuerpos y archivos. Los permisos nuevos seleccionados «Solo lectura» y «Acceso completo» sí incluyen content:read; «Inscripción propia» no lo necesita.

Punto de partida «Solo lectura»: school:read, catalog:read, content:read, students:read, enrollments:read, orders:read, certificates:read, progress:read, admissions:read. Consulta todo y no cambia nada: informes, un ERP, un panel propio.

Punto de partida «Inscripción propia»: school:read, catalog:read, students:read, students:write, enrollments:read, enrollments:write, admissions:read, admissions:write. Tu web o tu app: lee el catálogo, da de alta estudiantes, los matricula y envía admisiones.

Punto de partida «Acceso completo»: school:read, catalog:read, content:read, students:read, students:write, enrollments:read, enrollments:write, orders:read, certificates:read, progress:read, admissions:read, admissions:write, webhooks:write. Todo lo que permite la API, también Zapier.

Puedes crear hasta 20 claves. Caducidad: sin fecha o 30, 90 o 365 días. Rotar crea otra clave con el mismo nombre y permisos; elige una convivencia de 0 h, 1 h, 24 h o 7 días. Actualiza el secreto en tus sistemas y prueba /me antes de retirar la anterior. Caducadas o revocadas responden 401. Revocar y reducir permisos sigue disponible sin el plan; ampliar permisos o rotar requiere el plan.

Escuela: contiene la marca, moneda, zona horaria y dirección pública. Cada clave define una escuela. El panel administra la escuela; el campus es el espacio de aprendizaje de los estudiantes; la web pública presenta el catálogo y el checkout.

Cursos: organizan contenido y clases. ready significa listo para abrirse en un periodo; no garantiza que se venda en la web. El filtro published es un alias antiguo de ready. La API de catálogo permite leer rutas, sus cursos y las fichas del temario, sin crearlos ni editar sus clases. El cuerpo y los archivos de una clase requieren content:read.

Periodos: son aperturas de un curso, clase única, ruta o ciclo, con fechas, visibilidad y plazas. La matrícula se hace en un periodo, no solo en un curso. En rutas y ciclos course_id puede ser null. Un periodo activo no implica que sea visible: comprueba visible y public_from; la elegibilidad final se valida al matricular.

Membresías: GET /periods/{id} incluye membership_offers con precios mensual/anual, checkout_url, pitch, description, benefits (id, text, included), badge, featured, catalog, scope y contribution. La presentación usa la misma función que la página pública: sin herencia entre planes ni exclusiones inventadas. scope.kind es period; scope.target identifica el curso, clase o ruta de ese periodo, nunca acceso general a la escuela. Los textos ausentes son cadenas vacías y yearly_price/contribution pueden ser null. contribution.monthly_amount es la parte de la cuota mensual en céntimos; text coincide con la frase pública y no representa otro cargo. Las claves de benefits identifican líneas de cada plan, no permisos. Mostrar los textos como texto, no como HTML.

Ofertas: precios y condiciones de acceso asociados al catálogo. price y amount son enteros en céntimos (4900 = 49.00). En suscripciones o plazos, price corresponde a cada cuota. checkout_url puede ser null en ofertas archivadas, rutas o ediciones; utiliza su página de venta correspondiente.

Estudiantes y matrículas: crear una cuenta no da acceso a un curso. La matrícula relaciona al estudiante con su periodo y la justificación de acceso. Pedidos describe los cobros; progreso contiene clases completadas y certificados registra los documentos emitidos o revocados.

Programas: carreras con plan de estudios, materias y requisitos. Una solicitud de admisión no es una aceptación ni una matrícula; secretaría decide en el panel. Consulta su estado con GET /admission_requests/{id}. El catálogo actual de webhooks no incluye un evento específico de admisión.

La API v1 no expone edición de cursos, subida de archivos, emisión de certificados, cambio de notas, reembolsos ni cobros directos por tarjeta. Esas tareas se realizan en sus flujos del panel, campus o proveedor. No inventes endpoints PATCH/PUT ni reutilices APIs internas con una clave v1.

Las fechas date-time usan ISO 8601; date usa YYYY-MM-DD. El created del sobre de un webhook usa segundos Unix. null expresa un valor ausente o, para seats/seats_available, capacidad sin límite. Los ids son opacos: usa los devueltos por la API (rutas con letras, números, guion o guion bajo; hasta 200 caracteres).

Todos los listados devuelven object: list, data, has_more y next_cursor. limit va de 1 a 100; por defecto 25. La ordenación es por id, no por fecha de creación. Conserva los filtros y pasa next_cursor sin modificarlo como cursor para continuar; deja de paginar cuando has_more sea false.

El cursor es opaco: no lo generes a partir de fechas ni lo uses para saltar páginas por número. En consultas con filtros, una página puede contener menos elementos que limit y todavía tener has_more: true. Usa has_more para decidir si continuar.

La referencia de cada función enumera sus filtros admitidos. Construye los parámetros con URLSearchParams (o params en requests) para codificar correos, ids y cursores. Un filtro con un valor no válido devuelve 400 invalid_request. No se ofrece búsqueda por texto libre en los endpoints.

let cursor = null;
do {
  const url = new URL("https://sch.begraffic.com/api/v1/students");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);
  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.BESCHOOL_API_KEY}` },
  });
  const page = await response.json();
  if (!response.ok) throw new Error(page.error?.message);
  for (const student of page.data) {
    // Guarda o procesa cada estudiante en tu sistema.
    console.log(student.id);
  }
  if (!page.has_more) break;
  if (!page.next_cursor) throw new Error("Falta el cursor de continuación");
  cursor = page.next_cursor;
} while (cursor);

Los errores devuelven JSON con { "error": { "code": "…", "message": "…" } }. Comprueba el estado HTTP y error.code; usa message para el diagnóstico. Las respuestas de la API protegida no se almacenan en caché.

400 invalid_request / invalid_json: revisa parámetros o cuerpo. 401 unauthorized: falta la clave, no es válida, ha caducado o fue revocada. 403 insufficient_scope / plan_required / school_suspended: revisa permisos, plan o estado de la escuela.

404 not_found: recurso inexistente o fuera de tu escuela; una matrícula también puede devolver 404 enrollment_rejected si la persona no pertenece a ella. 405 method_not_allowed: consulta la cabecera Allow. 409 is_staff / suspended: no puedes dar de alta como estudiante a una persona del equipo ni readmitir por API a alguien suspendido.

413 payload_too_large: el JSON supera 64 KiB. 415 unsupported_media_type: envía Content-Type: application/json. 422 plan_limit / account_failed / enrollment_rejected / admission_rejected / invalid_target: la petición no puede aplicarse; lee message y corrige la causa. 500 internal_error y 503 unavailable: fallo o indisponibilidad del servicio.

429 rate_limited incluye Retry-After en segundos. El limitador admite una ráfaga de 120 solicitudes y repone 2/s por clave; funciona por instancia y no garantiza una cuota global. Con claves inválidas se permiten 20 intentos por IP y después uno cada 5 s. Limita concurrencia y espera Retry-After antes de volver a solicitar.

Para lecturas ante un 5xx o un fallo de red, usa reintentos acotados con espera exponencial y variación aleatoria. Ante un timeout de escritura el resultado puede ser desconocido: consulta el estado antes de repetir. No hay una cabecera Idempotency-Key documentada en v1.

POST /students devuelve 200 si ya era estudiante; POST /enrollments devuelve 200 si ya tenía la matrícula; POST /admission_requests puede reutilizar una solicitud con la misma nota. POST /hooks crea una suscripción nueva: no lo repitas a ciegas. Una operación exitosa puede devolver 200 o 201; comprueba created cuando exista.

{
  "error": {
    "code": "insufficient_scope",
    "message": "Esta clave no tiene el permiso students:read. Dáselo en Ajustes → API y claves."
  }
}

1. Lista las rutas con GET /paths (catalog:read). GET /paths/{id} devuelve course_ids en el orden configurado. Rutas y programas son recursos distintos: /programs describe carreras académicas.

2. Consulta GET /paths/{id}/courses para recibir las fichas de los cursos y clases únicas en ese mismo orden, con position. Recorre has_more/next_cursor hasta terminar; se omiten referencias eliminadas o ajenas. Los ids son ids de documento obtenidos por la API, no los códigos visibles del curso.

3. GET /courses/{id}/syllabus devuelve el temario completo: modules con lessons y unassigned_lessons al final. GET /courses/{id}/lessons es su versión paginada; admite status=published o status=draft. Sin filtro, ambas contienen fichas de borradores y publicadas. No incluyen texto ni enlaces privados.

4. Habilita el permiso específico content:read y usa GET /courses/{id}/lessons/{lessonId}. El detalle devuelve descripción saneada, lectura, objetivos, vídeo externo o media, documento, capítulos, transcripción, subtítulos, recursos, sesión en vivo y enunciado de asignación según el tipo de clase. Los campos que no aplican son null o listas/cadenas vacías según su esquema.

Los archivos media, document y resources[].file traen url, expires_at y available. Las URLs duran 15 minutos: renueva consultando de nuevo el detalle antes de expires_at. Un archivo inválido o ausente es null; si no se puede firmar, available=false y url/expires_at=null. No se entregan rutas de Storage como enlaces alternativos.

content:read es acceso privilegiado de tu integración, incluso a borradores; no representa a un estudiante ni aplica sus matrículas, plan o goteo. Autoriza en tu servidor quién puede ver cada contenido antes de entregarlo. No pongas la clave en una app pública. Las claves existentes, incluso las antiguas read/write, no reciben este permiso automáticamente; selecciónalo expresamente en API y claves.

Los cuestionarios incluyen resumen y campus_url; las preguntas de un intento y las respuestas permanecen en el campus. Los paquetes SCORM incluyen versión, título y campus_url: no se exporta el ZIP ni se ejecuta SCORM por esta API. Los enlaces al campus requieren sesión y acceso del estudiante. Leer contenido no marca clases completadas ni crea matrículas, intentos o entregas.

Las listas de rutas se paginan por id; cursos de una ruta y clases de un curso respetan el orden del panel. Mantén los mismos filtros mientras pagines. Si el cursor ya no pertenece a la lista (por retirada o cambio de filtro), recibirás 400 invalid_request: reinicia desde la primera página. Un padre o clase de otra escuela devuelve 404, aunque exista en otra escuela.

curl 'https://sch.begraffic.com/api/v1/paths?limit=25' -H "Authorization: Bearer $BESCHOOL_API_KEY"
curl 'https://sch.begraffic.com/api/v1/paths/ruta_ejemplo/courses?limit=25' -H "Authorization: Bearer $BESCHOOL_API_KEY"
curl 'https://sch.begraffic.com/api/v1/courses/curso_ejemplo/syllabus' -H "Authorization: Bearer $BESCHOOL_API_KEY"
curl 'https://sch.begraffic.com/api/v1/courses/curso_ejemplo/lessons?status=published&limit=25' -H "Authorization: Bearer $BESCHOOL_API_KEY"
# La siguiente petición requiere content:read.
curl 'https://sch.begraffic.com/api/v1/courses/curso_ejemplo/lessons/clase_ejemplo' -H "Authorization: Bearer $BESCHOOL_API_KEY"

Consulta GET /periods/{id} con catalog:read desde tu servidor. offers contiene las ofertas del periodo consultado y membership_offers, sus alternativas de membresía. content añade una lista de fichas de cursos y clases publicadas, identificadas por course_id y lesson_id (null en el curso). No incluye cuerpos ni archivos; para ellos sigue siendo necesario content:read.

Cada ficha incluye independent_sale. enabled confirma que hay una configuración activa y coherente; available comprueba publicación, contenido listo, calendario de inscripción, cupo del periodo y precio. Si available=true y offer.checkout_url tiene valor, muestra Comprar este curso o Comprar esta clase con offer.price (céntimos) y offer.currency. Si no, no ofrezcas ese enlace; unavailable_reason explica el motivo. Un periodo activo puede estar oculto, pendiente de lanzamiento, cerrado o lleno. El checkout vuelve a validar condiciones, derecho de acceso y métodos de cobro; la consulta no reserva plazas ni confirma pagos.

Para Acceder con membresía usa membership_offers de esa ficha: monthly_price, yearly_price (null si no hay anual), currency y checkout_url. Las tarjetas conservan sus textos y scope.id real. Una clase de un curso o un curso de una ruta pueden tener como alternativa la membresía del periodo principal. El enlace de membresía apunta a ese alcance, no al periodo individual; no lo reconstruyas sustituyendo ids. El mismo plan puede aparecer con distintos scope.id y alcances.

parent_period_id relaciona una venta independiente con su origen. lesson_ids indica qué clases abre el periodo y también aparece en sus ofertas: null = curso completo; una lista = solo esas clases; [] = alcance vacío o inválido. No uses course_id para asumir acceso al curso entero. Al consultar un periodo individual, content solo enumera las clases que realmente incluye y membership_offers puede anunciar la membresía de su origen únicamente si ese origen todavía incluye su contenido.

La compra individual de un curso dentro de una ruta no compra la ruta entera. Comprar una clase no abre las demás clases. Desactivar la venta impide nuevas compras y conserva los accesos ya adquiridos. No calcules el precio del curso completo tomando la oferta más barata de GET /offers: una oferta con lesson_ids limitado corresponde solo a esas clases.

const response = await fetch('https://sch.begraffic.com/api/v1/periods/periodo_ejemplo', {
  headers: { Authorization: `Bearer ${process.env.BESCHOOL_API_KEY}` },
  cache: 'no-store',
});
if (!response.ok) throw new Error('No se pudo consultar el periodo');
const period = await response.json();
const cards = period.content.map(item => ({
  course_id: item.course_id,
  lesson_id: item.lesson_id,
  title: item.title,
  buy: item.independent_sale.available && item.independent_sale.offer?.checkout_url
    ? {
        label: item.kind === 'course' ? 'Comprar este curso' : 'Comprar esta clase',
        price_cents: item.independent_sale.offer.price,
        currency: item.independent_sale.offer.currency,
        payment_type: item.independent_sale.offer.type,
        installment_count: item.independent_sale.offer.installment_count,
        billing_interval: item.independent_sale.offer.billing_interval,
        url: item.independent_sale.offer.checkout_url,
      }
    : null,
  memberships: item.membership_offers.map(plan => ({
    label: 'Acceder con membresía',
    title: plan.title,
    monthly_price_cents: plan.monthly_price,
    yearly_price_cents: plan.yearly_price,
    currency: plan.currency,
    scope_period_id: plan.scope.id,
    url: plan.checkout_url,
  })),
}));
// Envía cards a tu frontend. La clave de API permanece en este servidor.
// Para comprar el periodo completo usa sus offers, respetando lesson_ids.

Guías de integración

Inscripción y pagos desde tu web

1. Usa school:read y catalog:read para presentar la escuela y GET /periods?status=active. Consulta GET /periods/{id} para conocer plazas y ofertas. seats_available: null significa sin límite. La disponibilidad puede cambiar entre consulta y matrícula.

2. Desde tu backend llama a POST /students con email, name y send_invite. Conserva el id devuelto. send_invite es true por defecto; invited indica si se consiguió enviar/entregar la notificación, no si el estudiante ya entró al campus. Repetir un alta existente no reenvía automáticamente la invitación.

3. Para pagos con tarjeta, redirige a checkout_url de la oferta activa. El checkout y Stripe registran el cobro y ejecutan el acceso correspondiente. Verifica order.paid y enrollment.created de manera independiente: un pago recibido no garantiza que todo el procesamiento posterior haya terminado. No marques un pedido como pagado desde una URL de éxito.

4. Para una beca, un pago externo ya confirmado o un periodo gratuito, usa POST /enrollments con students:write y enrollments:write según las operaciones que hagas. La escritura de matrícula exige un estudiante de la escuela y valida fechas, plazas y reglas académicas. Selecciona el preset «Inscripción propia» y reduce permisos si tu flujo necesita menos.

Beca: justification.kind = scholarship y reason de 5 a 400 caracteres. Pago externo: kind = manual_payment, method = transfer/cash/card_terminal/other, reference de 2 a 120 caracteres y amount entero entre 1 y 100000000, en céntimos de la moneda de la escuela. No hay subida de comprobante en v1; queda registrado vía API y en auditoría.

Gratuito: justification.kind = free, solo si el periodo tiene una oferta gratuita activa. No conviertas una compra pendiente en una matrícula gratuita o becada. Para programas, crea una solicitud con POST /admission_requests y consulta el resultado de secretaría.

{
  "student_id": "uid_ejemplo",
  "period_id": "periodo-ejemplo",
  "justification": {
    "kind": "manual_payment",
    "method": "transfer",
    "reference": "TRX-2026-001",
    "amount": 4900
  }
}

Guías de integración

Conexiones disponibles

Web, app, ERP o CRM propio: utiliza HTTP desde tu servidor con una clave v1. Lee catálogo, estudiantes, matrículas, cobros, progreso y certificados según sus permisos. Sincroniza con paginación inicial y eventos posteriores; conserva la relación entre tus ids y los de Be School.

Zapier y Make: configura solicitudes HTTP o un conector propio. Prueba credenciales con GET /me, usa GET para búsquedas y POST para altas. Para disparadores instantáneos registra una URL con POST /hooks y un evento; guarda id y secret, y ejecuta DELETE /hooks/{id} al desconectar. Esta compatibilidad HTTP/REST Hooks no implica que exista una app publicada en sus directorios.

Webhooks propios: en Panel → Integraciones → Webhooks (/panel/integraciones/webhooks) registra destinos y eventos, envía pruebas y revisa entregas. Hasta 10 destinos por escuela. Las suscripciones creadas por /hooks aparecen identificadas como Zapier / API. DELETE /hooks solo da de baja suscripciones REST Hook, no destinos manuales del panel.

Be CRM: la conexión oficial se autoriza desde Panel → Integraciones → Be CRM (/panel/integraciones/becrm), con selección de cuenta y configuración de sincronización. Utiliza su flujo de autorización; una clave de Be School no es una credencial de Be CRM. Revisa qué datos y eventos habilitas y el consentimiento de comunicaciones.

Be Agent: conecta la cuenta desde Panel → Integraciones → Be Agent (/panel/integraciones/be-agent). Sus funciones educativas y la revisión humana se configuran en la escuela. El protocolo de conexión y sus callbacks son del conector oficial; no se autentican con bs_live_ ni forman parte de la API v1.

Stripe: autoriza tu cuenta desde Panel → Integraciones → Stripe (/panel/integraciones/stripe) y completa los requisitos del proveedor para habilitar cobros. El estudiante paga en el checkout de la escuela. La API v1 permite consultar pedidos; no sustituye Stripe Connect ni permite crear cobros o reembolsos.

Acceso con Google Workspace o Microsoft: configura el inicio de sesión de tu organización y los dominios admitidos en /panel/integraciones/acceso. Esta conexión gestiona el acceso al campus; no reemplaza las claves para integraciones de servidor.

Clases en vivo: /panel/integraciones/en-vivo permite usar la sala integrada o conectar Zoom y Google Meet para crear reuniones. La conexión a proveedores externos requiere la función de plan live_integrations y autorización de la cuenta correspondiente.

Automatizaciones de correo: /panel/integraciones/automatizaciones configura bienvenida, recordatorios y seguimiento. Para procesos externos usa webhooks y REST Hooks; no confundas una automatización interna con una operación de la API pública.

El acceso y la disponibilidad dependen del plan, los permisos de la persona y el estado de cada conexión. Una conexión guardada no garantiza que el proveedor esté listo: comprueba el estado en el panel y ensaya el recorrido completo antes de activar una automatización.

Guías de integración

Botón de compra en otra web

Para vender desde una landing sin desarrollar una integración de servidor, usa el botón oficial. En Panel → Ajustes → Ventas en otras webs autoriza cada origen HTTPS exacto (www y sin www cuentan como orígenes diferentes). En la ficha del curso o periodo, «Insertar en tu web» genera el código con el id correcto.

El script muestra un botón donde lo insertas y abre la inscripción en un modal. No requiere ni debe recibir una clave de API. El periodo debe estar activo, visible y tener una oferta que pueda pagarse con tarjeta, con Stripe habilitado. Los periodos con plazas limitadas o requisitos y las opciones no admitidas por el modal deben venderse desde la web de la escuela.

También puedes usar un botón propio con data-beschool-compra, data-escuela y data-periodo, cargando embed.js una sola vez; o abrirlo con BeSchoolCompra.open({ escuela, periodo }). El evento de navegador beschool:paid sirve para analítica: para confirmar operaciones en tu servidor utiliza pedidos, matrículas y webhooks firmados.

<script src="https://sch.begraffic.com/embed.js"
  data-escuela="tu-escuela"
  data-periodo="ID_DEL_PERIODO"
  data-texto="Inscríbete" async></script>

Guías de integración

Recibir y verificar webhooks

Registra un destino HTTPS público sin credenciales en la URL; no se aceptan localhost, redes privadas ni destinos internos. Para REST Hooks usa POST /hooks con target_url y un event del catálogo. Guarda secret (whsec_…) en tu servidor en el momento de crear la suscripción: se muestra una sola vez. Es distinto a tu clave bs_live_.

El cuerpo es un sobre JSON con id, type, created (segundos Unix), school y data. Los campos data usan camelCase, por ejemplo studentId, a diferencia de student_id en la API REST. El contenido depende del punto de emisión; no asumas que todos los campos ilustrativos están presentes.

Cabeceras: Be-School-Signature: t=<segundos>,v1=<hex>; Be-School-Event-Id; Be-School-Event. Calcula HMAC-SHA256 con el secreto y el texto exacto "<t>.<cuerpo original>". Verifica antes de convertir el JSON, compara en tiempo constante y rechaza diferencias de más de 300 segundos en ambas direcciones.

Comprueba que school coincide con la escuela esperada y type está entre tus eventos suscritos. Guarda el evento de forma duradera con una restricción única por (school, id) antes de responder; un duplicado ya guardado puede responder 2xx. Procesa el trabajo pesado en una cola. Diseña consumidores que toleren duplicados y eventos fuera de orden.

Solo un 2xx cuenta como entregado; las redirecciones no se siguen. Responde en menos de 10 segundos. Hay hasta siete intentos, con esperas de 1 min, 5 min, 30 min, 2 h, 8 h y 24 h, con una pequeña variación. El despacho depende del procesador de entregas, por lo que las esperas no son un horario exacto.

Tras agotar los intentos el envío queda failed y puede reenviarse manualmente. Un destino con al menos 10 fallos consecutivos y más de 24 h sin éxito se desactiva automáticamente. Consulta entregas en el panel, corrige la causa y reactívalo cuando esté listo.

Los eventos actuales no incluyen uno específico para aprobar o rechazar admisiones. Consulta su endpoint. No deduzcas eventos por nombres de funciones o por ejemplos de otro producto.

import { createHmac, timingSafeEqual } from "node:crypto";

export function validSignature(header, rawBody, secret) {
  if (!header || header.length > 2000 || !secret) return false;
  let timestamp;
  const signatures = [];
  for (const part of header.split(",")) {
    const [key, value] = part.trim().split("=", 2);
    if (key === "t" && /^\d{1,12}$/.test(value ?? "")) timestamp = Number(value);
    if (key === "v1" && /^[a-f0-9]{64}$/.test(value ?? "")) signatures.push(value);
  }
  if (timestamp === undefined || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`, "utf8").digest();
  return signatures.some((hex) => {
    const received = Buffer.from(hex, "hex");
    return received.length === expected.length && timingSafeEqual(received, expected);
  });
}

// En tu receptor: lee request.text() UNA vez antes de JSON.parse.
// Si !validSignature(header, rawBody, secret): responde 400.
// Valida school/type; persiste y deduplica (school, id); después responde 2xx.
// Si no puedes persistir, responde 5xx para que la entrega se reintente.

Cada sección y cada función tienen «Copiar para LLM»: obtendrás Markdown con el contexto de autenticación, permisos, parámetros, campos y ejemplos de esa sección. Cada bloque de código tiene también su propio botón de copia. «Copiar documentación completa» reúne todas las guías, funciones y eventos.

Para dar contexto por URL, comparte /documentacion/llms.txt (texto completo sin interfaz) y /api/v1/openapi.json (contrato estructurado). Ambos son públicos y no contienen datos de escuelas ni claves reales. Revisa siempre el código generado antes de ejecutarlo, especialmente las matrículas y suscripciones.

Sustituye credenciales y datos personales por marcadores antes de usar un asistente. Los ejemplos no establecen que exista un estudiante, un periodo o una oferta con esos ids. Obtén los ids reales por la API autorizada y conserva los permisos mínimos.

Implementa una integración de servidor con Be School API v1 usando la documentación adjunta.
Usa BESCHOOL_API_KEY desde un almacén de secretos y prueba GET /me.
Enumera los permisos mínimos para el flujo solicitado.
Respeta los esquemas, paginación, estados HTTP y Retry-After.
No inventes endpoints ni una cabecera de idempotencia.
No conviertas un pago pendiente en beca o matrícula gratuita.
Verifica firmas sobre el cuerpo original y deduplica los webhooks.
Usa marcadores para credenciales e ids. Explica cómo validar el recorrido antes de activarlo.

REST · JSON · v1

Referencia de API

31 operaciones con ejemplos de cURL, JavaScript y Python. Abre los detalles de cada función para ver sus parámetros, campos y respuestas.

Conexión

GET/api/v1/me

La clave, sus permisos y su escuela (prueba de conexión de Zapier)

Permiso: Cualquier clave válida

Cualquier clave válida.

curl --request GET 'https://sch.begraffic.com/api/v1/me' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Campos de respuesta

object"api_key"
prefixstring
scopesarray
schoolobject
school.idstring
school.namestring

Respuestas HTTP del contrato

  • 200La clave es válida.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Escuela

GET/api/v1/school

La ficha pública de la escuela

Permiso: school:read

Nombre, moneda, zona horaria y la dirección de su web (dominio propio si lo tiene). Permiso: school:read.

curl --request GET 'https://sch.begraffic.com/api/v1/school' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Campos de respuesta

idstring
object"school"
namestring
short_namestring
slugstring
taglinestring | null
public_urlstring
Su dominio propio si lo tiene activo. Formato: uri.
currencystring
Moneda de las ofertas nuevas (ISO 4217).
timezonestring
localestring
support_emailstring | null
brand_colorstring | null

Respuestas HTTP del contrato

  • 200La escuela.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Cursos

GET/api/v1/courses

Cursos de la escuela

Permiso: catalog:read

Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/courses?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
statusquery · string
Solo los de ese estado (`published` es el nombre antiguo de `ready`). Valores: draft, ready, archived, published.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].cover_urlstring | null
Portada resuelta, lista para mostrar. null si no hay imagen disponible. Nunca una ruta de Storage. Formato: uri.
data[].cover_sourcestring | null
Origen de la imagen elegida. Valores: "period", "course", "lesson", null.
data[].cover_expires_atstring | null
Refresca la respuesta antes de esta fecha para renovar la URL firmada. null para miniaturas o portadas servidas por la aplicación sin caducidad, o si no hay imagen. Formato: date-time.
data[].idstring
data[].object"course"
data[].titlestring
data[].codestring | null
data[].statusstring
Listo para abrirse en periodos; lo que se vende lo deciden sus periodos. Valores: "draft", "ready", "archived".
data[].kindstring
Valores: "course", "single".
data[].lesson_countinteger | null
data[].created_atstring | null
Formato: date-time.
data[].updated_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Cursos

GET/api/v1/courses/{id}

Un curso, con su presentación

Permiso: catalog:read

Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/courses/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

cover_urlstring | null
Portada resuelta, lista para mostrar. null si no hay imagen disponible. Nunca una ruta de Storage. Formato: uri.
cover_sourcestring | null
Origen de la imagen elegida. Valores: "period", "course", "lesson", null.
cover_expires_atstring | null
Refresca la respuesta antes de esta fecha para renovar la URL firmada. null para miniaturas o portadas servidas por la aplicación sin caducidad, o si no hay imagen. Formato: date-time.
idstring
object"course"
titlestring
codestring | null
statusstring
Listo para abrirse en periodos; lo que se vende lo deciden sus periodos. Valores: "draft", "ready", "archived".
kindstring
Valores: "course", "single".
lesson_countinteger | null
created_atstring | null
Formato: date-time.
updated_atstring | null
Formato: date-time.
descriptionstring | null
Presentación: HTML saneado.
outcomesarray

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Rutas

GET/api/v1/paths

Rutas de aprendizaje

Permiso: catalog:read

Incluye course_ids en el orden de la ruta y course_count. Los cursos deben existir dentro de la escuela de la clave. Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/paths?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
statusquery · string
Estado del contenido; published es alias de ready. Valores: draft, ready, archived, published.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"path"
data[].titlestring
data[].codestring | null
data[].taglinestring
data[].statusstring
Valores: "draft", "ready", "archived".
data[].course_idsarray
Cursos existentes de esta escuela, en el orden de la ruta. Se omiten referencias eliminadas o ajenas y duplicados.
data[].course_countinteger
data[].created_atstring | null
data[].updated_atstring | null
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Rutas

GET/api/v1/paths/{id}

Una ruta y sus cursos en orden

Permiso: catalog:read

Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/paths/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"path"
titlestring
codestring | null
taglinestring
statusstring
Valores: "draft", "ready", "archived".
course_idsarray
Cursos existentes de esta escuela, en el orden de la ruta. Se omiten referencias eliminadas o ajenas y duplicados.
course_countinteger
created_atstring | null
updated_atstring | null

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Rutas

GET/api/v1/paths/{id}/courses

Los cursos de una ruta, en orden

Permiso: catalog:read

Incluye fichas de cursos y clases únicas con position. Paginación en el orden definido en la ruta. Si el último curso del cursor fue retirado, devuelve 400 invalid_request: reinicia la lista. 404 si la ruta no existe en esta escuela. Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/paths/id_ejemplo/courses?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio
limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].cover_urlstring | null
Portada resuelta, lista para mostrar. null si no hay imagen disponible. Nunca una ruta de Storage. Formato: uri.
data[].cover_sourcestring | null
Origen de la imagen elegida. Valores: "period", "course", "lesson", null.
data[].cover_expires_atstring | null
Refresca la respuesta antes de esta fecha para renovar la URL firmada. null para miniaturas o portadas servidas por la aplicación sin caducidad, o si no hay imagen. Formato: date-time.
data[].idstring
data[].object"course"
data[].titlestring
data[].codestring | null
data[].statusstring
Listo para abrirse en periodos; lo que se vende lo deciden sus periodos. Valores: "draft", "ready", "archived".
data[].kindstring
Valores: "course", "single".
data[].lesson_countinteger | null
data[].created_atstring | null
Formato: date-time.
data[].updated_atstring | null
Formato: date-time.
data[].descriptionstring | null
Presentación: HTML saneado.
data[].outcomesarray
data[].positioninteger
Posición 1-based dentro de la ruta.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Clases y contenido

GET/api/v1/courses/{id}/syllabus

El temario completo de un curso

Permiso: catalog:read

Módulos en su orden, con sus clases, y unassigned_lessons al final. Incluye fichas de clases publicadas y borradores, sin texto, enlaces de archivos ni preguntas. No requiere paginación; devuelve todo el temario. Una clase única aparece en unassigned_lessons. Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/courses/id_ejemplo/syllabus' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

object"syllabus"
course_idstring
titlestring
lesson_countinteger
modulesarray
modules[].idstring
modules[].titlestring
modules[].positioninteger
modules[].lessonsarray
modules[].lessons[].idstring
modules[].lessons[].object"lesson"
modules[].lessons[].course_idstring
modules[].lessons[].module_idstring | null
modules[].lessons[].titlestring
modules[].lessons[].summarystring
modules[].lessons[].kindstring
Valores: "video", "audio", "text", "document", "live", "quiz", "assignment", "scorm".
modules[].lessons[].statusstring
Valores: "draft", "published".
modules[].lessons[].positioninteger
Posición 1-based en el temario completo, antes de filtrar por estado.
modules[].lessons[].duration_secondsnumber | null
modules[].lessons[].free_previewboolean
modules[].lessons[].has_quizboolean
modules[].lessons[].has_assignmentboolean
unassigned_lessonsarray
unassigned_lessons[].idstring
unassigned_lessons[].object"lesson"
unassigned_lessons[].course_idstring
unassigned_lessons[].module_idstring | null
unassigned_lessons[].titlestring
unassigned_lessons[].summarystring
unassigned_lessons[].kindstring
Valores: "video", "audio", "text", "document", "live", "quiz", "assignment", "scorm".
unassigned_lessons[].statusstring
Valores: "draft", "published".
unassigned_lessons[].positioninteger
Posición 1-based en el temario completo, antes de filtrar por estado.
unassigned_lessons[].duration_secondsnumber | null
unassigned_lessons[].free_previewboolean
unassigned_lessons[].has_quizboolean
unassigned_lessons[].has_assignmentboolean

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Clases y contenido

GET/api/v1/courses/{id}/lessons

Clases de un curso en el orden del temario

Permiso: catalog:read

Solo fichas; el cuerpo de la clase se consulta en su detalle con content:read. Orden: módulos en su orden, clases por order y desempate por id, y clases sin módulo al final. Un cursor de una clase retirada o fuera del filtro devuelve 400 invalid_request. 404 si el curso no existe en esta escuela. Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/courses/id_ejemplo/lessons?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio
limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
statusquery · string
Sin filtro: draft y published. Valores: draft, published.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"lesson"
data[].course_idstring
data[].module_idstring | null
data[].titlestring
data[].summarystring
data[].kindstring
Valores: "video", "audio", "text", "document", "live", "quiz", "assignment", "scorm".
data[].statusstring
Valores: "draft", "published".
data[].positioninteger
Posición 1-based en el temario completo, antes de filtrar por estado.
data[].duration_secondsnumber | null
data[].free_previewboolean
data[].has_quizboolean
data[].has_assignmentboolean
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Clases y contenido

GET/api/v1/courses/{id}/lessons/{lessonId}

Contenido de una clase

Permiso: content:read

Lectura privilegiada de integración, también de borradores: no aplica matrículas, planes ni goteo de un estudiante. Tu servidor debe autorizar quién recibe este contenido. La clave y las URLs firmadas nunca se publican en documentación ni se comparten indiscriminadamente. Archivos de esta escuela con URL de 15 minutos; vuelve a pedir el detalle para renovar. No incluye respuestas, preguntas de intentos, entregas, notas, chats ni credenciales de anfitrión. Cuestionarios y SCORM se abren en campus_url con sesión de estudiante. No registra progreso ni reproduce la experiencia completa del campus. Permiso: content:read.

curl --request GET 'https://sch.begraffic.com/api/v1/courses/id_ejemplo/lessons/clase_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio
lessonIdpath · stringObligatorio

Campos de respuesta

idstring
object"lesson"
course_idstring
module_idstring | null
titlestring
summarystring
kindstring
Valores: "video", "audio", "text", "document", "live", "quiz", "assignment", "scorm".
statusstring
Valores: "draft", "published".
positioninteger
Posición 1-based en el temario completo, antes de filtrar por estado.
duration_secondsnumber | null
free_previewboolean
has_quizboolean
has_assignmentboolean
descriptionstring
Descripción HTML saneada.
objectivesstring
key_pointsstring
campus_urlstring
Enlace al campus; requiere la sesión y el acceso del estudiante. Formato: uri.
text_contentstring
Texto plano de la lectura; vacío en otros tipos.
mediaobject | null
Archivo de contenido con enlace temporal. null si no aplica o su referencia no es válida. Si la firma falla, available=false y url/expires_at=null; no se entrega una ruta de Storage como sustituto.
media.urlstring | null
Formato: uri.
media.expires_atstring | null
Vuelve a consultar el detalle antes de esta fecha para renovar el enlace (15 minutos). Formato: date-time.
media.availableboolean
video_urlstring | null
Vídeo externo por HTTP(S), cuando existe; no caduca por la API. Formato: uri.
documentobject | null
Archivo de contenido con enlace temporal. null si no aplica o su referencia no es válida. Si la firma falla, available=false y url/expires_at=null; no se entrega una ruta de Storage como sustituto.
document.urlstring | null
Formato: uri.
document.expires_atstring | null
Vuelve a consultar el detalle antes de esta fecha para renovar el enlace (15 minutos). Formato: date-time.
document.availableboolean
chaptersarray
chapters[].atnumber
chapters[].titlestring
transcriptarray
transcript[].atnumber | null
transcript[].textstring
captionsstring
resourcesarray
resources[].namestring
resources[].descriptionstring
resources[].content_typestring | null
resources[].size_bytesnumber | null
resources[].fileobject | null
Archivo de contenido con enlace temporal. null si no aplica o su referencia no es válida. Si la firma falla, available=false y url/expires_at=null; no se entrega una ruta de Storage como sustituto.
resources[].file.urlstring | null
Formato: uri.
resources[].file.expires_atstring | null
Vuelve a consultar el detalle antes de esta fecha para renovar el enlace (15 minutos). Formato: date-time.
resources[].file.availableboolean
liveobject | null
live.starts_atstring | null
live.join_urlstring | null
Enlace del participante; no incluye credenciales de anfitrión. Formato: uri.
live.duration_minutesnumber | null
assignmentobject | null
assignment.briefstring
assignment.criteriastring
assignment.allow_attachmentsboolean
assignment.due_atstring | null
assignment.dueobject | null
assignment.due.fromstring
Valores: "start", "opened".
assignment.due.daysinteger
assignment.rubricarray
assignment.rubric[].keystring
assignment.rubric[].labelstring
assignment.rubric[].weightnumber
quizobject | null
Resumen del cuestionario. Los intentos, sus preguntas, corrección y respuestas se gestionan en el campus con sesión de estudiante.
quiz.introstring
quiz.question_countinteger
quiz.pass_scorenumber
quiz.time_limit_minutesnumber
quiz.campus_urlstring
Formato: uri.
scormobject | null
Información del paquete. La ejecución y el progreso SCORM se realizan en el campus, con sesión de estudiante; no se expone el ZIP ni las rutas internas.
scorm.versionstring
Valores: "1.2", "2004".
scorm.titlestring
scorm.campus_urlstring
Formato: uri.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Periodos

GET/api/v1/periods

Periodos (aperturas)

Permiso: catalog:read

Lo que se vende y se matricula: un curso, una clase única o una ruta abiertos por una temporada (o sin fecha). visible y public_from dicen si se ve en la web. Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/periods?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
statusquery · string
Activos o archivados. Valores: active, archived.
course_idquery · string
Los que abren ese curso.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].cover_urlstring | null
Portada resuelta, lista para mostrar. null si no hay imagen disponible. Nunca una ruta de Storage. Formato: uri.
data[].cover_sourcestring | null
Origen de la imagen elegida. Valores: "period", "course", "lesson", null.
data[].cover_expires_atstring | null
Refresca la respuesta antes de esta fecha para renovar la URL firmada. null para miniaturas o portadas servidas por la aplicación sin caducidad, o si no hay imagen. Formato: date-time.
data[].cover_modestring
auto: primera portada del contenido en orden, curso y luego clases publicadas; custom: portada propia del periodo. Valores: "auto", "custom".
data[].idstring
data[].object"period"
data[].titlestring
data[].targetobject
data[].target.kindstring
Valores: "course", "single", "path", "cycle".
data[].target.idstring
data[].course_idstring | null
null en un periodo de ruta o de ciclo.
data[].parent_period_idstring | null
Periodo de origen de una venta independiente; null en aperturas principales.
data[].independent_purchaseboolean
Este periodo abre una compra independiente, sin conceder acceso a la ruta o carrera completa.
data[].lesson_idsarray | null
Clases originales que abre; null = curso completo, [] = alcance vacío o inválido. Nunca asumir curso entero por course_id si esta lista está limitada.
data[].kindstring
Valores: "open", "dated".
data[].statusstring
Valores: "active", "archived".
data[].visibleboolean
Se ve y se vende en la web.
data[].public_fromstring | null
Formato: date-time.
data[].starts_atstring | null
Formato: date.
data[].ends_atstring | null
Formato: date.
data[].enroll_fromstring | null
Formato: date.
data[].enroll_untilstring | null
Formato: date.
data[].seatsinteger | null
null = sin límite.
data[].certificateboolean
data[].created_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Periodos

GET/api/v1/periods/{id}

Un periodo con contenido, compras y membresías

Permiso: catalog:read

Lo que necesita una web de venta: offers para comprar el periodo completo y content con fichas de sus cursos y clases publicadas. Cada ficha incluye independent_sale (habilitación, disponibilidad, periodo individual, precio en céntimos y checkout propio) y membership_offers para presentar Comprar este curso/clase o Acceder con membresía. available=false deja el enlace individual en null. Las membresías incluyen presentación, beneficios, insignia, destacado y aporte social; scope conserva su periodo real, incluso si es el padre de una venta individual. No implica acceso general ni al curso entero si lesson_ids limita las clases. seats_available null significa sin límite. El checkout valida de nuevo acceso y cobro. Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/periods/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

cover_urlstring | null
Portada resuelta, lista para mostrar. null si no hay imagen disponible. Nunca una ruta de Storage. Formato: uri.
cover_sourcestring | null
Origen de la imagen elegida. Valores: "period", "course", "lesson", null.
cover_expires_atstring | null
Refresca la respuesta antes de esta fecha para renovar la URL firmada. null para miniaturas o portadas servidas por la aplicación sin caducidad, o si no hay imagen. Formato: date-time.
cover_modestring
auto: primera portada del contenido en orden, curso y luego clases publicadas; custom: portada propia del periodo. Valores: "auto", "custom".
idstring
object"period"
titlestring
targetobject
target.kindstring
Valores: "course", "single", "path", "cycle".
target.idstring
course_idstring | null
null en un periodo de ruta o de ciclo.
parent_period_idstring | null
Periodo de origen de una venta independiente; null en aperturas principales.
independent_purchaseboolean
Este periodo abre una compra independiente, sin conceder acceso a la ruta o carrera completa.
lesson_idsarray | null
Clases originales que abre; null = curso completo, [] = alcance vacío o inválido. Nunca asumir curso entero por course_id si esta lista está limitada.
kindstring
Valores: "open", "dated".
statusstring
Valores: "active", "archived".
visibleboolean
Se ve y se vende en la web.
public_fromstring | null
Formato: date-time.
starts_atstring | null
Formato: date.
ends_atstring | null
Formato: date.
enroll_fromstring | null
Formato: date.
enroll_untilstring | null
Formato: date.
seatsinteger | null
null = sin límite.
certificateboolean
created_atstring | null
Formato: date-time.
seats_takeninteger
seats_availableinteger | null
null = sin límite.
offersarray
offers[].idstring
offers[].object"offer"
offers[].titlestring | null
offers[].period_idstring | null
offers[].course_idstring | null
offers[].parent_period_idstring | null
Periodo de origen cuando esta oferta vende un curso o clase de forma independiente.
offers[].lesson_idsarray | null
Alcance por clase del periodo de esta oferta; null abre el curso entero. No presentar una oferta con clases limitadas como precio del curso completo.
offers[].typestring
Valores: "one_time", "subscription", "installments", "cohort", "free".
offers[].priceinteger
Céntimos (de cada cuota en suscripciones y plazos).
offers[].currencystring
offers[].access_kindstring
offers[].access_daysinteger | null
offers[].installment_countinteger | null
offers[].billing_intervalstring | null
Valores: "weekly", "biweekly", "monthly", "quarterly", null.
offers[].seatsinteger | null
offers[].statusstring
Valores: "active", "archived".
offers[].checkout_urlstring | null
Formato: uri.
offers[].created_atstring | null
Formato: date-time.
membership_offersarray
membership_offers[].idstringObligatorio
membership_offers[].titlestringObligatorio
membership_offers[].pitchstringObligatorio
Frase de presentación en texto plano; cadena vacía si no se configuró.
membership_offers[].descriptionstringObligatorio
Descripción pública en texto plano; cadena vacía si no se configuró.
membership_offers[].benefitsarrayObligatorio
membership_offers[].benefits[].idstringObligatorio
Identificador de la línea en este plan (tipo y posición); apto como clave de renderizado, no como permiso de acceso ni identificador permanente si se reordenan beneficios.
membership_offers[].benefits[].textstringObligatorio
Texto público listo para mostrar como texto, no como HTML. Incluye los detalles configurados.
membership_offers[].benefits[].includedbooleanObligatorio
Si está incluido. Hoy, como en la página pública, solo se enumeran los beneficios incluidos (true); no se inventan exclusiones ni se heredan beneficios.
membership_offers[].badgestringObligatorio
Insignia de comunidad; cadena vacía si no se configuró. No es la etiqueta del plan destacado.
membership_offers[].featuredbooleanObligatorio
Misma regla de la página: segundo de dos planes, central con tres o más; false si solo hay uno. Depende del orden de los planes ofrecidos en este periodo.
membership_offers[].catalogstringObligatorio
Texto público del alcance que se muestra en la tarjeta; no implica acceso al catálogo general.
membership_offers[].scopeobjectObligatorio
membership_offers[].scope.kind"period"Obligatorio
Estas ofertas se contratan para un periodo, nunca como membresía general.
membership_offers[].scope.idstringObligatorio
ID del periodo donde se contrata esta membresía; puede ser el periodo de origen de una venta individual.
membership_offers[].scope.targetobjectObligatorio
membership_offers[].scope.target.kindstringObligatorio
Valores: "course", "single", "path".
membership_offers[].scope.target.idstringObligatorio
ID público del contenido de ese periodo; path abre esa ruta dentro del periodo, no toda la escuela.
membership_offers[].contributiononeOfObligatorio
membership_offers[].contribution.[opción 1].monthly_amountintegerObligatorio
Parte de la cuota mensual destinada al aporte, en céntimos de currency. Mínimo: 1.
membership_offers[].contribution.[opción 1].currencystringObligatorio
Moneda ISO 4217 del plan.
membership_offers[].contribution.[opción 1].labelstringObligatorio
Destino público del aporte.
membership_offers[].contribution.[opción 1].textstringObligatorio
Frase completa con dinero formateado en el locale de la escuela, idéntica a la tarjeta pública.
membership_offers[].monthly_priceintegerObligatorio
Precio mensual en céntimos.
membership_offers[].yearly_priceinteger | nullObligatorio
Precio anual en céntimos; null si solo se paga al mes.
membership_offers[].currencystringObligatorio
membership_offers[].checkout_urlstringObligatorio
Página de membresía del periodo, con ancla al plan y selector mensual/anual. Formato: uri.
contentarray
content[].kindstringObligatorio
Valores: "course", "single", "lesson".
content[].course_idstringObligatorio
content[].lesson_idstring | nullObligatorio
null en la ficha del curso; ID original en una clase.
content[].titlestringObligatorio
content[].positionintegerObligatorio
Posición del curso en su ruta o de la clase en su curso, empezando por 1.
content[].lesson_kindstring | nullObligatorio
Tipo de clase (video, text, quiz, live…); null en un curso.
content[].independent_saleobjectObligatorio
content[].independent_sale.enabledbooleanObligatorio
Existe una venta independiente activa y coherente para este curso o clase dentro del periodo.
content[].independent_sale.availablebooleanObligatorio
La venta está publicada, su contenido está listo, admite inscripciones y el precio es válido. Es disponibilidad de catálogo; el checkout vuelve a validar acceso, cupo y métodos de cobro.
content[].independent_sale.period_idstring | nullObligatorio
Periodo individual que abre únicamente este contenido; null si no existe una configuración coherente.
content[].independent_sale.unavailable_reasonstring | nullObligatorio
Motivo legible de indisponibilidad; null si available=true.
content[].independent_sale.offeroneOfObligatorio
Oferta propia con precio. null cuando la venta está desactivada o su alcance no coincide.
content[].independent_sale.offer.[opción 1].idstringObligatorio
content[].independent_sale.offer.[opción 1].titlestringObligatorio
content[].independent_sale.offer.[opción 1].priceintegerObligatorio
Precio de la compra independiente, en céntimos de currency; por cuota si type es installments o subscription.
content[].independent_sale.offer.[opción 1].typestringObligatorio
one_time en la configuración inicial de venta independiente; conserva el tipo real si se modifica desde las ofertas del periodo. Valores: "one_time", "subscription", "installments", "cohort", "free".
content[].independent_sale.offer.[opción 1].installment_countinteger | nullObligatorio
Cantidad de cuotas si type=installments; null en otro caso.
content[].independent_sale.offer.[opción 1].billing_intervalstring | nullObligatorio
Frecuencia si type=subscription (monthly por defecto); null en otro caso.
content[].independent_sale.offer.[opción 1].currencystringObligatorio
Moneda ISO 4217.
content[].independent_sale.offer.[opción 1].checkout_urlstring | nullObligatorio
Enlace de compra de esta oferta; null si available=false. No usar el checkout del periodo principal para comprar una sola clase. Formato: uri.
content[].membership_offersarrayObligatorio
Planes que incluyen este contenido por el periodo solicitado, su periodo de curso o el de origen de una venta individual. scope.id y checkout_url conservan el alcance real; el mismo plan puede aparecer para distintos alcances.
content[].membership_offers[].idstringObligatorio
content[].membership_offers[].titlestringObligatorio
content[].membership_offers[].pitchstringObligatorio
Frase de presentación en texto plano; cadena vacía si no se configuró.
content[].membership_offers[].descriptionstringObligatorio
Descripción pública en texto plano; cadena vacía si no se configuró.
content[].membership_offers[].benefitsarrayObligatorio
content[].membership_offers[].benefits[].idstringObligatorio
Identificador de la línea en este plan (tipo y posición); apto como clave de renderizado, no como permiso de acceso ni identificador permanente si se reordenan beneficios.
content[].membership_offers[].benefits[].textstringObligatorio
Texto público listo para mostrar como texto, no como HTML. Incluye los detalles configurados.
content[].membership_offers[].benefits[].includedbooleanObligatorio
Si está incluido. Hoy, como en la página pública, solo se enumeran los beneficios incluidos (true); no se inventan exclusiones ni se heredan beneficios.
content[].membership_offers[].badgestringObligatorio
Insignia de comunidad; cadena vacía si no se configuró. No es la etiqueta del plan destacado.
content[].membership_offers[].featuredbooleanObligatorio
Misma regla de la página: segundo de dos planes, central con tres o más; false si solo hay uno. Depende del orden de los planes ofrecidos en este periodo.
content[].membership_offers[].catalogstringObligatorio
Texto público del alcance que se muestra en la tarjeta; no implica acceso al catálogo general.
content[].membership_offers[].scopeobjectObligatorio
content[].membership_offers[].scope.kind"period"Obligatorio
Estas ofertas se contratan para un periodo, nunca como membresía general.
content[].membership_offers[].scope.idstringObligatorio
ID del periodo donde se contrata esta membresía; puede ser el periodo de origen de una venta individual.
content[].membership_offers[].scope.targetobjectObligatorio
content[].membership_offers[].scope.target.kindstringObligatorio
Valores: "course", "single", "path".
content[].membership_offers[].scope.target.idstringObligatorio
ID público del contenido de ese periodo; path abre esa ruta dentro del periodo, no toda la escuela.
content[].membership_offers[].contributiononeOfObligatorio
content[].membership_offers[].contribution.[opción 1].monthly_amountintegerObligatorio
Parte de la cuota mensual destinada al aporte, en céntimos de currency. Mínimo: 1.
content[].membership_offers[].contribution.[opción 1].currencystringObligatorio
Moneda ISO 4217 del plan.
content[].membership_offers[].contribution.[opción 1].labelstringObligatorio
Destino público del aporte.
content[].membership_offers[].contribution.[opción 1].textstringObligatorio
Frase completa con dinero formateado en el locale de la escuela, idéntica a la tarjeta pública.
content[].membership_offers[].monthly_priceintegerObligatorio
Precio mensual en céntimos.
content[].membership_offers[].yearly_priceinteger | nullObligatorio
Precio anual en céntimos; null si solo se paga al mes.
content[].membership_offers[].currencystringObligatorio
content[].membership_offers[].checkout_urlstringObligatorio
Página de membresía del periodo, con ancla al plan y selector mensual/anual. Formato: uri.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Ofertas

GET/api/v1/offers

Precios (ofertas)

Permiso: catalog:read

price en céntimos. checkout_url lleva al pago de la escuela (con tarjeta y lo que tenga activo); es null en las archivadas y en las de una ruta o edición, que se venden desde su página. Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/offers?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
period_idquery · string
Las de ese periodo.
course_idquery · string
Las de ese curso.
statusquery · string
Activas o archivadas. Valores: active, archived.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"offer"
data[].titlestring | null
data[].period_idstring | null
data[].course_idstring | null
data[].parent_period_idstring | null
Periodo de origen cuando esta oferta vende un curso o clase de forma independiente.
data[].lesson_idsarray | null
Alcance por clase del periodo de esta oferta; null abre el curso entero. No presentar una oferta con clases limitadas como precio del curso completo.
data[].typestring
Valores: "one_time", "subscription", "installments", "cohort", "free".
data[].priceinteger
Céntimos (de cada cuota en suscripciones y plazos).
data[].currencystring
data[].access_kindstring
data[].access_daysinteger | null
data[].installment_countinteger | null
data[].billing_intervalstring | null
Valores: "weekly", "biweekly", "monthly", "quarterly", null.
data[].seatsinteger | null
data[].statusstring
Valores: "active", "archived".
data[].checkout_urlstring | null
Formato: uri.
data[].created_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Programas

GET/api/v1/programs

Programas (carreras)

Permiso: catalog:read

Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/programs?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
statusquery · string
Solo los de ese estado. Valores: draft, published, archived.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"program"
data[].titlestring
data[].faculty_namestring | null
data[].versionstring | null
data[].statusstring
Valores: "draft", "published", "archived".
data[].pass_scorenumber | null
data[].cycle_kindstring
Valores: "trimester", "semester", "other".
data[].enrollment_modestring
Valores: "flexible", "full_load".
data[].subjectsarray
data[].subjects[].course_idstring | null
data[].subjects[].cycleinteger | null
data[].subjects[].creditsnumber | null
data[].subjects[].hoursnumber | null
data[].subjects[].requiredboolean
data[].subjects[].prerequisitesarray
data[].updated_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Programas

GET/api/v1/programs/{id}

Un programa con su plan de estudios

Permiso: catalog:read

Permiso: catalog:read.

curl --request GET 'https://sch.begraffic.com/api/v1/programs/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"program"
titlestring
faculty_namestring | null
versionstring | null
statusstring
Valores: "draft", "published", "archived".
pass_scorenumber | null
cycle_kindstring
Valores: "trimester", "semester", "other".
enrollment_modestring
Valores: "flexible", "full_load".
subjectsarray
subjects[].course_idstring | null
subjects[].cycleinteger | null
subjects[].creditsnumber | null
subjects[].hoursnumber | null
subjects[].requiredboolean
subjects[].prerequisitesarray
updated_atstring | null
Formato: date-time.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Estudiantes

GET/api/v1/students

Estudiantes de la escuela

Permiso: students:read

Permiso: students:read.

curl --request GET 'https://sch.begraffic.com/api/v1/students?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
emailquery · string
El que tiene ese correo (sin distinguir mayúsculas).

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"student"
data[].emailstring | null
Formato: email.
data[].namestring | null
data[].statusstring
Valores: "active", "invited", "suspended".
data[].created_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Estudiantes

POST/api/v1/students

Dar de alta (e invitar) a un estudiante

Permiso: students:write

Crea la cuenta si no existe y la membresía de estudiante. Si ya era estudiante, devuelve 200 sin cambiar nada. No matricula: para eso, POST /enrollments. Permiso: students:write.

curl --request POST 'https://sch.begraffic.com/api/v1/students' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "email": "ana@example.com",
  "name": "Ana Pérez",
  "send_invite": true
}'
Parámetros, campos y respuestas HTTP

Cuerpo JSON

emailstringObligatorio
Formato: email.
namestringObligatorio
Longitud máxima: 120.
send_inviteboolean
Por defecto: true.

Campos de respuesta

idstring
object"student"
emailstring
namestring
createdboolean
invitedboolean

Respuestas HTTP del contrato

  • 200Ya era estudiante de la escuela.
  • 201Dado de alta.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 409Esa persona es del equipo o está suspendida.
  • 422Válido, pero no se puede (sin plaza, fuera de plazo, límite del plan…).
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Estudiantes

GET/api/v1/students/{id}

Un estudiante

Permiso: students:read

Permiso: students:read.

curl --request GET 'https://sch.begraffic.com/api/v1/students/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"student"
emailstring | null
Formato: email.
namestring | null
statusstring
Valores: "active", "invited", "suspended".
created_atstring | null
Formato: date-time.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Matrículas

GET/api/v1/enrollments

Matrículas

Permiso: enrollments:read

Permiso: enrollments:read.

curl --request GET 'https://sch.begraffic.com/api/v1/enrollments?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
student_idquery · string
De ese estudiante.
course_idquery · string
De ese curso.
period_idquery · string
De ese periodo.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"enrollment"
data[].student_idstring | null
data[].course_idstring | null
data[].period_idstring | null
data[].statusstring
data[].sourcestring | null
data[].justificationstring
Valores: "stripe", "manual_payment", "scholarship", "free", "unverified".
data[].starts_atstring | null
Formato: date-time.
data[].expires_atstring | null
Formato: date-time.
data[].closed_atstring | null
Formato: date-time.
data[].created_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Matrículas

POST/api/v1/enrollments

Matricular en un periodo, con justificación

Permiso: enrollments:write

Las mismas reglas que en el Panel: plazas, fechas del periodo y requisitos académicos. La justificación es obligatoria: beca (con motivo), pago fuera de línea (método, referencia e importe en céntimos) o gratis (solo si el periodo tiene una oferta gratuita activa). Un pago con tarjeta no se registra aquí: manda al estudiante al checkout_url de la oferta. Permiso: enrollments:write.

curl --request POST 'https://sch.begraffic.com/api/v1/enrollments' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "student_id": "uid_ejemplo",
  "period_id": "periodo-ejemplo",
  "justification": {
    "kind": "scholarship",
    "reason": "Beca por convenio con la empresa"
  }
}'
Parámetros, campos y respuestas HTTP

Cuerpo JSON

student_idstringObligatorio
period_idstringObligatorio
justificationoneOfObligatorio
justification.[opción 1].kind"scholarship"Obligatorio
justification.[opción 1].reasonstringObligatorio
Longitud mínima: 5. Longitud máxima: 400.
justification.[opción 2].kind"manual_payment"Obligatorio
justification.[opción 2].methodstringObligatorio
Valores: "transfer", "cash", "card_terminal", "other".
justification.[opción 2].referencestringObligatorio
Longitud mínima: 2. Longitud máxima: 120.
justification.[opción 2].amountintegerObligatorio
En céntimos de la moneda de la escuela. Mínimo: 1.
justification.[opción 3].kind"free"Obligatorio

Campos de respuesta

idstring
object"enrollment"
student_idstring
period_idstring
justificationstring
createdboolean

Respuestas HTTP del contrato

  • 200Ya tenía matrícula en ese periodo.
  • 201Matriculado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 422Válido, pero no se puede (sin plaza, fuera de plazo, límite del plan…).
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Matrículas

GET/api/v1/enrollments/{id}

Una matrícula

Permiso: enrollments:read

Permiso: enrollments:read.

curl --request GET 'https://sch.begraffic.com/api/v1/enrollments/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"enrollment"
student_idstring | null
course_idstring | null
period_idstring | null
statusstring
sourcestring | null
justificationstring
Valores: "stripe", "manual_payment", "scholarship", "free", "unverified".
starts_atstring | null
Formato: date-time.
expires_atstring | null
Formato: date-time.
closed_atstring | null
Formato: date-time.
created_atstring | null
Formato: date-time.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Pedidos

GET/api/v1/orders

Pedidos y cobros

Permiso: orders:read

Permiso: orders:read.

curl --request GET 'https://sch.begraffic.com/api/v1/orders?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
statusquery · string
Solo los de ese estado. Valores: paid, pending, pending_verification, failed, refunded, needs_review, rejected, expired, disputed_lost.
student_idquery · string
De ese estudiante.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"order"
data[].student_idstring | null
data[].course_idstring | null
data[].offer_idstring | null
data[].emailstring | null
data[].amountinteger
Céntimos cobrados.
data[].refunded_amountinteger
data[].currencystring | null
data[].statusstring | null
data[].providerstring | null
Valores: "stripe", "scholarship", "transfer", null.
data[].created_atstring | null
Formato: date-time.
data[].paid_atstring | null
Formato: date-time.
data[].refunded_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Pedidos

GET/api/v1/orders/{id}

Un pedido

Permiso: orders:read

Permiso: orders:read.

curl --request GET 'https://sch.begraffic.com/api/v1/orders/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"order"
student_idstring | null
course_idstring | null
offer_idstring | null
emailstring | null
amountinteger
Céntimos cobrados.
refunded_amountinteger
currencystring | null
statusstring | null
providerstring | null
Valores: "stripe", "scholarship", "transfer", null.
created_atstring | null
Formato: date-time.
paid_atstring | null
Formato: date-time.
refunded_atstring | null
Formato: date-time.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Certificados

GET/api/v1/certificates

Certificados emitidos

Permiso: certificates:read

Permiso: certificates:read.

curl --request GET 'https://sch.begraffic.com/api/v1/certificates?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
student_idquery · string
De ese estudiante.
course_idquery · string
De ese curso.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"certificate"
data[].codestring | null
data[].student_idstring | null
data[].student_namestring | null
data[].course_idstring | null
data[].course_titlestring | null
data[].period_idstring | null
data[].statusstring
Valores: "issued", "revoked".
data[].issued_atstring | null
Formato: date-time.
data[].revoked_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Certificados

GET/api/v1/certificates/{id}

Un certificado

Permiso: certificates:read

Permiso: certificates:read.

curl --request GET 'https://sch.begraffic.com/api/v1/certificates/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"certificate"
codestring | null
student_idstring | null
student_namestring | null
course_idstring | null
course_titlestring | null
period_idstring | null
statusstring
Valores: "issued", "revoked".
issued_atstring | null
Formato: date-time.
revoked_atstring | null
Formato: date-time.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Progreso

GET/api/v1/progress

Clases completadas

Permiso: progress:read

Permiso: progress:read.

curl --request GET 'https://sch.begraffic.com/api/v1/progress?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
student_idquery · string
De ese estudiante.
course_idquery · string
De ese curso.
period_idquery · string
De ese periodo.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"lesson_completion"
data[].student_idstring | null
data[].course_idstring | null
data[].lesson_idstring | null
data[].period_idstring | null
data[].viastring | null
Valores: "manual", "video", "quiz", "assignment", "reading", null.
data[].completed_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Admisiones

GET/api/v1/admission_requests

Solicitudes de admisión

Permiso: admissions:read

Permiso: admissions:read.

curl --request GET 'https://sch.begraffic.com/api/v1/admission_requests?limit=25' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

limitquery · integer
Por defecto: 25. Mínimo: 1. Máximo: 100.
cursorquery · string
statusquery · string
Solo las de ese estado. Valores: pending, approved, rejected.
student_idquery · string
De ese estudiante.
program_idquery · string
A ese programa.

Campos de respuesta

object"list"Obligatorio
dataarrayObligatorio
data[].idstring
data[].object"admission_request"
data[].program_idstring | null
data[].program_titlestring | null
data[].program_versionstring | null
data[].student_idstring | null
data[].student_namestring | null
data[].notestring
data[].statusstring
Valores: "pending", "approved", "rejected".
data[].cohort_idstring | null
data[].created_atstring | null
Formato: date-time.
data[].resolved_atstring | null
Formato: date-time.
has_morebooleanObligatorio
next_cursorstring | nullObligatorio
Pásalo como `cursor` para la página siguiente.

Respuestas HTTP del contrato

  • 200Una página de resultados.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Admisiones

POST/api/v1/admission_requests

Solicitar admisión a un programa en nombre de un estudiante

Permiso: admissions:write

El estudiante tiene que estar activo en la escuela (POST /students) y el programa publicado. No admite a nadie: la decide secretaría en el Panel y llega el evento correspondiente. Repetirla con la misma nota devuelve la misma solicitud (200). Permiso: admissions:write.

curl --request POST 'https://sch.begraffic.com/api/v1/admission_requests' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "student_id": "uid_ejemplo",
  "program_id": "programa-ejemplo",
  "note": "Me interesa el programa."
}'
Parámetros, campos y respuestas HTTP

Cuerpo JSON

student_idstringObligatorio
program_idstringObligatorio
notestring
Por defecto: "". Longitud máxima: 2000.

Campos de respuesta

idstring
object"admission_request"
program_idstring
program_titlestring
student_idstring
statusstring
Valores: "pending", "approved", "rejected".
created_atstring
Formato: date-time.
createdboolean

Respuestas HTTP del contrato

  • 200Ya existía esa solicitud.
  • 201Solicitud enviada.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 422Válido, pero no se puede (sin plaza, fuera de plazo, límite del plan…).
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Admisiones

GET/api/v1/admission_requests/{id}

Una solicitud de admisión

Permiso: admissions:read

Permiso: admissions:read.

curl --request GET 'https://sch.begraffic.com/api/v1/admission_requests/id_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"admission_request"
program_idstring | null
program_titlestring | null
program_versionstring | null
student_idstring | null
student_namestring | null
notestring
statusstring
Valores: "pending", "approved", "rejected".
cohort_idstring | null
created_atstring | null
Formato: date-time.
resolved_atstring | null
Formato: date-time.

Respuestas HTTP del contrato

  • 200Encontrado.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

REST Hooks

POST/api/v1/hooks

Suscribirse a un evento (REST Hook)

Permiso: webhooks:write

Permiso: webhooks:write.

curl --request POST 'https://sch.begraffic.com/api/v1/hooks' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "target_url": "https://tu-servidor.example/webhooks/beschool",
  "event": "enrollment.created"
}'
Parámetros, campos y respuestas HTTP

Cuerpo JSON

target_urlstringObligatorio
https:// pública. Formato: uri.
eventstringObligatorio
Valores: "student.created", "enrollment.created", "enrollment.closed", "order.paid", "order.refunded", "submission.created", "submission.graded", "certificate.issued", "lesson.completed", "course.completed", "transfer.approved", "coupon.redeemed", "installment.paid", "installment.overdue", "invoice.issued".

Campos de respuesta

idstring
object"hook"
eventstring
target_urlstring
secretstring
Para verificar la firma. Solo se enseña aquí.

Respuestas HTTP del contrato

  • 201Suscrito. Guarda el `id` para darte de baja.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 422Válido, pero no se puede (sin plaza, fuera de plazo, límite del plan…).
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

REST Hooks

DELETE/api/v1/hooks/{id}

Darse de baja de un REST Hook

Permiso: webhooks:write

Permiso: webhooks:write.

curl --request DELETE 'https://sch.begraffic.com/api/v1/hooks/hook_ejemplo' \
  --header "Authorization: Bearer $BESCHOOL_API_KEY"
Parámetros, campos y respuestas HTTP

Parámetros de ruta y consulta

idpath · stringObligatorio

Campos de respuesta

idstring
object"hook"
deletedtrue

Respuestas HTTP del contrato

  • 200Baja hecha.
  • 400Petición no válida.
  • 401Falta la clave o no es válida.
  • 403La clave no tiene el permiso de esa operación, la escuela está suspendida o su plan no incluye la API y no tiene una gracia de API vigente.
  • 404No existe.
  • 429Demasiadas peticiones. Mira `Retry-After`.

Consulta también los errores generales: validación del JSON (400, 413, 415), método (405) y disponibilidad (500, 503).

Catálogo de eventos

Ejemplos del catálogo que acepta POST /hooks. Verifica firmas, conserva el evento y procesa cada id una sola vez.

Estudiante dado de alta

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "student.created",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez"
  }
}

Matrícula nueva

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "enrollment.created",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "periodId": "periodo-ejemplo",
    "enrollmentId": "uid_ejemplo__periodo-ejemplo"
  }
}

Matrícula cerrada o retirada

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "enrollment.closed",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "periodId": "periodo-ejemplo",
    "enrollmentId": "uid_ejemplo__periodo-ejemplo"
  }
}

Eventos

order.paid

Pago recibido

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "order.paid",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "orderId": "pedido-ejemplo",
    "amount": 4900,
    "currency": "USD"
  }
}

Pago reembolsado

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "order.refunded",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "orderId": "pedido-ejemplo",
    "amount": 4900,
    "currency": "USD"
  }
}

Entrega recibida

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "submission.created",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "lessonId": "clase-ejemplo",
    "submissionId": "entrega-ejemplo",
    "grade": 90
  }
}

Entrega calificada

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "submission.graded",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "lessonId": "clase-ejemplo",
    "submissionId": "entrega-ejemplo",
    "grade": 90
  }
}

Certificado emitido

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "certificate.issued",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "certificateId": "cert-ejemplo",
    "code": "ABCD-1234"
  }
}

Clase completada

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "lesson.completed",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "lessonId": "clase-ejemplo"
  }
}

Curso completado

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "course.completed",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo"
  }
}

Transferencia aprobada

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "transfer.approved",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "email": "ana@example.com",
    "name": "Ana Pérez",
    "courseId": "curso-ejemplo",
    "courseTitle": "Curso de ejemplo",
    "orderId": "pedido-ejemplo",
    "amount": 4900,
    "currency": "USD"
  }
}

Cupón canjeado

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "coupon.redeemed",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "orderId": "pedido-ejemplo",
    "couponId": "BIENVENIDA10"
  }
}

Cuota pagada

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "installment.paid",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "orderId": "pedido-ejemplo",
    "planId": "plan-ejemplo",
    "n": 2,
    "count": 3
  }
}

Cuota vencida

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "installment.overdue",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "planId": "plan-ejemplo",
    "n": 2,
    "count": 3
  }
}

Factura o recibo emitido

Sobre ilustrativo. data puede variar según el punto de emisión; valida los campos que usa tu integración. Verifica la firma sobre el cuerpo original y deduplica por (school, id) antes de procesarlo.

{
  "id": "evt_ejemplo",
  "type": "invoice.issued",
  "created": 1792584000,
  "school": "tu-escuela",
  "data": {
    "studentId": "uid_ejemplo",
    "orderId": "pedido-ejemplo",
    "number": "F-000042",
    "kind": "invoice"
  }
}