Skip to main content
Technology & EngineeringForm Survey Services258 lines

Surveymonkey

Integrate SurveyMonkey's REST API to create surveys, collect responses via collectors, process results, and configure webhooks. Covers survey design, collector management, response analysis, and TypeScript client patterns.

Quick Summary21 lines
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: 258 lines
Paste into your CLAUDE.md or agent config

SurveyMonkey 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 /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.

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

Get CLI access →

Related Skills

Tally

Integrate Tally forms via its API and embed SDK to handle submissions, configure webhooks, apply conditional logic, and embed forms seamlessly. Covers REST endpoints, webhook payloads, and front-end embedding with TypeScript.

Form Survey Services240L

Typeform

Integrate Typeform APIs to create forms, retrieve responses, configure webhooks, set up logic jumps, and embed forms in web applications. Covers the Create API, Responses API, and front-end embed SDK with TypeScript patterns.

Form Survey Services246L

Zod

Define and validate data schemas with Zod for parsing, transforms, refinements, and form integration. Covers schema composition, type inference, custom error messages, and integration with React Hook Form and tRPC.

Form Survey Services287L

Formbricks

Integrate Formbricks open-source surveys for in-app, website, and link-based feedback collection. Covers the JavaScript SDK for targeted in-app surveys, REST API for response management, and self-hosting configuration with TypeScript.

Form Survey Services246L

Formik

Build React forms with Formik using useFormik hook, Field components, and schema validation via Yup or Zod. Covers form state management, custom field components, async validation, and TypeScript integration patterns.

Form Survey Services279L

Jotform

Integrate JotForm's REST API to create forms, retrieve submissions, process payments, manage widgets, and configure webhooks. Covers form CRUD, submission handling, payment gateway integration, and TypeScript client patterns.

Form Survey Services234L