The module offers two HTTP APIs.
- The native Omeka S API resources
blog_posts,blog_categories,blog_tagsandblog_commentsunder/api/. These follow the standard Omeka S conventions (JSON-LD,pageandper_page, API key authentication withkey_identityandkey_credentialquery parameters) and support full create, read, update and delete with role-based permissions. Prefer these for integrations. - The legacy JSON endpoints under
/api/blog/, which return plain JSON, only expose published posts, and accept comments. They authenticate with the browser session only; Omeka API keys are not accepted on these routes.
If the API Browser module is installed, the four native resources appear in it with their parameters.
Native resources
Permissions
| Resource | search, read | create | update | delete |
|---|---|---|---|---|
blog_posts |
everyone | Editor and above | Editor and above | Editor and above |
blog_categories |
everyone | Editor and above | Editor and above | Editor and above |
blog_tags |
everyone | not available | not available | not available |
blog_comments |
everyone | everyone | Editor and above | Editor and above |
"Everyone" includes anonymous requests, but what a read returns depends on the caller's role:
- Editor and above (Editor, Site Administrator, Global Administrator, authenticated with an API key) see every post and comment and may filter by any
status. - Everyone else (anonymous requests, and Researcher or Author keys) gets exactly what the public site shows.
blog_postssearch is forced tostatus=publishedandexclude_future=true, andGET /api/blog_posts/{id}answers 404 for a draft, pending or scheduled post.blog_commentssearch is forced tostatus=approved,GET /api/blog_comments/{id}answers 404 for a pending or spam comment, and theo:author_emailfield is omitted.
Any status or exclude_future value such callers send is ignored rather than rejected.
Authenticate write requests with an API key created under Admin > Users > (user) > Edit > API keys:
/api/blog_posts?key_identity=IDENTITY&key_credential=CREDENTIAL
Posts: /api/blog_posts
GET /api/blog_posts lists posts, newest first, with Omeka-S-Total-Results in the response headers.
| Parameter | Type | Description |
|---|---|---|
status |
string | draft, pending or published. Honoured for Editor and above only; other callers always get published. |
exclude_future |
boolean | Omit posts whose published_at is in the future. Always on for callers below Editor. Editors combine it with status=published to get exactly what the public site shows. |
site_id |
integer | Only posts assigned to this site. |
category_id |
integer | Filter by category ID. |
category_slug |
string | Filter by category slug (takes precedence over category_id). |
tag_slug |
string | Filter by tag slug. |
author_id |
integer | Filter by author user ID. |
search |
string | Substring match against title, excerpt and content. |
page |
integer | Page number, default 1. |
per_page |
integer | Results per page, default 10. |
GET /api/blog_posts/{id} returns one post:
{
"@type": "o:BlogPost",
"o:id": 1,
"o:title": "Welcome to the Blog",
"o:slug": "welcome-to-the-blog",
"o:excerpt": "<p>The blog module is now installed and ready.</p>",
"o:content": "<p>Welcome!</p>",
"o:status": "published",
"o:author_id": 1,
"o:author_name": "Jane Editor",
"o:category_id": 1,
"o:category_name": "Announcements",
"o:category_slug": "announcements",
"o:featured_image_asset_id": null,
"o:featured_image_url": null,
"o:published_at": "2026-03-03 00:29:00",
"o:created_at": "2026-03-03 00:29:39",
"o:updated_at": "2026-03-04 00:38:56",
"o:tags": [{ "id": 2, "name": "getting-started", "slug": "getting-started" }]
}
POST /api/blog_posts creates a post. The authenticated user becomes the author.
| Body field | Required | Description |
|---|---|---|
title |
yes | |
slug |
no | Generated from the title when omitted. |
excerpt, content |
no | HTML strings. |
status |
no | draft (default), pending or published. |
category_id |
no | Category ID. Unlike the admin form, the API does not require one. |
featured_image_asset_id |
no | Omeka asset ID. |
featured_image_url |
no | Image URL used by the templates. The API does not derive it from the asset ID; set both if you want the image to display. |
published_at |
no | YYYY-MM-DD HH:MM:SS. Defaults to now when status is published. |
tags |
no | Array of tag names. Missing tags are created. |
site_ids |
no | Array of site IDs the post is visible on. Without it the post is not shown on any site. |
curl -X POST "https://example.org/api/blog_posts?key_identity=ID&key_credential=SECRET" \
-H "Content-Type: application/json" \
-d '{"title":"Hello","content":"<p>World</p>","status":"published","category_id":1,"tags":["news"],"site_ids":[1]}'
PUT or PATCH /api/blog_posts/{id} updates a post. Both behave as partial updates: only the fields present in the body change. tags replaces the whole tag list, site_ids replaces the site assignments, and sending status: "published" without published_at sets the publish time to now.
DELETE /api/blog_posts/{id} deletes the post and its comments.
Categories: /api/blog_categories
GET /api/blog_categoriesreturns all categories ordered by name (no filters).GET /api/blog_categories/{id}returnso:name,o:slugando:description.POST /api/blog_categorieswithname(required), optionalsluganddescription.PUTorPATCH /api/blog_categories/{id}with any ofname,slug,description.DELETE /api/blog_categories/{id}removes the category; its posts become uncategorised.
Tags: /api/blog_tags
Read-only. GET /api/blog_tags lists all tags ordered by name; GET /api/blog_tags/{id} returns o:name and o:slug. Tags are created through the tags field of a post.
Comments: /api/blog_comments
GET /api/blog_comments accepts status (pending, approved, spam), post_id, page and per_page (default 50). Each comment has o:post_id, o:author_name, o:author_email, o:body, o:status and o:posted_at. Callers below Editor only receive approved comments, whatever status they send, and o:author_email is left out of their responses; GET /api/blog_comments/{id} answers 404 for a pending or spam comment.
POST /api/blog_comments creates a pending comment and is open to anonymous requests. Body: post_id, author_name, author_email, body (all required). The post must be published, otherwise the request fails with a validation error. This resource does not check the Enable comments setting.
PUT or PATCH /api/blog_comments/{id} moderates a comment. The only supported field is status with the value approved or spam; any other field is ignored and any other status value is rejected.
DELETE /api/blog_comments/{id} removes the comment.
Legacy endpoints under /api/blog/
These return plain JSON (not JSON-LD) and are always public for reading.
GET /api/blog/posts
Published posts only, newest first.
| Parameter | Description |
|---|---|
page |
Page number, default 1. |
per_page |
Default 10, maximum 100. |
tag_slug, category_slug, author_id, search |
Same meaning as for blog_posts. |
Scheduled (future-dated) posts are included, and there is no site filter.
{
"data": [
{
"id": 1, "title": "Welcome to the Blog", "slug": "welcome-to-the-blog",
"excerpt": "...", "content": "...", "status": "published",
"author_id": 1, "category_id": 1, "tags": ["getting-started"],
"published_at": "2026-03-03 00:29:00",
"created_at": "2026-03-03 00:29:39", "updated_at": "2026-03-04 00:38:56"
}
],
"total": 2, "page": 1, "per_page": 10, "total_pages": 1
}
GET /api/blog/posts/{id}
One published post in the same shape. Unpublished posts return 404.
POST /api/blog/posts
Creates a post for the logged-in user (browser session required; anonymous requests get 401). title and content are required; slug, excerpt, status, category_id, published_at, featured_image_asset_id, featured_image_url and tags (array or comma-separated string) are optional. Site assignments cannot be set here. Responds with 201 and the post. Any logged-in role is accepted by this endpoint; use the native blog_posts resource when you need role enforcement.
POST /api/blog/posts/{id}/comments
Submits a comment for moderation. Body: author_name, author_email, body. Responds with 201 {"id": 12, "status": "pending"}, 403 when comments are disabled, 422 when a field is missing or the post is not published, and 405 for non-POST requests.
curl -X POST https://example.org/api/blog/posts/1/comments \
-H "Content-Type: application/json" \
-d '{"author_name":"Alice","author_email":"alice@example.org","body":"Great post!"}'