Shop It Docs
Developer ResourcesContent

Content Module API & Integration Guide

Admin and public contracts for section-wise CMS payloads with Tiptap JSON validation.

Audience: Frontend/admin-panel engineers and backend integrators Scope: Content page metadata, section payload CRUD, public/mobile page reads

Content Module - API & Integration Guide

1. Quick Metadata

  • Module: Content
  • Auth models:
    • Admin routes: JwtAuthGuard + RoleGuard + @Permissions(...)
    • Public route: @Public()
  • Base routes:
    • Admin: /api/admin/content/pages
    • Public: /api/content/pages
    • Mobile mirror: /api/mobile/content/pages
  • Response envelope: ResponseDto<T>
  • Swagger tags:
    • Content Pages (Admin)
    • Content Pages

1.1 Read Caching (Redis)

Content read endpoints are cache-aside:

  • GET /api/content/pages/:pageKey and GET /api/mobile/content/pages/:pageKey use public keyspace content:page:public:
  • GET /api/admin/content/pages/:pageKey uses admin keyspace content:page:admin:

TTL envs:

  • CONTENT_PAGE_PUBLIC_CACHE_TTL_SECONDS (default 300)
  • CONTENT_PAGE_ADMIN_CACHE_TTL_SECONDS (default 120)

Invalidation:

  • successful PATCH /meta, PUT /sections/:sectionKey, and DELETE /sections/:sectionKey invalidate both admin and public cache keys for that page.
  • Redis failures do not fail API responses; service falls back to DB flow and logs warnings.

2. Page and Section Keys

2.1 Page keys

  • home
  • about
  • faq
  • privacy_policy
  • terms_of_service
  • products_thangkas
  • products_singing_bowls
  • products_statues
  • products_jewellery
  • global_footer

2.2 Section keys

  • hero
  • top_picks
  • categories
  • heritage
  • why_us
  • faq_intro
  • blogs
  • quote
  • why_choose
  • story_blocks
  • products_header
  • blogs_header
  • brand_block
  • quick_links
  • collections_links
  • support_links
  • featured_collections_cards
  • copyright
  • document

2.3 Page-to-section compatibility

Page keyAllowed sections
homehero, top_picks, categories, heritage, why_us, faq_intro, blogs
abouthero, quote, why_choose, story_blocks, faq_intro, blogs
faqfaq_intro
privacy_policydocument
terms_of_servicedocument
products_thangkashero, products_header, blogs_header
products_singing_bowlshero, products_header, blogs_header
products_statueshero, products_header, blogs_header
products_jewelleryhero, products_header, blogs_header
global_footerbrand_block, quick_links, collections_links, support_links, featured_collections_cards, copyright

Invalid pair behavior:

  • HTTP 400
  • errorCode CONTENT_SECTION_KEY_INVALID

3. Admin Endpoints

3.1 Get page (admin)

AspectValue
MethodGET
Path/api/admin/content/pages/:pageKey
AuthJwtAuthGuard + RoleGuard
PermissionContent_READ
Throttle30/minute
ResponseResponseDto<ContentPageResponseDto>

Behavior:

  • auto-creates page if missing (with module default title)
  • returns all sections, including isEnabled=false

3.2 Update page meta

AspectValue
MethodPATCH
Path/api/admin/content/pages/:pageKey/meta
PermissionContent_UPDATE
Throttle10/minute
BodyUpdateContentPageMetaDto
ResponseResponseDto<ContentPageResponseDto>

Body fields:

  • title?: string (max 255)
  • seoId?: string | null (UUID or null)

SEO validation:

  • if non-null seoId does not exist -> 400 CONTENT_SEO_NOT_FOUND

3.3 Upsert section

AspectValue
MethodPUT
Path/api/admin/content/pages/:pageKey/sections/:sectionKey
PermissionContent_UPDATE
Throttle10/minute
BodyUpsertContentSectionDto
ResponseResponseDto<ContentSectionResponseDto>

Body fields:

  • payloadJson: Record<string, unknown> (required)
  • position?: number (default 0, min 0)
  • isEnabled?: boolean (default true)

Behavior:

  • validates sectionKey compatibility with pageKey
  • validates payloadJson against section schema
  • auto-creates page if missing
  • upserts by unique (pageId, sectionKey)

3.4 Delete section

AspectValue
MethodDELETE
Path/api/admin/content/pages/:pageKey/sections/:sectionKey
PermissionContent_DELETE
Throttle10/minute
ResponseResponseDto<void>

Behavior:

  • page must already exist
  • section row must already exist
  • otherwise returns 404 CONTENT_NOT_FOUND

4. Public + Mobile Endpoints

4.1 Get page (public)

AspectValue
MethodGET
Path/api/content/pages/:pageKey
AuthPublic
ResponseResponseDto<ContentPageResponseDto>

