Docs · API v1

API pública de JSPress

Consume el contenido de tus sitios desde cualquier stack — Nuxt, Next, React, Vue, PHP, Python. Endpoints REST, respuestas JSON, autenticación con Bearer token.

Introducción

JSPress es un CMS multi-tenant headless. Modelas contenido en el panel (posts, productos, inmuebles, cursos — lo que quieras) y lo consumes vía API REST en el front que prefieras. Sin plugin, sin tema, sin PHP.

Cada sitio tiene su propia API key con alcance aislado. Puedes tener decenas de sitios en un único panel sin que uno filtre contenido del otro.

Base URL

Todas las peticiones empiezan con el prefijo de abajo:

https://api.jspress.app/v1

Autenticación

Todas las llamadas requieren el header Authorization con tu API key:

header
Authorization: Bearer sk_live_...

Genera tu key en Panel → Sitio → API keys. Las claves pueden ser revocadas o rotadas sin afectar a las demás.

GET/v1/site

Datos del sitio

Devuelve metadatos, configuración del sitio y SEO listo para usar (favicon, OG image, theme color, redes sociales y JSON-LD de Organization + WebSite listos para pegar en un <script>). Los campos snippet_head y snippet_body_end ya vienen con los scripts oficiales de GTM, GA4 y Meta Pixel concatenados (basado en gtm_id, ga4_id, pixel_id configurados en el panel) — el consumidor solo inyecta esos 2 campos directamente, sin necesidad de generar tags.

Petición

curl
curl https://api.jspress.app/v1/site \
  -H "Authorization: Bearer sk_live_..."

Respuesta

json
{
  "data": {
    "name": "Meu Site",
    "domain": "meusite.com",
    "seo_title": "Meu Site — CMS headless",
    "seo_description": "...",
    "favicon_url": "https://.../favicon.png",
    "og_image_url": "https://.../og.png",
    "theme_color": "#e93d3d",
    "gtm_id": "GTM-XXXXXXX",
    "ga4_id": "G-XXXXXXXXXX",
    "pixel_id": "123456789012345",
    "snippet_head": "<!-- GTM + GA4 + Pixel gerados automaticamente + snippet custom -->",
    "snippet_body_end": "<!-- noscript GTM + Pixel + snippet custom -->",
    "contact_email": "...",
    "contact_whatsapp": "...",
    "social_instagram": "https://instagram.com/...",
    "organization_json_ld": {
      "@context": "https://schema.org",
      "@type": "Organization",
      "name": "Meu Site",
      "url": "https://meusite.com",
      "logo": "https://.../favicon.png",
      "sameAs": ["https://instagram.com/..."]
    },
    "website_json_ld": {
      "@context": "https://schema.org",
      "@type": "WebSite",
      "name": "Meu Site",
      "url": "https://meusite.com",
      "inLanguage": "pt-BR",
      "potentialAction": { "@type": "SearchAction", "target": "..." }
    }
  }
}
GET/v1/post-types

Tipos de contenido

Lista todos los Custom Post Types registrados — schema de los campos, slug, labels y taxonomías relacionadas.

Petición

curl
curl https://api.jspress.app/v1/post-types \
  -H "Authorization: Bearer sk_live_..."

Respuesta

json
{
  "data": [
    {
      "slug": "blog",
      "label": "Blog",
      "fields": ["title", "content", "featured_image"],
      "taxonomies": ["categoria", "tag"]
    }
  ]
}
GET/v1/posts

Lista de posts

Devuelve posts con paginación, filtros y orden. Acepta filtro por tipo, taxonomía, estado, idioma y búsqueda full-text.

Parámetros

NombreTipoDescripción
typestringSlug del post type (ej: blog).
taxonomystringSlug de la taxonomía para filtrar (usar con "term").
termstringSlug del término dentro de la taxonomía.
searchstringBúsqueda full-text en título y contenido.
languagestringFiltra posts por idioma (ej: pt-BR, en, es). Omite para devolver todos.
pageintegerPágina (default: 1).
per_pageintegerÍtems por página (default: 20, máx: 100).

Petición

curl
curl "https://api.jspress.app/v1/posts?type=blog&language=en&per_page=10" \
  -H "Authorization: Bearer sk_live_..."

Respuesta

