Surveymonkey
Integrate SurveyMonkey's REST API to create surveys, collect responses via
You are an expert at integrating SurveyMonkey via its v3 REST API. You design surveys programmatically, manage collectors for distribution, analyze response data, and react to completion events through webhooks with type-safe TypeScript patterns. ## Key Points - **Using `/responses` instead of `/responses/bulk`** for detailed answer data. The basic endpoint returns only metadata. - **Ignoring rate limits (120 req/min)**. Always implement backoff on 429 responses. - **Hardcoding `choice_id` values** instead of mapping them from the survey detail endpoint. IDs are opaque and change across survey copies. - **Fetching all responses without date or status filters**. Use `start_created_at`, `end_created_at`, and `status` params. - Running structured market research or customer satisfaction surveys at scale. - Building dashboards that aggregate and visualize survey response data. - Automating survey creation for recurring feedback programs (quarterly NPS, post-event). - Distributing surveys across multiple channels (web, email, embedded) with per-channel analytics. - Triggering workflows (CRM updates, alerts) when survey responses are completed. ## Quick Example ```bash SURVEYMONKEY_ACCESS_TOKEN=your-access-token ```
skilldb get form-survey-services-skills/SurveymonkeyFull skill: 259 linesSurveyMonkey API Skill
You are an expert at integrating SurveyMonkey via its v3 REST API. You design surveys programmatically, manage collectors for distribution, analyze response data, and react to completion events through webhooks with type-safe TypeScript patterns.
Core Philosophy
Survey-Collector-Response Hierarchy
SurveyMonkey's data model has three layers: surveys define questions, collectors define distribution channels (web links, email, embedded), and responses hold answers tied to a collector. Always scope API calls appropriately — fetch responses per collector when you need channel-specific metrics.
Bulk Collection, Selective Retrieval
SurveyMonkey surveys can accumulate millions of responses. Use the bulk endpoint with date filters and status parameters to fetch only what you need. Request per_page up to 100 and paginate with cursors. Never load all responses into memory at once.
OAuth-First Authentication
SurveyMonkey uses OAuth 2.0 for third-party apps. For internal tools, use a long-lived access token from your app settings. Always include the token in the Authorization header and respect the 120 requests/minute rate limit with exponential backoff.
Setup
Configure the API client:
const SM_ACCESS_TOKEN = process.env.SURVEYMONKEY_ACCESS_TOKEN;
const BASE_URL = "https://api.surveymonkey.com/v3";
async function smFetch<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
...init,
headers: {
Authorization: `Bearer ${SM_ACCESS_TOKEN}`,
"Content-Type": "application/json",
...init?.headers,
},
});
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") || "5", 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return smFetch<T>(path, init);
}
if (!res.ok) throw new Error(`SurveyMonkey ${res.status}: ${await res.text()}`);
return res.json() as Promise<T>;
}
Environment variables:
SURVEYMONKEY_ACCESS_TOKEN=your-access-token
Key Patterns
Create a Survey with Pages and Questions
Do this — structure surveys with typed page and question definitions:
interface SMChoice { text: string }
interface SMQuestion {
family: "single_choice" | "multiple_choice" | "open_ended" | "matrix" | "demographic";
subtype: "vertical" | "essay" | "single" | "rating" | "international";
headings: Array<{ heading: string }>;
answers?: { choices?: SMChoice[]; rows?: Array<{ text: string }> };
required?: { text: string; type: "all" | "at_least"; amount?: string };
}
// Step 1: Create empty survey
const survey = await smFetch<{ id: string }>("/surveys", {
method: "POST",
body: JSON.stringify({ title: "Q1 Customer Satisfaction" }),
});
// Step 2: Add a page with questions
const page = await smFetch<{ id: string }>(`/surveys/${survey.id}/pages`, {
method: "POST",
body: JSON.stringify({ title: "Satisfaction" }),
});
// Step 3: Add questions
await smFetch(`/surveys/${survey.id}/pages/${page.id}/questions`, {
method: "POST",
body: JSON.stringify({
family: "single_choice",
subtype: "vertical",
headings: [{ heading: "How satisfied are you with our service?" }],
answers: {
choices: [
{ text: "Very Satisfied" },
{ text: "Satisfied" },
{ text: "Neutral" },
{ text: "Dissatisfied" },
{ text: "Very Dissatisfied" },
],
},
required: { text: "This question is required.", type: "all" },
} satisfies SMQuestion),
});
Not this — creating surveys in the UI then hardcoding survey IDs. Use the API for reproducible, version-controlled survey creation.
Fetch Responses with Pagination
Do this — paginate with cursor and filter by status:
interface SMResponse {
id: string;
survey_id: string;
collector_id: string;
date_created: string;
response_status: "completed" | "partial" | "overquota" | "disqualified";
pages: Array<{
id: string;
questions: Array<{
id: string;
answers: Array<{ choice_id?: string; text?: string; row_id?: string }>;
}>;
}>;
}
interface SMPaginatedResponse<T> {
data: T[];
per_page: number;
total: number;
page: number;
links: { next?: string };
}
async function fetchAllResponses(surveyId: string, status = "completed"): Promise<SMResponse[]> {
const all: SMResponse[] = [];
let path: string | undefined = `/surveys/${surveyId}/responses/bulk?per_page=100&status=${status}`;
while (path) {
const page = await smFetch<SMPaginatedResponse<SMResponse>>(path);
all.push(...page.data);
path = page.links.next ? new URL(page.links.next).pathname + new URL(page.links.next).search : undefined;
}
return all;
}
Not this — fetching /responses (summary only) instead of /responses/bulk when you need answer details.
Configure Webhooks
Do this — register a webhook for completed responses:
interface SMWebhook {
id: string;
name: string;
event_type: "response_completed" | "response_updated" | "response_disqualified";
subscription_url: string;
}
const webhook = await smFetch<SMWebhook>("/webhooks", {
method: "POST",
body: JSON.stringify({
name: "Response Completed Hook",
event_type: "response_completed",
object_type: "survey",
object_ids: [surveyId],
subscription_url: "https://myapp.com/webhooks/surveymonkey",
}),
});
Not this — polling the responses endpoint on a timer instead of using webhooks for real-time processing.
Common Patterns
Create a Web Link Collector
interface SMCollector {
id: string;
type: "weblink" | "email" | "embedded";
url?: string;
status: "open" | "closed";
}
const collector = await smFetch<SMCollector>(`/surveys/${surveyId}/collectors`, {
method: "POST",
body: JSON.stringify({
type: "weblink",
thank_you_message: "Thanks for your feedback!",
allow_multiple_responses: false,
}),
});
console.log("Survey link:", collector.url);
Analyze Response Distribution
function analyzeChoiceDistribution(responses: SMResponse[], questionId: string) {
const counts = new Map<string, number>();
for (const response of responses) {
for (const page of response.pages) {
const q = page.questions.find((q) => q.id === questionId);
for (const answer of q?.answers ?? []) {
if (answer.choice_id) {
counts.set(answer.choice_id, (counts.get(answer.choice_id) ?? 0) + 1);
}
}
}
}
return Object.fromEntries(counts);
}
Get Survey Details with Question Mapping
interface SMSurveyDetail {
id: string;
title: string;
pages: Array<{
id: string;
questions: Array<{
id: string;
headings: Array<{ heading: string }>;
answers?: { choices?: Array<{ id: string; text: string }> };
}>;
}>;
}
const detail = await smFetch<SMSurveyDetail>(`/surveys/${surveyId}/details`);
const choiceMap = new Map<string, string>();
for (const page of detail.pages) {
for (const q of page.questions) {
for (const choice of q.answers?.choices ?? []) {
choiceMap.set(choice.id, choice.text);
}
}
}
Anti-Patterns
- Using
/responsesinstead of/responses/bulkfor detailed answer data. The basic endpoint returns only metadata. - Ignoring rate limits (120 req/min). Always implement backoff on 429 responses.
- Hardcoding
choice_idvalues instead of mapping them from the survey detail endpoint. IDs are opaque and change across survey copies. - Fetching all responses without date or status filters. Use
start_created_at,end_created_at, andstatusparams.
When to Use
- Running structured market research or customer satisfaction surveys at scale.
- Building dashboards that aggregate and visualize survey response data.
- Automating survey creation for recurring feedback programs (quarterly NPS, post-event).
- Distributing surveys across multiple channels (web, email, embedded) with per-channel analytics.
- Triggering workflows (CRM updates, alerts) when survey responses are completed.
Install this skill directly: skilldb add form-survey-services-skills
Related Skills
Formbricks
Integrate Formbricks open-source surveys for in-app, website, and link-based
Formik
Build React forms with Formik using useFormik hook, Field components, and
Jotform
Integrate JotForm's REST API to create forms, retrieve submissions, process
React Hook Form
Build performant React forms with React Hook Form using register, validation,
Tally
Integrate Tally forms via its API and embed SDK to handle submissions, configure
Typeform
Integrate Typeform APIs to create forms, retrieve responses, configure webhooks,