Install
$ agentstack add skill-letitbk-claude-academic-setup-qualtrics-survey ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Qualtrics Survey Management
A skill for programmatically managing Qualtrics surveys through the API. Supports reading survey structure, creating/updating questions, managing blocks, adding JavaScript, and configuring display logic.
Setup
Required Environment Variables
# In ~/.zshrc
export QUALTRICS_API_KEY='your-api-key'
export QUALTRICS_BASE_URL='nyu.qualtrics.com' # Your datacenter
Survey ID
The skill reads the survey ID from the project's CLAUDE.md file. Look for:
**Survey ID**: `SV_xxxxxxxx`
Shortcut Commands
list-blocks
List all blocks with their questions:
/qualtrics-survey list-blocks
list-questions
List all questions with IDs, types, and block assignments:
/qualtrics-survey list-questions
add-question
Interactive wizard to create a new question:
/qualtrics-survey add-question
update-js
Update a question's JavaScript from the project's js/ folder:
/qualtrics-survey update-js QID58 task_generator.js
API Patterns
Get Survey Structure
# Get full survey definition
curl -s -X GET "https://${QUALTRICS_BASE_URL}/API/v3/survey-definitions/${SURVEY_ID}" \
-H "X-API-TOKEN: ${QUALTRICS_API_KEY}" \
-H "Content-Type: application/json"
# Get specific question
curl -s -X GET "https://${QUALTRICS_BASE_URL}/API/v3/survey-definitions/${SURVEY_ID}/questions/${QID}" \
-H "X-API-TOKEN: ${QUALTRICS_API_KEY}"
# Get survey flow
curl -s -X GET "https://${QUALTRICS_BASE_URL}/API/v3/survey-definitions/${SURVEY_ID}/flow" \
-H "X-API-TOKEN: ${QUALTRICS_API_KEY}"
Create Questions
Multiple Choice (Single Answer)
payload = {
"QuestionText": "Your question text here",
"QuestionType": "MC",
"Selector": "SAVR", # Single answer vertical radio
"SubSelector": "TX",
"DataExportTag": "Q1_Tag",
"Choices": {
"1": {"Display": "Option 1"},
"2": {"Display": "Option 2"},
"3": {"Display": "Option 3"}
},
"ChoiceOrder": [1, 2, 3],
"Validation": {"Settings": {"ForceResponse": "ON", "Type": "None"}},
"Language": []
}
# POST to /API/v3/survey-definitions/{surveyId}/questions?blockId={blockId}
Multiple Choice (Multi-Select)
payload = {
"QuestionType": "MC",
"Selector": "MAVR", # Multiple answer vertical
# ... same structure as above
}
Matrix/Likert
payload = {
"QuestionText": "Rate the following:",
"QuestionType": "Matrix",
"Selector": "Likert",
"SubSelector": "SingleAnswer",
"DataExportTag": "Matrix1",
"Choices": { # Rows
"1": {"Display": "Item 1"},
"2": {"Display": "Item 2"}
},
"ChoiceOrder": [1, 2],
"Answers": { # Columns
"1": {"Display": "Strongly Disagree"},
"2": {"Display": "Disagree"},
"3": {"Display": "Neutral"},
"4": {"Display": "Agree"},
"5": {"Display": "Strongly Agree"}
},
"AnswerOrder": [1, 2, 3, 4, 5]
}
Text Entry
payload = {
"QuestionText": "Please describe:",
"QuestionType": "TE",
"Selector": "ML", # Multi-line, or "SL" for single-line
"DataExportTag": "OpenText1", # REQUIRED for TE questions
"Validation": {"Settings": {"ForceResponse": "OFF", "Type": "None"}}
}
Update Questions
# PUT to /API/v3/survey-definitions/{surveyId}/questions/{questionId}
# Must include ALL required fields (QuestionText, QuestionType, Selector, etc.)
payload = {
"QuestionText": "Updated text",
"QuestionType": "MC",
"Selector": "SAVR",
"Choices": {...},
"ChoiceOrder": [...],
"QuestionJS": "Qualtrics.SurveyEngine.addOnload(function() { ... });"
}
Manage Blocks
Reorder Questions in Block
payload = {
"Type": "Standard",
"Description": "Block Name",
"BlockElements": [
{"Type": "Question", "QuestionID": "QID1"},
{"Type": "Question", "QuestionID": "QID2"},
{"Type": "Question", "QuestionID": "QID3"}
]
}
# PUT to /API/v3/survey-definitions/{surveyId}/blocks/{blockId}
Display Logic
"DisplayLogic": {
"0": {
"0": {
"LogicType": "Question",
"QuestionID": "QID45",
"QuestionIsInLoop": "no",
"ChoiceLocator": "q://QID45/SelectableChoice/1",
"Operator": "Selected",
"QuestionIDFromLocator": "QID45",
"LeftOperand": "q://QID45/SelectableChoice/1",
"Type": "Expression",
"Description": "If QID45 Choice 1 Is Selected"
},
"Type": "If"
},
"Type": "BooleanExpression",
"inPage": False
}
Embedded Data in Survey Flow
IMPORTANT: Use Type: "Custom" to preserve default values. Type: "Recipient" strips the Value property.
{
"Type": "EmbeddedData",
"FlowID": "FL_1",
"EmbeddedData": [
{"Description": "field_name", "Type": "Custom", "Field": "field_name", "VariableType": "String", "Value": "default_value"},
{"Description": "another_field", "Type": "Custom", "Field": "another_field", "VariableType": "String", "Value": ""}
]
}
JavaScript Patterns
Update Matrix Row Labels from Embedded Data
Qualtrics.SurveyEngine.addOnload(function() {
var that = this;
var blockItems = Qualtrics.SurveyEngine.getEmbeddedData('block_A');
if (!blockItems) return;
var itemNumbers = blockItems.split(',').map(function(x) { return parseInt(x.trim(), 10); });
var itemTexts = [];
for (var i = 0; i < itemNumbers.length; i++) {
var text = Qualtrics.SurveyEngine.getEmbeddedData('rel_item_' + itemNumbers[i]);
itemTexts.push(text || 'Item not found');
}
setTimeout(function() {
var container = that.getQuestionContainer();
var choiceRows = container.querySelectorAll('.ChoiceRow');
choiceRows.forEach(function(row, idx) {
if (idx < itemTexts.length) {
var label = row.querySelector('.LabelWrapper');
if (label) label.textContent = itemTexts[idx];
}
});
}, 200);
});
Capture Checkbox Selections on Page Submit
Qualtrics.SurveyEngine.addOnPageSubmit(function() {
var questionContainer = this.getQuestionContainer();
var selectedCheckboxes = questionContainer.querySelectorAll('input[type="checkbox"]:checked');
var selectedItems = [];
selectedCheckboxes.forEach(function(checkbox) {
var choiceDiv = checkbox.closest('.Choice');
if (choiceDiv) {
var labelWrapper = choiceDiv.querySelector('.LabelWrapper');
if (labelWrapper) {
selectedItems.push(labelWrapper.textContent.trim());
}
}
});
Qualtrics.SurveyEngine.setEmbeddedData('selected_items', JSON.stringify(selectedItems));
});
LLM API Call via Proxy
Qualtrics.SurveyEngine.addOnload(function() {
var that = this;
var proxyUrl = 'https://your-proxy.vercel.app/api/endpoint';
var inputData = Qualtrics.SurveyEngine.getEmbeddedData('input_field');
jQuery("#NextButton").hide();
var timeoutId = setTimeout(function() { handleError('Timeout'); }, 30000);
fetch(proxyUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: inputData })
})
.then(function(response) {
clearTimeout(timeoutId);
if (!response.ok) throw new Error('Server error');
return response.json();
})
.then(function(data) {
Qualtrics.SurveyEngine.setEmbeddedData('result', JSON.stringify(data));
// Update UI...
jQuery("#NextButton").show();
})
.catch(function(error) {
clearTimeout(timeoutId);
handleError(error.message);
});
function handleError(msg) {
Qualtrics.SurveyEngine.setEmbeddedData('api_error', msg);
// Fallback logic...
jQuery("#NextButton").show();
}
});
Workflow
- Read CLAUDE.md to get survey ID and current structure
- Ask user which operation to perform
- Fetch current state from API (no caching)
- Show preview of proposed changes
- Confirm before executing
- Execute API calls
- Verify changes were applied
Common Tasks
Add LLM-Powered Question
- Create text entry question for user input
- Create confirmation question (MC) for LLM response
- Add JavaScript to confirmation question (from js/ folder)
- Add embedded data fields to survey flow
- Configure display logic if needed
Split Matrix into Randomized Blocks
- Create N matrix questions (one per block)
- Add randomization JS to first survey page
- Add embedded data fields for block assignments
- Add row-update JS to each matrix question
Add Conditional Follow-up
- Create primary question (MC)
- Create follow-up questions (one per response option)
- Add display logic to each follow-up
- Test skip patterns
Tips
- Always include all required fields when updating questions
- Use
DataExportTagfor text entry questions (required) Type: "Custom"for embedded data with default values- Use
setTimeout(fn, 200)for DOM manipulation after page load - Test JavaScript in browser console before adding to question
- Check survey preview after each change
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: letitbk
- Source: letitbk/claude-academic-setup
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.