- How to use this api
- Project model
- TV player
- System entry policies
- Session validation
- Success evaluation
- Instructions
- List of supported languages
- List of supported operating systems and browsers
- Query test results
- Contact
Version 4 of the eye-square api is a complete rebuild based on graphql. With this documentation comes a full api reference and an interactive explorer to make api calls.
The eyesquare api allows you to easily integrate the eye-square in-context solution into your services.
You can find the graphql endpoint under:
POST https://api.incontext-research.com/v4/graphql
Best starting point for using the api is the interactive explorer. Through it you can easily make api calls and have an interactive feedback for doing so.
We also provide you with some example queries that you can choose in the dropdown from the explorer.
Unfortunately, due to some restrictions in the current graphiql explorer you might see some linting errors where there in reality aren't. If you come across "Expected Value of Type JSON" error as shown in the task.data field cou can safely ignore it and make the api call. If something is wrong, the error in the response will tell you what and where.
The api supports two different authentication types:
- token based authentication. Use this for client based api calls.
- api secret based authentication. Use this for server to server based calls. Never expose your secret to the client side!
Our interactive explorer is able to use both authentication methods in behalf of your account. Just choose which method you want to use in the credentials dropdown. Team credentials uses the api secret of your team, user credentials use the token of your user account.
As we expose different graphql schemas for different authentication methods (a team might have different access restrictions than a normal user) please make sure to use the correct credentials here when testing queries.
We use jwt(json web tokens) to authenticate and authorize users. To make an api call, you have to first retrieve a token and then include this token in you api calls. A jwt has an expiration time of 30 days, means after 30 days a user has to request a new token in order to make api calls.
To get a token you have to make a post request with email and password of the user to:
POST https://api.incontext-research.com/v4/auth
When authentication of the user was successful you will recieve a user profile including a freshly generated token.
Example request using curl
curl \
-X POST \
-H 'Content-Type: application/json' \
-d '{ "email": "users@email", "password": "superSecretPassword" }' \
https://api.incontext-research.com/v4/auth
Recieved response:
{
"role": "user",
"name": "username",
"email": "user@email",
"createdAt": {},
"updatedAt": {},
"team": {
"name": "default"
},
"organization": {
"name": "eye-square",
"code": "ES"
},
"token": "your.access.token"
}
With the recieved token you can now make requests against our api (the explorer uses the token of the logged in user automatically, so you don't have to worry about this when exploring our api). To do so, set a header 'Authorization' with the value 'Bearer your.access.token'.
Example token request (view in explorer):
curl \
-X POST \
-H 'Content-Type: application/json' \
-d '{ "query": "{ me { ...on User { name } }}" }' \
-H 'Authorization: Bearer your.access.token' \
https://api.incontext-research.com/v4/graphql
Results in:
{
"data": {
"me": {
"name": "username"
}
}
}
Each team of an organization has an autogenerated api secret. This secret can be used to make server to server api calls in behalf of the team. You can get the team secret through the explorer:
To make an api secret based call, just either
-
include the secret as a query param:
https://api.incontext-research.com/v4/graphql?apiSecret=yourSecret
-
or set it as a header
Authorization: Secret your.team.secret
Example call from above:
curl \
-X POST \
-H 'Content-Type: application/json' \
-d '{ "query": "{ me { ...on Team { name } }}" }' \
https://api.incontext-research.com/v4/graphql?apiSecret=yourSecret
Results in:
{
"data": {
"me": {
"name": "default" // this is now your teams name
}
}
}
If you are not sure if you are logged in as user, team or use the public api, you can ask for the role of the authenticated user with the following query:
Roles can be one of
- user
- admin (not supported yet)
- team
- superUser
Every list of elements in the api requires pagination parameters. We are following best practices that can be seen here.
There are four input params that control pagination:
- first: integer => returns the first x of entries, if after is set, it returns the first x elements after the given Cursor
- last: integer => returns the last x of entries, if before is set, it returns the last x elements before the given Cursor
- after: Cursor
- before: Cursor
Note: first and last as well as after and before exclude each other. Means, that if you have a first param, you cannot set last at the same time.
At the same time the response Object contains a lot of useful information for pagination through the list, it's best explained with an actual query (view in explorer):
{
projects(last: 2) { # get the last 2 projects
pageInfo {
startCursor # cursor of first recieved edge
endCursor # cursor of last recieved edge
hasNextPage # true if there is content after the last recieved edge
hasPreviousPage # true if there is content before the first recieved edge
}
totalCount # total number of objects you can recieve
nodes { # shortcut to nodes in case you don't need a pagination cursor
...project
}
edges {
node { # the content itself
...project
}
cursor # cursor of the edge, only used in pagination params
}
}
}
fragment project on Project {
id
}
In a lot of cases you wont need the full capacities of pagination. The power of graphQL lets you decide which functionality you use.
For example if you want only retrieve the last 10 projects (view in explorer):
{
projects(last: 10) {
nodes {
...project
}
}
}
fragment project on Project {
id
description
}
Some of our data structures are very complex and would lead to very big Union types/Queries. For this reason, some of our fields are using a custom implementation of JSON type. A Json type can contain anything. This is mostly used in our project.subjectGroups.tasks.data as the data varies based on the Context type. This has the advantage that we keep your queries short, but the downside that we cannot provide autocompletion for fields inside those JSON formats in the explorer.
Example (view in explorer):
{
projects(first: 1) {
nodes {
subjectGroups {
nodes {
tasks {
nodes {
... on ContextTask {
data # this is our custom JSON type that can contain pretty much anything
}
}
}
}
}
}
}
}
For all those types we export fitting graphql types in our schema though, so you know which contextType requires which fields (explorer):
{
__type(name: "ContextTaskDataFacebookNewsfeedStatic") {
fields {
name
description
}
}
}
The project model is very versatile and it's easy to make mistakes. We try to validate every possible value/combination to prevent the creation of wrongly configured projects. Additionally we try to point you to the right places if an error occurs.
To do so, we return a custom Error format. It contains the original Error Message, some default graphql fields (locations and path) as well as an origin.
The origin contains the route to the error in form of an array. If the entry is a string, it's an object property, if it's an integer it's a field index of a list.
An Example:
{
"message": "invalid data at subjectGroups.0.tasks.0.data.mediaSrc - invalid video url",
"origin": ["subjectGroups", 0, "tasks", 0, "data", "mediaSrc"]
}
Translated into a javascript path this would look like:
project.subjectGroups[0].tasks[0].data.mediaSrc
Meaning, the mediaSrc of the specified task is invalid.
The most important parts of the eye-square api are projects. A project is a set of test setups. A test setup is called a Subject Group. Each Subject Group contains a list of routines called Tasks. Each task presents a different part of a setup. There are different kind of tasks:
- ContextTask: a subject is shown an ad inside of a context (for example facebook) and behaviour is measured.
- SurveyTask: an external survey is shown, usually only needed between other tasks.
- PresenterTask: a subject is shown different ads in a choosen presenter (TV, Online Slideshow, ...)
Tasks can be combined in any order. For example:
"project": {
"id": "2018-01_ES001",
"subjectGroups": [{
"id": "first group",
"tasks": [
{
"type": "context",
"contextType": "facebookNewsfeedVideoAutoplay"
// ...
},
{
"type": "survey"
},
{
type: "presenter",
persenterType: "tvPlayer"
// ...
}
],
// ...
}],
// ...
}
This project contains only one subject group. This group contains three tasks, one for each type.
When a subject would enter this test setup it would first get shown a video ad on facebook. Afterwards it would get send to an external survey. When done with the survey the last task would show a TV-setup.
You find a lot of example project setups in the Explorer, just choose one in the Examples dropdown.
A context task shows an ad in a defined context (Facebook for example) and measures behaviour. A context tasks has different phases, most of them optional:
- pre instructions (optional, see further below): some instructions that are shown before the task really starts
- context browsing: the subject is directed to the choosen context and is shown an ad, only required step of a context task.
- post instructions (optional, see further below): instructions a subject sees after the context task.
The following types are currently API supported:
Additional information about the ad formats can be found here: http://e2.ms/adsupport
Each Context type has different setup params. If you want to know which data is required for setup you can ask our graphql schema by searching for "ContextTaskData" Where ad type is one of the types you retrieved above, for example ContextTaskDataFacebookNewsfeedVideoAutoplay
As for now survey tasks don't require any additional setup and we send the subjects back to the provided referrer when entering our system.
The system allows to re-enter the survey between exposures. An intermission call will be made to either the HTTP referrer or the origin if no referrer is provided. The system appends a SessionID that must be returned back for follow up exposures:
BaseUrl: https://schedule.incontext-research.com/scheduler/entry/
Name | Type | Description |
---|---|---|
sessionID | integer | A session identifier we send you in the intermission step. Please save this value and pass it back to us. |
A presenter task has the same phases as a context task. But instead of showing an ad inside a context environment the subject is presented an ad setup in a fixed environment.
The TV player lets you setup up to 9 "TV channels" with different content and ads that are shown in between. The core unit of the setup is a block. You can setup as many blocks as you want and blocks are shown in consecutive order.
You can alternate/setup as many blocks as you want. A content block ends when the first video ends. An ad block ends after the last ad was seen/skipped. After a block ends, the next block is shown right away.
RECORDED DATA
Respondent behavior in TV tasks is recorded as timelines of events with timestamps. There is an example query setup in the explorer.
The following events are distinguished:
StimulusSequence
Gives back events when a video file started visibly playing.
"StimulusSequence": {
"nodes": [
{
"Timestamp": "1542967291284",
"StimulusID": "content1_ch1"
},
{
"Timestamp": "1542967356656",
"StimulusID": "spot_1"
},
{
"Timestamp": "1542967386612",
"StimulusID": "content2_ch1"
}
]
}
StimulusSequenceHost
Gives back absolute task start and end.
"StimulusSequenceHost": {
"nodes": [
{
"Timestamp": "1542967291279",
"StimulusID": "TaskStart"
},
{
"Timestamp": "1542967447604",
"StimulusID": "TaskEnd"
}
]
}
Interact
Gives back when the user zapped away from a video. "Value" is the video ID that was playing when the user zapped away.
"Interact": {
"nodes": [
{
"Timestamp": "1542967314343",
"Value": "content1_ch1",
"EventType": "zapping"
},
{
"Timestamp": "1542967369191",
"Value": "spot_1",
"EventType": "zapping"
}
]
}
CustomEvents
Gives back events if the playback of any video was stopped for whatever reason (e.g. bad internet connection).
"CustomEvent": {
"nodes": [
{
"Timestamp": "1542889006504",
"Name": "ch1_1",
"Value": "stopped"
},
{
"Timestamp": "1542889006505",
"Name": "ch1_1",
"Value": "restart failed"
},
{
"Timestamp": "1542889170304",
"Name": "ch1_1",
"Value": "resumed"
}
]
}
The system entry to start and resume a session is
https://schedule.incontext-research.com/scheduler/entry
A session start requires at least the parameters ProjectID and SubjectGroupID. The UserHash is optional but recommended. If no UserHash is provided, the system will provide a unique user hash. If a UserHash is provided and if it is unique for the called project, stimulus presentation and data collection will be performed normally. If a UserHash already exists in the system, the user will see a termination page: "Your session is expired. Unfortunately, you can only access this survey once. If you have questions or concerns, please contact us at [email protected] and include your SessionID: 123456"
A session resume for the case "task-survey-task" (see chapter Survey Intermission) has to be directed to the same URL:
https://schedule.incontext-research.com/scheduler/entry?SessionID=
but requires the SessionID as an additional parameter (all other parameters can be omitted).
The following parameters will be sent back to the survey to be used for filtering/disqualifying respondents:
Name | Type | Description |
---|---|---|
payload | GET parameters | All variables the system gets passed into will also be returned unmodified. |
eligible | integer | Will be passed after every exposure. Defines if the respondent meets the browser/OS requirements and has successfully installed the browser extension (if required). Values are 0 and 1. |
success | integer | Will be passed after every exposure. Defines if the respondent completed the task. Criteria for success depends on the project configuration. Values are 0 and 1. |
The success of a task can be defined and evaluated on a per project basis. Based on those definitions we analyze the validity of collected metrics. Different evaluation rules are applied by default based on the ad formats and if a task has user guidance enabled or not. These evaluation rules can be customized for each task.
If no metrics are recorded we don't evaluate success and return always 1.
In the table below you see the defaults used:
Context Type | Guided Tasks | Non Guided Tasks |
---|---|---|
facebook-multiple | {"loaded":true} | {"loaded":true} |
facebook-newsfeed-static | {"visible":1} | {"visible":1} |
facebook-newsfeed-video-autoplay | {"percentPlayed":90} | {"visible":1} |
facebook-newsfeed-carousel | {"elements":[{"id":"carousel-container","visible":1}]} | {"elements":[{"id":"carousel-container","visible":1}]} |
facebook-newsfeed-midroll | {"elements":[{"id":"post--midroll--item-0","percentPlayed":90}]} | {"elements":[{"id":"post-*","visible":1}]} |
facebook-newsfeed-content-video | {"percentPlayed":90} | {"visible":1} |
facebook-stories-static | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
facebook-stories-video | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
facebook-stories-carousel | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
facebook-stories-content | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
instagram-multiple | {"loaded":true} | {"loaded":true} |
instagram-newsfeed-static | {"visible":1} | {"visible":1} |
instagram-newsfeed-video-autoplay | {"percentPlayed":90} | {"visible":1} |
instagram-newsfeed-carousel | {"elements":[{"id":"carousel-container","visible":1}]} | {"elements":[{"id":"carousel-container","visible":1}]} |
instagram-reels-infeed-video | {"percentPlayed":90} | {"visible":1} |
instagram-stories-static | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
instagram-stories-video | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
instagram-stories-carousel | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
instagram-stories-content | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
snapchat-static | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
snapchat-video | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
snapchat-carousel | {"elements":[{"id":"story-0","visible":1}]} | {"elements":[{"id":"story-0","visible":1}]} |
snapchat-inline-static | {"visible":1} | {"visible":1} |
snapchat-inline-video | {"visible":1} | {"visible":1} |
tiktok-multiple | {"loaded":true} | {"loaded":true} |
tiktok-brand-takeover-static | {"elements":[{"id":"tiktok-post","visible":1}]} | {"elements":[{"id":"tiktok-post","visible":1}]} |
tiktok-brand-takeover-video | {"elements":[{"id":"tiktok-post","percentPlayed":90}]} | {"elements":[{"id":"tiktok-post","visible":1}]} |
tiktok-infeed-video | {"elements":[{"id":"tiktok-post","percentPlayed":90}]} | {"elements":[{"id":"tiktok-post","visible":1}]} |
tiktok-topview-video | {"elements":[{"id":"tiktok-post","percentPlayed":90}]} | {"elements":[{"id":"tiktok-post","visible":1}]} |
twitch-preroll | {"played":5000} | {"visible":1} |
twitch-midroll | {"played":5000} | {"visible":1} |
twitter-newsfeed-static | {"visible":1} | {"visible":1} |
twitter-newsfeed-video-autoplay | {"percentPlayed":90} | {"visible":1} |
websites-homepage-static | {"visible":1} | {"visible":1} |
websites-homepage-video | {"visible":1} | {"visible":1} |
websites-multiple | {"loaded":true} | {"loaded":true} |
youku-preroll | {"visible":1} | {"visible":1} |
youtube-discovery | {"loaded":true} | {"loaded":true} |
youtube-content | {"loaded":true} | {"loaded":true} |
youtube-frequency | {"played":5000} | {"visible":1} |
youtube-pods | {"played":5000} | {"visible":1} |
youtube-preroll | {"played":5000} | {"visible":1} |
youtube-masthead | {"elements":[{"id":"youtube-content-video","played":5000}]} | {"elements":[{"id":"mastheadAd-0","visible":1}]} |
youtube-multiple | {"loaded":true} | {"loaded":true} |
youtube-shorts-infeed | {"percentPlayed":90} | {"visible":1} |
youtube-shorts-content | {"percentPlayed":90} | {"visible":1} |
Presenter and Context tasks can have instructions shown before (pre instructions) and after the task (post instructions).
A instruction is made of following fields:
- html: html that is shown as the instruction text.
- button: content of the instruction button.
Special cases:
- if no instructions (
null/undefined
) are set, default instructions are used. - if instructions is an empty onject (
{}
), no instructions are shown.
We will add default instructions in the following way:
- we always add pre instructions
- if the task is the last task, we add post instructions as well.
Replacements and placeholders:
It is possible to have dynamic values inside of the instructions html (pre instructions only for now) to make instruction strings more generic. Following placeholders will be replaced with the according value on runtime:
{{duration}}
=> exposureDuration(context task) or timeout(presenter task) of the task.{{name}}
=> adName of the task (context tasks only).{{context}}
=> context of the task (context tasks only).
The following would show a custom instruction before a task but none after. It would also replace {{duration}}
with the set exposure duration.
"instructions": {
"pre": {
"html": "You will now see a Facebook newsfeed. Please browse it for the next {{duration}}s as you would normally do.",
"button": "continue"
}
}
// first task:
"instructions": {},
// second task:
"instructions": {
"pre": {
"html": "You will now see a Facebook newsfeed.<br/> Please browse it for the next {{duration}}s as you would normally do.",
"button": "continue"
},
"post": {
"html": "That was nice, wasn't it?",
"button": "continue"
}
}
// third task:
"instructions": null // null|undefined
This example shows no instructions in the first, custom instructions for the second, and default instructions in the third task.
The current list of supported languages per context can be found here: http://e2.ms/local
The current list of supported OSs and browsers can be found here: http://e2.ms/surveyintegration
The supported Operating Systems and browsers can be overwritten using deploymentFilters
, either on the subjectGroup or project level, in that order of precedence.
Allow Windows (Xp and above), Linux (any version) and any browser.
"deploymentFilters": {
"os": [
{
"name": "windows",
"min": "5.2"
},
{
"name": "linux"
}
]
}
The above is equivalent to this:
"deploymentFilters": {
"os": [
{
"name": "windows",
"min": "5.2"
},
{
"name": "linux"
}
],
"browsers": [ ]
}
Example with browsers:
"deploymentFilters": {
"os": [
{
"name": "mac os",
"min": "10.6"
},
]
"browsers": [
{
"name": "chrome",
"min": "41"
},
{
"name": "firefox",
"min": "51"
}
]
}
Matches are case insensitive, see ua-parser-js for possible OS and browser name values.
Metrics and timelines of a project can be queried through the api. Both are part of a project response, so you can query metrics and timelines on every project object.
Both, timelines and metrics let you set filter for retrieving subsets of the recorded data.
- subjectGroup: String, if set only returns data for that subjectGroup inside a project
- task: String, if set, only returns data for a matching taskId. Note: if you don't set a subjectGroup but a taskId you may recieve entries for different subjectGroups.
- subject: Only returns data of a specific subject.
All filter can be combined together. View in Explorer
A metric is a (ad/media) preformance indicator derived of events of a recording. It typically measures things like durations a video was seen, the time a button was clicked.
Every Metric Set contains one or more elementSets in which metrics are grouped. An element describes an observed element during a test run. In most cases it's the tested ad and its properties (like video controls).
A timeline is a sequence of recorded events.
For technical support regarding this API please send an email to [email protected] or ask us in slack.