Skip to content

Posts

Post management endpoints. Posts are dynamic content entries such as blog posts, news articles, and similar time-based content. They support multiple languages, categories, tags, and a featured image.

Base path: /api/v1/posts


List Posts

GET /api/v1/posts

Required permission: posts:read

Query parameters

Parameter Type Default Description
skip int 0 Pagination offset
limit int 100 Pagination limit
status string Filter by status: draft, published, archived

Response 200 OK

[
  {
    "id": "uuid",
    "slug": "world-cup-final",
    "status": "published",
    "published_at": "2026-01-01T00:00:00Z",
    "author_id": "uuid"
  }
]


List Posts by Category

GET /api/v1/posts/by-category/{category_id}

Required permission: posts:read

Query parameters

Parameter Type Default Description
skip int 0 Pagination offset
limit int 100 Pagination limit

Response 200 OK — list of PostSummary objects.

Errors

Status Description
404 Category not found

List Posts by Tag

GET /api/v1/posts/by-tag/{tag_id}

Required permission: posts:read

Query parameters

Parameter Type Default Description
skip int 0 Pagination offset
limit int 100 Pagination limit

Response 200 OK — list of PostSummary objects.

Errors

Status Description
404 Tag not found

Create Post

POST /api/v1/posts

Required permission: posts:create

Request body

{
  "slug": "world-cup-final",
  "status": "draft",
  "featured_image_id": "uuid",
  "author_id": "uuid",
  "category_ids": ["uuid-1", "uuid-2"],
  "tag_ids": ["uuid-1", "uuid-2"],
  "translations": [
    {
      "language_id": "uuid",
      "title": "World Cup Final",
      "content": "<p>Full article content here...</p>",
      "excerpt": "A short summary of the article.",
      "meta_title": "World Cup Final 2026",
      "meta_description": "Read about the thrilling World Cup Final."
    }
  ]
}

Field Type Required Constraints
slug string 2–255 characters, unique
status string draft, published, archived. Default: draft
featured_image_id UUID Must exist in media
author_id UUID Defaults to current user
category_ids array List of category UUIDs
tag_ids array List of tag UUIDs
translations array List of translations

Response 201 Created — full PostResponse object.

Published at

If status is set to published on creation, published_at is automatically set to the current timestamp. On subsequent publish actions, published_at is never overwritten — it always reflects the first publish date.

Errors

Status Description
404 Featured image not found
404 One or more categories not found
404 One or more tags not found
409 Post slug already exists

Get Post

GET /api/v1/posts/{id}

Required permission: posts:read

Response 200 OK

{
  "id": "uuid",
  "slug": "world-cup-final",
  "status": "published",
  "published_at": "2026-01-01T00:00:00Z",
  "featured_image_id": "uuid",
  "author_id": "uuid",
  "created_by": "uuid",
  "translations": [
    {
      "id": "uuid",
      "post_id": "uuid",
      "language_id": "uuid",
      "title": "World Cup Final",
      "content": "<p>Full article content here...</p>",
      "excerpt": "A short summary of the article.",
      "meta_title": "World Cup Final 2026",
      "meta_description": "Read about the thrilling World Cup Final.",
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": null
    }
  ],
  "categories": [
    {
      "id": "uuid",
      "slug": "sport",
      "is_active": true,
      "order": 0,
      "parent_id": null
    }
  ],
  "tags": [
    {
      "id": "uuid",
      "slug": "fifa",
      "is_active": true
    }
  ],
  "featured_image": {
    "id": "uuid",
    "filename": "550e8400.jpg",
    "original_filename": "stadium.jpg",
    "mime_type": "image/jpeg",
    "size": 204800,
    "path": "uploads/media/550e8400.jpg",
    "alt_text": "Stadium photo"
  },
  "created_at": "2026-01-01T00:00:00Z",
  "updated_at": null
}

Errors

Status Description
404 Post not found

Update Post

PATCH /api/v1/posts/{id}

Required permission: posts:update

Request body — all fields optional

{
  "slug": "world-cup-final-2026",
  "status": "published",
  "featured_image_id": "uuid",
  "author_id": "uuid",
  "category_ids": ["uuid-1"],
  "tag_ids": ["uuid-1", "uuid-2"]
}

Replacing relations

When category_ids or tag_ids are provided, they replace the existing list entirely. To remove all categories, pass an empty array [].

Response 200 OK — updated PostResponse object.

Status transitions

draft → published   → published_at set (only on first publish)
draft → archived
published → draft
published → archived
archived → draft
archived → published

Errors

Status Description
404 Post not found
404 Featured image not found
404 One or more categories not found
404 One or more tags not found
409 Post slug already exists

Delete Post

Soft-deletes a post.

DELETE /api/v1/posts/{id}

Required permission: posts:delete

Response 204 No Content

Errors

Status Description
404 Post not found

Upsert Translation

Creates or updates a translation for a specific language. If a translation for the given language already exists, it will be updated.

PUT /api/v1/posts/{id}/translations

Required permission: posts:update

Request body

{
  "language_id": "uuid",
  "title": "World Cup Final",
  "content": "<p>Full article content here...</p>",
  "excerpt": "A short summary of the article.",
  "meta_title": "World Cup Final 2026",
  "meta_description": "Read about the thrilling World Cup Final."
}

Field Type Required Description
language_id UUID Must exist in DB
title string Post title
content string Full post content
excerpt string Short summary
meta_title string SEO title
meta_description string SEO description

Response 200 OK — updated PostResponse object.

Errors

Status Description
404 Post not found
404 Language not found

Delete Translation

Removes a translation for a specific language from a post.

DELETE /api/v1/posts/{id}/translations/{language_id}

Required permission: posts:update

Response 200 OK — updated PostResponse object.

Errors

Status Description
404 Post not found
404 Translation not found