Skip to content

Content API

The Content API provides access to published content items including articles, pages, recipes, hero banners, homepages, and more.

Endpoints

MethodEndpointDescription
GET/api/v1/public/contentList published content items
GET/api/v1/public/content/{slug}Get a content item by slug

List Content

Retrieve a paginated list of published content items for your organization.

http
GET /api/v1/public/content

Query Parameters

ParameterTypeDefaultDescription
per_pageinteger20Number of items per page (1-100)
pageinteger1Page number
typestring|array-Filter by content type (single or multiple)
tagsarray-Filter by tags (OR logic)
categorystring-Filter by category (max 100 chars)
featuredboolean-Filter featured items only
is_primaryboolean-Filter primary items only (useful for homepage)
location_idstring-Location hashkey (org_xxxxx) for multi-location content distribution
placementstring-Filter heroes by placement (home, page, landing). Filters on metadata.placement.
includestring-Comma-separated list of additional fields to include (content, metadata)
expandstring-Comma-separated list of expansion types (linked_content)

Content Types

The type parameter accepts the following values:

TypeDescription
pageStatic pages
articleBlog posts, news articles, and informational content
recipeRecipes with ingredients and instructions
menuMenu pages
heroHero banner content
landingLanding pages
homepagePrimary site homepages (supports multiple variants)
circularDigital circular content

Custom types

The type parameter also accepts any organization-defined custom type type_key (see Custom Types), not just the built-in values above. A custom type's field values are returned under metadata.custom_fields — request them with include=metadata on this list endpoint (the /{slug} endpoint always includes metadata).

Type Filtering

The type parameter supports both single values and arrays for filtering multiple content types at once.

Single type (string):

http
GET /api/v1/public/content?type=article

Multiple types (array):

http
GET /api/v1/public/content?type[]=article&type[]=recipe&type[]=page

Returns content matching any of the specified types (OR logic).

Tags Filtering

Tags use OR logic - content matching any provided tag will be returned:

http
GET /api/v1/public/content?tags[]=summer&tags[]=sale

Returns content tagged with "summer" OR "sale".

Homepage Filtering

To retrieve the active (primary) homepage:

http
GET /api/v1/public/content?type=homepage&is_primary=true

Organizations can have multiple homepage variants (seasonal, A/B tests), but only one can be marked as primary at a time. Use is_primary=true to fetch the currently active homepage.

Hero Filtering by Placement

Heroes can be filtered server-side by their intended placement using the placement query parameter. This allows you to distinguish between heroes created for the home page carousel versus heroes intended for other content pages.

Available placement values:

ValueDescription
homeHeroes intended for the home page carousel
pageHeroes for general content pages
landingHeroes for landing pages

Get all home page heroes:

http
GET /api/v1/public/content?type=hero&placement=home&include=content

Example: Fetching home page hero carousel

javascript
const response = await fetch(
  'https://cdn.engagehq.retailsuccessplatform.com/api/v1/public/content?type=hero&placement=home&include=content',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  }
);

const { data } = await response.json();

// Get slides from all home heroes for carousel
const carouselSlides = data.flatMap(hero => hero.content?.slides || []);

Default Placement

Heroes created without an explicit placement default to home. This ensures backward compatibility with existing hero content.

Selective Field Expansion

By default, the list endpoint returns minimal fields optimized for listings. Use the include parameter to request additional fields without making a second API call.

Available include values:

  • content - The full content blocks (useful for menus, banners)
  • metadata - Additional metadata (author, reading time, etc.)

Include content only:

http
GET /api/v1/public/content?type=menu&include=content

Include metadata only:

http
GET /api/v1/public/content?type=article&include=metadata

Include both fields:

http
GET /api/v1/public/content?type=menu&include=content,metadata

This is particularly useful for content types like menus where you need the full content structure but want to avoid a second API call for each item.

CDN Caching

Requests with different include values are cached separately by the CDN via CloudFront cache policies. All variations are automatically invalidated when content is published.

Linked Content Expansion

Content sections can reference other content items (e.g., a hero section linking to a reusable hero content item). Use the expand parameter to resolve these linked references inline, eliminating the need for additional API calls.

Available expand values:

  • linked_content - Resolves linked content references within sections

Expand linked content:

http
GET /api/v1/public/content?type=page&include=content&expand=linked_content

