Content API
Content API
In this article, you’ll learn how to use the Content API to retrieve Placement content for API implementations of Coveo Experience Hub.
Glossary
-
Solution: A solution is High-level personalization type, and one of {personalized content}, {product recommendations}, or product badging.
-
Tag: A tag is a 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: Triggers are rules attached to a Placement to determine when and where the Placement is rendered. These only affect Placements delivered client-side.
-
Placement: A Placement is 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: A campaign is a mean 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: An audience is a group of people, as defined in the Experience Hub.
-
Qubit visitor Id: A Qubit visitor Id is the Qubit’s device-level identifier for a visitor, stored in a cookie called
_qubitTracker
. -
Smartserve: Smartserve is the Coveo Qubit’s JavaScript bundle, containing event tracking, segmentation, and client-side experimentation code.
Endpoint
All content is served from a single GraphQL API:
-
URL:`https://api.qubit.com/placements/query`
-
HTTP method: POST
-
Content types: application/json or text/plain (see Content types below)
-
Body format: JSON GraphQL request (see Body format below)
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
andclickthrough
. If unspecified, animpression
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.
-
gsid: optional, contains the Google Analytics session Id of the actual visitor's session. 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}
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:
-
If the business user hasn’t published a campaign
-
If the business user has paused an existing campaign
-
If the business user has published a campaign with a 50% or 95% traffic split and the visitor is in the control group
-
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
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
orSAMPLE
. For campaigns, the mode can beLIVE
orPREVIEW
. -
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.
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 |
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
.
The preview will be displayed successfully only if the |
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 |
|
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 |
view.subtypes (String array) |
Always |
|
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.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 |
|
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 |
|
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 |
|
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:
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"
}
}
}