# Content pages

Keaze has a flexible built-in Headless CMS that allows creating content pages for different sections in a portal, like blog pages, news pages, FAQ pages or any other content pages.

For compatibility reason with Property Booking, one of the content type (Area pages) has their own endpoints, but it could also be consumed by using the generic endpoints. In this guide, Area pages are introduced first, and then the generic endpoints, under the topic Headless CMS, used to consume other content pages.

Info

These endpoints are only available for Keaze Marketplace Portal.

# Area pages

# List of area pages per scheme

To get the list of all the areas per scheme and regions, send a GET to /api/rest/portal/scheme-areas.

# Response

Every element in the collection is an object with three levels of information: Scheme details, region details and area details.

Scheme fields

Field name Type Nullable Description
id number false Scheme reference id in the database
name string false Scheme name
slug string false Unique slug for the scheme
color string false Scheme color
regions array false Collection of all the regions

Region fields

Field name Type Nullable Description
name string false Region name
areas array false Collection of all the areas

Area fields

Field name Type Nullable Description
id number false Area reference id in the database
name string false Name of the area
slug string false Unique slug for the area

Example

{
    "data": [
        {
            "id": 1,
            "name": "Shared ownership",
            "slug": "shared-ownership",
            "color": "#FF4F35",
            "regions": [
                {
                    "name": "London",
                    "areas": [
                        {
                            "id": 26,
                            "name": "Archway",
                            "slug": "archway"
                        },
                        {
                            "id": 24,
                            "name": "Barking",
                            "slug": "barking"
                        },
                        ...
                    ]
                },
                {
                    "name": "South East",
                    "areas": [
                        {
                            "id": 69,
                            "name": "Berkshire",
                            "slug": "berkshire"
                        },
                        {
                            "id": 29,
                            "name": "Brighton",
                            "slug": "brighton"
                        },
                        ...
                    ]
                },
                ...    
            ]
        },
        {
            "id": 7,
            "name": "Help to Buy",
            "slug": "help-to-buy",
            "color": "#29908F",
            "regions": [
                {
                    "name": "London",
                    "areas": [
                        {
                            "id": 43,
                            "name": "Acton",
                            "slug": "acton"
                        },
                        {
                            "id": 45,
                            "name": "Beam Park",
                            "slug": "beampark"
                        },
                        ...
                    ]
                },
                {
                    "name": "South East",
                    "areas": [
                        {
                            "id": 94,
                            "name": "Epsom",
                            "slug": "epsom"
                        },
                        {
                            "id": 37,
                            "name": "Folkestone",
                            "slug": "otterpool-park"
                        },
                        ...
                    ]
                }
            ]
        }
    ]
}

# Area page content

To get the page content of a specific area, send a GET to /api/rest/portal/scheme-areas/pages/{id}, where id is the area page reference id. There's an alternative endpoint to use the area page slug instead of its id: GET /api/rest/portal/scheme-areas/pages/slug/{slug}. In both cases the response is the same.

# Response

Field name Type Nullable Description
id number false Area page reference id in the database
name string false Area name
slug string false Unique slug for the area page
template string false Template name that should match the template to use in the front-end to render the content.
scheme object false Scheme details: id, name, slug and color
region string false Region name
titleTag string false Html page suggested title
metaKeywords string false Html meta keywords
metaDescription string false Html meta description
headline string false Html H1 tag content
content object false The structure of this object will depend on the template. See below the current templates and their corresponding content structure.
faqs array true Collection of question - answer to be displayed in a FAQ section. This field is not mandatory, so notice it could be NULL
mainImageUrl string true Main image url if needed
socialImageUrl string true Image url for twitter or other social networks
socialTitle string true Suggested title for the twitter card of other social networks
socialDescription string true Suggested description for the twitter card of other social networks

# Content structure

Right now there are only two templates supported for Area pages: basic and featured-listings, the only difference between both, is that featured-listings includes in the content JSON an extra field with a collection of relevant listings to display.

The general content structure is as follow:

Field name Type Nullable of empty
heading string true
description string true
title1 string true
copy1 string true
title2 string true
copy2 string true

The template is intended to have a top heading and description and two columns with their title and copy.

When the template is featured-listings the new field coming in content is called featuredListings and it's a collection of objects, with the following structure:

Field name Type Nullable Description
image string false Url for the image to display
imageAltText string false Alternative text for the image
title string false Title for the featured listing section
content string false Content for the featured listing section
url string true Listing slug

Example:

{
    "id": 7,
    "name": "London",
    "slug": "london",
    "template": "featured-listings",
    "scheme": {
        "id": 1,
        "name": "Shared ownership",
        "slug": "shared-ownership",
        "color": "#FF4F35"
    },
    "region": "London",
    "titleTag": "10 BEST Shared Ownership properties in London to view for 2021",
    "metaKeywords": "Shared ownership, Hackney, Islington, London city Island, shared ownership properties, London",
    "metaDescription": "We've selected 10 of our best shared ownership properties for sale in London. Updated for 2021. Including new build homes in Hackney, Greenwich and London City Island.",
    "headline": "Top 10 shared ownership properties in London",
    "content": {
        "heading": "Should I buy with shared ownership in London?",
        "description": "London: it’s lively, it’s cool, it’s cosmopolitan. This is a city obsessed with the Next...",
        "title1": "Part-buy Part-rent London",
        "copy1": "<p>Culturally, it ticks all the boxes, job-wise &ndash; it&rsquo;ll never let you down, and when it ...</p>",
        "title2": "Resales in London",
        "copy2": "<p>Living amongst the grandeur of Zone 1 isn&rsquo;t ...</p>",
        "featuredListings": [
            {
                "image": "https://static.propertybooking.co.uk/assets/locations/7/featured_properties/union-yard-cgi-2.jpg",
                "image_alt_text": "Union Yard",
                "title": "Union Yard, Bethnal Green",
                "content": "<p>If you haven&rsquo;t yet been enchanted by the quirk and hip of this down-to-Earth East End corner, you&rsquo;re certainly missing out. Bethnal Green is a young professionals paradise with Overground and Underground stations as well as all the locals&rsquo; favourite haunts, ...</p>",
                "url": "union-yard"
            },
            {
                "image": "https://static.propertybooking.co.uk/assets/locations/7/featured_properties/ews-p1-cgi-2-bed-living-room.jpg",
                "image_alt_text": "East Wick & Sweetwater",
                "title": "East Wick & Sweetwater, Hackney",
                "content": "<p>Just one of the new housing developments enriching the Queen Elizabeth Olympic Park in East Wick, this first-time buyer opportunity is one that certainly packs a punch. ...</p>",
                "url": "east-wick-sweetwater"
            },
            ...
        ]
    },
    "faqs": [
        {
            "question": "Who can qualify to buy a home with shared ownership in London?",
            "answer": "<p>The eligibility criteria for shared ownership is simple; as long as you don&rsquo;t currently own, or won&rsquo;t own another property when you move in to a new home, ...</p>"
        },
        {
            "question": "How much in savings will I need to buy with shared ownership in London?",
            "answer": "<p>Shared ownership is much more affordable than buying outright, however you&rsquo;ll still need a mortgage deposit. This is usually a minimum of 5 or 10%, ...</p>"
        },
        ...
    ],
    "mainImageUrl": "https://static.propertybooking.co.uk/assets/locations/7/main/Shared-Ownership-London-royal-docks-new.jpg",
    "socialImageUrl": "https://static.propertybooking.co.uk/assets/locations/7/social/churchfield-quarter-shared-ownership.jpg",
    "socialTitle": "The 10 BEST shared ownership properties in London for 2021",
    "socialDescription": "Buying in London without a trust fund. Browse 4000+ shared ownership, new build, part buy part rent and resale homes in London. View apartments with balconies and houses with gardens."
}

# Get matching listings for an area

To get some listings that match the area, send a GET /api/rest/portal/scheme-areas/pages/{id}/listings

This endpoint will return a non-paginated collection of the most relevant listings considering the area scheme and location.

It allows the optional QueryString parameter size (default value is 6) to specify the maximum number of listings to return. Notice that in some cases you could receive an empty collection.

All responses are JSON encoded, and when the request is correct, the object will contain a unique field data, with the collection of listing details, using the same object structure as the ones returned by the search.

# Alternative endpoint

There exists an alternative endpoints when you use the slug of the area.

GET /api/rest/portal/scheme-areas/pages/slug/{slug}/listings

