Verita Content Service API (1.1.0)

Download OpenAPI specification:

License: Apache 2.0

Comprehensive service for managing the lifecycle of posts, hierarchical comments, adding topic, user interactions (likes/bookmarks), and content moderation.

Internal

Delete all content-service data owned by a user

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.

Authorizations:
bearerAuth
path Parameters
userId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "timestamp": "2019-08-24T14:15:22Z",
  • "status": 0,
  • "error": "string",
  • "message": "string"
}

Posts

Retrieve a paginated list of posts

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.

query Parameters
page
integer
Default: 0
size
integer
Default: 10
topic
string

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Create a new post or draft

Authorizations:
bearerAuth
Request Body schema: application/json
required
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"

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "author": {
    },
  • "status": "DRAFT",
  • "title": "string",
  • "excerpt": "string",
  • "summary": "string",
  • "summaryStatus": "PENDING",
  • "summaryGeneratedAt": "2019-08-24T14:15:22Z",
  • "summaryModel": "string",
  • "content": "string",
  • "coverImageUrl": "http://example.com",
  • "topics": [
    ],
  • "sourceUrl": [],
  • "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"
}

Fetch lightweight PostCard objects for multiple posts by ID

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.

query Parameters
ids
required
Array of strings <uuid> <= 50 items [ items <uuid > ]

Comma-separated list of post UUIDs, e.g. ids=id1,id2,id3.

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Search posts by keyword

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.

query Parameters
q
required
string non-empty
page
integer
Default: 0
size
integer
Default: 10

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Get detailed post by ID

path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "author": {
    },
  • "status": "DRAFT",
  • "title": "string",
  • "excerpt": "string",
  • "summary": "string",
  • "summaryStatus": "PENDING",
  • "summaryGeneratedAt": "2019-08-24T14:15:22Z",
  • "summaryModel": "string",
  • "content": "string",
  • "coverImageUrl": "http://example.com",
  • "topics": [
    ],
  • "sourceUrl": [],
  • "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"
}

Update an existing post

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
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"

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "author": {
    },
  • "status": "DRAFT",
  • "title": "string",
  • "excerpt": "string",
  • "summary": "string",
  • "summaryStatus": "PENDING",
  • "summaryGeneratedAt": "2019-08-24T14:15:22Z",
  • "summaryModel": "string",
  • "content": "string",
  • "coverImageUrl": "http://example.com",
  • "topics": [
    ],
  • "sourceUrl": [],
  • "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"
}

Partially update an existing post

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.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
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"

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "author": {
    },
  • "status": "DRAFT",
  • "title": "string",
  • "excerpt": "string",
  • "summary": "string",
  • "summaryStatus": "PENDING",
  • "summaryGeneratedAt": "2019-08-24T14:15:22Z",
  • "summaryModel": "string",
  • "content": "string",
  • "coverImageUrl": "http://example.com",
  • "topics": [
    ],
  • "sourceUrl": [],
  • "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"
}

Soft-delete a post

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "timestamp": "2019-08-24T14:15:22Z",
  • "status": 0,
  • "error": "string",
  • "message": "string"
}

Get post AI summary status

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.

path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "status": "PENDING",
  • "summary": "string",
  • "generatedAt": "2019-08-24T14:15:22Z",
  • "model": "string"
}

Digests

Get the current user's digest history

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).

Authorizations:
bearerAuth
query Parameters
page
integer
Default: 0
size
integer
Default: 10

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Get today's public digest

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.

Responses

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "topics": [
    ],
  • "generatedAt": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z"
}

Get one digest with its full event stream

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).

path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "topics": [
    ],
  • "events": [
    ],
  • "model": "string",
  • "generatedAt": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z"
}

Create a digest (internal, service-authenticated) Deprecated

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.

Request Body schema: application/json
required
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>

Responses

Request samples

Content type
application/json
{
  • "digestType": "PERSONAL",
  • "targetUserId": "73727401-c2dc-4b4b-ad9b-350075d6b049",
  • "digestDate": "2019-08-24",
  • "title": "string",
  • "subtitle": "string",
  • "summary": "string",
  • "events": [
    ],
  • "topics": [
    ],
  • "eventCount": 0,
  • "sourceCount": 0,
  • "readTimeMinutes": 0,
  • "model": "string",
  • "generatedAt": "2019-08-24T14:15:22Z"
}

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "topics": [
    ],
  • "events": [
    ],
  • "model": "string",
  • "generatedAt": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z"
}

Generate one user's AI Daily Digest (internal)

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.

path Parameters
userId
required
string <uuid>
query Parameters
force
boolean
Default: false

Responses

Response samples

Content type
application/json
{
  • "status": "GENERATED",
  • "message": "string",
  • "digest": {
    }
}

Interactions

Like or dislike a post

Send LIKE or DISLIKE to cast/change a reaction. Send NONE to remove any existing reaction.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
type
required
string
Enum: "LIKE" "DISLIKE" "NONE"

LIKE or DISLIKE to cast/change reaction. NONE to remove any existing reaction.

Responses

Request samples

Content type
application/json
{
  • "type": "LIKE"
}