Behavior:

  • does not auto-create pages
  • missing page -> 404 CONTENT_NOT_FOUND
  • returns only enabled sections
  • ordered by position ASC, then id ASC

4.2 Get page (mobile-composed)

AspectValue
MethodGET
Path/api/mobile/content/pages/:pageKey
AuthPublic
ResponseResponseDto<ContentPageResponseDto>

Behavior is identical to /api/content/pages/:pageKey.

5. DTO Contract

5.1 ContentPageResponseDto

{
  "id": 1,
  "pageKey": "home",
  "title": "Home Page",
  "seoId": "018f3c98-8dcf-7b1c-a8b9-c32b3f6de101",
  "seo": {
    "id": "018f3c98-8dcf-7b1c-a8b9-c32b3f6de101",
    "metaTitle": "Thangka Home",
    "metaDescription": "Authentic handmade thangkas.",
    "metaKeywords": "thangka,nepal,art",
    "canonicalUrl": "https://thangka.shop",
    "robotsIndex": true,
    "robotsFollow": true,
    "robotsAdvanced": null,
    "ogTitle": "Thangka Home",
    "ogDescription": "Authentic handmade thangkas.",
    "ogType": "website",
    "ogUrl": "https://thangka.shop",
    "ogImageUrl": "https://thangka.shop/og-home.jpg",
    "ogSiteName": "Thangka",
    "twitterCard": "summary_large_image",
    "twitterSite": "@thangka",
    "twitterCreator": "@thangka",
    "twitterTitle": "Thangka Home",
    "twitterDescription": "Authentic handmade thangkas.",
    "twitterImageUrl": "https://thangka.shop/twitter-home.jpg",
    "alternates": null,
    "structuredDataJsonLd": null
  },
  "sections": [],
  "createdAt": "2026-05-15T12:00:00.000Z",
  "updatedAt": "2026-05-15T12:15:00.000Z"
}

5.2 ContentSectionResponseDto

{
  "id": 11,
  "sectionKey": "hero",
  "position": 0,
  "isEnabled": true,
  "payloadJson": {
    "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Handcrafted" }] }] },
    "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Sacred Art from Nepal" }] }] },
    "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Traditional lineage, modern delivery." }] }] }
  },
  "createdAt": "2026-05-15T12:00:00.000Z",
  "updatedAt": "2026-05-15T12:15:00.000Z"
}

6. Validation Rules

6.1 Display text fields

Display text is Tiptap JSON and must look like:

{
  "type": "doc",
  "content": [
    {
      "type": "paragraph",
      "content": [{ "type": "text", "text": "Example" }]
    }
  ]
}

Rules:

  • type must be doc
  • content must be non-empty

6.2 URL fields

Allowed:

  • https://...
  • http://...
  • /relative-path

Rejected:

  • ftp://...
  • javascript:...
  • empty strings

6.3 Field-size constraints

Field classLimit
label (rich)60 chars equivalent
heading (rich)110 chars equivalent
description (rich)320 chars equivalent
card title (rich)60 chars equivalent
card description (rich)220 chars equivalent
image alt255 chars
iconKey80 chars

7. Section Payload Schemas and Examples

All examples below are valid payloadJson values for PUT /sections/:sectionKey.

7.1 hero

Required:

  • label (Tiptap)
  • heading (Tiptap)
  • description (Tiptap)

Optional:

  • images.main/top/bottom (src, alt)
  • cta (label Tiptap, href safe URL)
{
  "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Authentic" }] }] },
  "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Handmade Thangkas" }] }] },
  "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Rooted in Himalayan lineage." }] }] },
  "images": {
    "main": { "src": "https://cdn.example.com/hero-main.webp", "alt": "Main hero" },
    "top": { "src": "/images/hero-top.webp", "alt": "Top accent" },
    "bottom": { "src": "/images/hero-bottom.webp", "alt": "Bottom accent" }
  },
  "cta": {
    "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Explore" }] }] },
    "href": "/products/thangkas"
  }
}

7.2 top_picks and categories

Required:

  • heading (Tiptap)

Optional:

  • label (Tiptap)
  • description (Tiptap)
{
  "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Curated" }] }] },
  "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Top Picks" }] }] },
  "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Most loved this month." }] }] }
}

7.3 heritage

Required:

  • label, heading, description, quote (all Tiptap)

Optional:

  • images.main/collageTop/collageBottom/logo
{
  "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Heritage" }] }] },
  "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Tradition in Every Stroke" }] }] },
  "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Each piece follows traditional iconography." }] }] },
  "quote": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Art as devotion." }] }] },
  "images": {
    "main": { "src": "https://cdn.example.com/heritage-main.webp", "alt": "Heritage main" },
    "collageTop": { "src": "/images/heritage-top.webp", "alt": "Collage top" },
    "collageBottom": { "src": "/images/heritage-bottom.webp", "alt": "Collage bottom" },
    "logo": { "src": "/images/heritage-logo.svg", "alt": "Heritage logo" }
  }
}