json
{
  "data": [
    {
      "id": "abc123",
      "type": "blog",
      "title": "Post de exemplo",
      "slug": "post-de-exemplo",
      "content": "...",
      "language": "pt-BR",
      "published_at": "2026-07-10T14:30:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 10,
    "total": 42,
    "total_pages": 5
  }
}
GET/v1/posts/{slug}

Detalle del post

Devuelve el post completo (con content en Tiptap JSON), terms agrupados por taxonomía, y el campo json_ld — schema.org BlogPosting completo (headline, author, publisher, dateModified, mainEntityOfPage, articleSection, keywords) listo para pegar en un <script type="application/ld+json">.

Parámetros

NombreTipoDescripción
languagestringFiltra por idioma cuando el mismo slug existe en varias versiones (ej: pt-BR, en, es). Sitios monolingües pueden omitir.

Petición

curl
curl "https://api.jspress.app/v1/posts/meu-post?language=pt-BR" \
  -H "Authorization: Bearer sk_live_..."

Respuesta

json
{
  "data": {
    "id": "abc123",
    "title": "Meu post",
    "slug": "meu-post",
    "excerpt": "...",
    "content": { /* Tiptap JSON doc */ },
    "cover_url": "https://...",
    "seo_title": null,
    "seo_description": null,
    "seo_og_image": null,
    "focus_keyword": "cms headless",
    "language": "pt-BR",
    "published_at": "2026-07-10T14:30:00Z",
    "updated_at": "2026-07-13T09:15:00Z",
    "terms": {
      "category": [{ "id": "...", "name": "Guias", "slug": "guias" }],
      "tag": [{ "id": "...", "name": "SEO", "slug": "seo" }]
    },
    "json_ld": {
      "@context": "https://schema.org",
      "@type": "BlogPosting",
      "headline": "Meu post",
      "image": "https://...",
      "datePublished": "2026-07-10T14:30:00Z",
      "dateModified": "2026-07-13T09:15:00Z",
      "author": { "@type": "Organization", "name": "Meu Site" },
      "publisher": {
        "@type": "Organization",
        "name": "Meu Site",
        "logo": { "@type": "ImageObject", "url": "https://.../favicon.png" }
      },
      "mainEntityOfPage": { "@type": "WebPage", "@id": "https://meusite.com/blog/meu-post" },
      "articleSection": "Guias",
      "keywords": "SEO",
      "inLanguage": "pt-BR"
    }
  }
}
GET/v1/taxonomies

Lista de taxonomías

Devuelve todas las taxonomías del sitio — categorías, tags o cualquier clasificación personalizada.

Petición

curl
curl https://api.jspress.app/v1/taxonomies \
  -H "Authorization: Bearer sk_live_..."

Respuesta

json
{
  "data": [
    {
      "slug": "categoria",
      "label": "Categoria",
      "hierarchical": true
    }
  ]
}
GET/v1/terms

Términos de una taxonomía

Devuelve los términos dentro de una taxonomía. Pasa el slug de la taxonomía como filtro.

Parámetros

NombreTipoDescripción
taxonomystringSlug de la taxonomía (obligatorio).

Petición

curl
curl "https://api.jspress.app/v1/terms?taxonomy=categoria" \
  -H "Authorization: Bearer sk_live_..."

Respuesta

json
{
  "data": [
    {
      "id": "t1",
      "slug": "marketing",
      "label": "Marketing",
      "parent": null
    }
  ]
}

SEO & Discovery

JSPress entrega listos los artefactos que Google, Bing y los LLMs (Claude, ChatGPT, Perplexity) esperan. Haces un proxy simple en tu dominio o pegas el JSON-LD en un <script> — sin estudiar schema.org, sin generar sitemap manual, sin escribir RSS a mano.

Cómo usarlo en el día a día:

  • Pega el organization_json_ld y website_json_ld de /site en un <script type="application/ld+json"> en el layout global.
  • En cada página de post, pega el json_ld de /posts/{'{'}slug{'}'} — schema BlogPosting completo.
  • Haz proxy de los endpoints /feed.xml, /robots.txt y /llms.txt en las rutas correspondientes de tu sitio.
  • Usa /sitemap-urls como source dinámico de tu generador de sitemap (ej: @nuxtjs/sitemap).
GET/v1/feed.xml

RSS Feed del blog

RSS 2.0 de los posts publicados. Haz proxy en tu dominio y enlaza en el <head> — lectores como Feedly y crawlers de LLM lo descubren automáticamente.

Parámetros

NombreTipoDescripción
languagestringGenera el feed solo con posts del idioma elegido (ej: pt-BR, en, es). Sitios multi-idioma sirven un feed por versión.

Petición

curl
curl "https://api.jspress.app/v1/feed.xml?language=pt-BR" \
  -H "Authorization: Bearer sk_live_..."

Respuesta

xml
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Meu Site</title>
    <link>https://meusite.com/blog</link>
    <atom:link href="..." rel="self" type="application/rss+xml" />
    <description>...</description>
    <language>pt-BR</language>
    <lastBuildDate>Mon, 13 Jul 2026 12:00:00 GMT</lastBuildDate>
    <item>
      <title>Meu post</title>
      <link>https://meusite.com/blog/meu-post</link>
      <guid isPermaLink="true">https://meusite.com/blog/meu-post</guid>
      <pubDate>Fri, 10 Jul 2026 14:30:00 GMT</pubDate>
      <description>...</description>
    </item>
  </channel>
</rss>
GET/v1/robots.txt

robots.txt por defecto

robots.txt base con Sitemap: ya apuntado a tu dominio + Allow explícito para los bots de LLM (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot).

Petición

curl
curl https://api.jspress.app/v1/robots.txt \
  -H "Authorization: Bearer sk_live_..."

Respuesta

text
User-agent: *
Allow: /

User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: Google-Extended
Allow: /

User-agent: CCBot
Allow: /

Sitemap: https://meusite.com/sitemap.xml
GET/v1/sitemap-urls

URLs para el sitemap

Lista JSON con todas las URLs de los posts publicados + lastmod. Iteras y armas tu sitemap.xml — o lo pasas directamente a @nuxtjs/sitemap como source dinámico.

Parámetros

NombreTipoDescripción
languagestringFiltra las URLs por idioma (ej: pt-BR, en, es). Útil para generar un sitemap por versión del sitio.

Petición

curl
curl "https://api.jspress.app/v1/sitemap-urls?language=en" \
  -H "Authorization: Bearer sk_live_..."

Respuesta

json
{
  "data": [
    {
      "loc": "https://meusite.com/blog/meu-post",
      "lastmod": "2026-07-13T09:15:00Z",
      "changefreq": "weekly",
      "priority": 0.8
    }
  ]
}
GET/v1/llms.txt

llms.txt (propuesta de Anthropic)

Markdown estructurado que describe el sitio + posts para que los LLMs (Claude, ChatGPT, Perplexity) los entiendan en un solo escaneo. Aumenta la posibilidad de que tu contenido sea citado en respuestas generadas por IA.

Parámetros

NombreTipoDescripción
languagestringGenera el llms.txt solo con posts del idioma elegido (ej: pt-BR, en, es).

Petición

curl
curl "https://api.jspress.app/v1/llms.txt?language=pt-BR" \
  -H "Authorization: Bearer sk_live_..."

Respuesta

markdown
# Meu Site

> Descrição do site em uma linha.

## Sobre

Meu Site — conteúdo publicado em pt-BR.

## Blog

- [Meu post](https://meusite.com/blog/meu-post): resumo do post

## Contato

- Email: ...
- WhatsApp: ...

## Links

- Site: https://meusite.com
- Feed RSS: https://meusite.com/feed.xml
- Sitemap: https://meusite.com/sitemap.xml

Paginación

Los endpoints de lista aceptan ?page y ?per_page. La respuesta trae un objeto meta con el total de ítems y páginas.

json
{
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 132,
    "total_pages": 7
  }
}

Filtros

/v1/posts acepta filtros combinados vía query string. Algunos ejemplos:

# Posts do tipo blog
/v1/posts?type=blog

# Posts com termo "marketing" na taxonomia categoria
/v1/posts?taxonomy=categoria&term=marketing

# Busca full-text
/v1/posts?search=api+headless

Node types (contenido Tiptap)

El campo content del post es un documento Tiptap JSON. Cada nodo tiene type, opcionalmente attrs y content (array de hijos). Armas un switch por type en tu renderer.

TypeDescripción
paragraphPárrafo estándar. es un array de text nodes.
headingTítulo. va de 1 a 6.
textTexto puro. puede tener bold, italic, underline, strike, code, link.
image y .
bulletList / orderedList / listItemListas con o sin numeración.
blockquoteBloque de cita.
table / tableRow / tableHeader / tableCellTablas. contiene o .
htmlBlock — HTML crudo pegado por el admin (embed, iframe, script). Renderiza con v-html o dangerouslySetInnerHTML.
embedPlayer de YouTube, Vimeo o Spotify (detectado automáticamente al pegar la URL en el editor).
attrs.provider= .
attrs.videoId — el ID en el provider.
attrs.contentType(solo Spotify) = .
horizontalRuleLínea divisoria .
hardBreakSalto de línea manual .

Cómo renderizar un embed en Vue/React:

js
function renderEmbed(node) {
  const { provider, videoId, contentType } = node.attrs

  if (provider === 'youtube') {
    return `<iframe src="https://www.youtube.com/embed/${videoId}"
      class="w-full aspect-video" frameborder="0"
      allow="accelerometer; autoplay; encrypted-media; picture-in-picture"
      allowfullscreen></iframe>`
  }
  if (provider === 'vimeo') {
    return `<iframe src="https://player.vimeo.com/video/${videoId}"
      class="w-full aspect-video" frameborder="0"
      allow="autoplay; fullscreen"></iframe>`
  }
  if (provider === 'spotify') {
    const h = contentType === 'track' ? 152
      : (contentType === 'episode' || contentType === 'show') ? 232
      : 352
    return `<iframe src="https://open.spotify.com/embed/${contentType}/${videoId}"
      width="100%" height="${h}" frameborder="0"
      allow="encrypted-media"></iframe>`
  }
  return ''
}

Errores

Los errores se devuelven como JSON en el formato de abajo, con el status HTTP correspondiente.

json
{
  "error": {
    "code": "invalid_api_key",
    "message": "API key inválida ou revogada."
  }
}
StatusCódigoSignificado
401unauthorizedAPI key ausente o inválida.
403forbiddenAPI key sin permiso para el recurso.
404not_foundEl recurso no existe.
429rate_limitSe excedió el límite de peticiones.
500server_errorError inesperado — repórtalo con nosotros.