Download OpenAPI specification:
Comprehensive service for managing the lifecycle of posts, hierarchical comments, adding topic, user interactions (likes/bookmarks), and content moderation.
Internal endpoint called by user-service during account deletion. Soft-deletes authored posts and comments, removes user-owned bookmarks and votes, and refreshes affected counters. The operation is idempotent.
| userId required | string <uuid> |
{- "timestamp": "2019-08-24T14:15:22Z",
- "status": 0,
- "error": "string",
- "message": "string"
}General-purpose endpoint for fetching a batch of posts with optional topic filtering. Intended for use cases such as topic-based browsing, admin tooling, or any client that needs raw paginated post data.
Not the homepage feed — the personalised and trending feeds are served by the
Recommendation Service (GET /api/v1/feed/personal and GET /api/v1/feed/trending).
Those endpoints apply algorithmic ranking and return enriched post payloads.
The topic parameter performs an exact-match filter and returns results in
reverse-chronological order. For popularity-ranked topic browsing, use
GET /api/v1/feed/trending?topic= in the Recommendation Service instead.
| page | integer Default: 0 |
| size | integer Default: 10 |
| topic | string |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}| title required | string [ 5 .. 100 ] characters |
| content required | string [ 1 .. 50000 ] characters |
| excerpt | string <= 500 characters Short preview shown in feed cards. Auto-generated from content if omitted. |
| coverImageUrl | string or null <uri> |
| sourceUrl | Array of strings <uri> [ items <uri > ] |
| topics | Array of strings Topic names. New topics are created automatically. |
| status | string Default: "PUBLISHED" Enum: "DRAFT" "PUBLISHED" |
{- "title": "string",
- "content": "string",
- "excerpt": "string",
- "topics": [
- "string"
], - "status": "DRAFT"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}Returns PostCard objects (no article body) for the given post IDs, preserving
the order of the ids parameter. Called by the frontend after receiving a postIds
list from the Recommendation Service to populate feed cards.
| ids required | Array of strings <uuid> <= 50 items [ items <uuid > ] Comma-separated list of post UUIDs, e.g. |
[- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "title": "string",
- "excerpt": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "isLikedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z"
}
]Full-text keyword search across post titles and content. Returns results ranked by relevance to the query string, not by time or popularity.
This endpoint is driven by user intent (the user typed something to find) and is distinct from feed discovery. Future iterations may back this with a dedicated search engine (e.g. Elasticsearch) without changing the contract.
Authentication is optional. When a valid Bearer token is present, isLikedByMe
and isBookmarkedByMe are populated per the authenticated user; otherwise they
default to false.
| q required | string non-empty |
| page | integer Default: 0 |
| size | integer Default: 10 |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}| id required | string <uuid> |
| title required | string [ 5 .. 100 ] characters |
| content required | string [ 1 .. 50000 ] characters |
| excerpt | string <= 500 characters Short preview shown in feed cards. Auto-generated from content if omitted. |
| coverImageUrl | string or null <uri> |
| sourceUrl | Array of strings <uri> [ items <uri > ] |
| topics | Array of strings Topic names. New topics are created automatically. |
| status | string Default: "PUBLISHED" Enum: "DRAFT" "PUBLISHED" |
{- "title": "string",
- "content": "string",
- "excerpt": "string",
- "topics": [
- "string"
], - "status": "DRAFT"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}Updates only the fields provided in the request body; omitted fields are left
unchanged. Use this for single-field updates such as unpublishing a post
({"status": "DRAFT"}) without re-sending the full post payload.
For full replacement (e.g. the post editor), use PUT.
Only the post author may patch a post.
| id required | string <uuid> |
| title | string [ 5 .. 100 ] characters |
| content | string [ 1 .. 50000 ] characters |
| excerpt | string or null <= 500 characters |
| coverImageUrl | string or null <uri> |
| sourceUrl | Array of strings <uri> [ items <uri > ] |
| topics | Array of strings Topic names. Replaces the post's topics. New topics are created automatically. |
| status | string Enum: "DRAFT" "PUBLISHED" |
{- "title": "string",
- "content": "string",
- "excerpt": "string",
- "topics": [
- "string"
], - "status": "DRAFT"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}Returns the AI summary state for a single post. Uses the same visibility rules as post detail: published posts are public, drafts are visible only to their author, and non-visible posts return 404.
| id required | string <uuid> |
{- "status": "PENDING",
- "summary": "string",
- "generatedAt": "2019-08-24T14:15:22Z",
- "model": "string"
}Union of the caller's personal digests (digest_type = PERSONAL, target_user_id = me) and the public digests assigned to the caller on days they had no subscriptions, newest first, paginated. Auth-required (fails closed).
| page | integer Default: 0 |
| size | integer Default: 10 |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "digestType": "PERSONAL",
- "digestDate": "2019-08-24",
- "title": "string",
- "subtitle": "string",
- "summary": "string",
- "eventCount": 0,
- "sourceCount": 0,
- "readTimeMinutes": 0,
- "previewHeadlines": [
- "string"
], - "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "generatedAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}Returns the newest PUBLIC digest, readable without authentication so the logged-out /digest surface has something to show (ADR-0018/0019). Responds 404 when none exists.
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "digestType": "PERSONAL",
- "digestDate": "2019-08-24",
- "title": "string",
- "subtitle": "string",
- "summary": "string",
- "eventCount": 0,
- "sourceCount": 0,
- "readTimeMinutes": 0,
- "previewHeadlines": [
- "string"
], - "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "generatedAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z"
}Optional authentication (ADR-0006/0016). A PERSONAL digest whose target_user_id is not the caller responds 404; a PUBLIC digest is open (including logged-out).
| id required | string <uuid> |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "digestType": "PERSONAL",
- "digestDate": "2019-08-24",
- "title": "string",
- "subtitle": "string",
- "summary": "string",
- "eventCount": 0,
- "sourceCount": 0,
- "readTimeMinutes": 0,
- "previewHeadlines": [
- "string"
], - "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "events": [
- {
- "headline": "string",
- "summaryBullets": [
- "string"
], - "topicIds": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "sources": [
- {
- "sourceName": "string",
- "provider": "string",
- "publishedAt": "2019-08-24T14:15:22Z",
- "title": "string"
}
]
}
], - "model": "string",
- "generatedAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z"
}Deprecated service-only endpoint (gated by X-Internal-Service-Token, ADR-0007). Existing callers may still persist an already-generated digest, but content-service ignores the request title and applies its deterministic personal/public title rule. Scheduled and admin generation persist in-process and do not call this endpoint.
| digestType required | string (DigestType) Enum: "PERSONAL" "PUBLIC" PERSONAL = generated for one subscriber (target set); PUBLIC = shared platform digest (target null). |
| targetUserId | string or null <uuid> Required when digestType=PERSONAL; must be null when PUBLIC. |
| digestDate required | string <date> |
| title | string or null Deprecated Deprecated compatibility field. Content-service ignores it and constructs the digest title. |
| subtitle | string or null |
| summary | string or null |
required | Array of objects (DigestEvent) |
Array of objects (DigestTopicRef) | |
| eventCount | integer or null |
| sourceCount | integer or null |
| readTimeMinutes | integer or null |
| model | string or null |
| generatedAt | string or null <date-time> |
{- "digestType": "PERSONAL",
- "targetUserId": "73727401-c2dc-4b4b-ad9b-350075d6b049",
- "digestDate": "2019-08-24",
- "title": "string",
- "subtitle": "string",
- "summary": "string",
- "events": [
- {
- "headline": "string",
- "summaryBullets": [
- "string"
], - "topicIds": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "sources": [
- {
- "sourceName": "string",
- "provider": "string",
- "publishedAt": "2019-08-24T14:15:22Z",
- "title": "string"
}
]
}
], - "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "eventCount": 0,
- "sourceCount": 0,
- "readTimeMinutes": 0,
- "model": "string",
- "generatedAt": "2019-08-24T14:15:22Z"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "digestType": "PERSONAL",
- "digestDate": "2019-08-24",
- "title": "string",
- "subtitle": "string",
- "summary": "string",
- "eventCount": 0,
- "sourceCount": 0,
- "readTimeMinutes": 0,
- "previewHeadlines": [
- "string"
], - "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "events": [
- {
- "headline": "string",
- "summaryBullets": [
- "string"
], - "topicIds": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "sources": [
- {
- "sourceName": "string",
- "provider": "string",
- "publishedAt": "2019-08-24T14:15:22Z",
- "title": "string"
}
]
}
], - "model": "string",
- "generatedAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z"
}Service-only manual trigger for the same generation orchestration used by the scheduled daily digest job. Branches on whether the user has topic subscriptions: subscribers get a freshly generated PERSONAL digest; zero-subscription users are assigned the day's PUBLIC digest (ADR-0018/0019). By default, skips when the user already has a digest for the current Platform Day; force=true regenerates.
| userId required | string <uuid> |
| force | boolean Default: false |
{- "status": "GENERATED",
- "message": "string",
- "digest": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "digestType": "PERSONAL",
- "digestDate": "2019-08-24",
- "title": "string",
- "subtitle": "string",
- "summary": "string",
- "eventCount": 0,
- "sourceCount": 0,
- "readTimeMinutes": 0,
- "previewHeadlines": [
- "string"
], - "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "events": [
- {
- "headline": "string",
- "summaryBullets": [
- "string"
], - "topicIds": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "sources": [
- {
- "sourceName": "string",
- "provider": "string",
- "publishedAt": "2019-08-24T14:15:22Z",
- "title": "string"
}
]
}
], - "model": "string",
- "generatedAt": "2019-08-24T14:15:22Z",
- "createdAt": "2019-08-24T14:15:22Z"
}
}Send LIKE or DISLIKE to cast/change a reaction. Send NONE to remove any existing reaction.
| id required | string <uuid> |
| type required | string Enum: "LIKE" "DISLIKE" "NONE" LIKE or DISLIKE to cast/change reaction. NONE to remove any existing reaction. |
{- "type": "LIKE"
}{- "likeCount": 0,
- "dislikeCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true
}Send LIKE to cast a like. Send NONE to remove an existing like.
| id required | string <uuid> |
| type required | string Enum: "LIKE" "NONE" LIKE to cast a like. NONE to remove an existing like. |
{- "type": "LIKE"
}{- "likeCount": 0,
- "isLikedByMe": true
}[- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "text": "string",
- "likeCount": 0,
- "isLikedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "replies": [
- { }
]
}
]| id required | string <uuid> |
| text required | string [ 1 .. 500 ] characters |
| parentId | string or null <uuid> ID of parent comment for replies |
{- "text": "string",
- "parentId": "70850378-7d3c-4f45-91b7-942d4dfbbd43"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "text": "string",
- "likeCount": 0,
- "isLikedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "replies": [
- { }
]
}| page | integer Default: 0 |
| size | integer Default: 10 |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}| id required | string <uuid> |
| page | integer Default: 0 |
| size | integer Default: 10 |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}Returns bookmarked posts for the given user. Visibility is controlled by the user's
showBookmarks preference (from UserPreferences). The current user can always access
their own bookmarks; other users' bookmarks are only visible when showBookmarks is true.
| id required | string <uuid> |
| page | integer Default: 0 |
| size | integer Default: 10 |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}Returns liked posts for the given user. Visibility is controlled by the user's
showLikes preference (from UserPreferences). The current user can always access
their own likes; other users' likes are only visible when showLikes is true.
| id required | string <uuid> |
| page | integer Default: 0 |
| size | integer Default: 10 |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "author": {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "username": "string",
- "displayName": "string",
- "role": "USER",
- "organisation": "string"
}, - "status": "DRAFT",
- "title": "string",
- "excerpt": "string",
- "summary": "string",
- "summaryStatus": "PENDING",
- "summaryGeneratedAt": "2019-08-24T14:15:22Z",
- "summaryModel": "string",
- "content": "string",
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
], - "readTimeMinutes": 0,
- "likeCount": 0,
- "dislikeCount": 0,
- "commentCount": 0,
- "viewCount": 0,
- "saveCount": 0,
- "isLikedByMe": true,
- "isDislikedByMe": true,
- "isBookmarkedByMe": true,
- "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}Returns all topics with full stats, grouped by category. Used by the Manage Topics page.
[- {
- "id": "string",
- "label": "string",
- "sortOrder": 0,
- "topics": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "displayName": "string",
- "categoryId": "string",
- "sortOrder": 0,
- "totalPostCount": 0,
- "postsThisWeek": 0,
- "postsPrevWeek": 0,
- "activityScore": 0,
- "isHot": true,
- "followerCount": 0
}
]
}
]Returns minimal { id, name } objects for the given topic IDs — for callers that only
need to resolve names (e.g. recommendation-service populating subscribed-topic names).
Unknown IDs are silently omitted; the caller tolerates gaps.
This is a dedicated sibling of GET /api/v1/topics (which returns the full
category-grouped catalog with stats), mirroring the GET /api/v1/posts/cards?ids=
pattern: one operation, one response shape.
| ids required | Array of strings <uuid> [ 1 .. 100 ] items [ items <uuid > ] Comma-separated list of topic UUIDs, e.g. |
[- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string"
}
][- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "displayName": "string",
- "categoryId": "string",
- "sortOrder": 0,
- "totalPostCount": 0,
- "postsThisWeek": 0,
- "postsPrevWeek": 0,
- "activityScore": 0,
- "isHot": true,
- "followerCount": 0
}
]Service-only endpoint (gated by X-Internal-Service-Token, ADR-0007). Increments or decrements follower_count for each named topic. +1 = follow, -1 = unfollow.
required | object |
{- "deltas": {
- "property1": 0,
- "property2": 0
}
}{- "timestamp": "2019-08-24T14:15:22Z",
- "status": 0,
- "error": "string",
- "message": "string"
}[- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "displayName": "string",
- "categoryId": "string",
- "sortOrder": 0,
- "totalPostCount": 0,
- "postsThisWeek": 0,
- "postsPrevWeek": 0,
- "activityScore": 0,
- "isHot": true,
- "followerCount": 0
}
]Accepts a single image as multipart/form-data, validates type and size, stores it in object storage, and returns its public URL for use as a cover image or inline post image.
| file required | string <binary> |
{
}Returns the live (provider, model) pair GenAI is currently using — the in-memory override when an admin has set one, otherwise GenAI's environment default — plus every supported provider and whether its API key is configured.
{- "provider": "string",
- "model": "string",
- "temperature": 0.1,
- "availableProviders": [
- {
- "name": "string",
- "configured": true
}
]
}Sets GenAI's in-memory (provider, model) override. The override is deliberately not persisted: it resets to the environment default when genai-service restarts (ADR-0020). Selecting a provider whose API key is absent is rejected with 400.
| provider required | string non-empty Must be a provider reported as |
| model required | string non-empty Free-text model id for that provider |
{- "provider": "string",
- "model": "string"
}{- "provider": "string",
- "model": "string",
- "temperature": 0.1,
- "availableProviders": [
- {
- "name": "string",
- "configured": true
}
]
}Paged list of posts left in summaryStatus = FAILED after the retrying summary listener gave up. Feeds the admin Operations tab, where each row can be re-triggered.
| page | integer Default: 0 |
| size | integer Default: 20 |
{- "content": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "title": "string",
- "authorId": "ee6f7132-bd0a-4fcd-83b3-a8022377067b",
- "summaryStatus": "PENDING",
- "updatedAt": "2019-08-24T14:15:22Z"
}
], - "page": 0,
- "size": 0,
- "totalPages": 0,
- "totalElements": 0
}Moves the post to summaryStatus = PENDING and republishes the summary event, reusing the same retrying listener as authoring (there is no separate synchronous overwrite path). The call returns 202 immediately; poll GET /api/v1/posts/{id}/summary for the outcome. Admin-gated because it spends LLM budget on demand.
| id required | string <uuid> |
{- "timestamp": "2019-08-24T14:15:22Z",
- "status": 0,
- "error": "string",
- "message": "string"
}Admin-JWT front door to the same orchestration the scheduled daily job runs (ADR-0018/0019). The work blocks on a GenAI job plus external news fetches, so it is run in the background and the call returns 202 immediately rather than risking a gateway or browser timeout — with a DigestGenerationJob whose id is polled via GET /api/v1/admin/digests/jobs/{jobId} for the outcome.
date selects the day to generate for and defaults to yesterday: the current Platform Day is still accumulating news, so the day worth (re)generating is the last complete one. By default a user who already has a digest for that day is left alone (job status SKIPPED); force=true regenerates.
| userId required | string <uuid> |
| date | string <date> Platform Day to generate for; defaults to yesterday in the digest timezone. |
| force | boolean Default: false |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "targetUserId": "73727401-c2dc-4b4b-ad9b-350075d6b049",
- "digestDate": "2019-08-24",
- "status": "PENDING",
- "message": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "finishedAt": "2019-08-24T14:15:22Z"
}Outcome of a run started by POST /api/v1/admin/digests/generate/users/{userId}. A digest row only exists once generation succeeds, so this job — not the digest — is where a PENDING, SKIPPED or FAILED run reports itself.
| jobId required | string <uuid> |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "targetUserId": "73727401-c2dc-4b4b-ad9b-350075d6b049",
- "digestDate": "2019-08-24",
- "status": "PENDING",
- "message": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "finishedAt": "2019-08-24T14:15:22Z"
}