7.4 why_us and why_choose

Required:

  • heading (Tiptap)
  • cards[] length 1..12

Card schema:

  • title (Tiptap)
  • description (Tiptap)
  • iconKey (string)
{
  "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Why Us" }] }] },
  "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Trust by Design" }] }] },
  "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Authenticity, care, and provenance." }] }] },
  "cards": [
    {
      "title": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Verified Craft" }] }] },
      "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Direct from lineage artists." }] }] },
      "iconKey": "verified-craft"
    }
  ]
}

7.5 faq_intro and blogs

faq_intro required:

  • label, heading, description

blogs required:

  • heading

Both can include optional CTA.

{
  "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "FAQ" }] }] },
  "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Questions Answered" }] }] },
  "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Everything about shipping and authenticity." }] }] },
  "cta": {
    "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Read More" }] }] },
    "href": "/faq"
  }
}

7.6 quote

Required:

  • text (Tiptap)

Optional:

  • author (Tiptap)
{
  "text": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "A thangka is meditation made visible." }] }] },
  "author": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "House of Thangka" }] }] }
}

7.7 story_blocks

Required:

  • blocks[] length 1..10

Block schema:

  • label, heading, description (Tiptap)
  • image (src, alt)

Optional:

  • overlayLines[] (up to 6 Tiptap lines)
  • reverse (boolean)
{
  "blocks": [
    {
      "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Mission" }] }] },
      "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Preserve Living Traditions" }] }] },
      "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "We partner with craftsmen to sustain lineage work." }] }] },
      "image": { "src": "https://cdn.example.com/about-mission.webp", "alt": "Mission image" },
      "overlayLines": [
        { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Craft" }] }] },
        { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Culture" }] }] }
      ],
      "reverse": false
    }
  ]
}

7.8 products_header and blogs_header

Required:

  • heading (Tiptap)

Optional:

  • label, description, cta
{
  "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Category" }] }] },
  "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Featured Thangkas" }] }] },
  "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Handpicked by masters." }] }] },
  "cta": {
    "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "View All" }] }] },
    "href": "/products/thangkas"
  }
}

brand_block

Required:

  • heading, tagline (Tiptap)

Optional:

  • legalLabel (Tiptap)
{
  "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Thangka" }] }] },
  "tagline": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Authentic Himalayan art." }] }] },
  "legalLabel": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "All rights reserved" }] }] }
}

Required:

  • title (Tiptap)
  • items[] length 1..20 with { label: Tiptap, href: safeUrl }
{
  "title": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Quick Links" }] }] },
  "items": [
    {
      "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "About" }] }] },
      "href": "/about"
    },
    {
      "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Contact" }] }] },
      "href": "/contact"
    }
  ]
}

Required:

  • items[] length 1..12

Item schema:

  • label (Tiptap)
  • href (safe URL)

Optional:

  • title (Tiptap)
{
  "title": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Featured" }] }] },
  "items": [
    {
      "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Green Tara" }] }] },
      "href": "/products/thangkas?collection=green-tara"
    }
  ]
}

Required:

  • text (Tiptap)
{
  "text": {
    "type": "doc",
    "content": [
      {
        "type": "paragraph",
        "content": [{ "type": "text", "text": "© 2026 Thangka. All rights reserved." }]
      }
    ]
  }
}

Used by:

  • privacy_policy
  • terms_of_service

Required:

  • eyebrow (string)
  • title (string)
  • summary (string)
  • lastUpdated (string)
  • sections[] (length 1..50)

sections[] item:

  • id (kebab-case string)
  • title (string)
  • paragraphs[] (length 1..20)
  • optional bullets[] (length 1..40)
{
  "eyebrow": "Privacy",
  "title": "Privacy Policy",
  "summary": "We respect your privacy and protect your personal information.",
  "lastUpdated": "Last updated: April 1, 2026",
  "sections": [
    {
      "id": "information-we-collect",
      "title": "1. Information We Collect",
      "paragraphs": [
        "We collect account and order details needed to process purchases.",
        "We also collect limited usage telemetry to improve reliability."
      ],
      "bullets": [
        "Name and email",
        "Shipping details",
        "Order metadata"
      ]
    }
  ]
}

8. End-to-End Flow Examples

8.1 Initial admin authoring flow

  1. Admin fetches page:
    • GET /api/admin/content/pages/home
  2. Page row is auto-created if missing.
  3. Admin writes each section with PUT /sections/:sectionKey.
  4. Frontend can consume from public endpoint immediately.

