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 |
|---|---|---|
id | UUID | Unique identifier of the post |
titulo | string | Post title |
slug | string | Unique URL-friendly string per website and language |
excerpt | string | null | Short summary, if defined |
contenido | string | Full HTML content of the article |
featured_image_url | string | null | Absolute URL of the featured image |
status | string | Always published in the public API |
published_at | ISO 8601 | Publication date and time |
meta_title | string | null | Custom SEO title |
meta_description | string | null | Custom SEO description |
author.name | string | Author name |
categories | array | Categories assigned to the post |
tags | array | Tags 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=10or 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
excerptfield for blog preview cards. Never manually truncatecontenido— the excerpt is designed exactly for that purpose and may have its own formatting. - Always handle the case where
featured_image_urlisnull. Prepare a placeholder image or conditionally render with a check first. - Always use the
langparameter 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, usemeta_titleandmeta_descriptionwhen available, falling back totituloandexcerpt.
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