Content API

In this article, you’ll learn how to use the Content API to retrieve Placement content for API implementations of Coveo Merchandising Hub.

Glossary

  • Solution—high-level personalization type, and one of {personalized content}, {product recommendations}, or product badging.

  • Tag—way to group Placements within Coveo Qubit UI. A Placement can have multiple tags and be named in any way to make it more understandable for the business user. Typically, tags are 1-to-1 with a page type, for example, "Homepage," Product detail page (PDP), or Product Listing Page (PLP).

  • Triggers—rules attached to a Placement to determine when and where the Placement is rendered. These only affect Placements delivered client-side.

  • Placement - specific area on a site designated to render content served by the Content API with a set schema. The Placement can map to one of our three solutions.

  • Campaign - means of deploying content(product data) into a placement.

  • Experience - In the context of deploying personalized content campaigns, an experience is a pairing of an audience with content which can help you filter who sees what content.

  • Audience - a group of people, as defined in the Merchandising Hub.

  • Qubit visitor Id—Qubit’s device-level identifier for a visitor, stored in a cookie called _qubitTracker.

  • Smartserve - Coveo Qubit’s JavaScript bundle, containing event tracking, segmentation, and client-side experimentation code.

Endpoint

All content is served from a single GraphQL API:

  • URLhttps://api.qubit.com/placements/query

  • HTTP method—POST

  • Content types—application/json or text/plain - see below

  • Body format—JSON GraphQL request - see below

Tip

You can try the Content API through our GraphQL playground interface.

Content types

You’ll usually send GraphQL POST requests as application/json. However, if the request is being made cross-origin, this will cause the browser to make a preflight CORS request (with the HTTP OPTIONS method) and will increase overall latency for the query.

To mitigate this, the Qubit Content API also supports content being sent as text/plain, which won’t cause the browser to send a preflight request.

Body format

You should send GraphQL requests as a JSON with two fields, a query string, and a variables object.

Example request:

{
  "query": "<GraphQL query>",
  "variables": { ... }
}

See GraphQL argument reference for more information on the "variables" object.

Getting content for a single Placement

The following example shows how you can get content for a simple Placement with three fields—​an image, a URL, and some text.

Example GraphQL query:

query PlacementContent (
  $mode: Mode!
  $placementId: String!
  $attributes: Attributes!
  $resolveVisitorState: Boolean!
) {
  placementContent(
    mode: $mode
    placementId: $placementId
    attributes: $attributes
    resolveVisitorState: $resolveVisitorState
  ) {
    content
    callbackData
    visitorId
  }
}

Variables:

{
  "mode": "LIVE",
  "placementId": "IMAmsWP4T827kUWUPEQEqA",
  "attributes": {
    "visitor": {
      "id": "qs9rlhhzets-0k1g9i70n-dzq3rto",
      "url": "https://example.com",
    },
    "user": {},
    "product": {},
    "view": {
      "currency": "EUR",
      "type": "home",
      "subtypes": [],
      "language": "en-gb"
    }
  }
}

Example response:

{
  "data": {
    "placementContent": {
      "content": {
        "image": "https://cdn.example.com/foo.jpg",
        "link": "https://example.com/foo",
        "message": "Foo bar!"
      },
      "callbackData": "aaa-111-bbb-222-ccc-333"
    }
  }
}

See Response schema reference for more details on the response object.

Handling the Qubit visitor ID

The Qubit Visitor Id is a device-specific identifier, usually stored in a first-party cookie. Qubit relies on the Visitor Id to track user activity around a site and measure campaign performance.

Therefore, it’s essential that the Visitor Id is persisted and passed correctly to the Content API.

Qubit’s Smartserve JavaScript bundle will create the Visitor Id if it doesn’t exist; this is typically the case for new visitors to the site or users who have cleared their cookies.

How you work with the Visitor Id depends on where you’re calling the Content API from. To use a self-generated identifier, see Using a self-generated Visitor Id.