8.2 SEO attach/clear flow

Attach:

  • PATCH /api/admin/content/pages/home/meta with {"seoId": "<uuid>"}

Clear:

  • PATCH /api/admin/content/pages/home/meta with {"seoId": null}

8.3 Hide section flow

Update section:

  • PUT /api/admin/content/pages/home/sections/why_us with isEnabled: false

Result:

  • section still visible in admin page response
  • section omitted from public/mobile response

9. Error Code Mapping

errorCodeTypical HTTPCause
CONTENT_NOT_FOUND404Missing page on public read, or missing page/section on delete
CONTENT_SECTION_KEY_INVALID400Section key not allowed for selected page
CONTENT_SECTION_PAYLOAD_INVALID400Payload schema invalid (Tiptap/URL/shape/constraints)
CONTENT_SEO_NOT_FOUND400seoId not found in SEO table
RATE_LIMIT_EXCEEDED429Admin endpoint throttling exceeded

10. Request/Response Examples

10.1 Admin get page

GET /api/admin/content/pages/about

{
  "message": "Content page fetched successfully",
  "data": {
    "id": 2,
    "pageKey": "about",
    "title": "About Page",
    "seoId": null,
    "seo": null,
    "sections": [
      {
        "id": 14,
        "sectionKey": "hero",
        "position": 0,
        "isEnabled": true,
        "payloadJson": {
          "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "About" }] }] },
          "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Our Story" }] }] },
          "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Rooted in tradition." }] }] }
        },
        "createdAt": "2026-05-15T12:00:00.000Z",
        "updatedAt": "2026-05-15T12:00:00.000Z"
      }
    ],
    "createdAt": "2026-05-15T12:00:00.000Z",
    "updatedAt": "2026-05-15T12:00:00.000Z"
  }
}

10.2 Public get page

GET /api/content/pages/about

{
  "message": "Content page fetched successfully",
  "data": {
    "id": 2,
    "pageKey": "about",
    "title": "About Page",
    "seoId": null,
    "seo": null,
    "sections": [
      {
        "id": 14,
        "sectionKey": "hero",
        "position": 0,
        "isEnabled": true,
        "payloadJson": {
          "label": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "About" }] }] },
          "heading": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Our Story" }] }] },
          "description": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Rooted in tradition." }] }] }
        },
        "createdAt": "2026-05-15T12:00:00.000Z",
        "updatedAt": "2026-05-15T12:00:00.000Z"
      }
    ],
    "createdAt": "2026-05-15T12:00:00.000Z",
    "updatedAt": "2026-05-15T12:00:00.000Z"
  }
}

10.3 Validation failure response

PUT /api/admin/content/pages/home/sections/hero with invalid payload:

{
  "payloadJson": {
    "heading": "plain-string-not-tiptap"
  }
}

Typical response:

{
  "statusCode": 400,
  "errorCode": "CONTENT_SECTION_PAYLOAD_INVALID",
  "message": "Invalid payload for section 'hero': ..."
}

11. Frontend Integration Notes

Recommended frontend pattern:

  1. Resolve page key by route.
  2. Call /api/content/pages/:pageKey.
  3. If 404 CONTENT_NOT_FOUND, load local fallback content.
  4. Use runtime feature APIs for lists:
    • products for top_picks / category product grid
    • FAQ API for FAQ items
    • blog API for blog cards
  5. Render sections in backend-provided order.

12. Environment and Runtime Notes

No new environment variables are required by the Content module itself.

Dependencies:

  • database availability
  • permission seed consistency
  • SEO table availability for metadata linking

13. API Release Checklist

  • Admin role has Content_READ/UPDATE/DELETE permissions.
  • Client page keys match backend enums exactly.
  • Admin panel sends valid Tiptap JSON docs.
  • Link/image URLs satisfy safe URL validation.
  • Public fallback behavior for CONTENT_NOT_FOUND is implemented.
  • Mobile app uses /api/mobile/content/pages/:pageKey route where needed.

14. Endpoint Summary Table

MethodPathPermissionNotes
GET/api/admin/content/pages/:pageKeyContent_READAuto-create page if missing
PATCH/api/admin/content/pages/:pageKey/metaContent_UPDATEUpdates title and/or seoId
PUT/api/admin/content/pages/:pageKey/sections/:sectionKeyContent_UPDATEUpserts payload + position + visibility
DELETE/api/admin/content/pages/:pageKey/sections/:sectionKeyContent_DELETEDeletes existing section row
GET/api/content/pages/:pageKeyPublicEnabled sections only
GET/api/mobile/content/pages/:pageKeyPublicMobile-composed mirror

15. Integration Diagram

Time fields in this module are stored as timezone-aware values and should be handled as ISO-8601 instants by API consumers.


See Also