Requires include=content

The expand=linked_content parameter only works when include=content is also specified, as linked content exists within the content structure.

When a page contains a hero section that links to a separate hero content item:

Without expansion (default):

json
{
  "content": {
    "sections": [{
      "type": "hero",
      "content": {
        "source_mode": "linked",
        "linked_hero_id": "con_00000a1B2c3D4e5"
      }
    }]
  }
}

With expand=linked_content:

json
{
  "content": {
    "sections": [{
      "type": "hero",
      "content": {
        "source_mode": "linked",
        "linked_hero_id": "con_00000a1B2c3D4e5",
        "_linked_content": {
          "id": "con_00000a1B2c3D4e5",
          "title": "Summer Sale Hero",
          "type": "hero",
          "status": "published",
          "content": {
            "slides": [
              {
                "headline": "Summer Sale",
                "subheadline": "Up to 50% off",
                "background_image": "https://cdn.example.com/hero-bg.jpg"
              }
            ]
          },
          "published_at": "2024-06-01T00:00:00+00:00"
        },
        "slides": [
          {
            "headline": "Summer Sale",
            "subheadline": "Up to 50% off",
            "background_image": "https://cdn.example.com/hero-bg.jpg"
          }
        ]
      }
    }]
  }
}

Expanded Fields

FieldTypeDescription
_linked_contentobjectFull linked content item data
_linked_content.idstringLinked content hashkey
_linked_content.titlestringLinked content title
_linked_content.typestringContent type of linked item
_linked_content.statusstringPublication status
_linked_content.contentobjectFull content structure
_linked_content.published_atstringISO 8601 publish timestamp
slidesarrayConvenience field with slides array merged directly into content (hero sections only)

Error Handling

If a linked content reference cannot be resolved, an error indicator is included:

json
{
  "content": {
    "sections": [{
      "type": "hero",
      "content": {
        "source_mode": "linked",
        "linked_hero_id": "con_00000invalid",
        "_expansion_error": "not_found"
      }
    }]
  }
}
ErrorDescription
not_foundLinked content item doesn't exist or isn't published
circular_referenceCircular reference detected (content links to itself)
fetch_errorServer error while fetching linked content

Performance

Use expand=linked_content judiciously. Each linked reference requires an additional database query. For pages with many linked sections, consider caching the expanded response client-side.

Example Request

javascript
const params = new URLSearchParams({
  type: 'article',
  featured: 'true',
  per_page: '10',
});

const response = await fetch(
  `https://cdn.engagehq.retailsuccessplatform.com/api/v1/public/content?${params}`,
  {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  }
);

Response

