Docs · API v1

The public API of JSPress

Consume the content of your sites from any stack — Nuxt, Next, React, Vue, PHP, Python. REST endpoints, JSON responses, Bearer token authentication.

Introduction

JSPress is a multi-tenant headless CMS. You model content in the panel (posts, products, listings, courses — whatever you need) and consume it via REST API on your favorite front end. No plugin, no theme, no PHP.

Each site has its own API key with isolated scope. You can have dozens of sites in a single panel without one leaking content into the other.

Base URL

All requests start with the prefix below:

https://api.jspress.app/v1

Authentication

All calls require the Authorization header with your API key:

header
Authorization: Bearer sk_live_...

Generate your key at Panel → Site → API keys. Keys can be revoked or rotated without affecting the others.

GET/v1/site

Site data

Returns metadata, site settings and ready-to-use SEO (favicon, OG image, theme color, social networks, and Organization + WebSite JSON-LD ready to paste inside a <script>). The snippet_head and snippet_body_end fields already come with the official GTM, GA4 and Meta Pixel scripts concatenated (based on gtm_id, ga4_id, pixel_id set in the panel) — the consumer just injects these 2 fields directly, no need to generate tags.

Request

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

Response

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

Content types

Lists all registered Custom Post Types — field schema, slug, labels and related taxonomies.

Request

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

Response

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

Post list

Returns posts with pagination, filters and sorting. Accepts filter by type, taxonomy, status, language and full-text search.

Parameters

NameTypeDescription
typestringPost type slug (e.g. blog).
taxonomystringTaxonomy slug to filter by (use with "term").
termstringTerm slug inside the taxonomy.
searchstringFull-text search on title and content.
languagestringFilters posts by language (e.g. pt-BR, en, es). Omit to return all.
pageintegerPage (default: 1).
per_pageintegerItems per page (default: 20, max: 100).

Request

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

Response

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}

Post detail

Returns the full post (with content in Tiptap JSON), terms grouped by taxonomy, and the json_ld field — complete schema.org BlogPosting (headline, author, publisher, dateModified, mainEntityOfPage, articleSection, keywords) ready to paste inside a <script type="application/ld+json">.

Parameters

NameTypeDescription
languagestringFilters by language when the same slug exists in multiple versions (e.g. pt-BR, en, es). Monolingual sites can omit.

Request

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

Response

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

Taxonomy list

Returns all taxonomies of the site — categories, tags or any custom classification.

Request

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

Response

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

Terms of a taxonomy

Returns the terms inside a taxonomy. Pass the taxonomy slug as filter.

Parameters

NameTypeDescription
taxonomystringTaxonomy slug (required).

Request

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

Response

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

SEO & Discovery

JSPress ships the artifacts Google, Bing and LLMs (Claude, ChatGPT, Perplexity) expect. You proxy simply on your domain or paste the JSON-LD inside a <script> — no need to study schema.org, no manual sitemap, no writing RSS by hand.

How to use day-to-day:

  • Paste the organization_json_ld and website_json_ld from /site inside a <script type="application/ld+json"> in your global layout.
  • On each post page, paste the json_ld from /posts/{'{'}slug{'}'} — complete BlogPosting schema.
  • Proxy the endpoints /feed.xml, /robots.txt and /llms.txt into the corresponding routes of your site.
  • Use /sitemap-urls as dynamic source of your sitemap generator (e.g. @nuxtjs/sitemap).
GET/v1/feed.xml

Blog RSS feed

RSS 2.0 of published posts. Proxy it on your domain and link in the <head> — readers like Feedly and LLM crawlers discover automatically.

Parameters

NameTypeDescription
languagestringBuilds the feed with posts of the chosen language only (e.g. pt-BR, en, es). Multi-language sites serve one feed per version.

Request

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

Response

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

Default robots.txt

Base robots.txt with Sitemap: already pointing to your domain + explicit Allow for LLM bots (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot).

Request

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

Response

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

Sitemap URLs

JSON list with all published post URLs + lastmod. Iterate and build your sitemap.xml — or pass directly to @nuxtjs/sitemap as a dynamic source.

Parameters

NameTypeDescription
languagestringFilters URLs by language (e.g. pt-BR, en, es). Useful to generate one sitemap per site version.

Request

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

Response

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 (Anthropic proposal)

Structured Markdown describing the site + posts for LLMs (Claude, ChatGPT, Perplexity) to understand in one scan. Increases the chance of your content becoming a citation in AI-generated responses.

Parameters

NameTypeDescription
languagestringBuilds the llms.txt with posts of the chosen language only (e.g. pt-BR, en, es).

Request

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

Response

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

Pagination

List endpoints accept ?page and ?per_page. The response brings a meta object with the total number of items and pages.

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

Filters

/v1/posts accepts combined filters via query string. Some examples:

# 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 (Tiptap content)

The post's content field is a Tiptap JSON document. Each node has type, optionally attrs and content (array of children). You build a switch by type in your renderer.

TypeDescription
paragraphStandard paragraph. is an array of text nodes.
headingHeading. goes from 1 to 6.
textPlain text. can have bold, italic, underline, strike, code, link.
image and .
bulletList / orderedList / listItemLists with or without numbering.
blockquoteQuote block.
table / tableRow / tableHeader / tableCellTables. contains or .
htmlBlock — raw HTML pasted by admin (embed, iframe, script). Render with v-html or dangerouslySetInnerHTML.
embedYouTube, Vimeo or Spotify player (automatically detected when pasting a URL in the editor).
attrs.provider= .
attrs.videoId — the ID on the provider.
attrs.contentType(Spotify only) = .
horizontalRuleDivider line .
hardBreakManual line break .

How to render an embed in 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 ''
}

Errors

Errors are returned as JSON in the format below, with the corresponding HTTP status.

json
{
  "error": {
    "code": "invalid_api_key",
    "message": "API key inválida ou revogada."
  }
}
StatusCodeMeaning
401unauthorizedMissing or invalid API key.
403forbiddenAPI key without permission for the resource.
404not_foundResource does not exist.
429rate_limitExceeded the request limit.
500server_errorUnexpected error — report it to us.