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/v1Authentication
All calls require the Authorization header with your API key:
Authorization: Bearer sk_live_...Generate your key at Panel → Site → API keys. Keys can be revoked or rotated without affecting the others.
/v1/siteSite 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 https://api.jspress.app/v1/site \
-H "Authorization: Bearer sk_live_..."Response
{
"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-typesContent types
Lists all registered Custom Post Types — field schema, slug, labels and related taxonomies.
Request
curl https://api.jspress.app/v1/post-types \
-H "Authorization: Bearer sk_live_..."Response
{
"data": [
{
"slug": "blog",
"label": "Blog",
"fields": ["title", "content", "featured_image"],
"taxonomies": ["categoria", "tag"]
}
]
}/v1/postsPost list
Returns posts with pagination, filters and sorting. Accepts filter by type, taxonomy, status, language and full-text search.
Parameters
| Name | Type | Description |
|---|---|---|
| type | string | Post type slug (e.g. blog). |
| taxonomy | string | Taxonomy slug to filter by (use with "term"). |
| term | string | Term slug inside the taxonomy. |
| search | string | Full-text search on title and content. |
| language | string | Filters posts by language (e.g. pt-BR, en, es). Omit to return all. |
| page | integer | Page (default: 1). |
| per_page | integer | Items per page (default: 20, max: 100). |
Request
curl "https://api.jspress.app/v1/posts?type=blog&language=en&per_page=10" \
-H "Authorization: Bearer sk_live_..."Response
{
"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}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
| Name | Type | Description |
|---|---|---|
| language | string | Filters by language when the same slug exists in multiple versions (e.g. pt-BR, en, es). Monolingual sites can omit. |
Request
curl "https://api.jspress.app/v1/posts/meu-post?language=pt-BR" \
-H "Authorization: Bearer sk_live_..."Response
{
"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/taxonomiesTaxonomy list
Returns all taxonomies of the site — categories, tags or any custom classification.
Request
curl https://api.jspress.app/v1/taxonomies \
-H "Authorization: Bearer sk_live_..."Response
{
"data": [
{
"slug": "categoria",
"label": "Categoria",
"hierarchical": true
}
]
}/v1/termsTerms of a taxonomy
Returns the terms inside a taxonomy. Pass the taxonomy slug as filter.
Parameters
| Name | Type | Description |
|---|---|---|
| taxonomy | string | Taxonomy slug (required). |
Request
curl "https://api.jspress.app/v1/terms?taxonomy=categoria" \
-H "Authorization: Bearer sk_live_..."Response
{
"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_ldandwebsite_json_ldfrom/siteinside a<script type="application/ld+json">in your global layout. - On each post page, paste the
json_ldfrom/posts/{'{'}slug{'}'}— complete BlogPosting schema. - Proxy the endpoints
/feed.xml,/robots.txtand/llms.txtinto the corresponding routes of your site. - Use
/sitemap-urlsas dynamic source of your sitemap generator (e.g.@nuxtjs/sitemap).
/v1/feed.xmlBlog 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
| Name | Type | Description |
|---|---|---|
| language | string | Builds 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 "https://api.jspress.app/v1/feed.xml?language=pt-BR" \
-H "Authorization: Bearer sk_live_..."Response
<?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.txtDefault 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 https://api.jspress.app/v1/robots.txt \
-H "Authorization: Bearer sk_live_..."Response
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-urlsSitemap 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
| Name | Type | Description |
|---|---|---|
| language | string | Filters URLs by language (e.g. pt-BR, en, es). Useful to generate one sitemap per site version. |
Request
curl "https://api.jspress.app/v1/sitemap-urls?language=en" \
-H "Authorization: Bearer sk_live_..."Response
{
"data": [
{
"loc": "https://meusite.com/blog/meu-post",
"lastmod": "2026-07-13T09:15:00Z",
"changefreq": "weekly",
"priority": 0.8
}
]
}/v1/llms.txtllms.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
| Name | Type | Description |
|---|---|---|
| language | string | Builds the llms.txt with posts of the chosen language only (e.g. pt-BR, en, es). |
Request
curl "https://api.jspress.app/v1/llms.txt?language=pt-BR" \
-H "Authorization: Bearer sk_live_..."Response
# 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.xmlPagination
List endpoints accept ?page and ?per_page. The response brings a meta object with the total number of items and pages.
{
"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+headlessNode 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.
| Type | Description |
|---|---|
| paragraph | Standard paragraph. is an array of text nodes. |
| heading | Heading. goes from 1 to 6. |
| text | Plain text. can have bold, italic, underline, strike, code, link. |
| image | and . |
| bulletList / orderedList / listItem | Lists with or without numbering. |
| blockquote | Quote block. |
| table / tableRow / tableHeader / tableCell | Tables. contains or . |
| htmlBlock | — raw HTML pasted by admin (embed, iframe, script). Render with v-html or dangerouslySetInnerHTML. |
| embed | YouTube, 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) = . |
| horizontalRule | Divider line . |
| hardBreak | Manual line break . |
How to render an embed in 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 ''
}Errors
Errors are returned as JSON in the format below, with the corresponding HTTP status.
{
"error": {
"code": "invalid_api_key",
"message": "API key inválida ou revogada."
}
}| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key. |
| 403 | forbidden | API key without permission for the resource. |
| 404 | not_found | Resource does not exist. |
| 429 | rate_limit | Exceeded the request limit. |
| 500 | server_error | Unexpected error — report it to us. |