NAV Navbar
shell
  • Introduction to Quintype APIs
  • Basic Concepts
  • Architecting a front end application
  • Implementing a Front End Application
  • Sample Applications and Libraries
  • Analytics
  • Deployments and Infrastructure
  • Advanced Topics
  • Introduction to Quintype APIs

    This repository is a knowledge base for developers working on projects backed by the Quintype platform. The live documentation is available as Swagger

    Basic Concepts

    Stories- Cards and Elements

    Quintype provides a structured CMS system. A story is comprised of cards, and cards contain story elements.

    Cards

    Cardification is a new paradigm targetted towards mobile-first consumption of news. Short and concise chunked blocks of content tend to have much higher engagement. Quintype stories are split into multiple cards. Frontend applications can choose to represent these cards visually, allowing users to interact with these cards directly.

    Any API request for stories contain the cards that comprise the story in the "cards" field (as an array). These cards comprise the body of the story.

    Story Elements

    Story elements are the smallest logical unit in the Quintype platform. Each story element represents a single paragraph of text, image, video, or other unit of content. Story elements form a card. Quintype also tracks these story elements and derives analytics reports based on user engagement on stories containing specific elements.

    For eg: A story on tennis containing more photo story elements might get higher engagement.

    The story elements can be found in the "story-elements" field of individual cards.

    Story Element Types

    Each story element has a "type", and optionally a "subtype". There are currently five major types of story elements, which all front end applications must support. They are as follows:

    Story Element Subtypes

    Story elements may also have a "subtype" field, which gives hints on rendering for clients that know how to render the subtype. For example, a jsembed element may have the twitter subtype. Looking at the metadata, you will find the tweet-id of the particular tweet. Clients may choose to render this element as a jsembed, or as a native twitter element (and provide optimizations such as ensuring that the twitter SDK is only loaded once).

    Story Types

    Story type are predefined templates which can be used to write articles of various domains ranging from photo blogs, listicles, video stories, blogs etc. Story types give a definite structure and a "starting point" to authors when they start writing a story. They also help in predictive analytics. Data can be derived and studied based on audience engagement on various story types for different domains, for example, a photo story on wildlife may get higher engagement than a text story.

    A story type may give subtle hints to the behaviour of the story. For example, a live-blog may choose to auto update every 30seconds.

    Stacks

    A stack is a group of stories, which can be displayed anywhere on the homepage or other pages. The grouping of stories is done manually (or programatically), using the Sorters interface on the CMS. A closely related concept is the Story Collection.

    Sections

    Stacks can have stories in the "Home" section (global), as well as within a section. For information on how this works, please see the Story Groups page.

    API requests

    A stack can be requested using the story-group parameter to the stories API. The Swagger Documentation contains information on how to fetch stories within a stack.

    Story Groups

    story-group is the most important parameter to the stories API. This parameter indicates which set of stories you'd like to fetch. Currently, the story group can be top, or published, or stack-{{id}}, corresponding to top stories, stories in published order and stacks respectively.

    Story groups that are backed by a sorter can be sorted by

    top stories

    The 'top stories' group is an infinitely long list. This first manually picks sorted stories from the list, then continues with stories in published order (reverse chronological).

    published stories

    The 'published stories' group returns stories in published order (reverse chronological), skipping the order from the sorters.

    stacks

    The stack stories will only return stories in the sorter. As an exception, if a sorter for a (stack / section) combination is empty, it will fall back to the stories in (stack / "Home")

    Behavior of various story groups

    Various story groups behave slightly differently. Each story group typically has a sorter, except for 'published'.

    story-group "Home" section
    top Stories as per sorter, then stories reverse chronologically Stories as per sorter, then stories in that section reverse chronologically
    stack-id Stories as per sorter If the sorter has stories, then it returns the sorted stories. If the sorter is empty, it will return stories for the same stack in the "Home" section

    Breaking News

    Breaking news can be used to quickly push current events to users as they are happening. These stories may not be fully formed, and only require a headline. These Breaking news events can be linked to a story, after the push has happened.

    Linking to another story

    The breaking news story can be linked to an existing story, or added later. Clicking on the breaking news opens the story. The breaking news API automatically fetches the latest headline and slug of the linked story.

    Story Group

    The breaking news API accepts a story-group parameter, to give access to another breaking news sorter.

    API Documentation

    Please see the swagger documentation for details on how to use the API.

    Entities

    Entity provide structured information which is relevant to a publisher. An entity can be referred in relevant stories.

    The representation of linked entities on the front end is contextual to the purpose for which it is referred in stories. Example of entity is a person (referred to as ‘entity type’) which has the structure such as name, avatar, email address, social handle, bio, company associated with it.

    Entity types can only be created by the Quintype team, based on a publisher's requirement. Entity type can contain text, numerics, image, or another entity.

    Publishers can add values (referred to as entities) to the entity type by using the ‘Entity Manager’ and these values can be referred to in stories. The Entity manager can also be used to modify existing entities.

    There are three endpoints to access entities:

    1. Getting all the entities

    Use the GET ENTITIES API to get all the entities created.

    2. Getting particular entity details.

    Use the GET ENTITY API with the entity-id.

    3. Getting the nested entities linked to an entity

    Use the GET ENTITY API with the root entity-id followed by the nested-entity key.

    Architecting a front end application

    Front End Application on the Quintype platform should be built in such a way that they can be served from a CDN cache. This drastically reduces load time, and the amount of load on the backend server. However, this puts a few constraints on the architecture of many pages.

    All pages that want to be cached

    Logged In Content

    As a general practice, it's important to render the page as a logged out page, so that it can be served from the CDN. The page can then be updated via AJAX queries. In case the page must be rendered server side, with logged in content (ex: Personalized Home Page), then mark the page as uncacheable. Do keep in mind that all calls to /api/v1/... will automatically pass the session-cookie cookie to the API server, which will in turn be able to find out who the user is.

    Getting the logged in user on the backend

    Sketches and the client app share the session-cookie cookie. However, it’s an encrypted object only sketches can read. In order to read the current member, call /api/v1/members/me, passing in the value of session-cookie as the X-QT-AUTH header, to get the current user. Don’t bother saving a separate.

    Checking Device Type

    As the content is cached, it is not permitted to check for Mobile / Tablet / Desktop or other Form Factor. Please use responsive CSS to style the content as required. If it is not possible to avoid device checks, please mark the page as uncacheable.

    Cache Headers and Keys

    The following HTTP Headers should be returned with every HTTP response which should be cached.

    Cache-Control: public,max-age=0
    Surrogate-Control: public,max-age=30,stale-while-revalidate=120,stale-if-error=3600
    Surrogate-Key: q/55/top/home s/55/d7f8965d s/55/b93cbb75 s/55/2eb36ec1 ss/55/fae74aa1
    Vary: Accept-Encoding
    

    The Cache-Control header is passed on the the browser, while the Surrogate-Key is processed by the CDN provider. However, they behave very similar. The options are as follows.

    Cache Keys

    Currently, we support the following keys, which will be invalidated by the Quintype editor when a story or group updates. Currently, the following keys are supported:

    If you would like a soft purge on this key, prefix the key above with an s.

    Cookies

    It is very important that all cookies are stripped from requests which are to be cached. This can be enforced by the following methods:

    Failures

    In order for the CDN to serve error pages (in case the site is down), the backend server must return a 5xx response.

    Our CDN treats 404 and other status codes < 500 as intentional failures, and these pages will be served instead of using a page marked with stale-if-error.

    Marking a page uncacheable

    To mark a page as uncacheable, please use the following header. Cache-Control: private,no-cache

    Implementing a Front End Application

    Images

    Quintype currently uses Imgix to display images in various aspect ratios.

    Base Url

    A image's URL can be obtained by appending the "image-s3-key" to "http://quintype-01.imgix.net/". For example, the hero image whose s3 key is "quintype-demo/1234/foo.png" is "http://quintype-01.imgix.net/quintype-demo/1234/foo.png". This is the image in it's original resolution, and can be transformed using any of Imgix's Transforms.

    Focus Point

    var FocusedImage = require("quintype-js").FocusedImage;
    var image = new FocusedImage('quintype-demo/1234/foo.png', metadata);
    var output_url = "http://quintype-01.imgix.net/" + image.path([16, 9], {w: 640});
    

    Our editor allows the placement of a focus point on any image. The placement of a focus point guarantees that that point is always present in the viewport when the image is cropped, across different aspect ratios.

    It is recommended that the focus point is used in conjuncture with the picture spec of HTML5, in order to show images at different aspect ratios on different devices. Using an object-fit: cover; will further center the image.

    The refrence implementation for the focus point algorithm can be found in the javascript implementation.

    The focus final image url can be calculated with the following pseudo code (for a 16x9 image with final width 640)

    var transforms = {
      w: 640,       // actual width
      q: 60,        // quality
      auto: format  // use WebP when available
      fm: pjpg      // fallback to progressive jpeg
    };
    

    In order to save bandwidth, and provide a good experience, we suggest you use the following transforms:

    The list of all transforms can be found in the Imgix Documentatation.

    Preview

    var StoryPreview = (function() {
      window.addEventListener("message", function(event){
      var template = getLiquidTemplate("pages/story");
      var story = event.data['story'];
        if (story) {
          $("#story-preview").html(template.render({story: story, preview: true}));
    
          // Do other things to make the page work correctly, such as post load JS
        }
      });
    });
    

    Technical Overview

    The preview functionality is implemented with a combination of the backend editor, and the front end UI. The front end page has to implement two routes. /preview/story and /preview/home. The editor fetches this page via either HTTP or HTTPS, then served in an IFrame on the editor (via HTTPS). As the story is updated in the editor, the story is passed to the page via the message API, and the page is expected to rerender on the Front End.

    HTTPs Only

    As the content is loaded via HTTPS, any HTTP content (external JS, etc...) will not render.

    Example

    On the right is sample code explaining how to listen via the IFrame message API.

    Testing

    window.postMessage({story: {"author-name":"Tapan Bhat","headline":"The Greatest !","slug":"sports/2016/06/08/the-greatest","last-published-at":1465407509866,"alternative":{},"sections":[{"id":82,"name":"Sports"}],"hero-image-metadata":{"width":2133,"height":1906,"focus-point":[988,258]},"published-at":1465407509866,"id":"1d2fc836-4113-4ae1-8735-377167664892","hero-image-s3-key":"quintype-demo/2016-06/cca8f31e-9264-4ee2-9af0-08eb53be2a26/ABP-1.jpg","author-id":2041,"first-published-at":1465407509866,"story-template":"photo"}, "action": "reloadStory"}, window.location.origin)
    

    Head to /preview/home, then paste the following in the console. The page should immediately render with the sample story.

    The /preview/story should also behave similarly, but render the story.

    Isomorphic Rendering

    In order to render from both the server side, and the client side, it is suggested that you use a templating language capable of Isomorphic Rendering. Some suggestions are Liquid Templates or Twig

    Voting and Rating

    End users have the ability vote on/rate a specific story. Potential applications include upvote/downvote, like/dislike, star rating etc.

    Each story has multiple types of ratings ('magnitude') whose value can be incremented using an API.

    1. Configuring allowed magnitudes

    This is a one-time setup, where the publisher needs to come up with a list of allowed magnitudes for their voting system. It could be ['upvote', 'downvote'], ['like', 'dislike'], [1, 2, 3, 4, 5], etc.

    Currently, there is no API to set this. Please contact us to make this change.

    2. Authentication

    Make sure the user is logged in, via one of our supported authentication methods.

    Currently, we do not support anonymous voting.

    3. Vote

    Use the POST Vote API to vote on an article. You need to send an allowed magnitude.

    Use any one of the GET Stories APIs, but make sure you pass votes in the fields list.

    5. Get votes for a story for a user

    Use the GET Vote API to get the votes for a particular story, for the currently logged-in member. Possible uses include finding if a user has voted or not, and the magnitude of their vote.

    Social Logins

    Facebook:

    1. Create an app at https://developers.facebook.com
    2. Click on ‘Get started’ against Facebook Login.
    3. Under Valid OAuth redirect URIs in Facebook Login settings, add the following urls:
      1. site_url/auth
      2. site_url/auth/facebook/callback
      3. site_url/admin/add-social.callback
    4. In the app’s Basic settings, click on Add Platform and add Website with the site_url.
    5. Insert the App ID and App secret generated in our database.
    6. Now, to login to our site using Facebook login, call the URL: site_url/login?auth-provider=facebook&remote-host=site_url
    7. To get the details of the logged in user, call api_host/api/v1/members/me

    Twitter:

    1. Create an app at https://apps.twitter.com
    2. Enter the basic details
    3. Insert API key and API secret generated in our database.
    4. Now, to login to our site using Facebook login, call the URL: site_url/login?auth-provider=twitter&remote-host=site_url
    5. To get the details of the logged in user, call api_host/api/v1/members/me

    Sample Applications and Libraries

    We have published a number of starter-kits for various popular languages. Please clone these, and use this as a starting point.

    Front End Javascript

    Ruby on Rails

    The Ruby on Rails starter kit can be found here: Coconut. The following libraries are used (and patches appreciated)

    PHP (with Laravel)

    The PHP / Laravel application can be found here: Pina Colada

    Node.js (with React)

    Node.js application can be found here: Pina Colada

    Analytics

    Web Tracking (qlitics.js)

    <!-- Qlitics Snippet -->
    <script>
      window.qlitics=window.qlitics||function(){(qlitics.q=qlitics.q||[]).push(arguments);};
      qlitics('init');
      qlitics('track', 'page-view', {'page-type': <page_type> });
    </script>
    <script async src='/qlitics.js'></script>
    <!-- End Qlitics Snippet -->
    

    The qlitics.js Javascript library is used for tracking user interaction with the frontend website.

    The frontend website needs to implement a route /qlitics.js that proxies to the same route on API_HOST. It also needs to embed the following script before the closing </head> tag (or as early as possible).

    <page_type> is a placeholder for current page's type. The qlitics.js snippet served by API_HOST has the correct publisher-id hardcoded in it.

    Google AMP Tracking

    <!-- Qlitics Tracking -->
    <amp-analytics>
    <script type="application/json">
    {
        "requests": {
            "storyview":"<qlitics_host>/api/${random}/amp?publisher-id=${publisherId}&event-type=${eventType}&story-content-id=${storyContentId}&url=${ampdocUrl}&referrer=${documentReferrer}"
        }
        ,
        "vars": {
            "publisherId": <publisher_id>, "storyContentId": "<story_content_id>"
        }
        ,
        "triggers": {
            "trackStoryview": {
                "on":"visible",
                "request":"storyview",
                "vars": {
                    "eventType": "story-view"
                }
            }
        }
    }
    
    </script>
    </amp-analytics>
    <!-- End Qlitics Tracking -->
    

    <publisher_id>, <qlitics_host> <story_content_id> are placeholders and need to be replaced with equivalent placeholders as per the templating language being used.

    Automatically collected data

    The snippet automatically records the following data

    API Methods

    qlitics('init');
    
    qlitics('set', 'member-id', 1234);
    
    qlitics('track', 'page-view', {'page-type': 'home'});
    
    qlitics('track', 'story-view', {
      'story-content-id': '9b2fe90f-b155-4624-862e-88c981c9da6c',
    });
    
    qlitics('track', 'story-element-view', {
      'story-content-id': '9b2fe90f-b155-4624-862e-88c981c9da6c',
      'story-version-id': 'bc1295de-1b29-4588-8822-3949510b5fd6',
      'card-content-id': '505d5c9d-e776-4f17-bd53-8dd8d579122d',
      'card-version-id': 'abfcabf3-6dcc-4791-a87e-16a36c1b1ae6',
      'story-element-id': '1f97a56d-be01-4a2d-b319-0e88cf9a2259',
      'story-element-type': 'youtube-video',
    });
    
    qlitics('track', 'story-element-action', {
      'story-content-id': '9b2fe90f-b155-4624-862e-88c981c9da6c',
      'story-version-id': 'bc1295de-1b29-4588-8822-3949510b5fd6',
      'card-content-id': '505d5c9d-e776-4f17-bd53-8dd8d579122d',
      'card-version-id': 'abfcabf3-6dcc-4791-a87e-16a36c1b1ae6',
      'story-element-id': '1f97a56d-be01-4a2d-b319-0e88cf9a2259',
      'story-element-type': 'youtube-video',
      'story-element-action': 'play',
    });
    
    qlitics('track', 'story-share', {
      'story-content-id': '9b2fe90f-b155-4624-862e-88c981c9da6c',
      'social-media-type': 'facebook',
      'url': 'https://publisher-domain.com/story-slug',
    });
    

    Settable API Properties

    Common API Fields

    card-content-id

    String UUID. The id for the card.

    card-version-id

    String UUID. The current version of the card.

    member-id

    Int. A unique identifier for the currently logged in member.

    page-type

    String. A short identifier for the current page. Standard values for some of the pages,

    For the remaining pages, any value can be provided.

    social-media-type

    String. A tag to refer to the social media service. Ex facebook, twitter, email, whatsapp, google_plusone_share, linkedin etc

    story-content-id

    String UUID. The id for the current story being displayed.

    story-element-action

    String. The user interaction being tracked with a story element. The value will depend on the element type and iteraction being recorded.

    story-element-id

    String UUID. The id for the story element.

    story-element-type

    String. The type of the story element. Check the docs for available types.

    story-version-id

    String UUID. The current version of the story.

    Testing

    The snippet generates a tracking pixel for every api call made. This pixel is embedded in the DOM and deleted immediately after call succeeds. If the api calls are being made properly, then there should be entries for the resource named event.gif in the network tab of the browser's developer console. The request for this resource should have a data query parameter whose value should be the base64 encoded payload. Decode the data and verify the correct payload is being passed. The server will return a non 200 response if the payload is incorrect.

    Deployments and Infrastructure

    Default URLs

    The Quintype application is made up of at least 3 host names.

    Staging

    Staging urls are typically hosted by Quintype, and will have the following URLs.

    Production

    Production urls typically have the editor and API server on the quintype domains, and the front end on the final domain

    Black Knight

    During the process of creating your account, the Quintype team will also create a publisher (The Foobar) on the Black Knight application. You will be able to deploy all your front end applications from the same place.

    Production DNS Entries

    www.thefoobar.com   CNAME thefoobar.publisher.quintype.io
    next.thefoobar.com  CNAME thefoobar.publisher.quintype.io
    beta.thefoobar.com  CNAME thefoobar.publisher.quintype.io
    thefoobar.com       A     174.129.25.170 # Redirect to www.thefoobar.com using http://wwwizer.com/naked-domain-redirect
    

    The ON the right is an example for the DNS entries to be made

    We strongly recommend that you serve your content off a www domain, and use the apex to forward 301 to your www domain. However, do use a provider that sets a Cache-Control header with the redirect (like GoDaddy).

    # If you serve domain from an APEX domain
    thefoobar.com A 151.101.0.204
    thefoobar.com A 151.101.64.204
    thefoobar.com A 151.101.128.204
    thefoobar.com A 151.101.192.204
    

    If your main site is on an APEX domain, then you must configure A records to our CDN.

    SSL / HTTPS

    If your site requires SSL / HTTPS, contact us support@quintype.com

    Deploying with Black Knight

    The Black Knight application is a tool to deploy your front end code across the Quintype Client Cloud

    Overall Workflow

    The deployment process has three steps

    Compilation Steps

    Deployment Steps

    Setting Up Configuration

    Black Knight can be configure to copy in configuration files. These configuration files are per environment, and allow you to have different behavior across environments, and can be used for many use cases, such as

    Advanced Topics

    RSS Feed

    The stories RSS feed can be used to syndicate stories out from the CMS

    End point

    Supported parameters

    1. Parameter to fetch stories with different time durations

      • /stories.rss?time-period=last-24-hours -> Fetches RSS feed with stories from last 24 hours
      • /stories.rss?time-period=last-7-days -> Fetches RSS feed with stories from last 7 days
      • /stories.rss?time-period=last-1-month -> Fetches RSS feed with stories from last 1 month
    2. Parameter to fetch stories by section slug

      • section slug can be obtained from api/v1/config
      • /stories.rss?section=section-slug -> Ex: /stories.rss?section=sponsored-content This end point fetches stories from Sponsored Content section.
    3. Parameter to fetch stories by section id(Preferable way to fetch stories by section)

      • section id can be obtained from api/v1/config
      • /stories.rss?section-id=id -> Ex: /stories.rss?section-id=2809 This end point fetches stories from Sponsored Content section.
    4. Parameter to skip syndicated stories

      • /stories.rss?skip=value -> This removes syndicated stories from a particular source; Example /stories.rss?skip=bloomberg on bloombergquint skips stories syndicated from bloomberg
      • /stories.rss?skip=all -> This all value as parameter removes all syndicated stories
      • This parameter supports comma separated values; Example /stories.rss?skip=bloomberg,thequint -> This filters out stories syndicated from bloomberg, thequint on bloomberg quint
    5. Parameter to fetch stories from a sorter

      • /stories.rss?story-group=story-group -> This fetches stories by sorter.
      • The story group for any sorter can be obtained from api/v1/config
      • This doesn't have the time limit of 4 hours. This just pulls the stories from a sorter.
    6. Parameters to fetch stories by excluding stories from particular section(s).

      • /stories.rss?exclude-section-ids=section-ids -> This fetches stories by excluding stories from section-id(s) lister; Example: /stories.rss?exclude-section-ids=2435 this fetches stories and filters out stories(if there are any) with section id 2435.
      • section-ids can be obtained from /api/v1/config
      • this parameter supports multiple sections ids as comma separated values. This helps filtering out stories based on multiple sections