Where slug is the area page slug.

# Headless CMS

The Headless CMS is a Keaze built-in module that allows creating content pages for different sections in a portal, like blog pages, news pages, FAQ pages or any other content pages. It was built using a generic structure that doesn't respond to any specific data need and with the basic idea to separate content from presentation.

# Basic concepts

Find below a compact diagram of the Headless CMS structure.

Image

The main concepts (in red) are:

  • Content types: Any of the different types of content pages portals usually have like blog post, news page, items list pages or generic pages which a fallback for pages likes about. They define general behaviour of pages with the same purposes by customizing the meaning of topic, source and series, and the use of the different tag categories. Content types can be added in the back-end based on the needs of Portals. Content types can be used to filter pages when consuming the API, by using the code of the content type, which must be unchangeable once there are defined.
  • Sitemap: Defines the entries in the hierarchical structure of the portal which are related to the routes of the Portal. Think on a sitemap entry as the wrapper of all the pages with the same url structure in the portal. These pages have the same content type, and share a group of properties that are defined in the sitemap entry.
  • Content families: It's the central entity for pages. It can group several pages that are going to be displayed in the portal by using a menu, but where every entry has a unique url, like several pages about the company, although they usually contain a single pages like in blogs. This entity is the one that establishes the relationships with most of the different concepts in the CMS like authors, topics, series, etc.
  • Content pages: It's the representation of a physical page, which contains information about the page content, but also about some meta tags focused on SEO and social networks.

There are other important concepts that are explained in the below sections.

# Sitemap templates

In order to separate content from presentation, sitemaps use the concept of templates, which can be described as references to the real html templates used on Portals. A sitemap must have at least one template, but it can have multiple ones. When a new content page is created, one of the templates need to be assigned.

Templates in modern Portals are basically html code with embedded expressions, usually coming from models, where every template usually requires a particular model. These are the basic ideas used to build the templates in the back-end, they have a model definition, which is specific for the template itself, and should match, in some way, the implementation of the real html template in the Portal.

Template models are defined by using sections and fields, where a section is a group of fields that generate a json object in the model. Sections can have multiple instances of the object or be a single object. The final model will have as many root attributes as sections, and every section will be an array of a single object, which definition is based on its fields. When requesting a page content the model will come under the attribute sections in the payload.

There are 5 supported types of fields:

  • Text: A single line string.
  • TextArea: A multi line string.
  • Html: An HTML-formatted string.
  • Image: An image which in the model will be the the full url of the image. At the moment there is no support for responsive images with different responsive versions.
  • Url: It's basically a Text field, but by using a different type it provides semantic extra information.

Every section and every field has a name which is always a valid json attribute and it's the one used in the model, although some user-friendly labels are displayed in the back-end to create new contents.

Fields can have validation rules, which are helpful to create valid models. For sections it's mandatory to define if it's a single object or not, in which case it can be defined the minium and maximum number of instances allowed. Depending on the rules, some fields and even some sections (when they are collections) could be empty.

# Taxonomy

Taxonomy is defined by using categories and tags, which are created by the CMS staff. Every content type specify which categories are going to be available when creating content pages under that content type. Content pages return the list of all the tags assigned, and if needed you can search contents related to a tag.

# Content relations

This is a concept that allow associating a content with an object from our business model, like a Scheme, a Local authority or even a listing. This make it possible to get all contents related to a product, or suggest products from content pages.

There are two concepts: the entity and the item. The first is the type of object the content family is related to, like Scheme, while the second one is the specific element, like Shared ownership.

All content families required to specify the object to which it's related. By default it's the entity Site and the item is the specific Portal where the content belongs.

# Content family attributes

As stated before, content families establish the relationships with most of the different concepts in the CMS:

  • Topic: Content types can use topics to add a new attribute that is mandatory to content families. It's useful in cases like portals with multiple blogs, to identify which blog the content family belongs.
  • Series: They can be used to add a new non-mandatory attribute to content families, like the series a blog post belong.
  • Sources: Sources are mainly used to define all the original sources employed to write a content, usually newspapers. A content family can have multiple sources.
  • Collections: That are used to group multiple content families and generate a new content that generate values to customers.
  • Authors: For some content types like blogs, it's important to define all the authors involved in the production of the content. The CMS also allows defining roles and order.
  • Tags: Link contents to tags is a primary concept for content types like blogs and news.

