Las páginas son el caballo de batalla de la API. Una petición te da una página entera (cada sección, cada bloque, cada traducción) lista para verterla en tus plantillas. Sin N+1, sin cascada de fetch, sin “¿por qué el hero carga después del pie?”.
Hay dos sabores:
GET /v1/pages: el índice, todas las páginas del sitio sin contenido. Ideal para menús y sitemaps.GET /v1/pages/:marker: una página con todo dentro. Es la que vas a llamar el 95% de las veces.
Listar todas las páginas
GET
/v1/pageshttps://back.sitecog.com/content/v1/pages
Devuelve todas las páginas del sitio como un objeto con el marcador de cada página como clave. Las secciones y los bloques no se incluyen: piensa en ello como el directorio del vestíbulo, no como el edificio en sí.
Parámetros de consulta
langquerystringopcionalPor defecto: todos los idiomas activosQué traducciones incluir en los parámetros de la página (title, description, keywords). Un código, una lista separada por comas (
ru,en) o el parámetro repetido. Consulta Idiomas.Ejemplo
curl https://back.sitecog.com/content/v1/pages \
-H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"const res = await fetch('https://back.sitecog.com/content/v1/pages', {
headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
});
const pages = await res.json();
Object.values(pages).forEach((page) => console.log(page.href, page.name));{
"home": {
"id": 26,
"marker": "home",
"name": "Home",
"href": "/",
"index": 0,
"params": {
"title": { "en": "VERTEX Air 3 — wireless earbuds" },
"description": { "en": "Hybrid noise cancelling, 42 hours of battery." }
}
},
"contacts": {
"id": 29,
"marker": "contacts",
"name": "Contacts",
"href": "/contacts",
"index": 3,
"params": {}
}
}Obtener una página con contenido
GET
/v1/pages/:markerhttps://back.sitecog.com/content/v1/pages/:marker
La página entera de una vez: la página en sí, sus secciones en
content y los bloques de cada sección en el propio content de la sección.Parámetros
markerpathstringobligatorioEl marcador de página que definiste en el CRM, p. ej.
home o pricing. Letras latinas, dígitos y guiones bajos, de 2 a 40 caracteres. Las mayúsculas importan: Home no es home.langquerystringopcionalPor defecto: todos los idiomas activosLimita las traducciones a estos idiomas.
?lang=en, ?lang=en,de o ?lang=en&lang=de. Cada código tiene que ser un idioma activo del sitio; si no, recibes 400 unknown_lang.emptyqueryflagopcionalPor defecto: desactivadoDevuelve la página sin su
content. Si está presente, está activado: ?empty, ?empty=1, ?empty=true. Se desactiva con 0 o false. Muy útil para los metadatos SEO cuando el cuerpo sale de otro sitio.x-crm-keyheaderstringobligatorioLa clave de tu sitio. También puedes pasarla como
?key= si las cabeceras no son una opción. Consulta Claves del sitio.Ejemplo de petición
curl "https://back.sitecog.com/content/v1/pages/home?lang=en" \
-H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"const res = await fetch('https://back.sitecog.com/content/v1/pages/home?lang=en', {
headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
});
if (!res.ok) throw new Error(`Content API: ${res.status}`);
const page = await res.json();
const hero = page.content.hero.content;
console.log(hero.hero_title.content.en); // "Earbuds that mute the city"// app/page.tsx — un Server Component: la clave nunca llega al navegador
export default async function Home() {
const res = await fetch('https://back.sitecog.com/content/v1/pages/home?lang=en', {
headers: { 'x-crm-key': process.env.CRM_KEY! },
next: { revalidate: 60 },
});
const page = await res.json();
const hero = page.content.hero.content;
return <h1>{hero.hero_title.content.en}</h1>;
}Ejemplo de respuesta
{
"id": 26,
"marker": "home",
"name": "Home",
"href": "/",
"index": 0,
"params": {
"title": { "en": "VERTEX Air 3 — wireless earbuds" }
},
"content": {
"hero": {
"id": 41,
"marker": "hero",
"name": "Hero",
"index": 0,
"show": true,
"content": {
"hero_title": {
"id": 95,
"marker": "hero_title",
"name": "Hero title",
"type": "text",
"multilang": true,
"updatedAt": "2026-09-20T16:33:23.000Z",
"content": { "en": "Earbuds that mute the city" }
},
"hero_image": {
"id": 96,
"marker": "hero_image",
"name": "Hero image",
"type": "image",
"multilang": false,
"updatedAt": "2026-09-18T09:12:40.000Z",
"content": { "file": "https://cdn.example.com/storage/your-site/hero.jpg" }
}
}
},
"faq": {
"id": 44,
"marker": "faq",
"name": "FAQ",
"index": 5,
"show": false,
"content": { "…": "…" }
}
}
}Campos de la respuesta
Tres niveles, una forma por nivel. Cuando has visto una página, las has visto todas.
idnumberId interno de la página. Es estable, pero en tu código mejor usa el marcador: los ids cambian de un entorno a otro.
markerstringEl marcador de la página, el mismo que pones en la URL.
namestringNombre legible del CRM (“Home”). Es para los editores, no para los visitantes: no lo pintes en la página.
hrefstring | nullLa ruta de esta página en tu web, si el editor la ha rellenado.
null en páginas de servicio como common (cabecera y pie).indexnumberPosición en el menú del CRM, empezando por 0. Ordena por ella para reconstruir el orden que ven los editores.
paramsobjectAjustes de la página.
title, description y keywords son mapas de idiomas y respetan lang; todo lo demás (etiquetas Open Graph, scripts) llega exactamente como se guardó.params →
title{ [lang]: string }El
<title> de la página.description{ [lang]: string }Meta description.
keywords{ [lang]: string }Meta keywords, si es que alguien todavía las usa. No juzgamos.
content{ [sectionMarker]: Section }Las secciones de la página, con su marcador como clave. No aparece cuando pasas
?empty.content →
idnumberId de la sección.
markerstringMarcador de la sección, p. ej.
hero.namestringNombre para los editores.
indexnumberOrden en la página. Las claves del objeto mantienen el orden de inserción, pero ordenar por index es lo honesto.
showbooleanSi el editor quiere que esta sección se vea. Las secciones ocultas se siguen devolviendo: ocultarlas es trabajo de tu plantilla (
{section.show && <Faq />}).content{ [blockMarker]: Block }Los bloques de la sección, con su marcador como clave.
content →
idnumberId del bloque.
markerstringMarcador del bloque, p. ej.
hero_title.namestringNombre para los editores.
typestringUno de
text, html, image, video, link, number, color, date, date_range, boolean, object, array. Decide la forma de content.multilangbooleanPara imágenes y vídeos: true si cada idioma tiene su propio archivo.
updatedAtstring (ISO 8601)Cuándo se editó el bloque por última vez. Perfecto para etiquetas de “actualizado hace 2 horas” y claves de caché.
contentdepende del tipoEl valor en sí. Un mapa de idiomas para el texto,
{ file } para una imagen única, etc.: todas las formas están en la página Tipos de bloque.Errores
| Estado | Cuerpo | Qué ha pasado |
|---|---|---|
| 400 | {"message":"invalid_marker"} | El marcador tiene caracteres fuera de A–Z a–z 0–9 _ o una longitud incorrecta. |
| 400 | {"message":"unknown_lang", …} | Un idioma de lang no está activo en el sitio. El cuerpo enumera los disponibles. |
| 401 | {"message":"invalid_key"} | Clave ausente, mal formada o revocada. |
| 404 | {"message":"page_not_found"} | No hay ninguna página con este marcador. ¿Una errata? ¿Otra clave de sitio? |
| 429 | {"message":"rate_limit_exceeded"} | Demasiadas peticiones en este minuto. Consulta Límites de peticiones. |
La lista completa, con soluciones, está en la página Errores.