Community blog posts. Every route is also served under
/api/communities/communityId/blogs (same handlers, same rules; {blogId}
there, {id} here). A router-level gate returns 403 FEATURE_NOT_AVAILABLE on a
plan without blogs, reads included.
Reads are optionalAuth and follow the community's read scope:
- An APPROVED member reads published posts. A moderator also reads drafts and archived posts (?status=).
- A guest, or a signed-in non-member, reads only posts opted into guest preview, in a PUBLIC community whose plan includes blogs.
- Anyone else gets an empty list or 404. Author objects carry the community name and avatar, never an email.
Writes need an APPROVED member. Editing or deleting ANY post needs blogs:manage or blogs:moderate. The author may edit and delete their own. blogs:create lets you create and edit your own, never anyone else's.
9 endpoints.
| Method | Path | Summary |
|---|---|---|
GET | /api/blogs | List posts |
POST | /api/blogs | Create a post (blogs:manage | blogs:create) |
DELETE | /api/blogs/{id} | Delete a post (author, or blogs:manage | blogs:moderate) |
GET | /api/blogs/{id} | One post |
PUT | /api/blogs/{id} | Edit a post (author, or blogs:manage | blogs:moderate) |
POST | /api/blogs/{id}/like | Toggle your like (members only) |
GET | /api/blogs/{id}/like-status | Did I like this post (members only) |
GET | /api/blogs/{id}/likes | Who liked a post (members only) |
PATCH | /api/blogs/{id}/toggle-visibility | Switch a post between DRAFT and PUBLISHED (blogs:manage | blogs:moderate) |
GET /api/blogsAuth: optional — a token changes what you can see (see "Who may read what").
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
communityId | query | no | string (uuid) | — |
q | query | no | string | — |
categoryId | query | no | string (uuid) | — |
sectionId | query | no | string (uuid) | — |
status | query | no | enum: DRAFT, PUBLISHED, ARCHIVED | Moderators only; everyone else is pinned to PUBLISHED. Invalid → 400 |
page | query | no | integer | — |
pageSize | query | no | integer | — |
Responses
| Status | Description | Body |
|---|---|---|
200 | success, data: [Blog], pagination — each post has isLiked for the caller | — |
400 | Invalid status or pagination | — |
POST /api/blogsThe community is the one the guards checked. A body communityId that disagrees is refused with 400 COMMUNITY_MISMATCH. The header image and inline images count against storage, and a new post is refused over the cap. Rate limited.
Auth: Bearer JWT required.
Request body (required) — multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
title | string | yes | — |
contentHtml | string | yes | Rich text; sanitized; inline data-URI images are uploaded and rewritten |
status | enum: DRAFT, PUBLISHED, ARCHIVED | no | — |
slug | string | no | — |
allowComments | boolean | no | Also accepts "true"/"false" from forms |
categoryIds | string | no | JSON array of blog-category ids |
newCategories | string | no | JSON array of names to create |
sectionId | string (uuid) | no | — |
isGuestVisible | boolean | no | — |
headerImage | string (binary) | no | — |
Responses
| Status | Description | Body |
|---|---|---|
201 | success, data: Blog | — |
400 | Missing title/contentHtml | — |
403 | Not a member | — |
DELETE /api/blogs/{id}Soft delete. The post also leaves the home feed. Auth: Bearer JWT required.
Responses
| Status | Description | Body |
|---|---|---|
200 | success | — |
GET /api/blogs/{id}A post the caller may not read answers 404, exactly as a missing id does. The response includes isLiked and the author's avatar. Auth: optional — a token changes what you can see (see "Who may read what").
Responses
| Status | Description | Body |
|---|---|---|
200 | success, data: Blog | — |
404 | Not found or not visible to this caller | — |
PUT /api/blogs/{id}Same fields as create, all optional. Bytes are recorded, but an edit is never refused over the storage cap. Auth: Bearer JWT required.
Responses
| Status | Description | Body |
|---|---|---|
200 | success, data: Blog | — |
404 | Not found | — |
POST /api/blogs/{id}/likeAuth: Bearer JWT required.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string (uuid) | — |
Responses
| Status | Description | Body |
|---|---|---|
200 | success, data: { liked, likeCount } | — |
404 | No such published post | — |
GET /api/blogs/{id}/like-statusAuth: Bearer JWT required.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string (uuid) | — |
Responses
| Status | Description | Body |
|---|---|---|
200 | success, hasLiked | — |
GET /api/blogs/{id}/likesAuth: Bearer JWT required.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string (uuid) | — |
Responses
| Status | Description | Body |
|---|---|---|
200 | success, data: [{ userId, firstName, familyName, avatar, createdAt }] — community names, at most 100 | — |
PATCH /api/blogs/{id}/toggle-visibilityAuth: Bearer JWT required.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string (uuid) | — |
Responses
| Status | Description | Body |
|---|---|---|
200 | success, data: Blog | — |