Response samples

Content type
application/json
{
  • "likeCount": 0,
  • "dislikeCount": 0,
  • "isLikedByMe": true,
  • "isDislikedByMe": true
}

Save a post to the current user's bookmarks

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "timestamp": "2019-08-24T14:15:22Z",
  • "status": 0,
  • "error": "string",
  • "message": "string"
}

Remove a post from the current user's bookmarks

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "timestamp": "2019-08-24T14:15:22Z",
  • "status": 0,
  • "error": "string",
  • "message": "string"
}

Like or un-like a comment

Send LIKE to cast a like. Send NONE to remove an existing like.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
type
required
string
Enum: "LIKE" "NONE"

LIKE to cast a like. NONE to remove an existing like.

Responses

Request samples

Content type
application/json
{
  • "type": "LIKE"
}

Response samples

Content type
application/json
{
  • "likeCount": 0,
  • "isLikedByMe": true
}

Comments

Get all comments for a post

path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Post a comment or reply

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
text
required
string [ 1 .. 500 ] characters
parentId
string or null <uuid>

ID of parent comment for replies

Responses

Request samples

Content type
application/json
{
  • "text": "string",
  • "parentId": "70850378-7d3c-4f45-91b7-942d4dfbbd43"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "author": {
    },
  • "text": "string",
  • "likeCount": 0,
  • "isLikedByMe": true,
  • "createdAt": "2019-08-24T14:15:22Z",
  • "replies": [
    ]
}

Delete a comment owned by the current user, or any comment on a post they authored

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "timestamp": "2019-08-24T14:15:22Z",
  • "status": 0,
  • "error": "string",
  • "message": "string"
}

Me

Get current user's draft posts

Authorizations:
bearerAuth
query Parameters
page
integer
Default: 0
size
integer
Default: 10

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Users

Get published posts authored by a user

path Parameters
id
required
string <uuid>
query Parameters
page
integer
Default: 0
size
integer
Default: 10

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Get a user's bookmarked posts

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.

path Parameters
id
required
string <uuid>
query Parameters
page
integer
Default: 0
size
integer
Default: 10

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Get posts liked by a user

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.

path Parameters
id
required
string <uuid>
query Parameters
page
integer
Default: 0
size
integer
Default: 10

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Topics

Get all topics grouped by category

Returns all topics with full stats, grouped by category. Used by the Manage Topics page.

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Batch-resolve topic names by ID

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.

query Parameters
ids
required
Array of strings <uuid> [ 1 .. 100 ] items [ items <uuid > ]

Comma-separated list of topic UUIDs, e.g. ids=id1,id2,id3. Max 100.

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Autocomplete topic search

query Parameters
q
required
string non-empty

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Apply batch follower-count deltas (internal, service-authenticated)

Service-only endpoint (gated by X-Internal-Service-Token, ADR-0007). Increments or decrements follower_count for each named topic. +1 = follow, -1 = unfollow.

Request Body schema: application/json
required
required
object

Responses

Request samples

Content type
application/json
{
  • "deltas": {
    }
}

Response samples

Content type
application/json
{
  • "timestamp": "2019-08-24T14:15:22Z",
  • "status": 0,
  • "error": "string",
  • "message": "string"
}

Get popular topics

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Files

Upload an image, returning its public URL

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.

Authorizations:
bearerAuth
Request Body schema: multipart/form-data
required
file
required
string <binary>

Responses

Response samples

Content type
application/json

Admin

Get the active GenAI LLM configuration (admin only)

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.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "provider": "string",
  • "model": "string",
  • "temperature": 0.1,
  • "availableProviders": [
    ]
}

Switch the GenAI provider/model at runtime (admin only)

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.

Authorizations:
bearerAuth
Request Body schema: application/json
required
provider
required
string non-empty

Must be a provider reported as configured

model
required
string non-empty

Free-text model id for that provider

Responses

Request samples

Content type
application/json
{
  • "provider": "string",
  • "model": "string"
}

Response samples

Content type
application/json
{
  • "provider": "string",
  • "model": "string",
  • "temperature": 0.1,
  • "availableProviders": [
    ]
}

List posts whose AI summary failed (admin only)

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.

Authorizations:
bearerAuth
query Parameters
page
integer
Default: 0
size
integer
Default: 20

Responses

Response samples

Content type
application/json
{
  • "content": [
    ],
  • "page": 0,
  • "size": 0,
  • "totalPages": 0,
  • "totalElements": 0
}

Re-trigger AI summarization for a post (admin only)

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.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "timestamp": "2019-08-24T14:15:22Z",
  • "status": 0,
  • "error": "string",
  • "message": "string"
}

Generate a user's digest for a given day on demand (admin only)

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.

Authorizations:
bearerAuth
path Parameters
userId
required
string <uuid>
query Parameters
date
string <date>

Platform Day to generate for; defaults to yesterday in the digest timezone.

force
boolean
Default: false

Responses

Response samples

Content type
application/json
{
  • "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"
}

Poll an admin-triggered digest generation (admin only)

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.

Authorizations:
bearerAuth
path Parameters
jobId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "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"
}