Be CMSDocs
← Toda la documentación

Manual de uso del API de Blog

El API de blog expone endpoints para consultar artículos, autores y categorías, y para que tus visitantes comenten, valoren con estrellas y voten los artículos. Todas las rutas viven bajo /api/blog/* y devuelven JSON.

Autenticación

  • Incluye Authorization: Bearer <token> en cada solicitud.
  • El token debe estar activo y tener permisos sobre el recurso que consultas.
  • Aunque los endpoints actuales no requieren cuerpo, mantén Content-Type: application/json en caso de enviar parámetros serializados.

Permisos

  • read: permite ejecutar peticiones GET (incluida la lectura de comentarios).
  • blog_comments («Comentarios del blog» en API & Tokens): permite que tus visitantes publiquen comentarios y respuestas y voten. Va aparte de read a propósito: un token de solo lectura suele acabar en el JavaScript público de la web, y sin este permiso no sirve para escribir nada.

Si el token está vencido, inactivo o carece del permiso necesario, la respuesta será 401 o 403.

Códigos de respuesta comunes

  • 200 OK: respuesta exitosa.
  • 400 Bad Request: parámetros inválidos.
  • 401 / 403: token ausente, inválido o sin permisos.
  • 404 Not Found: recurso inexistente o no publicado.
  • 405 Method Not Allowed: método no admitido por esa ruta.
  • 429 Too Many Requests: demasiados comentarios o votos seguidos desde la misma IP.
  • 500 Internal Server Error: error no controlado en el servidor.

Artículos — /api/blog/articles

GET listado

Devuelve todos los artículos publicados accesibles para el token.

Query params

  • limit (número, opcional): máximo de resultados (1–100).
  • categoryId (string, opcional): devuelve solo artículos con esa categoría.
  • authorId (string, opcional): devuelve solo artículos del autor indicado.
  • excludeCategoryId (string, opcional): lista separada por comas de categorías a omitir (excludeCategoryId=marketing,eventos).
  • excludeAuthorId (string, opcional): lista separada por comas de autores a omitir (excludeAuthorId=ana-garcia,pedro-lopez).
  • sortBy (string, opcional): campo para ordenar resultados. Valores permitidos: publishedAt, title. Por defecto se usa orden descendente por publishedAt si se envía este parámetro sin sortDirection.
  • sortDirection (string, opcional): dirección de orden. Valores permitidos: asc, desc. Si no se envía, se asume desc.

Puedes combinar categoryId y authorId para filtrar por ambos criterios y, adicionalmente, aplicar exclusiones. Las exclusiones también se respetan cuando consultas un artículo puntual con slug.

Ejemplo listado básico

bash
curl "https://cms.begraffic.com/api/blog/articles?limit=10&categoryId=marketing&authorId=ana-garcia" \
  -H "Authorization: Bearer TU_TOKEN"

Ejemplo con orden

bash
curl "https://cms.begraffic.com/api/blog/articles?sortBy=title&sortDirection=asc&limit=5" \
  -H "Authorization: Bearer TU_TOKEN"

Ejemplos extra

  • Orden por fecha descendente (default) con exclusión de categorías:
    bash
    curl "https://cms.begraffic.com/api/blog/articles?sortBy=publishedAt&excludeCategoryId=marketing,eventos" \
      -H "Authorization: Bearer TU_TOKEN"
  • Orden por fecha ascendente filtrando por autor:
    bash
    curl "https://cms.begraffic.com/api/blog/articles?authorId=ana-garcia&sortBy=publishedAt&sortDirection=asc" \
      -H "Authorization: Bearer TU_TOKEN"
  • Orden por título descendente con límite:
    bash
    curl "https://cms.begraffic.com/api/blog/articles?sortBy=title&sortDirection=desc&limit=20" \
      -H "Authorization: Bearer TU_TOKEN"

Notas de orden:

  • Si envías sortBy sin sortDirection, se usa desc por defecto.
  • El orden aplica sobre todos los artículos publicados que cumplan tus filtros (categoryId, authorId, exclusiones); no afecta al endpoint de detalle.
  • El orden por publishedAt está soportado de forma nativa.

Publicación programada

  • Los artículos creados con isScheduled = true y un scheduledFor en el futuro permanecen ocultos hasta la fecha.
  • Los artículos programados se publican automáticamente al llegar su fecha; a partir de ese momento aparecen con status: published.

Respuesta

json
{
  "count": 2,
  "articles": [
    {
      "id": "guia-crm",
      "title": "Guía CRM",
      "slug": "guia-crm",
      "excerpt": "Resumen...",
      "status": "published",
      "tags": ["crm", "ventas"],
      "categoryId": "marketing",
      "categoryName": "Marketing",
      "authorId": "ana-garcia",
      "authorName": "Ana García",
      "isScheduled": false,
      "scheduledFor": null,
      "metadata": null,
      "publishedAt": "2024-05-01T10:00:00.000Z",
      "createdAt": "2024-05-01T10:00:00.000Z",
      "updatedAt": "2024-05-02T12:30:00.000Z",
      "externalId": null,
      "comments": { "count": 12, "ratingCount": 5, "ratingAverage": 4.4 }
    }
  ]
}

Cada artículo trae comments con sus cifras: count (comentarios visibles, respuestas incluidas), ratingCount (cuántos pusieron estrellas) y ratingAverage (media con un decimal, o null si nadie valoró). Si en ese momento no se pudieron leer, comments llega como null: no significa que no tenga comentarios.

Los artículos programados (isScheduled = true) solo se incluyen cuando scheduledFor es menor o igual a la hora actual.
El campo content se omite en el listado para reducir el tamaño de la respuesta; solo está disponible en el endpoint de detalle.

Ejemplo omitidos

bash
curl "https://cms.begraffic.com/api/blog/articles?excludeCategoryId=marketing&excludeAuthorId=ana-garcia" \
  -H "Authorization: Bearer TU_TOKEN"

Respuesta

json
{
  "count": 1,
  "articles": [
    {
      "id": "caso-exito",
      "title": "Caso de éxito",
      "slug": "caso-exito",
      "categoryId": "finanzas",
      "authorId": "carlos-mendez",
      "status": "published",
      "isScheduled": false,
      "scheduledFor": null,
      "publishedAt": "2024-06-01T12:00:00.000Z",
      "createdAt": "2024-05-15T08:00:00.000Z",
      "updatedAt": "2024-05-20T09:30:00.000Z"
    }
  ]
}

GET detalle

GET /api/blog/articles?slug=<slug>

Devuelve un artículo publicado que coincida con el slug. Si existe pero no está en estado published, la respuesta será 404. Este endpoint incluye el campo content con la información completa del artículo.

Respuesta

json
{
  "article": {
    "id": "guia-crm",
    "title": "Guía CRM",
    "slug": "guia-crm",
    "excerpt": "Resumen...",
    "categoryId": "marketing",
    "categoryName": "Marketing",
    "authorId": "ana-garcia",
    "authorName": "Ana García",
    "tags": ["crm", "ventas"],
    "status": "published",
    "isScheduled": false,
    "scheduledFor": null,
    "content": {
      "html": "<h1>Hola</h1>",
      "htmlPreview": "<h1>Hola</h1>"
    },
    "metadata": null,
    "publishedAt": "2024-05-01T10:00:00.000Z",
    "createdAt": "2024-05-01T10:00:00.000Z",
    "updatedAt": "2024-05-02T12:30:00.000Z"
  }
}

El endpoint de detalle devuelve el contenido en content, con los campos html y htmlPreview. También incluye comments con las mismas cifras que el listado.


Autores — /api/blog/authors

GET listado

Lista todos los autores con estado published accesibles para el token. Acepta limit como parámetro opcional.

bash
curl "https://cms.begraffic.com/api/blog/authors" \
  -H "Authorization: Bearer TU_TOKEN"

Respuesta

json
{
  "count": 2,
  "authors": [
    {
      "id": "ana-garcia",
      "name": "Ana García",
      "slug": "ana-garcia",
      "title": "Editora",
      "bio": "Periodista especializada en marketing.",
      "avatar": "https://cdn.example.com/avatars/ana.jpg",
      "featured": true,
      "status": "published",
      "publishedAt": "2024-01-10T15:00:00.000Z",
      "createdAt": "2024-01-10T15:00:00.000Z",
      "updatedAt": "2024-03-05T09:30:00.000Z"
    },
    {
      "id": "carlos-mendez",
      "name": "Carlos Méndez",
      "slug": "carlos-mendez",
      "title": "Analista",
      "featured": false,
      "status": "published",
      "publishedAt": "2024-02-01T09:00:00.000Z",
      "createdAt": "2024-02-01T09:00:00.000Z",
      "updatedAt": "2024-02-15T11:45:00.000Z"
    }
  ]
}

GET detalle

GET /api/blog/authors?slug=<slug> devuelve la información de un autor publicado. Responde 404 si el slug no existe o el autor no está publicado.

Respuesta

json
{
  "author": {
    "id": "ana-garcia",
    "name": "Ana García",
    "slug": "ana-garcia",
    "title": "Editora",
    "email": "ana@example.com",
    "bio": "Periodista especializada en marketing.",
    "avatar": "https://cdn.example.com/avatars/ana.jpg",
    "website": "https://ana.blog",
    "linkedin": "https://linkedin.com/in/anagarcia",
    "twitter": "@anagarcia",
    "featured": true,
    "status": "published",
    "publishedAt": "2024-01-10T15:00:00.000Z",
    "createdAt": "2024-01-10T15:00:00.000Z",
    "updatedAt": "2024-03-05T09:30:00.000Z"
  }
}

Categorías — /api/blog/categories

GET listado

Recupera las categorías publicadas. También admite el parámetro opcional limit. Cada documento de categoría incluye articleCount, que refleja cuántos artículos están asignados actualmente a esa categoría.

Respuesta

json
{
  "count": 2,
  "categories": [
    {
      "id": "marketing",
      "name": "Marketing",
      "slug": "marketing",
      "description": "Estrategias y tácticas de marketing.",
      "accent": "#00AEEF",
      "featured": true,
      "articleCount": 12,
      "status": "published",
      "publishedAt": "2024-01-05T08:00:00.000Z",
      "createdAt": "2024-01-05T08:00:00.000Z",
      "updatedAt": "2024-03-20T10:15:00.000Z"
    },
    {
      "id": "finanzas",
      "name": "Finanzas",
      "slug": "finanzas",
      "description": "Noticias y consejos financieros.",
      "featured": false,
      "articleCount": 4,
      "status": "published",
      "publishedAt": "2024-02-12T14:00:00.000Z",
      "createdAt": "2024-02-12T14:00:00.000Z",
      "updatedAt": "2024-02-18T16:45:00.000Z"
    }
  ]
}

GET detalle

GET /api/blog/categories?slug=<slug> devuelve una categoría publicada. Si la categoría existe pero no está publicada, responde 404.

Respuesta

json
{
  "category": {
    "id": "marketing",
    "name": "Marketing",
    "slug": "marketing",
    "description": "Estrategias y tácticas de marketing.",
    "accent": "#00AEEF",
    "featured": true,
    "articleCount": 12,
    "status": "published",
    "publishedAt": "2024-01-05T08:00:00.000Z",
    "createdAt": "2024-01-05T08:00:00.000Z",
    "updatedAt": "2024-03-20T10:15:00.000Z"
  }
}

Comentarios — /api/blog/comments

Tus visitantes pueden comentar un artículo publicado, valorarlo con estrellas, responder a un comentario y votar comentarios con «me gusta» / «no me gusta».

Cómo funciona

  • Se publica al instante. Desde el panel (Blog → Comentarios) el negocio puede ocultar, volver a mostrar o borrar cualquier comentario, y responder como el negocio. Un comentario oculto desaparece de la API.
  • Un solo nivel de respuestas. Se responde a un comentario principal; una respuesta no admite respuestas (400).
  • Estrellas opcionales, de 1 a 5, solo en el comentario principal. Una respuesta con estrellas se rechaza (400).
  • Respuestas oficiales. Las que escribe el negocio desde el panel llegan con official: true: píntalas distinto.
  • El correo de quien comenta nunca sale por la API. Solo lo ve el negocio en su panel.
  • Texto plano. body y authorName son texto, no HTML. Al guardar se quitan las etiquetas, pero escápalos al pintarlos (usa textContent, o el escapado por defecto de tu framework), nunca innerHTML. Los saltos de línea llegan como \n.
  • Solo artículos publicados. Un artículo en borrador, oculto o programado a futuro responde 404, tanto al leer como al comentar.

GET listado

GET /api/blog/comments?article=<slug> · permiso read

Query params

  • article (obligatorio): slug del artículo (el mismo de /api/blog/articles).
  • limit (opcional): comentarios principales por página, de 1 a 30. Por defecto 10.
  • order (opcional): newest (por defecto) u oldest.
  • cursor (opcional): el nextCursor de la página anterior.
  • visitorId (opcional): si lo envías, cada comentario trae myVote ("like", "dislike" o null) para pintar el voto de ese visitante.

Cada comentario principal trae sus respuestas en replies, de la más antigua a la más reciente, hasta 50. replyCount siempre tiene el total real.

bash
curl "https://cms.begraffic.com/api/blog/comments?article=guia-crm&limit=10&visitorId=v_8f2k1x9q" \
  -H "Authorization: Bearer TU_TOKEN"

Respuesta

json
{
  "article": "guia-crm",
  "stats": { "count": 3, "ratingCount": 1, "ratingAverage": 5 },
  "count": 1,
  "comments": [
    {
      "id": "Xb12…",
      "parentId": null,
      "authorName": "Ana García",
      "body": "Muy útil, gracias.\nLo aplico mañana.",
      "rating": 5,
      "official": false,
      "likes": 4,
      "dislikes": 0,
      "replyCount": 2,
      "createdAt": "2026-09-27T10:00:00.000Z",
      "myVote": "like",
      "replies": [
        {
          "id": "Qa77…",
          "parentId": "Xb12…",
          "authorName": "Equipo Be",
          "body": "¡Gracias, Ana!",
          "rating": null,
          "official": true,
          "likes": 1,
          "dislikes": 0,
          "replyCount": 0,
          "createdAt": "2026-09-27T11:30:00.000Z",
          "myVote": null
        }
      ]
    }
  ],
  "nextCursor": null
}

nextCursor es null cuando no hay más páginas. stats son las cifras de todo el artículo, no solo de esta página.

POST publicar un comentario o una respuesta

POST /api/blog/comments · permiso blog_comments

Cuerpo

  • article (obligatorio): slug del artículo.
  • authorName (obligatorio): hasta 80 caracteres.
  • body (obligatorio): hasta 2000 caracteres.
  • authorEmail (opcional): el negocio lo ve en su panel; nunca se publica.
  • rating (opcional): entero de 1 a 5. Solo en comentarios principales.
  • parentId (opcional): id del comentario principal al que se responde.
bash
curl -X POST "https://cms.begraffic.com/api/blog/comments" \
  -H "Authorization: Bearer TU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"article":"guia-crm","authorName":"Ana García","authorEmail":"ana@correo.com","body":"Muy útil, gracias.","rating":5}'

Responde 201 con { "comment": { … } }, en la misma forma que el listado. El negocio recibe un aviso en su panel.

POST votar un comentario

POST /api/blog/comments/vote · permiso blog_comments

Cuerpo

  • commentId (obligatorio).
  • visitorId (obligatorio, de 8 a 200 caracteres): quién vota. Usa el id de tu usuario si tu web tiene login; si no, genera uno aleatorio la primera vez (por ejemplo, crypto.randomUUID()) y guárdalo en el navegador. Guardamos solo su hash.
  • vote (obligatorio): "like", "dislike" o "none" para quitar el voto.

Hay un voto por visitante y comentario: repetir el mismo no suma, cambiarlo lo mueve de un contador al otro, y "none" lo quita. Se pueden votar comentarios y respuestas.

bash
curl -X POST "https://cms.begraffic.com/api/blog/comments/vote" \
  -H "Authorization: Bearer TU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"commentId":"Xb12…","visitorId":"v_8f2k1x9q","vote":"like"}'

Respuesta

json
{ "commentId": "Xb12…", "likes": 5, "dislikes": 0, "myVote": "like" }

El visitorId lo decide tu web, así que alguien que borre los datos de su navegador puede volver a votar. Sirve para evitar el voto repetido por accidente, no como identificación.

Errores y límites

  • 400: faltan datos o no son válidos. El campo error trae un mensaje listo para enseñar («Escribe tu comentario.», «Las estrellas van de 1 a 5.»…).
  • 403: el token no tiene read (al leer) o blog_comments (al escribir o votar).
  • 404: el artículo no está publicado, o el comentario no existe o está oculto.
  • 429: más de 10 comentarios o 60 votos por minuto desde la misma IP en tu workspace.

¿Desde el servidor o desde el navegador?

  • Desde el servidor de tu web (recomendado). El token no sale nunca al navegador: tu formulario llama a una ruta de tu propio servidor, y esa ruta llama a Be CMS. Ten en cuenta que entonces todas tus visitas comparten la IP de tu servidor y, con ella, el límite de 10 comentarios y 60 votos por minuto (de sobra para la mayoría de los blogs).
  • Desde el navegador. Solo con un token dedicado que tenga únicamente read y blog_comments: cualquiera puede copiar un token que viaja en el JavaScript de tu web, y así lo único que podría hacer con él es leer lo publicado y comentar como un visitante más. Nunca pongas en el navegador un token con otros permisos.

Ejemplo de integración (JavaScript)

js
const API = 'https://cms.begraffic.com/api/blog/comments'
const headers = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' }

// Un id estable por visitante: el de tu usuario si hay login; si no, uno aleatorio guardado.
function visitorId() {
  let id = localStorage.getItem('becms_visitor')
  if (!id) { id = crypto.randomUUID(); localStorage.setItem('becms_visitor', id) }
  return id
}

// 1) Leer los comentarios de un artículo (con el voto de este visitante).
async function cargarComentarios(slug, cursor) {
  const qs = new URLSearchParams({ article: slug, limit: '10', visitorId: visitorId() })
  if (cursor) qs.set('cursor', cursor)
  const res = await fetch(`${API}?${qs}`, { headers })
  return res.json() // { stats, comments, nextCursor }
}

// 2) Publicar un comentario (con estrellas) o una respuesta (con parentId, sin estrellas).
async function comentar({ slug, nombre, correo, texto, estrellas, parentId }) {
  const res = await fetch(API, {
    method: 'POST',
    headers,
    body: JSON.stringify({ article: slug, authorName: nombre, authorEmail: correo, body: texto, rating: estrellas ?? null, parentId: parentId ?? null }),
  })
  const data = await res.json()
  if (!res.ok) throw new Error(data.error) // mensaje listo para enseñar
  return data.comment
}

// 3) Votar: 'like', 'dislike' o 'none' para quitar el voto.
async function votar(commentId, vote) {
  const res = await fetch(`${API}/vote`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ commentId, visitorId: visitorId(), vote }),
  })
  return res.json() // { likes, dislikes, myVote }
}

// Al pintar: SIEMPRE como texto.
parrafo.textContent = comentario.body

Buenas prácticas

  • Normaliza slugs a minúsculas antes de consultarlos.
  • Envía fechas en formato ISO 8601 al compararlas con scheduledFor o publishedAt.
  • Maneja específicamente los códigos 404 (recurso no publicado) y 405 (método no permitido) en tus integraciones.
  • Pinta body y authorName de los comentarios como texto, nunca como HTML.
  • Si llegas a necesitar otras operaciones de escritura, deberás coordinar con el equipo para habilitar endpoints adicionales.