Integrations
Integrations
Seamlessly integrate Roundtable Alias into your existing survey platforms. Whether you’re using a custom solution or popular survey software like Forsta, Qualtrics, or jsPsych, our API provides robust behavioral tracking and fraud detection capabilities to ensure the integrity of your survey data.
Forsta
Integrate Roundtable Alias with your Forsta surveys to gain comprehensive insights into respondent behavior. With our seamless integration process, you can easily embed tracking code into your surveys and analyze data for fraudulent activity with precision.
Contact Forsta Support
Contact Forsta support and ask them to whitelist https://www.roundtable.ai
for API calls.
Embed tracking code
In the XML for your survey, add the following lines of code to every “Text” or “Essay” questions you want to track (text
and textarea
elements in XML). This code embeds our Javascript tracker for the question and automatically extracts the question and response:
<style name="question.after"><![CDATA[<script src='https://roundtable.ai/js/forsta-tracker.js'></script>]]></style>
<style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
const label = q['label']
const oes = document.querySelectorAll('input[type="text"], textarea');
oes.forEach(el => {
el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
});
initializeClientDict(label, q);
]]></style>
Store data
Add the following lines of code to the end of your survey and set the api_key
to your Alias API Key (you can generate keys on your account).
This code will call the API on the page the code is embedded in. This generally takes a few seconds and must finish executing before the next page is loaded. Also note that the code must be placed after any open-ends you are tracking and on a separate page from any open-ends (i.e., after a <suspend/>
in XML).
<exec>
survey_id = gv.survey.root.alt
api_key = ''
p.headers = {'api_key': api_key, 'Content-Type': 'application/json'}
p.data = {'responses': p.client_dict['responses'], 'questions': p.client_dict['questions'],
'question_histories': p.client_dict['question_histories'], 'survey_id': survey_id, 'participant_id': uuid }
</exec>
<logic cond="p.client_dict['num_non_empty'] != 0" label="alias_api" api:data="p.data" api:headers="p.headers" api:method="POST" api:url="https://roundtable.ai/api/alias/latest" uses="api.1"/>
<exec>
def checks_array_to_string(array):
return ', '.join(map(str, array)) if array else "None"
def format_checks(response, keys):
return {key: checks_array_to_string(response.r.get('checks', {}).get(key, [])) for key in keys}
def format_response_groups(response, keys):
return {key: response.r.get('response_groups', {}).get(key, 0) for key in keys}
labels = p.client_dict['question_labels']
alias_responses.r1.val = alias_api.r.get('flagged', False)
alias_responses.r2.val = format_checks(alias_api, labels)
alias_responses.r3.val = format_response_groups(alias_api,labels)
alias_responses.r4.val = alias_api.r.get('num_checks_failed', 0)
alias_responses.r5.val = alias_api.r.get('model', 'API not called')
alias_responses.r6.val = alias_api.r.get('error', False)
alias_responses.r7.val = alias_api.r.get('problem', 'None')
</exec>
<html label="h1"><script>
sessionStorage.removeItem('client_dict');
</script></html>
<text
label="alias_responses"
optional="0"
size="25"
translateable="0"
where="execute,survey,report">
<title>roundtable data holder</title>
<row label="r1">alias-flagged</row>
<row label="r2">alias-checks</row>
<row label="r3">alias-response_groups</row>
<row label="r4">alias-num_checks_failed</row>
<row label="r5">alias-model</row>
<row label="r6">alias-error</row>
<row label="r7">alias-problem</row>
</text>
The Alias response will now be stored in your survey data in the alias_responses
field. The keys of the checks
and response_groups
objects will match the labels of the questions being tracked.
Example XML script
Here is a example of a simple survey with all of the Alias code integrated:
<?xml version="1.0" encoding="UTF-8"?>
<survey
alt="test-survey-1"
autosave="0"
builder:wizardCompleted="1"
builderCompatible="1"
compat="153"
delphi="1"
extraVariables="source,record,decLang,list,userAgent"
fir="on"
html:showNumber="0"
mobile="compat"
mobileDevices="smartphone,tablet,desktop"
name="Survey"
secure="1"
setup="term,decLang,quota,time"
ss:disableBackButton="1"
ss:enableNavigation="1"
ss:hideProgressBar="0"
state="testing">
<samplesources default="0">
<samplesource list="0">
<title>Open Survey</title>
<invalid>You are missing information in the URL. Please verify the URL with the original invite.</invalid>
<completed>It seems you have already entered this survey.</completed>
<exit cond="terminated">Thank you for taking our survey.</exit>
<exit cond="qualified">Thank you for taking our survey. Your efforts are greatly appreciated!</exit>
<exit cond="overquota">Thank you for taking our survey.</exit>
</samplesource>
</samplesources>
<suspend/>
<text
label="q1"
optional="1"
size="25">
<title>What do you do first in the morning?</title>
<comment>Be specific</comment>
<style name="question.after"><![CDATA[<script src='https://roundtable.ai/js/forsta-tracker.js'></script>]]></style>
<style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
const label = q['label']
const oes = document.querySelectorAll('input[type="text"], textarea');
oes.forEach(el => {
el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
});
initializeClientDict(label, q);
]]></style>
</text>
<suspend/>
<textarea
label="q2"
height="10"
optional="1"
width="50">
<title>Tell me about yourself</title>
<comment>Be specific</comment>
<style name="question.after"><![CDATA[
<script src='https://roundtable.ai/js/forsta-tracker.js'></script>]]></style>
<style name="question.after" wrap="ready"><![CDATA[
const q = ${jsexport()};
const label = q['label']
const oes = document.querySelectorAll('input[type="text"], textarea');
oes.forEach(el => {
el.addEventListener('input', e => handleChange(e, 'input', el, label, q));
el.addEventListener('copy', e => handleChange(e, 'copy', el, label, q));
});
initializeClientDict(label, q);
]]></style>
</textarea>
<suspend/>
<exec>
survey_id = gv.survey.root.alt
api_key = ''
p.headers = {'api_key': api_key, 'Content-Type': 'application/json'}
p.data = {'responses': p.client_dict['responses'], 'questions': p.client_dict['questions'],
'question_histories': p.client_dict['question_histories'], 'survey_id': survey_id, 'participant_id': uuid }
</exec>
<logic cond="p.client_dict['num_non_empty'] != 0" label="alias_api" api:data="p.data" api:headers="p.headers" api:method="POST" api:url="https://roundtable.ai/api/alias/latest" uses="api.1"/>
<exec>
def checks_array_to_string(array):
return ', '.join(map(str, array)) if array else "None"
def format_checks(response, keys):
return {key: checks_array_to_string(response.r.get('checks', {}).get(key, [])) for key in keys}
def format_response_groups(response, keys):
return {key: response.r.get('response_groups', {}).get(key, 0) for key in keys}
labels = p.client_dict['question_labels']
alias_responses.r1.val = alias_api.r.get('flagged', False)
alias_responses.r2.val = format_checks(alias_api, labels)
alias_responses.r3.val = format_response_groups(alias_api,labels)
alias_responses.r4.val = alias_api.r.get('num_checks_failed', 0)
alias_responses.r5.val = alias_api.r.get('model', 'API not called')
alias_responses.r6.val = alias_api.r.get('error', False)
alias_responses.r7.val = alias_api.r.get('problem', 'None')
</exec>
<html label="h1"><script>
sessionStorage.removeItem('client_dict');
</script></html>
<text
label="alias_responses"
optional="0"
size="25"
translateable="0"
where="execute,survey,report">
<title>roundtable data holder</title>
<row label="r1">alias-flagged</row>
<row label="r2">alias-checks</row>
<row label="r3">alias-response_groups</row>
<row label="r4">alias-num_checks_failed</row>
<row label="r5">alias-model</row>
<row label="r6">alias-error</row>
<row label="r7">alias-problem</row>
</text>
</survey>
Qualtrics
The Qualtrics integration works by adding a new column to your datasets with response behavior for each participant. This data is then fed to the Alias API at the end of the survey (or whenever you choose to pass the survey data to the API).
Follow these steps to integrate Alias with Qualtrics. Alternatively, you can important the survey template and adjust it as needed.
Add Javascript tracker
Our Qualtrics javascript tracker generates a history of every change made for each open-ended textbox. To use this tracker, simply paste the code into the “Javascript” block for any open-ended question (you must have at least one open-ended question to call the Alias API).
Add an embedded data field
Add an embedded data field called alias_data
to your survey.
Run your survey
All of the data needed for the Alias API will be stored in the alias_data
embedded data field.
Export your Qualtrics data
After running your survey, export your dataset from Qualtrics as a CSV.
Upload to the Dashboard
Upload the exported CSV to the Alias Dashboard. This will call the Alias API for every tracked question and merge the results with your dataset.
jsPsych
Empower your jsPsych experiments with Roundtable Alias to identify and mitigate fraudulent responses. Our integration seamlessly captures user behavior data, enabling in-depth analysis and fraud detection within your experimental settings.
Setup tracker
The Alias tracker integrates with JsPsych survey-text
trials to identify potential fraudelent, inattentive, or bot-like responses to open-ended questions. You can use this as a naturalistic captcha to identify participants to exclude from your analyses.
The extension generates arrayss of all the change events to open-ended questions (called “question histories”), which you can then pass to our API. To use this extension, simply add a link to it in index.html
, include it in the initJsPsych
call, and then pass it as an extension to any jsPsychSurveyText
questions you want to track (note that you must include at least one survey-text
question with our extension).
The Alias tracker takes an optional initialization argument max_n_characters
, when specifies the max number of characters the JSON string of each question_history
can be (by default, this is 50,000; we highly recommend setting it to at least 20,000). The extension also requires a page_id
parameter on every trial where the extension is used. This allows you to easily compare responses across participants even if there are conditional timelines or repeated questions.
We include a full example of using our extension in a JsPsych experiment in the public/ directory of our GitHub repository. Here is a simplified example:
const jsPsych = initJsPsych({
extensions: [
{ type: jsPsychAliasTracker, params: { max_n_characters:55000 } }
],
});
...
const openEndsTrial = {
type: jsPsychSurveyText,
questions: [
{ prompt: 'What do you think was the purpose of this experiment?', required: true, rows: 6 },
],
extensions: [
{ type: jsPsychAliasTracker, params: { page_id:'page1' } }
]
}
...
Call the API
Our tracker automatically adds all of the data the Alias API needs to trial data with the jsPsychAliasTracker
extension. This data is stored as alias_questions
, alias_responses
, and alias_question_histories
. This data can be passed to our API without any further modifications. An example of parsing the data from our example experiment and passing it to the API is included in call-api.py.
Deploy on Heroku
Deploy an Alias JsPsych experiment to Heroku, a cloud platform service. Follow the steps below to clone the repository, install necessary dependencies, and launch your experiment on Heroku.
git clone https://github.com/roundtableAI/alias-tracker.git # Clones the repository
cd alias-tracker/ # Navigates into the directory
npm install # Installs all dependencies
heroku create your-app-name # Creates a new Heroku app
git push heroku master # Deploys the app to Heroku
heroku open # Opens the app in a web browser
Custom
For those with unique survey platforms or specific integration requirements, our API offers flexibility and adaptability. Follow our simple steps to integrate Roundtable Alias into your custom solution, allowing you to capture and analyze user behavior effectively.
Setup tracker
Our Javascript tracker generates a history of every change made for each open-ended textbox. To use this tracker, replace the value of the textbox_id
variable at the top of the script with the HTML id of the relevant textbox and then store the generated question_history
. You can also specify the max number of characters the parsed question_history
should take before it stops tracking (by default, this is set to 25,000). Note that as is, the script only works if there is one textbox per page.
const textbox_id = 'exampleTextarea'
// Max number of characters to store in history
const max_characters_for_history = 25000;
document.addEventListener('DOMContentLoaded', function () {
// Text changes are stored here
let question_history = [];
let old_text = document.getElementById(textbox_id).value;
let text_over_length = false;
let start_time;
// Handle input changes
document.getElementById(textbox_id).addEventListener('input', function (e) {
if (text_over_length) return;
if (!start_time) {
start_time = Date.now();
t = 0;
} else {
t = Date.now() - start_time;
}
const state = e.target.value;
const new_history = {
s: state,
t,
};
const length_of_history = JSON.stringify([...question_history, new_history]).length;
if (length_of_history > max_characters_for_history) {
text_over_length = true;
return;
}
question_history.push(new_history);
old_text = state;
});
// Handle copy changes
document.getElementById(textbox_id).addEventListener('copy', function (e) {
if (text_over_length) return;
if (!start_time) {
start_time = Date.now();
t = 0;
} else {
t = Date.now() - start_time;
}
const new_history = {
s: e.target.value,
t,
o: 'c',
ct: window.getSelection().toString(),
};
const length_of_history = JSON.stringify([...question_history, new_history]).length;
if (length_of_history > max_characters_for_history) {
text_over_length = true;
return;
}
question_history.push(new_history);
});
});
Call the API
Substitute the question_histories
below with the data collected from the tracker above.
curl --request POST \
--url https://roundtable.ai/api/alias/latest \
--header 'Content-Type: application/json' \
--header 'api_key: <api_key>' \
--data '{
"participant_id": "participant_123",
"question_histories": {
"Q1": [
[
{
"s": "I",
"t": 0
},
{
"s": "Im",
"t": 429
}
]
]
},
"questions": {
"Q1": "How do you feel our software has impacted your daily workflow?"
},
"responses": {
"Q1": "I really like pineapple on pizza."
},
"survey_id": "survey_456"
}'