Browser-side

When calling the Content API from the browser, smartserve exposes the value of the Visitor Id via the getVisitorState method. This method will be provided to your Placement code or can also be invoked using __qubit.jolt.getVisitorState().

Server-side

Calling the Content API from the server is more complex because you also need to manage site visitors that don’t yet have a Visitor Id.

When this happens, you’ll need to pass an empty string for the visitor.id attribute into your Content API request. The Content API will then generate a new Visitor Id and return it to you in the response body.

Example GraphQL query:

query PlacementContent (
  $mode: Mode!
  $placementId: String!
  $attributes: Attributes!
  $resolveVisitorState: Boolean!
) {
  placementContent(
    mode: $mode
    placementId: $placementId
    attributes: $attributes
    resolveVisitorState: $resolveVisitorState
  ) {
    content
    callbackData
    visitorId
  }
}

Variables:

{
  "mode": "LIVE",
  "placementId": "IMAmsWP4T827kUWUPEQEqA",
  "attributes": {
    "visitor": {
      "id": "",
      "url": "https://example.com",
    },
    "user": {},
    "product": {},
    "view": {
      "currency": "EUR",
      "type": "home",
      "subtypes": [],
      "language": "en-gb"
    }
  }
}

Example response:

{
  "data": {
    "placementContent": {
      "content": {
        "image": "https://cdn.example.com/foo.jpg",
        "link": "https://example.com/foo",
        "message": "Foo bar!"
      },
      "callbackData": "aaa-111-bbb-222-ccc-333",
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}

You’ll then need to persist this new Visitor Id by using the Set-Cookie HTTP header in the response to the user’s browser. You should set the cookie using the following configuration:

  • Cookie name_qubitTracker

  • Path/

  • Domain—the site domain, with a leading ., such as .example.com

  • Expiry—one year from the time of the request

  • HttpOnly—no (you need to read it from JavaScript)

  • Secure—typically, yes (the only reason not to include this flag would be if there are any HTTP-only sections on the site)

Using a self-generated Visitor ID

It’s possible to use any unique identifier as the Visitor Id instead of using the one generated by Qubit. You can do this both browser- and server-side.

Browser-side

When setting a self-generated Visitor Id from the browser, you must set the _qubitTracker cookie before the Smartserve script loads. Otherwise, Smartserve will generate its own Visitor Id.

You can also edit your prescript to delay Smartserve from loading until you can set the cookie.

You only need to do this for visitors that don’t have this cookie value already set.

The cookie should have the following configuration:

  • Cookie name_qubitTracker

  • Path/

  • Domain—the domain of the website, with a leading ., such as .testdomain.com

  • Expiry—one year from the time of the request

  • HttpOnly—no (you need to read it from JavaScript)

  • Secure—typically, yes (the only reason not to include this flag would be if there are any HTTP-only sections on the site)

Server-side

When setting a self-generated Id from the server, you must set the _qubitTracker cookie using the Set-Cookie HTTP header in the response to the visitor’s browser.

You only need to do this for visitors that don’t have this cookie value already set.

The cookie should have the following configuration:

  • Cookie name_qubitTracker

  • Path/

  • Domain—the site domain, with a leading ., such as .testdomain.com

  • Expiry—one year from the time of the request

  • HttpOnly—no (you need to read it from JavaScript)

  • Secure—typically, yes (the only reason not to include this flag would be if there are any HTTP-only sections on the site)

Executing callback URLs

The Content API response contains a callbackData blob allowing you to form callback URLs. Calling these URLs will cause tracking events to be sent by the Content API, allowing Qubit to measure campaign performance.

To execute a callback, you must make an HTTP GET request to the callback URL. The callbackData blob is a compressed set of campaign attributes used for performance measurement that can’t be interpreted directly.

A typical Content API response will look like this:

{
  "data": {
    "placementContent": {
      "content": { ... },
      "callbackData": "aaa-111-bbb-222-ccc-333"
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}

A typical callback URL will look like this:

https://api.qubit.com/placements/cb?d={callbackData}&t={eventType}...

The following query parameters are used:

  • d—required, contains the callbackData blob from the Content API response

  • t—optional, contains the event type. Supported event types are impression and clickthrough. If unspecified, an impression event is recorded

  • ts—recommended, contains the actual timestamp of the event. The format is an integer number of milliseconds since January 1, 1970, equivalent to Date.now() in JavaScript. If unspecified, the event is recorded with the timestamp of the callback URL request

  • gcid—optional, contains the Google Analytics client Id of the actual visitor. Only used when GA tracking of campaigns is enabled for the property.

  • imp—optional, contains the Placement’s unique implementation Id. This parameter is automatically appended to injected Placements and not used for Placements delivered via API.

  • debug—optional, causes the callback endpoint to return debugging information formatted as JSON. This should only be used for development and debugging purposes. Using this parameter doesn’t stop the events from being emitted. There are three modes:

    • debug without any value returns decoded callback data

    • debug=qp returns the QP event being emitted

    • debug=ga returns the GA event being emitted

The response to the callback GET request could be one of the following:

  • HTTP code 200 and an empty body for requests that were parsed without error

  • HTTP code 200 and a JSON body when the debug mode is being invoked

  • HTTP code 400 and a plain text error message in the body for requests that failed to parse. Example error messages are "invalid data" or "invalid type"

Impressions

The impression callback should be executed as soon as the Placement content is in view for the visitor. If you’re calling the Content API server-side, you should pass the callback data to the browser so that the callback URL request is executed via JavaScript when a visitor sees the Placement.

Clickthroughs

The clickthrough callback should be executed when the user clicks the Placement’s primary CTA. If clicking the CTA will cause the browser to navigate, it’s important to execute the callback before navigation occurs; otherwise, it will cancel the callback HTTP request.

If there are many clickable elements, for example, in a product recommendations carousel, the clickthrough callback should be executed for a click on any of the elements.

Reporting on individual products

In addition to reporting on Placement-level impressions and clickthrough events, you should also report individual products that were seen or clicked. To do this, form a separate callback request and add product IDs to the callback URLs:

  • Add product IDs to the query string as query parameters - &p=id

  • Multiple product Ids are supported - &p=id&p=id

  • The Ids must be URL-encoded

  • The Ids must match those in your product feed and QProtocol events (product.productId)

"URL-encoded" means serializing the Ids according to the URL Standard, Section 5.2, step 3.4 on encoding values. For example, that means that spaces, single and double quotes, among other characters, have to be percent-encoded.

This is typically done for product recommendations Placements but can also be done in personalized content Placements containing a "product" element in their schema.

A callback URL with added product Ids will look like this:

https://api.qubit.com/placements/cb?d={callbackData}&t={eventType}&p={product-id}
Warning

Emitting a callback request with the product ID query parameter doesn’t replace a Placement-level callback. A well-implemented Placement will emit both global callbacks and product-specific ones, where applicable.

Handling no content for a Placement

There are several scenarios where the Content API may not return content to be rendered into the page:

  1. If the business user hasn’t published a campaign

  2. If the business user has paused an existing campaign

  3. If the business user has published a campaign with a 50% or 95% traffic split and the visitor is in the control group

  4. If the business user has published a campaign and the visitor doesn’t match any audience in it

Example response:

{
  "data": {
    "placementContent": {
      "content": null,
      "callbackData": "aaa-111-bbb-222-ccc-333"
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}

You must handle this scenario. There are a couple of options:

  • If the Placement is replacing an existing piece of content on the page, then the original content should be shown:

    • Impression and clickthrough callbacks should still be called in this scenario to compute A/B testing metrics. They should be called under conditions as similar as possible to the "content returned" case to ensure the fairness of A/B tests

  • If the Placement is inserting an entirely new piece of content, then it’s best to show nothing and to call the impression callback:

    • The impression should be fired under conditions as similar as possible to the "content returned" case to ensure the fairness of A/B tests.

GraphQL argument reference

  • placementId (string) - unique Id of the implemented Placement

    Important

    Make sure that the placementId for the placement you want to launch in your campaign matches the `placementId used in your payload request to the Content API. If this isn’t the case, you won’t see any changes in the preview of the placement.

  • mode (enum) - In the case of placements, the mode can be either LIVE or SAMPLE. For campaigns, the mode can be LIVE or PREVIEW.

  • campaignId (string) - in PREVIEW mode, identifies the campaign to preview.

  • experienceId (string) - in PREVIEW mode, identifies the campaign experience to preview.

When the mode is set to LIVE, the request will return the current live content for a Placement.

When the mode is set to SAMPLE, the URL for the placement generated will load using the information stored in qb_opts cookies. You can avail the SAMPLE option by going to a specific placement and clicking "Preview" button in the panel on the upper-right corner of the screen.

Preview Placement

When the mode is set to PREVIEW, the previewOptions object (see below) has to be present in the Content API payload request as one of the parameters. The group and `campaignId values also need to be present in the object (other values are optional). The request will return the latest draft of a specific campaign or experience.

Note

The "Preview" option for placements is different from the PREVIEW option for campaigns. Previewing a campaign allows you to see a draft version of the campaign as a whole whereas the "Preview" option for placements can be used for a single placement.

When a business user uses the preview functionality in the Qubit application, the URL they will land on will have the following format:

https://<baseUrl>?qb_opts=preview,remember&qb_placement_id=<placementId>&qb_campaign_id=<campaignId>&qb_experience_id=<experienceId>&qb_remember=1&qb_mode=PREVIEW

previewOptions.campaignId (string)

When provided in PREVIEW mode by itself (that is, previewOptions.experienceId isn’t specified), a campaign level preview will be requested. This means that the latest draft of the campaign will be returned. When previewing through the Qubit platform, this will be passed through the URL search parameters as qb_campaign_id.

Important

The preview will be displayed successfully only if the qb_opts parameter is present in the URL and the value of the preview parameter is true. The presence of these parameters overrides the values of the parameters present in Content API payload request.

previewOptions.experienceId (string)

When provided in PREVIEW mode along with previewOptions.campaignId, an experience level preview will be requested. This means that the latest draft of the experience will be returned. When previewing through the Qubit platform, this will be passed through the URL search parameters as qb_experience_id.

visitorId (string)

The unique Qubit Visitor Id. This Id is a device-specific identifier, usually stored in a first-party cookie and is used to track user activity around a site and measure campaign performance.

See Handling the Qubit Visitor Id for details of how to handle this argument.

resolveVisitorState (Boolean)

Attribute (JS data type) Details

attributes.visitor.conversionNumber (int)

Inferred from attributes.visitor.id based on stored history

attributes.visitor.sessionNumber (int)

Inferred from attributes.visitor.id based on stored history

attributes.visitor.lifetimeValue (float)

Inferred from attributes.visitor.id based on stored history

attributes.visitor.firstViewTs (int)

Inferred from attributes.visitor.id based on stored history

attributes.visitor.lastViewTs (int)

Inferred from attributes.visitor.id based on stored history

attributes.visitor.firstConversionTs (int)

Inferred from attributes.visitor.id based on stored history

attributes.visitor.lastConversionTs (int)

Inferred from attributes.visitor.id based on stored history

attributes.location.areaCode (string)

Inferred from attributes.visitor.ipAddress

attributes.location.cityCode (string)

Inferred from attributes.visitor.ipAddress

attributes.location.regionCode (string)

Inferred from attributes.visitor.ipAddress

attributes.location.countryCode (string)

Inferred from attributes.visitor.ipAddress

Values for the following attributes must be set on all pages:

Attribute (JS data type)

attributes.visitor.ipAddress (String)

attributes.visitor.url (String)

attributes.visitor.userAgent.userAgentString (String)

attributes.view.type (String)

attributes.view.subtypes (String array)

attributes.view.currency (String)

attributes.view.language (String)

attributes.user.id (String)

attributes.user.email (String)

Values for the following attributes must be set on product detail pages:

Attribute (JS data type)

attributes.product.id (String)

attributes.product.name (String)

attributes.product.categories (String array)

Values for the following attributes must be set on basket and checkout pages:

Attribute (JS data type)

attributes.basketProducts.[].id (string)

attributes.basketProducts.[].name (string)

attributes.basketProducts.[].categories (string array)

Values for the following attribute must be set on confirmation pages:

Attribute (JS data type)

attributes.transactionProducts.[].id (String)

attributes.transactionProducts.[].name (String)

attributes.transactionProducts.[].categories (String array)

Full attribute reference

All attributes are assumed to be children of the attributes argument.

Attribute (JS data type) When to set Example

visitor.id (String)

Always

aaaa-bbbb-cccc-dddd

visitor.ipAddress (String)

Always

0.0.0.0

visitor.url (String)

Always

https://www.qubit.com

visitor.userAgent.userAgent (String)

Always

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36

view.type (String)

Always

Can be one of basket, category, checkout, confirmation, home, product, search

view.subtypes (String array)

Always

["womens"," "dresses"]

view.currency (String)

Always

USD

view.language (String)

Always

en-us

user.id (String)

Always, if available

eabc123

user.email (String)

Always, if available

user@qubit.com

user.loyalty.membershipType (String)

Always, if available

gold

visitor.conversionNumber (Int)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

1

visitor.sessionNumber (Int)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

1

visitor.lifetimeValue (Float)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

1000.00

visitor.firstViewTs (Int)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

1613681906471

visitor.lastViewTs (Int)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

1613681906471

visitor.firstConversionTs (Int)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

1613681906471

visitor.lastConversionTs (Int)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

1613681906471

location.areaCode (String)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

124462

location.cityCode (String)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

12997

location.regionCode (String)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

65

location.countryCode (String)

Always, if available. If unavailable, it can be resolved if resolveVisitorState is set to true

CA

product.id (String)

On product detail pages

abc123

product.name (String)

On product detail pages

Red Dress

product.categories (String array)

On product detail pages

["Women’s > Dresses"]

basketProducts.[].id (String)

On any page, for each product in a visitor’s basket

abc123

basketProducts.[].name (String)

On any page, for each product in a visitor’s basket

Red Dress

basketProducts.[].categories (String array)

On any page, for each product in a visitor’s basket

["Women’s > Dresses"]

transactionProducts.[].id (String)

On the confirmation page, for each product in a visitor’s transaction

abc123

transactionProducts.[].name (String)

On the confirmation page, for each product in a visitor’s transaction

Red Dress

transactionProducts.[].categories (String array)

On the confirmation page, for each product in a visitor’s transaction

["Women’s > Dresses"]

Response schema reference

The response schema will depend on the GraphQL query provided.

Example GraphQL query:

query PlacementContent (
  $mode: Mode!
  $placementId: String!
  $attributes: Attributes!
) {
  placementContent(
    mode: $mode
    placementId: $placementId
    attributes: $attributes
  ) {
    content
    callbackData
    visitorId
  }
}

Example response:

{
  "data": {
    "placementContent": {
      "content": {...},
      "callbackData": "aaa-111-bbb-222-ccc-333",
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}

The data.placementContent.content object will have a different schema depending on the Placement’s solution type.

Personalized content

For personalized content, the Placement’s schema is set in our Placement builder and is fully customizable:

pc schema

Example Placement query response:

{
  "data": {
    "placementContent": {
      "content": {
        "message": "Hello there",
        "link": "https://example.com/cta",
        "image": "https://www.instantprint.co.uk/umbraco-media/6627/indoorevents-category-banner-web.jpg"
      },
      "callbackData": "aaa-111-bbb-222-ccc-333",
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}

Product recommendations

For product recommendations, the Placement schema contains a headline string as well as a recs array of recommended products. You can also define custom fields in the schema builder.

Example Placement query response:

{
  "data": {
    "placementContent": {
      "content": {
        "headline": "Recommended for you",
        "link": "https://example.com/cta",
        "recs": [
          {
            "details": {
              "categories": [
                "Dresses"
              ],
              "images": [ "https://cdn.shopify.com/s/files/1/0525/2343/4176/products/premium-black-floral-embroidered-maxi-dress_400x.jpg?v=1610720134"
              ],
              "language": "en",
              "id": "6152963096768",
              "views": 7059,
              "name": "Black floral summer dress",
              "stock": 9641,
              "size": null,
              "unit_sale_price": 300,
              "currency": "gbp",
              "base_currency": "GBP",
              "unit_price": 300,
              "url": "https://qubit-demo.myshopify.com/products/short-sleeve-t-shirt",
              "description": "Casual summer dress, cotton",
              "category": null,
              "subcategory": null,
              "locale": "en-gbp",
              "image_url": "https://cdn.shopify.com/s/files/1/0525/2343/4176/products/premium-black-floral-embroidered-maxi-dress_400x.jpg?v=1610720134"
            }
          },
          {
            "details": {
              "categories": [
                "Dresses"
              ],
              "images": [
                "https://cdn.shopify.com/s/files/1/0525/2343/4176/products/Midi_Wrap_Dress_049aed2e-601d-4338-b7ef-debc0d74f2a1_400x.jpg?v=1610720082"
              ],
              "language": "en",
              "id": "6134818603200",
              "views": 7071,
              "name": "Astrid Wool-Cashmere Midi Wrap Dress",
              "stock": 9644,
              "size": null,
              "unit_sale_price": 448,
              "currency": "gbp",
              "base_currency": "GBP",
              "unit_price": 448,
              "url": "https://qubit-demo.myshopify.com/products/astrid-wool-cashmere-midi-wrap-dress",
              "description": "This is a product",
              "category": null,
              "subcategory": null,
              "locale": "en-gbp",
              "image_url": "https://cdn.shopify.com/s/files/1/0525/2343/4176/products/Midi_Wrap_Dress_049aed2e-601d-4338-b7ef-debc0d74f2a1_400x.jpg?v=1610720082"
            }
          }
        ]
      },
      "callbackData": "aaa-111-bbb-222-ccc-333",
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}

Each element in the data.placementContent.content.recs.[] array contains metadata that’s defined in the product feed.

Product badging

For product badging, the Placement schema contains a message string and an imageUrl string (in a URL format) that can both be set to null. The merchandiser who created the campaign has to set at least one of these two strings to a value that’s not null. You can also define custom fields in the schema builder.

The returned content is formatted depending on whether a single or multi-product source is injected into the Placement.

Example Placement query response for single-product sources:

{
  "data": {
    "placementContent": {
      "content": {
        "message": "Selling Fast",
        "imageUrl": "https://dd6zx4ibq538k.cloudfront.net/static/images/5797/89912f668090747c00863e3fe21f1ff3_256_256.png"
      },
      "callbackData": "aaa-111-bbb-222-ccc-333",
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}

Example Placement query response for multi-product sources:

{
  "data": {
    "placementContent": {
      "content": {
        "badges": {
          "PRODUCT001": [
            {
              "message": "Selling Fast",
              "imageUrl": "https://dd6zx4ibq538k.cloudfront.net/static/images/5797/89912f668090747c00863e3fe21f1ff3_256_256.png"
            }
          ],
          "PRODUCT002": [
            {
              "message": "Popular!",
              "imageUrl": null
            }
          ],
        }
      },
      "callbackData": "aaa-111-bbb-222-ccc-333",
      "visitorId": "qs9rlhhzets-0k1g9i70n-dzq3rto"
    }
  }
}