json
{
  "success": true,
  "message": "Content retrieved",
  "data": [
    {
      "id": "con_00000k1L2m3N4o5",
      "type": "article",
      "slug": "summer-sale-announcement",
      "title": "Summer Sale Announcement",
      "description": "Get ready for our biggest summer sale event of the year...",
      "meta_title": "Summer Sale 2024 | Acme Grocery",
      "meta_description": "Shop our summer sale with discounts up to 50%",
      "tags": ["summer", "sale", "promotions"],
      "category": "promotions",
      "media_ids": ["med_00000x1Y2z3A4b5"],
      "published_at": "2024-06-01T00:00:00+00:00",
      "is_featured": true,
      "is_primary": false
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 3,
    "per_page": 10,
    "total": 28,
    "links": [
      { "url": null, "label": "« Previous", "active": false },
      { "url": "...", "label": "1", "active": true },
      { "url": "...", "label": "2", "active": false },
      { "url": "...", "label": "Next »", "active": false }
    ]
  }
}

Response Fields (List)

FieldTypeDescription
idstringUnique content identifier (hashkey)
typestringContent type
slugstringURL-friendly identifier
titlestringDisplay title
descriptionstringShort description/excerpt
meta_titlestringSEO meta title
meta_descriptionstringSEO meta description
tagsarrayArray of tag strings
categorystringContent category
media_idsarrayArray of associated media hashkeys
published_atstringISO 8601 publish timestamp
is_featuredbooleanWhether content is featured
is_primarybooleanWhether this is the primary content for its type (e.g., active homepage)

Optional Fields (via include parameter)

These fields are only returned when explicitly requested using the include query parameter:

FieldInclude ValueTypeDescription
contentcontentarrayFull content blocks (see Schemas)
metadatametadataobjectType-specific metadata (see below)

Metadata by Content Type

When include=metadata is specified, the metadata object contains type-specific fields:

Articles (article):

FieldTypeDescription
authorstringAuthor name
excerptstringBrief summary for listings
reading_timeintegerEstimated minutes to read

Recipes (recipe):

FieldTypeDescription
prep_timestringPreparation time in minutes
cook_timestringCooking time in minutes
servingsstringNumber of servings
difficultystringDifficulty level (easy, medium, hard)
cuisinestringType of cuisine
diet_typesarrayDietary categories (vegetarian, gluten-free, etc.)
descriptionstringRecipe description

Heroes (hero):

FieldTypeDescription
placementstringWhere the hero is used: home, page, or landing
heightstringDisplay height: full, large, medium, small
text_alignstringText alignment: left, center, right
text_colorstringText color scheme: white, black, auto
animationstringEntry animation: none, fade, slide-up, slide-down, zoom
autoplaybooleanAuto-advance slides in carousel mode (default: true)
autoplay_intervalintegerMilliseconds between slides (default: 5000)
show_arrowsbooleanShow navigation arrows (default: true)
show_dotsbooleanShow navigation dots (default: true)
pause_on_hoverbooleanPause autoplay on hover (default: true)

Menus (menu):

FieldTypeDescription
menu_typestringMenu placement: header or footer

Custom types:

For organization-defined custom types, metadata contains a custom_fields object keyed by the field keys defined in the type's schema:

FieldTypeDescription
custom_fieldsobjectCustom field values, keyed by field key. A repeater field's value is an array of objects (one per row). Booleans are returned as real JSON booleans.

Sort Order

Results are sorted by:

  1. Featured items first (is_featured descending)
  2. Sort order (sort_order ascending)
  3. Publication date (published_at descending)

Caching

CacheTTL
Browser (max-age)60 seconds
CDN (CloudFront policy)Up to 24 hours (invalidated on publish)
Stale-while-revalidate60 seconds

CDN caching is managed by CloudFront cache policies, not the Cache-Control response header. The browser max-age controls client-side caching.


Get Content by Slug

Retrieve a single content item by its URL slug.

http
GET /api/v1/public/content/{slug}

Path Parameters

ParameterTypeDescription
slugstringThe URL-friendly slug of the content

Query Parameters

ParameterTypeDefaultDescription
location_idstring-Location hashkey (org_xxxxx) for org-hierarchy resolution
expandstring-Comma-separated list of expansion types (linked_content)

Hierarchy Resolution with location_id

When location_id is provided, the endpoint walks the organization hierarchy to find the most specific content match for the given slug. The resolution order is:

  1. Store-level content (highest priority)
  2. Retailer-level content
  3. Wholesaler-level content (fallback default)

This allows a wholesaler to publish default content (e.g., a generic privacy policy), while individual retailers or stores can override it with their own version using the same slug. Ancestor content is only visible if its distribution_mode is set to all_descendants or custom.

Without location_id

When no location_id is provided, only content belonging to the authenticated organization is searched. The hierarchy walk requires a location_id to determine the ancestry chain.

Example Request

javascript
const response = await fetch(
  'https://cdn.engagehq.retailsuccessplatform.com/api/v1/public/content/summer-sale-announcement',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  }
);

With hierarchy resolution (multi-location):

javascript
const response = await fetch(
  'https://cdn.engagehq.retailsuccessplatform.com/api/v1/public/content/privacy-policy?location_id=org_store123',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  }
);

In this example, if org_store123 is a store, the API checks the store first for content with the slug privacy-policy, then its parent retailer, then the wholesaler -- returning the first match found. Ancestor content must have a distribution_mode of all_descendants or custom to be returned.

With linked content expansion:

javascript
const response = await fetch(
  'https://cdn.engagehq.retailsuccessplatform.com/api/v1/public/content/homepage-summer?expand=linked_content',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  }
);

Response

