Posts

Posts are the central unit of editorial content in Ágora CMS. Use them to publish blog articles, news, guides, announcements or any narrative content. Each post has a title, full HTML content, excerpt, featured image, SEO metadata, categories and tags. The API exposes two endpoints: one for paginated listings with filters, and another to retrieve a single post by its slug.

GET /posts — Post listing

Returns posts with published status for a website, ordered by publication date descending. Supports filtering by category, tag and text search, plus full pagination. This is the primary endpoint for building blog listings, archive pages and search results.

Parameter Type Required Description
web_id string Yes Website ID
lang string No Language code. If omitted, returns the default language
category string No Filter by category slug
tag string No Filter by tag slug
search string No Search in title and content
page integer No Page number. Default: 1
limit integer No Results per page. Default: 20, maximum: 100
status string No Defaults to published only. Cannot be changed from the public API.
curl "https://your-panel.com/api/v1/posts?web_id=abc-123&lang=en&page=1&limit=10" \
  -H "X-API-Key: your-api-key"
Expected response 200 OK
{
  "data": [
    {
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "titulo": "My first article",
      "slug": "my-first-article",
      "excerpt": "Brief introduction to the article...",
      "contenido": "<p>Full HTML content...</p>",
      "featured_image_url": "https://your-panel.com/uploads/abc/image.jpg",
      "status": "published",
      "published_at": "2026-01-15T10:00:00Z",
      "meta_title": "My first article | Blog",
      "meta_description": "SEO description of the article...",
      "author": { "name": "Ana García" },
      "categories": [
        { "id": "...", "nombre": "Technology", "slug": "technology" }
      ],
      "tags": [
        { "id": "...", "nombre": "API", "slug": "api" }
      ]
    }
  ],
  "meta": {
    "total": 42,
    "page": 1,
    "limit": 10,
    "pages": 5
  }
}

Response fields

Field Type Description
idUUIDUnique identifier of the post
titulostringPost title
slugstringUnique URL-friendly string per website and language
excerptstring | nullShort summary, if defined
contenidostringFull HTML content of the article
featured_image_urlstring | nullAbsolute URL of the featured image
statusstringAlways published in the public API
published_atISO 8601Publication date and time
meta_titlestring | nullCustom SEO title
meta_descriptionstring | nullCustom SEO description
author.namestringAuthor name
categoriesarrayCategories assigned to the post
tagsarrayTags assigned to the post

The contenido field contains the full HTML of the article as written in the editor. To render it in your frontend use dangerouslySetInnerHTML in React, the v-html directive in Vue, or the set:html directive in Astro. The content comes from your own CMS, so it is safe to render directly.

GET /posts/{slug} — Single post

Returns a specific post by its slug. This is the endpoint you will use on article detail pages — both in static generation (getStaticPaths in Next.js or Astro) and in SSR. The slug is unique per website and language, guaranteeing no collisions between translations.

Parameter Type Required Description
slug string (path) Yes Post slug in the URL
web_id string Yes Website ID
lang string No Language code
curl "https://your-panel.com/api/v1/posts/my-first-article?web_id=abc-123&lang=en" \
  -H "X-API-Key: your-api-key"
Expected response 200 OK
{
  "data": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "titulo": "My first article",
    "slug": "my-first-article",
    "excerpt": "Brief introduction to the article...",
    "contenido": "<p>Full HTML content...</p>",
    "featured_image_url": "https://your-panel.com/uploads/abc/image.jpg",
    "status": "published",
    "published_at": "2026-01-15T10:00:00Z",
    "meta_title": "My first article | Blog",
    "meta_description": "SEO description of the article...",
    "author": { "name": "Ana García" },
    "categories": [ { "id": "...", "nombre": "Technology", "slug": "technology" } ],
    "tags": [ { "id": "...", "nombre": "API", "slug": "api" } ]
  }
}

Always use the slug to identify posts in your site URLs — never the UUID id. The slug is designed to be readable and stable. The id is useful for internal references or when you need to identify a post unambiguously regardless of language or slug changes. If a post's slug changes, the id remains constant.

Use cases

Static blog with Astro. In getStaticPaths call the listing endpoint to retrieve all slugs and generate one static route per post. In each route, call the single-post endpoint to get the full content. The result is a fully static blog with no runtime dependencies.

// src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const res = await fetch(
    `${import.meta.env.API_URL}/posts?web_id=${import.meta.env.WEB_ID}&limit=100`,
    { headers: { 'X-API-Key': import.meta.env.API_KEY } }
  )
  const { data } = await res.json()
  return data.map(post => ({ params: { slug: post.slug } }))
}

News site with Next.js ISR. Use getStaticProps with revalidate: 3600 to regenerate pages every hour without a full build. You get the speed of static pages with near real-time content.

Portfolio. If you don't need categories or custom fields, posts work perfectly for a simple portfolio: each project is a post with its featured image, excerpt and HTML content. For more complex portfolios with structured fields, consider custom content types.

Category pages. Use the category parameter to filter posts by category slug. Combine it with the categories API to build the navigation menu and generate static routes per category.

Tips and best practices

  • Use limit=10 or less in blog listings. Lighter pages load faster and improve SEO.
  • Cache responses on the server whenever possible. Published posts don't change frequently — a TTL of 5–60 minutes is reasonable depending on your publishing frequency.
  • Use the excerpt field for blog preview cards. Never manually truncate contenido — the excerpt is designed exactly for that purpose and may have its own formatting.
  • Always handle the case where featured_image_url is null. Prepare a placeholder image or conditionally render with a check first.
  • Always use the lang parameter if your site is multilingual. Omitting it returns the default language configured in the panel, which can give unexpected results on multilingual sites.
  • To generate the <title> and <meta description> for each post, use meta_title and meta_description when available, falling back to titulo and excerpt.

Common errors

Code Likely cause Fix
401 API Key not included, incorrect or revoked Check that the X-API-Key header is present and the key is valid in Settings → API
404 Post not found or not published Verify the slug is correct, the post exists, and its status is published — drafts do not appear in the public API
400 Missing web_id parameter Always include web_id in the query string. Find it in Settings → Websites
429 Too many requests in a short time Implement server-side caching and use retry with backoff. See the Rate limiting section in the introduction

See also

  • Categories — filter posts by category and build navigation menus
  • Tags — filter posts by tag and build tag clouds
  • Media — access images and files uploaded to the workspace
  • Content Types — for structured content beyond the blog