Be CMS · Docs
← Toda la documentación

Manual de uso del API de Calendario

Este API permite gestionar categorías y eventos del calendario desde integraciones externas.
Las rutas viven bajo /api/calendar/* y devuelven JSON.

Autenticación

  • Envía Authorization: Bearer <token> en cada solicitud.
  • El token debe estar activo y tener permisos sobre el recurso que consultas.
  • Mantén Content-Type: application/json cuando el endpoint acepte cuerpo.

Permisos

  • read: requerido para todas las solicitudes (GET).
  • update: permite usar includeDrafts=true en /events para ver borradores/eventos programados.

Si el token está vencido, inactivo o no cuenta con los permisos necesarios, la respuesta será 401 o 403.

Respuestas comunes

  • 200 OK: solicitud exitosa.
  • 400 Bad Request: parámetros o cuerpo inválidos.
  • 401 / 403: token inválido, expirado o sin permisos.
  • 404 Not Found: recurso inexistente o no visible para el modo actual.
  • 405 Method Not Allowed: se usó un método no soportado.
  • 500 Internal Server Error: error inesperado en el servidor.

Categorías — /api/calendar/categories

GET listado

Lista las categorías disponibles para la página asociada al token.

Parámetros opcionales

  • limit (número, 1‑100): restringe la cantidad de resultados.
  • id / slug (string): devuelve una única categoría que coincida con el identificador.
bash
curl "https://cms.begraffic.com/api/calendar/categories?limit=50" \
  -H "Authorization: Bearer TU_TOKEN"

Respuesta

json
{
  "count": 2,
  "categories": [
    {
      "id": "lanzamientos",
      "name": "Lanzamientos",
      "description": "Actividades de lanzamiento",
      "color": "#f97316",
      "createdAt": "2024-07-12T15:00:00.000Z",
      "updatedAt": "2024-07-12T15:00:00.000Z"
    }
  ]
}

Si envías id/slug, la respuesta tendrá la forma:

json
{ "category": { ... } }

Eventos — /api/calendar/events

GET listado

Devuelve eventos futuros o ya publicados para la página del token.

Parámetros opcionales

  • start (ISO): obtiene eventos cuyo inicio sea igual o posterior a la fecha.
  • end (ISO): obtiene eventos cuyo inicio sea igual o anterior a la fecha indicada.
  • categoryId (string): filtra por categoría.
  • limit (1‑200): máximo de registros.
  • includeDrafts (booleano): requiere permiso update y devuelve además borradores/programados.
  • includeImages (booleano): incluye la galería completa (images) de cada evento. Sin él, el listado trae solo la portada (coverImage) y imageCount, para no inflar la respuesta cuando pides muchos eventos.
  • includeContent (booleano): incluye el contenido enriquecido (content.html) de cada evento. Mismo criterio: sin él el listado solo dice hasContent.

Con includeImages o includeContent el listado se acota a 25 eventos aunque pidas más: son respuestas mucho más pesadas y el ancho de banda se factura a tu workspace. Para recorrer muchos eventos, lístalos sin esos parámetros y pide el detalle solo de los que vayas a mostrar.

bash
curl "https://cms.begraffic.com/api/calendar/events?start=2024-07-01&end=2024-07-31" \
  -H "Authorization: Bearer TU_TOKEN"

Respuesta

json
{
  "count": 1,
  "events": [
    {
      "id": "launch-demo",
      "title": "Demo de lanzamiento",
      "description": "Presentación del nuevo producto.",
      "start": "2024-07-08T15:00:00.000Z",
      "end": "2024-07-08T16:30:00.000Z",
      "location": "Google Meet",
      "visibility": "public",
      "categoryId": "lanzamientos",
      "categoryName": "Lanzamientos",
      "categoryColor": "#f97316",
      "publish": true,
      "publishAt": null,
      "isPublished": true,
      "isScheduled": false,
      "isAllDay": false,
      "coverImageUrl": "https://cms.begraffic.com/api/storage/proxy?path=...",
      "coverImage": { "id": "...", "url": "...", "alt": "Escenario principal", "variants": { "...": {} } },
      "imageCount": 3,
      "images": null,
      "slug": "demo-de-lanzamiento",
      "details": {
        "subtitle": "Una tarde para conocer el producto",
        "organizer": "Equipo de producto",
        "contactEmail": "eventos@tudominio.com",
        "tags": ["producto", "gratuito"],
        "modality": "hybrid",
        "onlineUrl": "https://meet.google.com/abc-defg-hij",
        "place": { "address": "Calle 1 #2-3, Bogotá", "lat": 4.65, "lng": -74.05 },
        "capacity": 120,
        "registrationUrl": "https://tudominio.com/inscripcion",
        "registrationLabel": "Reservar mi cupo",
        "price": { "isFree": true, "amount": null, "currency": "cop" }
      },
      "hasContent": true,
      "content": null,
      "createdAt": "2024-07-01T10:00:00.000Z",
      "updatedAt": "2024-07-01T10:00:00.000Z"
    }
  ]
}

Los eventos programados (publish = false y publishAt futuro) solo se devuelven cuando el token incluye includeDrafts=true y cuenta con permiso update.

GET detalle

GET /api/calendar/events?id=<id> o GET /api/calendar/events?slug=<slug>
Devuelve un único evento publicado (o borrador, si includeDrafts=true y el token tiene update). El detalle siempre incluye la galería completa (images) y el contenido (content): no hace falta includeImages ni includeContent.

Si envías id y slug a la vez, manda id. Al guardar, el dashboard comprueba que el slug esté libre y le añade un sufijo (-2, -3…) si chocara con otro evento. No es una restricción de la base de datos: si dos personas guardan el mismo slug exactamente a la vez, ?slug= devolverá uno de los dos — usa id cuando necesites una referencia estable para siempre.


Detalles del evento

Todo lo que se edita en la página interna del evento viaja en details. Los campos vacíos llegan como "", [] o null — nunca desaparecen, así que puedes leerlos sin comprobar.

CampoTipoNotas
slugstring | nullVa en la raíz del evento, no dentro de details. Sirve como clave de búsqueda.
details.subtitlestringBajada corta para la cabecera.
details.organizerstringQuién organiza.
details.contactEmailstringCorreo de contacto ya validado (o "").
details.tagsstring[]Hasta 12, sin repetidos.
details.modality"in_person" | "online" | "hybrid"Cómo se asiste.
details.onlineUrlstringSala virtual. Siempre "" si la modalidad es presencial.
details.place{ address, lat, lng } | nullDirección. Siempre null si la modalidad es online. lat/lng son null cuando la dirección se escribió a mano y no se geocodificó — no pintes un marcador en ese caso.
details.capacitynumber | nullAforo. null = sin límite declarado.
details.registrationUrlstringCTA de inscripción, ya normalizada a http(s).
details.registrationLabelstringTexto del botón (p. ej. «Reservar mi cupo»).
details.price{ isFree, amount, currency }amount en unidades mayores (25.5 = 25,50) y currency en minúsculas.

Sobre price: isFree: true significa gratis de forma explícita. isFree: false con amount: null significa precio sin definir — no lo pintes como «0».

Las URLs (onlineUrl, registrationUrl) se guardan ya normalizadas y percent-codificadas: si no son http(s) válidas llegan como "". Nunca recibirás un javascript:, un enlace con credenciales (https://sitio.com@otro-host) ni caracteres sin escapar — son seguras de interpolar en un href.


Contenido del evento

Cada evento puede tener una página completa montada con el editor visual de Be CMS, igual que un artículo del blog.

  • hasContent (booleano): viene siempre, también en los listados.
  • content: { "html": "...", "htmlPreview": "..." } o null.
bash
curl "https://cms.begraffic.com/api/calendar/events?slug=demo-de-lanzamiento" \
  -H "Authorization: Bearer TU_TOKEN"
  • El HTML se sanitiza al servir (se eliminan scripts, manejadores on* y esquemas peligrosos), así que puedes inyectarlo con seguridad en tu página.
  • Nunca se expone el formato interno del editor (designJson / projectDataJson).
  • htmlPreview es un recorte de ~1000 caracteres para listados y tarjetas.

Imágenes de los eventos

Cada evento puede llevar hasta 12 imágenes ordenadas. La primera es la portada.

  • coverImage: la primera imagen, o null si el evento no tiene ninguna. Viene siempre, también en los listados.
  • coverImageUrl: atajo con la URL lista para usar en un <img src>.
  • imageCount: cuántas imágenes tiene el evento (aunque images venga en null).
  • images: array ordenado con TODAS las imágenes. Viene en el detalle y en el listado con includeImages=true; en caso contrario es null (no [], para que puedas distinguir «no las pediste» de «no tiene»).

Cada imagen tiene esta forma:

json
{
  "id": "0b0a…",
  "url": "https://cms.begraffic.com/api/storage/proxy?path=...",
  "previewUrl": "https://cms.begraffic.com/api/storage/proxy?path=...",
  "alt": "Escenario principal",
  "width": 3000,
  "height": 2000,
  "variants": {
    "original": { "url": "...", "width": 3000, "height": 2000, "size": 850123, "contentType": "image/jpeg", "storagePath": "tables/…" },
    "medium":    { "url": "...", "width": 1920, "height": 1280, "size": 210344, "contentType": "image/webp", "storagePath": "tables/…" },
    "small":     { "url": "...", "width": 1280, "height": 853,  "size": 98211,  "contentType": "image/webp", "storagePath": "tables/…" },
    "thumbnail": { "url": "...", "width": 480,  "height": 320,  "size": 21044,  "contentType": "image/webp", "storagePath": "tables/…" }
  }
}

Recomendaciones de uso:

  • Usa variants.thumbnail en listados y variants.medium en la vista de detalle: original puede pesar varios MB y el ancho de banda se factura a tu workspace.
  • alt es el texto alternativo que escribió quien creó el evento; úsalo tal cual en el atributo alt (accesibilidad y SEO). Puede venir vacío.
  • Las URLs se sirven por el proxy de assets de Be CMS, no por Firebase Storage: no las guardes en caché de forma indefinida ni las reescribas.

Visibilidad

Cada evento tiene visibility, que filtra lo que sirve la API:

ValorListado?id= / ?slug=
public
unlistedNoSí — para enlaces que compartes tú (no aparece en índices)
privateNoNo (404)

Es independiente de la publicación: un evento private no se sirve aunque esté publicado.


Notas generales

  • Todas las fechas se esperan y devuelven en formato ISO 8601.
  • Los errores se devuelven en la forma { "error": "mensaje descriptivo" }.
  • El API está pensado para integraciones de confianza; aplica límites razonables (limit, rangos de fechas) para evitar lecturas muy grandes.