# Content page sections

When creating a new content page, it must be assigned one of the templates from the content family sitemap. The template could contain sections or not. When the template has sections, all sections are completed in the edition process based on the fields every section has, and returned when the content page is requested.

Although sections is the advisable way to define the content for a page, because it allows to add structured information, in simple pages the htmlInfo field can be used, which is an html field available and always returned in the payload.

# Comments

Although nowadays third-party platform like Disqus are used as a comment system, the Headless CMS includes a comment module, that allows to add comments to any content page.

Info

Endpoints to add and get comments is currently under development.

# Get the list of contents

To get the list of all contents based on some filters, send a GET to /api/rest/portal/contents.

This endpoint support any subset of the following parameters to filter the contents you want to display.

  • sitemap_code
  • content_type_code
  • related_entity_code
  • related_item_id
  • related_item_slug
  • collection_id
  • collection_slug
  • topic_id
  • topic_code
  • topic_slug
  • series_id
  • series_slug
  • tag_slug
  • category_slug
  • author_id

The filters to use will depend on every specific use case, although sitemap_code and content_type_code are the most common ones. To list all the posts in a blog, for example, the filters to use should be sitemap_code and topic_id, the latter when there are more than one blog in the portal.

Results are paginated based on parameters page (page number, optional, default is 1) and size (maximum number of records to return per page, optional, default is 10), and they are sorted by published date and then by creation date, both in descending order.

When a content family contains more than one page, only the main page is listed.

# Response

Every item in data will have the following fields:

Field name Type Nullable Description
id number false Content page identifier
familyId number false Content family identifier
url string false Url in the portal to access the main content of this content family
blurb string true Blurb of the content page
publishedAt string false DateTime when the content was set to go live
image string true Url of the main responsive image related to the content family if applicable, useful in content types like blogs.
headerImages array false Array of objects with the Header images when applicable. Every object contains the fields: id, alternativeText, caption, masterURL, thumbnailUrl.
sitemapCode string false Code of the sitemap
contentTypeCode string false Code of the content type
name string false Name of the main content in the content family
headline string true Headline of the main content in the content family
slug string false Slug of the main content in the content family
abstract string true Abstract of the main content in the content family
location object true When the content family uses location, an object with the details of the location. See details below.
topic object true Object containing details about the content family topic, when applicable. The object contains the fields: id, name, slug and code.
series object true Object containing details about the content family series, when applicable. The object contains the fields: id, name and slug.
sources array false Array of objects with the used sources, when applicable. Every object contain the fields: id, name, url and displayOrder.
authors array false Array of objects with the authors, when applicable. Every object contain the fields: id, name, role and trails.
tags array false Array of objects with the tags, when applicable. Every object contain the fields: name, slug, category. The last field is an object with fields: name and slug.

# Location details

Field name Type Nullable Description
region string false Region name.
type string false One of the following options: county, local_authority, city, town
reference number false When type is county or local_authority the reference id of the entry in Keaze.
name string false Name to display
latitude number true When type is city or town the latitude on the map.
longitude number true When type is city or town the longitude on the map.
radius number true When type is city or town the radius in miles from the coordinates that defines the location area.

Example

{
    "data": [
      {
        "id": 6,
        "familyId": 6,
        "url": "https://keaze.com/faq/shared-ownership",
        "blurb": "Shared Ownership is set to change with the government announcing the new model for the scheme to make it even easier to get yourself on the property ladder.", 
        "publishedAt": "2021-03-01T00:00:00+00:00",
        "image": null,
        "headerImages": [
          {
            "id": 19,
            "alternativeText": "Image ALT tag",
            "caption": "Caption",
            "masterUrl": "https://static.propertybooking.co.uk/assets/contents/115/header/photo-1505761671935-60b3a7427bad.jpeg",
            "thumbnailUrl": "https://static.propertybooking.co.uk/assets/contents/115/header/thumbnails/photo-1505761671935-60b3a7427bad.jpeg"
          }
        ],        
        "sitemapCode": "faq-page",
        "contentTypeCode": "page",
        "name": "FAQ - Guides to Shared Ownership",
        "headline": "Keaze FAQs & Guides to Shared Ownership",
        "slug": "shared-ownership",
        "abstract": null,
        "location": null,
        "topic": null,
        "series": null,
        "sources": [ ],
        "authors": [ ],
        "tags": [ ]
      },
      ...
    ]
    ...
}

