Developer Resourcescontent
Content Module API & Integration Guide
API contracts for content admin and customer/mobile newsletter flows.
Content Module - API & Integration Guide
1. How to Read / Quick Metadata
- Module:
Content - Primary base URLs:
- Admin:
/api/admin/content-series,/api/admin/content-items,/api/admin/content-featured - Customer:
/api/content/... - Mobile-composed customer:
/api/mobile/content/...
- Admin:
- Auth model:
- Admin routes:
JwtAuthGuard + RoleGuard + @Permissions(...) - Customer full/library routes:
JwtAuthGuard - Customer preview/list routes: public
- Admin routes:
- Response envelope:
ResponseDto<T>with paginated responses viaApiPaginatedResponseDto
2. High-Level Overview
Content API is split into three resource families:
- Series: grouping, metadata, and optional tier rules
- Items: newsletter publishing lifecycle, preview/full retrieval, send actions
- Featured: curated placement with date windows and explicit ordering
Entitlement for full body responses is subscription-tier based, not product-purchase based.
3. Core Concepts and Terminology
- content series: parent grouping (
content_series) - content item: newsletter entity (
content_item) - newsletter tier rule: access requirement (
newsletter_content_tier) - featured content: curated surfaced entries (
featured_content) - slug history: backward-compatible route resolution for renamed slugs
- recipient filter: newsletter send/list filter (
all,basic,premium)
4. Route Summary
4.1 Admin routes
| Method | Path | Permission |
|---|---|---|
GET | /api/admin/content-series | ContentSeries_READ |
GET | /api/admin/content-series/:id | ContentSeries_READ |
POST | /api/admin/content-series | ContentSeries_CREATE |
PATCH | /api/admin/content-series/:id | ContentSeries_UPDATE |
POST | /api/admin/content-series/:id/tier | ContentSeries_UPDATE |
DELETE | /api/admin/content-series/:id | ContentSeries_DELETE |
GET | /api/admin/content-items | Content_READ |
GET | /api/admin/content-items/subscribers | Content_READ |
GET | /api/admin/content-items/:id | Content_READ |
POST | /api/admin/content-items | Content_CREATE |
PATCH | /api/admin/content-items/:id | Content_UPDATE |
DELETE | /api/admin/content-items/:id | Content_DELETE |
POST | /api/admin/content-items/:id/publish | Content_UPDATE |
POST | /api/admin/content-items/:id/unpublish | Content_UPDATE |
POST | /api/admin/content-items/:id/tier | Content_UPDATE |
POST | /api/admin/content-items/:id/send-email | Content_UPDATE |
POST | /api/admin/content-items/:id/send-push | Content_UPDATE |
GET | /api/admin/content-items/:id/preview | Content_READ |
GET | /api/admin/content-items/:id/full | Content_READ |
GET | /api/admin/content-featured | Content_READ |
GET | /api/admin/content-featured/:id | Content_READ |
POST | /api/admin/content-featured | Content_CREATE |
PATCH | /api/admin/content-featured/:id | Content_UPDATE |
DELETE | /api/admin/content-featured/:id | Content_DELETE |
POST | /api/admin/content-featured/reorder | Content_UPDATE |
4.2 Customer / Mobile routes
| Method | Path | Notes |
|---|---|---|
GET | /api/content/series | list active series |
GET | /api/content/series/:slug | series detail |
GET | /api/content/items | list published items |
GET | /api/content/items/:slug | preview |
GET | /api/content/items/:slug/full | JWT + entitlement |
GET | /api/content/my-library | JWT |
GET | /api/content/items/featured | active featured list |
GET | /api/content/items/featured/all | paginated featured list |
Mobile-composed equivalents are available under /api/mobile/content/....
5. Query Parameters
Common pagination fields (from QueryDto):
pagination(defaulttrue)page(default1)size(default20)search(optional trimmed string)order(asc/desc, defaultdescunless endpoint overrides)
Resource-specific filters:
- customer item list:
seriesId,seriesType,sort(title|publishedAt|createdAt) - customer series list:
type,sort(name|createdAt|updatedAt) - customer featured list (
/featured/all):isActive,sort(order|title|createdAt|updatedAt),order(defaultasc) - admin item list:
seriesId,status,previewEnabled,sort - admin series list:
type,isActive,sort - admin featured list:
isActive,sort,order - subscribers list:
tier(all|basic|premium)
6. Response Shape Examples
Subscriber filter example:
"
"tier": "premium",
"search": "reader@domain.com",
"pagination": true,
"page": 1,
"size": 20
"Full-content success envelope example:
"
"message": "Content full body fetched successfully",
"data": "
"id": 101,
"seriesId": 12,
"seriesName": "Weekly Market Newsletter",
"slug": "market-outlook-apr-2026",
"title": "Market Outlook",
"contentBody": "...full body...",
"previewEnabled": true,
"previewMode": "paragraphs",
"previewWatermarkEnabled": false,
"isLocked": false,
"isEntitled": true,
"requiredTier": "premium",
"publishedAt": "2026-04-01T08:00:00.000Z"
"",
"errorCode": null
"7. Enums
Core enums used by content APIs:
content_type:newslettercontent_status:draft,published,hidden,archivedpreview_mode:paragraphs,words,sectionsnewsletter_tier:basic,premium- newsletter recipient filter:
all,basic,premium
8. Integration Diagram
9. Caching
Primary content cache key prefixes:
content:customer:items:list:content:customer:item:slug:content:customer:library:content:customer:subscriptions:content:customer:series:list:content:customer:series:slug:content:featured:admin:list:content:featured:customer:active:content:featured:customer:all:
TTL envs:
CONTENT_CUSTOMER_SERIES_CACHE_TTL_SECONDSCONTENT_CUSTOMER_ITEM_CACHE_TTL_SECONDSCONTENT_CUSTOMER_MY_LIBRARY_CACHE_TTL_SECONDSCONTENT_CUSTOMER_PURCHASES_CACHE_TTL_SECONDSCONTENT_CACHE_TTL_SECONDS
10. Error Handling
All content API errors return the standard envelope with errorCode.
"
"statusCode": 404,
"errorCode": "CONTENT_NOT_FOUND",
"message": "Content item not found.",
"timestamp": "2026-03-20T10:00:00.000Z",
"path": "/api/content/items/some-slug"
"| HTTP | errorCode | Condition |
|---|---|---|
| 400 | CONTENT_STATUS_INVALID | scheduledAt is in the future but status=published |
| 400 | CONTENT_ARCHIVED_NOT_PUBLISHABLE | attempted to publish archived content |
| 400 | CONTENT_SCHEDULED_AT_INVALID | invalid scheduledAt value |
| 400 | CONTENT_SERIES_CREATE_FAILED | content series creation failed |
| 400 | CONTENT_NOT_PUBLISHED | attempted send for unpublished newsletter |
| 400 | FEATURED_CONTENT_DUPLICATE_IDS | duplicate IDs in featured reorder payload |
| 400 | FEATURED_CONTENT_INVALID_DATE_RANGE | featured startDate is after endDate |
| 400 | FEATURED_CONTENT_INVALID_SORT | unsupported sort field for featured listing |
| 403 | CONTENT_ACCESS_DENIED | user lacks entitlement for full content |
| 404 | CONTENT_NOT_FOUND | content item does not exist |
| 404 | CONTENT_SERIES_NOT_FOUND | content series does not exist |
| 404 | FEATURED_CONTENT_NOT_FOUND | featured row does not exist |
| 404 | CONTENT_CATEGORY_NOT_FOUND | referenced category does not exist |
| 404 | TAG_NOT_FOUND | one or more tag IDs do not exist |
| 429 | RATE_LIMIT_EXCEEDED | endpoint rate limit exceeded |
| 503 | ERR_CONFIG_INVALID | required config invalid/missing |
11. Endpoint Reference + Payload Cheatsheet
11.1 Payload Cheatsheet Table (Every Endpoint)
| Method | Path | Auth / Permission | Request DTO / Params | Success DTO | Notes |
|---|---|---|---|---|---|
| GET | /api/admin/content-series | Admin + ContentSeries_READ | QueryContentSeriesAdminDto "" page?, size?, pagination?, type?, isActive?, sort?, order? "" | ContentSeriesAdminListItemDto[] paginated | List all content series with filters |
| GET | /api/admin/content-series/:id | Admin + ContentSeries_READ | path: id (int) | ContentSeriesAdminDetailDto | Get single series with items |
| POST | /api/admin/content-series | Admin + ContentSeries_CREATE | CreateContentSeriesDto "" name: string, type: "newsletter", slug?: string, description?: string, isActive?: boolean "" | ContentSeriesResponseDto | Creates new content series |
| PATCH | /api/admin/content-series/:id | Admin + ContentSeries_UPDATE | path: id (int), body: UpdateContentSeriesDto "" name?: string, description?: string, isActive?: boolean "" | ContentSeriesResponseDto | Update series metadata |
| POST | /api/admin/content-series/:id/tier | Admin + ContentSeries_UPDATE | path: id (int), body: SetContentSeriesTierDto "" tier: "basic"|"premium" "" | ContentSeriesTierResponseDto | Set required tier for series |
| DELETE | /api/admin/content-series/:id | Admin + ContentSeries_DELETE | path: id (int) | DeleteResponseDto | Soft delete series (archived) |
| GET | /api/admin/content-items | Admin + Content_READ | QueryContentItemsAdminDto "" page?, size?, pagination?, seriesId?, status?, previewEnabled?, sort?, order? "" | ContentItemAdminListItemDto[] paginated | List all content items |
| GET | /api/admin/content-items/subscribers | Admin + Content_READ | QuerySubscribersDto "" tier?: "all"|"basic"|"premium", search?, page?, size?, pagination? "" | SubscriberListDto[] paginated | List newsletter subscribers |
| GET | /api/admin/content-items/:id | Admin + Content_READ | path: id (int) | ContentItemAdminDetailDto | Get item detail with series info |
| POST | /api/admin/content-items | Admin + Content_CREATE | CreateContentItemDto "" seriesId: number, title: string, slug?: string, contentBody: string, previewEnabled?: boolean, previewMode?: "paragraphs"|"words"|"sections", status?: "draft" "" | ContentItemResponseDto | Create new content item |
| PATCH | /api/admin/content-items/:id | Admin + Content_UPDATE | path: id (int), body: UpdateContentItemDto "" title?: string, contentBody?: string, previewEnabled?, previewMode?, status? "" | ContentItemResponseDto | Update content item |
| DELETE | /api/admin/content-items/:id | Admin + Content_DELETE | path: id (int) | DeleteResponseDto | Delete content item |
| POST | /api/admin/content-items/:id/publish | Admin + Content_UPDATE | path: id (int), body: PublishContentItemDto "" scheduledAt?: string "" | ContentItemResponseDto | Publish content item (optionally schedule) |
| POST | /api/admin/content-items/:id/unpublish | Admin + Content_UPDATE | path: id (int) | ContentItemResponseDto | Unpublish/hide content item |
| POST | /api/admin/content-items/:id/tier | Admin + Content_UPDATE | path: id (int), body: SetContentItemTierDto "" tier: "basic"|"premium"|null "" | ContentItemTierResponseDto | Set required tier for item |
| POST | /api/admin/content-items/:id/send-email | Admin + Content_UPDATE | path: id (int), body: SendContentEmailDto "" recipientFilter: "all"|"basic"|"premium" "" | SendEmailResponseDto | Send newsletter via email |
| POST | /api/admin/content-items/:id/send-push | Admin + Content_UPDATE | path: id (int) | SendPushResponseDto | Send push notification |
| GET | /api/admin/content-items/:id/preview | Admin + Content_READ | path: id (int) | ContentItemPreviewDto | Preview content (unpublished items) |
| GET | /api/admin/content-items/:id/full | Admin + Content_READ | path: id (int) | ContentItemFullDto | Full content for admin review |
| GET | /api/admin/content-featured | Admin + Content_READ | QueryFeaturedContentAdminDto "" page?, size?, pagination?, isActive?, sort?, order? "" | FeaturedContentAdminListDto[] paginated | List featured content |
| GET | /api/admin/content-featured/:id | Admin + Content_READ | path: id (int) | FeaturedContentAdminDetailDto | Get featured content detail |
| POST | /api/admin/content-featured | Admin + Content_CREATE | CreateFeaturedContentDto "" contentItemId: number, startDate: string, endDate: string, order?: number "" | FeaturedContentResponseDto | Create featured content entry |
| PATCH | /api/admin/content-featured/:id | Admin + Content_UPDATE | path: id (int), body: UpdateFeaturedContentDto "" startDate?: string, endDate?: string, order?: number, isActive?: boolean "" | FeaturedContentResponseDto | Update featured content |
| DELETE | /api/admin/content-featured/:id | Admin + Content_DELETE | path: id (int) | DeleteResponseDto | Remove featured content |
| POST | /api/admin/content-featured/reorder | Admin + Content_UPDATE | body: ReorderFeaturedContentDto "" ids: number[] "" | ReorderResponseDto | Reorder featured items |
| GET | /api/content/series | Public | QueryContentSeriesCustomerDto "" page?, size?, pagination?, type?, sort?, order? "" | ContentSeriesListItemDto[] paginated | List active series |
| GET | /api/content/series/:slug | Public | path: slug (string) | ContentSeriesDetailDto | Get series by slug with item count |
| GET | /api/content/items | Public | QueryContentItemsCustomerDto "" page?, size?, pagination?, seriesId?, seriesType?, search?, sort?, order? "" | ContentItemListItemDto[] paginated | List published items |
| GET | /api/content/items/:slug | Public | path: slug (string) | ContentItemPreviewDto | Preview mode (entitlement not required) |
| GET | /api/content/items/:slug/full | User JWT | path: slug (string) | ContentItemFullDto | Full content (requires entitlement) |
| GET | /api/content/my-library | User JWT | QueryMyLibraryDto "" page?, size?, pagination?, sort?, order? "" | MyLibraryItemDto[] paginated | User's subscribed content |
| GET | /api/content/items/featured | Public | FeaturedContentListDto[] | Active featured items (ordered) | |
| GET | /api/content/items/featured/all | Public | QueryFeaturedContentCustomerDto "" page?, size?, pagination?, isActive?, sort?, order? "" | FeaturedContentListDto[] paginated | All featured items paginated |
| POST | /api/content/items/:id/bookmark | User JWT | path: id (int), body: BookmarkContentDto "" isBookmarked: boolean "" | BookmarkResponseDto | Toggle bookmark on content |
| GET | /api/content/bookmarks | User JWT | QueryBookmarksDto "" page?, size?, pagination? "" | BookmarkListDto[] paginated | User's bookmarked items |
| POST | /api/content/items/:id/track-progress | User JWT | path: id (int), body: TrackProgressDto "" progress: number (0-100) "" | ProgressResponseDto | Track reading progress |
| GET | /api/content/progress/:contentId | User JWT | path: contentId (int) | ProgressDetailDto | Get user's progress for content |
11.1.1 Customer List Endpoint Query Variations
| Method | Path | Query Parameters | Notes |
|---|---|---|---|
| GET | /api/content/items | (no params) | Default: page=1, size=20, sort=publishedAt, order=desc |
| GET | /api/content/items | ?page=2&size=10 | Custom page and size |
| GET | /api/content/items | ?seriesId=5 | Filter by series |
| GET | /api/content/items | ?seriesType=newsletter | Filter by series type |
| GET | /api/content/items | ?search=market | Search title/content |
| GET | /api/content/items | ?sort=title&order=asc | Sort by title ascending |
| GET | /api/content/items | ?pagination=false | Returns all records |
11.1.2 Admin List Endpoint Query Variations
| Method | Path | Query Parameters | Notes |
|---|---|---|---|
| GET | /api/admin/content-items | (no params) | Default pagination |
| GET | /api/admin/content-items | ?seriesId=5&status=published | Filter by series + status |
| GET | /api/admin/content-items | ?status=draft | Filter by draft status |
| GET | /api/admin/content-items | ?previewEnabled=true | Filter by preview enabled |
| GET | /api/admin/content-items | ?search=quarterly | Search content items |
| GET | /api/admin/content-series | ?type=newsletter&isActive=true | Filter series |
11.1.3 Content Item Create/Update Variations
| Method | Path | Request Body | Notes |
|---|---|---|---|
| POST | /api/admin/content-items | "" "seriesId": 1, "title": "Q1 Update", "contentBody": "...", "status": "draft" "" | Create draft item |
| POST | /api/admin/content-items | "" "seriesId": 1, "title": "Q1 Update", "contentBody": "...", "status": "published" "" | Create and publish |
| POST | /api/admin/content-items/:id/publish | "" | Publish immediately |
| POST | /api/admin/content-items/:id/publish | "" "scheduledAt": "2026-04-15T09:00:00.000Z" "" | Schedule publish |
| POST | /api/admin/content-items/:id/unpublish | "" | Hide published item |
| PATCH | /api/admin/content-items/:id | "" "title": "Updated Title" "" | Partial update |
11.1.4 Tier and Access Variations
| Method | Path | Body | Notes |
|---|---|---|---|
| POST | /api/admin/content-series/1/tier | "" "tier": "premium" "" | Require premium for series |
| POST | /api/admin/content-items/1/tier | "" "tier": "basic" "" | Override series tier for item |
| POST | /api/admin/content-items/1/tier | "" "tier": null "" | Use series default tier |
11.1.5 Newsletter Send Variations
| Method | Path | Body | Notes |
|---|---|---|---|
| POST | /api/admin/content-items/1/send-email | "" "recipientFilter": "all" "" | Send to all subscribers |
| POST | /api/admin/content-items/1/send-email | "" "recipientFilter": "premium" "" | Send to premium only |
| POST | /api/admin/content-items/1/send-push | "" | Send push to all users |
11.1.6 Featured Content Variations
| Method | Path | Body | Notes |
|---|---|---|---|
| POST | /api/admin/content-featured | "" "contentItemId": 5, "startDate": "2026-04-01", "endDate": "2026-04-30", "order": 1 "" | Create featured |
| POST | /api/admin/content-featured/reorder | "" "ids": [3, 1, 5, 2] "" | Reorder featured items |
| PATCH | /api/admin/content-featured/1 | "" "isActive": false "" | Deactivate featured |
11.1.7 Error Response Variations
| HTTP | errorCode | Message | Scenario |
|---|---|---|---|
| 400 | CONTENT_STATUS_INVALID | Invalid status transition. | Publish archived item |
| 400 | CONTENT_ARCHIVED_NOT_PUBLISHABLE | Cannot publish archived content. | Attempt publish on archived |
| 400 | CONTENT_SCHEDULED_AT_INVALID | Invalid scheduled date. | Invalid timestamp format |
| 400 | CONTENT_SERIES_CREATE_FAILED | Failed to create content series. | DB constraint failure |
| 400 | CONTENT_NOT_PUBLISHED | Content is not published. | Send unpublished newsletter |
| 400 | FEATURED_CONTENT_DUPLICATE_IDS | Duplicate IDs provided. | Reorder with duplicates |
| 400 | FEATURED_CONTENT_INVALID_DATE_RANGE | Start date after end date. | Invalid date window |
| 403 | CONTENT_ACCESS_DENIED | Access denied. | Full content without entitlement |
| 404 | CONTENT_NOT_FOUND | Content item not found. | Invalid content ID |
| 404 | CONTENT_SERIES_NOT_FOUND | Content series not found. | Invalid series ID |
11.1.8 Cache Key Patterns
| Cache Key | Pattern | TTL (seconds) | Invalidation |
|---|---|---|---|
| Customer series list | content:customer:series:list:pagination:"page"-size:"size"-type:"type"-sort:"sort"-order:"order" | 300 | Series write |
| Customer series detail | content:customer:series:slug:"slug" | 300 | Series update |
| Customer items list | content:customer:items:list:pagination:"page"-size:"size"-seriesId:"seriesId"-sort:"sort" | 120 | Item publish/unpublish |
| Customer item preview | content:customer:item:slug:"slug":preview | 300 | Item update |
| Customer my library | content:customer:library:user:"userId":pagination:"page"-size:"size" | 60 | User subscribes/access changes |
| Featured active | content:featured:customer:active | 300 | Featured reorder/change |
| Featured all paginated | content:featured:customer:all:pagination:"page"-size:"size" | 300 | Featured change |
11.1.9 Rate Limit Patterns
| Route Family | Key Pattern | Limit |
|---|---|---|
| Customer series list | rl:content:customer:series:list-key:"userId" | 60 req/min |
| Customer items list | rl:content:customer:items:list-key:"userId" | 60 req/min |
| Customer full content | rl:content:customer:full-key:"userId" | 30 req/min |
| Customer my-library | rl:content:customer:library-key:"userId" | 30 req/min |
| Admin content mutations | rl:content:admin:action:mutate-key:"adminId" | 20 req/min |
11.1.10 DTO Field Reference
CreateContentSeriesDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| name | string | Yes | 1-200 chars | Series name |
| type | enum | Yes | "newsletter" | Series type |
| slug | string | No | auto-generated if null | URL slug |
| description | string | No | max 2000 chars | Series description |
| isActive | boolean | No | default true | Active status |
CreateContentItemDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| seriesId | number | Yes | positive integer | Parent series |
| title | string | Yes | 1-500 chars | Item title |
| slug | string | No | auto-generated if null | URL slug |
| contentBody | string | Yes | min 1 char | Full content HTML/markdown |
| previewEnabled | boolean | No | default true | Enable preview |
| previewMode | enum | No | default "paragraphs" | Preview generation mode |
| status | enum | No | default "draft" | Initial status |
PublishContentItemDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| scheduledAt | string | No | ISO-8601 datetime | Schedule future publish |
SetContentItemTierDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| tier | enum | Yes | "basic"|"premium"|null | Required tier (null = use series) |
SendContentEmailDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| recipientFilter | enum | Yes | "all"|"basic"|"premium" | Target recipient segment |
CreateFeaturedContentDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| contentItemId | number | Yes | positive integer | Content to feature |
| startDate | string | Yes | ISO-8601 date | Feature start |
| endDate | string | Yes | ISO-8601 date | Feature end (after start) |
| order | number | No | positive integer | Display order |
QueryContentSeriesCustomerDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| page | number | No | default 1 | Page number |
| size | number | No | default 20, max 100 | Page size |
| pagination | boolean | No | default true | Enable pagination |
| type | enum | No | "newsletter" | Filter by type |
| sort | enum | No | default "createdAt" | Sort field |
| order | enum | No | default "desc" | Sort direction |
QueryContentItemsCustomerDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
| page | number | No | default 1 | Page number |
| size | number | No | default 20, max 100 | Page size |
| pagination | boolean | No | default true | Enable pagination |
| seriesId | number | No | positive integer | Filter by series |
| seriesType | enum | No | "newsletter" | Filter by series type |
| search | string | No | trimmed string | Search with similarity |
| sort | enum | No | default "publishedAt" | title|publishedAt|createdAt |
| order | enum | No | default "desc" | asc|desc |
12. Testing Scenarios
12.1 Suggested Test Scenarios
- Admin creates content series with tier rule (
premium), publishes item, and premium user accesses full content via/items/:slug/full. - Basic tier user accesses premium-series item and receives
403 CONTENT_ACCESS_DENIED. - Public user accesses preview (
GET /api/content/items/:slug) without authentication, content unlocks. - Admin publishes item with
scheduledAtin future, system publishes automatically at scheduled time. - Admin sends newsletter to
premiumrecipients only and email delivers to premium subscribers. - Featured content appears in ordered list (
GET /api/content/items/featured) within date window. - Admin reorders featured items via
POST /featured/reorder, new order reflects in customer list. - Full content endpoint returns
429rate limit exceeded after sliding window threshold.
See Also
- Feature Guide: See Content - Feature List Section 6 (State Models) for content lifecycle and tier gating diagrams.
- Backend Reference: See Content - Backend Documentation Section 9 for system architecture diagram.
Time fields in this module are stored as timezone-aware values and should be handled as ISO-8601 instants by API consumers.