json
{
  "success": true,
  "message": "Content retrieved",
  "data": {
    "id": "con_00000k1L2m3N4o5",
    "type": "article",
    "slug": "summer-sale-announcement",
    "title": "Summer Sale Announcement",
    "description": "Get ready for our biggest summer sale...",
    "meta_title": "Summer Sale 2024 | Acme Grocery",
    "meta_description": "Shop our summer sale with discounts up to 50%",
    "tags": ["summer", "sale", "promotions"],
    "category": "promotions",
    "media_ids": ["med_00000x1Y2z3A4b5", "med_00000m1N2o3P4q5"],
    "published_at": "2024-06-01T00:00:00+00:00",
    "is_featured": true,
    "is_primary": false,
    "content": [
      {
        "type": "heading",
        "text": "Our Biggest Sale Ever",
        "level": 2
      },
      {
        "type": "paragraph",
        "text": "This summer, we're bringing you incredible savings..."
      },
      {
        "type": "image",
        "url": "https://cdn.example.com/images/sale-items.jpg",
        "alt": "Featured sale items",
        "caption": "Just a few of our amazing deals"
      }
    ],
    "metadata": {
      "author": "Marketing Team",
      "reading_time": 5,
      "custom_field": "value"
    },
    "canonical_url": "https://acmegrocery.com/blog/summer-sale-announcement",
    "og_data": {
      "title": "Summer Sale Announcement",
      "description": "Don't miss our biggest summer sale",
      "image": "https://cdn.example.com/images/og-summer-sale.jpg"
    }
  }
}

Additional Response Fields (Detail)

The detail response includes all list fields plus:

FieldTypeDescription
contentarrayArray of content blocks (see Schemas). May include platform_element entries from consuming platforms (see Platform Elements).
metadataobjectCustom metadata key-value pairs
canonical_urlstringCanonical URL for SEO
og_dataobjectOpen Graph data for social sharing
og_data.titlestringOG title
og_data.descriptionstringOG description
og_data.imagestringOG image URL
media_idsarrayArray of associated media hashkeys

Caching

CacheTTL
Browser (max-age)300 seconds
CDN (CloudFront policy)Up to 24 hours (invalidated on publish)
Stale-while-revalidate300 seconds

CDN caching is managed by CloudFront cache policies, not the Cache-Control response header. The browser max-age controls client-side caching.


Preview Mode

When a request includes a valid preview token, preview session, or signed preview URL, the API enters preview mode. This affects the response in two ways:

Additional Fields in List Responses

In preview mode, each content item in the list response includes two extra fields:

FieldTypeDescription
statusstringContent status (published, draft, scheduled)
expires_atstringISO 8601 expiration timestamp (if set)

Preview Metadata

A preview object is appended to the response envelope:

List endpoint:

json
{
  "success": true,
  "data": [...],
  "meta": {...},
  "preview": {
    "is_preview": true,
    "scope": "all",
    "preview_at": "2024-06-01T12:00:00+00:00",
    "expires_at": "2024-06-02T12:00:00+00:00"
  }
}

Detail endpoint:

json
{
  "success": true,
  "data": {...},
  "preview": {
    "is_preview": true,
    "content_status": "draft",
    "preview_at": "2024-06-01T12:00:00+00:00",
    "is_visible_at_preview_time": false,
    "expires_at": "2024-06-02T12:00:00+00:00"
  }
}

See the Authentication guide for details on generating preview tokens and sessions.

No Caching

Preview mode responses are never cached. The Cache-Control header is set to private, no-store, no-cache, must-revalidate and an X-Preview-Mode: true header is added.


Error Responses

400 Bad Request

json
{
  "success": false,
  "message": "Organization context required",
  "errors": null
}

Ensure your request includes proper authentication with organization access.

404 Not Found

json
{
  "success": false,
  "message": "Content not found",
  "errors": null
}

The content either:

  • Doesn't exist
  • Is not published
  • Has expired
  • Belongs to a different organization

Changelog
DateChange
2026-06-03Documented custom-type type_key filtering and custom field values under metadata.custom_fields.
2026-03-24Added platform elements documentation to content responses.
2026-03-24Added custom types support for user-defined content types.
2026-03-08Corrected content API documentation and added placement filter.
2026-02-28Removed content_key; added circular-campaign integration.
2026-02-24Added content_key resolution documentation.
2026-02-23Updated for CloudFront CDN delivery.
2026-02-11Added hero placement documentation.
2026-02-09Added selective include parameter.
2026-02-05Consolidated content type documentation.
2026-02-04Added section settings documentation.
2026-01-28Expanded documentation.
2026-01-15Initial publication.

EngageHQ Public Content Delivery API