# Get a content page

To get the list of all the areas per scheme and regions, send a GET to /api/rest/portal/contents/{content-page-id} or to /api/rest/portal/contents/slug/{content-page-slug}

In both endpoints the Query String parameter ignore_visibility can be send as true or 1 to display the content even when it's not enabled or the published date is not arrived yet.

Warning

When using content-page-slug it's advisable to send the Query String parameter sitemap_code with the code of the content family page sitemap, to avoid unexpected results, because the same slug could be used in pages from different sitemaps.

# Response

Field name Type Nullable Description
id number false Content page identifier
familyId number false Content family identifier
url string false Url in the portal to access the content page
publishedAt string false DateTime when the content was set to go live
image string true Url of the main responsive image related to the content family if applicable, useful in content types like blogs.
headerImages array false Array of objects with the Header images when applicable. Every object contains the fields: id, alternativeText, caption, masterURL, thumbnailUrl.
sitemapCode string false Code of the sitemap
contentTypeCode string false Code of the content type
relatedTo object false Object with two fields entity and item, containing information about the element in the model the content is related to.
name string false Name of the content page.
headline string true Headline of the content page.
slug string false Slug of the content page.
blurb string true Blurb of the content page
titleTag string true Html title tag.
metaKeywords string true Html meta name="keywords".
metaDescription string true Html meta name="description".
canonicalUrl string true Html link rel="canonical".
socialTitle string true Title to display when sharing to social networks.
socialDescription string true Description to display when sharing to social networks.
abstract string true Abstract of the content page.
htmlInfo string true Body content in html format.
template object false name and filePath of the template associated with the content.
sections object false The structure of this object will depend on the template. Every field will correspond with one of the sections for the template.
location object true When the content family uses location, an object with the details of the location. See Location details.
family array true When the content family contains more than one page, the array will contain the collection of pages in the family. Every item will have the fields: id, slug, name, url, menuLabel, displayOrder.
topic object true Object containing details about the content family topic, when applicable. The object contains the fields: id, name, slug and code.
series object true Object containing details about the content family series, when applicable. The object contains the fields: id, name and slug.
sources array false Array of objects with the used sources, when applicable. Every object contain the fields: id, name, url and displayOrder.
authors array false Array of objects with the authors, when applicable. Every object contain the fields: id, name, role and trails.
tags array false Array of objects with the tags, when applicable. Every object contain the fields: name, slug, category. The last field is an object with fields: name and slug.

Example

{
    "id": 17,
    "familyId": 17,
    "url": "https://keaze.com/shared-ownership/london",
    "publishedAt": "2021-03-01T00:00:00+00:00",
    "image": null,
    "headerImages": [
        {
          "id": 19,
          "alternativeText": "Image ALT tag",
          "caption": "Caption",
          "masterUrl": "https://static.propertybooking.co.uk/assets/contents/115/header/photo-1505761671935-60b3a7427bad.jpeg",
          "thumbnailUrl": "https://static.propertybooking.co.uk/assets/contents/115/header/thumbnails/photo-1505761671935-60b3a7427bad.jpeg"
        }
    ],  
    "sitemapCode": "area-page",
    "contentTypeCode": "area",
    "relatedTo": {
        "entity": {
            "code": "scheme",
            "name": "Scheme"
        },
        "item": {
            "id": 1,
            "slug": "shared-ownership"
        }
    },
    "name": "London",
    "headline": "Top 10 shared ownership properties in London",
    "slug": "london",
    "blurb": "We've selected 10 of our best shared ownership properties for sale in London...",
    "titleTag": "10 BEST Shared Ownership properties in London to view for 2021",
    "metaKeywords": "Shared ownership, Hackney, Islington, London city Island, shared ownership properties, London",
    "metaDescription": "We've selected 10 of our best shared ownership properties for sale in London...",
    "canonicalUrl": "https://keaze.com/shared-ownership/london",
    "socialTitle": "The 10 BEST ? shared ownership properties in London for 2021",
    "socialDescription": "",
    "abstract": "",
    "htmlInfo": "",
    "template": {
        "name": "featured-listings",
        "filePath": ""
    },
    "sections": {
        "content": {
            "copy1": "<p>Culturally, it ticks all the boxes, job-wise...",
            "copy2": "<p>Living amongst the grandeur of Zone 1 isn’t...",
            "title1": "Part-buy Part-rent London",
            "title2": "Resales in London",
            "heading": "Should I buy with shared ownership in London?",
            "description": "London: it’s lively, it’s cool, it’s cosmopolitan. ..."
        },
        "featuredListings": [
            {
                "url": "union-yard",
                "image": "https://static.propertybooking.co.uk/assets/contents/17/content/union-yard-cgi-2.jpg",
                "title": "Union Yard, Bethnal Green",
                "content": "<p>If you haven’t yet been enchanted by the quirk...",
                "imageAltText": "Union Yard"
            },
            ...
        ],
        "faqs": [
            {
                "question": "Who can qualify to buy a home with shared ownership in London?",
                "answer": "<p>The eligibility criteria for shared ownership..."
            },
           ...
        ]
    },
    "location": {
        "region": "London",
        "type": "local_authority",
        "reference": 60,
        "name": "London",
        "latitude": null,
        "longitude": null,
        "radius": 1
    },
    "family": null,
    "topic": null,
    "series": null,
    "sources": [],
    "authors": [],
    "tags": []
}

