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/v1Autenticación
Todas las llamadas requieren el header Authorization con tu API key:
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.
/v1/siteDatos 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 https://api.jspress.app/v1/site \
-H "Authorization: Bearer sk_live_..."Respuesta
{
"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": "..." }
}
}
}/v1/post-typesTipos de contenido
Lista todos los Custom Post Types registrados — schema de los campos, slug, labels y taxonomías relacionadas.
Petición
curl https://api.jspress.app/v1/post-types \
-H "Authorization: Bearer sk_live_..."Respuesta
{
"data": [
{
"slug": "blog",
"label": "Blog",
"fields": ["title", "content", "featured_image"],
"taxonomies": ["categoria", "tag"]
}
]
}/v1/postsLista 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
| Nombre | Tipo | Descripción |
|---|---|---|
| type | string | Slug del post type (ej: blog). |
| taxonomy | string | Slug de la taxonomía para filtrar (usar con "term"). |
| term | string | Slug del término dentro de la taxonomía. |
| search | string | Búsqueda full-text en título y contenido. |
| language | string | Filtra posts por idioma (ej: pt-BR, en, es). Omite para devolver todos. |
| page | integer | Página (default: 1). |
| per_page | integer | Ítems por página (default: 20, máx: 100). |
Petición
curl "https://api.jspress.app/v1/posts?type=blog&language=en&per_page=10" \
-H "Authorization: Bearer sk_live_..."Respuesta
{
"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
}
}/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
| Nombre | Tipo | Descripción |
|---|---|---|
| language | string | Filtra por idioma cuando el mismo slug existe en varias versiones (ej: pt-BR, en, es). Sitios monolingües pueden omitir. |
Petición
curl "https://api.jspress.app/v1/posts/meu-post?language=pt-BR" \
-H "Authorization: Bearer sk_live_..."Respuesta
{
"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"
}
}
}/v1/taxonomiesLista de taxonomías
Devuelve todas las taxonomías del sitio — categorías, tags o cualquier clasificación personalizada.
Petición
curl https://api.jspress.app/v1/taxonomies \
-H "Authorization: Bearer sk_live_..."Respuesta
{
"data": [
{
"slug": "categoria",
"label": "Categoria",
"hierarchical": true
}
]
}/v1/termsTé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
| Nombre | Tipo | Descripción |
|---|---|---|
| taxonomy | string | Slug de la taxonomía (obligatorio). |
Petición
curl "https://api.jspress.app/v1/terms?taxonomy=categoria" \
-H "Authorization: Bearer sk_live_..."Respuesta
{
"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_ldywebsite_json_ldde/siteen un<script type="application/ld+json">en el layout global. - En cada página de post, pega el
json_ldde/posts/{'{'}slug{'}'}— schema BlogPosting completo. - Haz proxy de los endpoints
/feed.xml,/robots.txty/llms.txten las rutas correspondientes de tu sitio. - Usa
/sitemap-urlscomo source dinámico de tu generador de sitemap (ej:@nuxtjs/sitemap).
/v1/feed.xmlRSS 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
| Nombre | Tipo | Descripción |
|---|---|---|
| language | string | Genera 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 "https://api.jspress.app/v1/feed.xml?language=pt-BR" \
-H "Authorization: Bearer sk_live_..."Respuesta
<?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>/v1/robots.txtrobots.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 https://api.jspress.app/v1/robots.txt \
-H "Authorization: Bearer sk_live_..."Respuesta
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/v1/sitemap-urlsURLs 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
| Nombre | Tipo | Descripción |
|---|---|---|
| language | string | Filtra las URLs por idioma (ej: pt-BR, en, es). Útil para generar un sitemap por versión del sitio. |
Petición
curl "https://api.jspress.app/v1/sitemap-urls?language=en" \
-H "Authorization: Bearer sk_live_..."Respuesta
{
"data": [
{
"loc": "https://meusite.com/blog/meu-post",
"lastmod": "2026-07-13T09:15:00Z",
"changefreq": "weekly",
"priority": 0.8
}
]
}/v1/llms.txtllms.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
| Nombre | Tipo | Descripción |
|---|---|---|
| language | string | Genera el llms.txt solo con posts del idioma elegido (ej: pt-BR, en, es). |
Petición
curl "https://api.jspress.app/v1/llms.txt?language=pt-BR" \
-H "Authorization: Bearer sk_live_..."Respuesta
# 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.xmlPaginación
Los endpoints de lista aceptan ?page y ?per_page. La respuesta trae un objeto meta con el total de ítems y páginas.
{
"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+headlessNode 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.
| Type | Descripción |
|---|---|
| paragraph | Párrafo estándar. es un array de text nodes. |
| heading | Título. va de 1 a 6. |
| text | Texto puro. puede tener bold, italic, underline, strike, code, link. |
| image | y . |
| bulletList / orderedList / listItem | Listas con o sin numeración. |
| blockquote | Bloque de cita. |
| table / tableRow / tableHeader / tableCell | Tablas. contiene o . |
| htmlBlock | — HTML crudo pegado por el admin (embed, iframe, script). Renderiza con v-html o dangerouslySetInnerHTML. |
| embed | Player 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) = . |
| horizontalRule | Línea divisoria . |
| hardBreak | Salto de línea manual . |
Cómo renderizar un embed en Vue/React:
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.
{
"error": {
"code": "invalid_api_key",
"message": "API key inválida ou revogada."
}
}| Status | Código | Significado |
|---|---|---|
| 401 | unauthorized | API key ausente o inválida. |
| 403 | forbidden | API key sin permiso para el recurso. |
| 404 | not_found | El recurso no existe. |
| 429 | rate_limit | Se excedió el límite de peticiones. |
| 500 | server_error | Error inesperado — repórtalo con nosotros. |