# Get the list of topics

To get the list of all the topics send a GET request to /api/rest/portal/topics.

# Response

Every item in data will have the following fields:

Field name Type Nullable Description
id number false Topic identifier
contentType object false Related Content type object
name string false Name of the topic
code string false Code of the topic
slug string false Slug of the topic
blurb string true Blurb of the topic
htmlDetails string true Html info if needed to show in a content
tags array false Array of objects with the tags, when applicable. Every object contain the fields: name, slug, count.
updatedAt string true DateTime when the topic was updated.
createdAt string true DateTime when the topic was created.

Example

{
  "data": [
    {
      "id": 1,
      "contentType": {
        "id": 3,
        "name": "Blog post",
        "singleContent": true,
        "activateSources": false,
        "singularTopicLabel": "Topic",
        "singularSeriesLabel": "Series"
      },
      "name": "Buying property guides",
      "code": "buying-guides",
      "slug": "buying-property-guides",
      "blurb": "",
      "htmlDetails": "",
      "tags": [
        {
          "name": "Help to Buy",
          "slug": "help-to-buy",
          "count": 3
        },
        {
          "name": "Shared Ownership",
          "slug": "shared-ownership",
          "count": 2
        }        
      ],
      "updatedAt": "2021-04-26T20:43:18+00:00",
      "createdAt": "2021-04-26T20:43:18+00:00"
    },
    ...
  ]
}

# Get a specific topic

To get a specific topic, send a GET request to
/api/rest/portal/topics/{topic-id} or to
/api/rest/portal/topics/slug/{topic-slug}

# Response

Field name Type Nullable Description
id number false Topic identifier
contentType object false Related Content type object
name string false Name of the topic
code string false Code of the topic
slug string false Slug of the topic
blurb string true Blurb of the topic
htmlDetails string true Html info if needed to show in a content
tags array false Array of objects with the tags, when applicable. Every object contain the fields: name, slug, count.
updatedAt string true DateTime when the topic was updated.
createdAt string true DateTime when the topic was created.

Example

{
  "id": 3,
  "contentType": {
    "id": 3,
    "name": "Blog post",
    "singleContent": true,
    "activateSources": false,
    "singularTopicLabel": "Topic",
    "singularSeriesLabel": "Series"
  },
  "name": "First-time buyer guides",
  "code": "1st-time-guides",
  "slug": "first-time-buyer-guides",
  "blurb": "",
  "htmlDetails": "",
  "tags": [
    {
      "name": "Help to Buy",
      "slug": "help-to-buy",
      "count": 2
    },
    {
      "name": "Shared ownership",
      "slug": "shared-ownership",
      "count": 2
    }
  ],
  "updatedAt": "2021-04-26T20:43:18+00:00",
  "createdAt": "2021-04-26T20:43:18+00:00"
}