The agentic survey system for Shopify

Search for the articles here or browse the categories below.

Browse by topic

Find guides, tutorials, and answers organised by category.

Popular articles

What other people are reading right now.

πŸ›’ Checkout Surveys

Checkout Extensibility

UserLoop supports Shopify's checkout extensibility, letting you add surveys to the thank you page and order status page through Shopify's drag-and-drop editor. Watch: UserLoop Shopify Checkout Extensibility Adding UserLoop to Your Checkout Pages Step 1. In Shopify Admin, go to Settings > Checkout and find the checkout customization option. Step 2. Select either the Order Status or Thank You page. We recommend adding the survey to both. Click Add App Block. Step 3. Select UserLoop from the list to add the survey block. Step 4. Drag the block to position it on the page. For best visibility, place it at the top of the Order Details section. Step 5. Save your changes. Which Survey Is Displayed? UserLoop displays whichever checkout survey you've marked as active in your UserLoop account. To change which survey appears, go to Surveys, select a different checkout survey, and click Activate Survey. Features Checkout extensibility surveys support: - Multi-language translations - Follow-up questions - Info Screens with headings, multi-line text, optional images, and no response field - Customer targeting (all, new, or returning customers) - Discount code rewards - Auto Proceed mode Need Help? If you have any questions about checkout extensibility, reach out to us via live chat and we'll be happy to help.

❓ Survey Questions

Question Types

UserLoop offers a flexible range of question types to help you collect different kinds of feedback. Each type is designed for a specific purpose, from simple multiple choice to video responses and data collection. Adding Questions to a Survey 1. Go to Surveys and select your survey (or create a new one). 2. Click the Questions tab, then click Add Question. 3. Choose your question type from the list. Add a question type 4. Type your question and click Suggest Answers to get AI-generated answer options (for Single Select and Multi Select questions). 5. Drag and drop questions to reorder them. Single Select Single Select questions let customers choose exactly one answer from a list of options. This is the most common question type. Single Select question When to use it: - Attribution β€” "How did you hear about us?" - Demographics β€” "What is your age range?" - Preferences β€” "Which product category are you most interested in?" - Satisfaction β€” "How would you describe your experience?" Click Suggest Answers to get AI-generated options tailored to your question, or add answers manually. You can drag and drop answers to reorder them. Single Select questions support the Other toggle (adds a free-text option at the end of the list) and Randomize Order (shuffles answer positions each time the survey is shown). You can also add follow-up questions to individual answers. Multi Select Multi Select questions let customers choose one or more answers from a list of options. Use this when multiple answers could apply. Multi Select question When to use it: - Product interests β€” "Which of these products are you interested in?" - Feature feedback β€” "Which features do you use the most?" - Pain points β€” "What challenges are you facing?" - Shopping habits β€” "Where else do you shop for similar products?" Click Suggest Answers to get AI-generated options, or add answers manually. Multi Select also supports the Other toggle, Randomize Order, and follow-up questions. Open Ended Open Ended questions give customers a free-text field to write their response in their own words. This is the best way to collect detailed, qualitative feedback. Open Ended question When to use it: - General feedback β€” "Is there anything else you'd like to share with us?" - Product suggestions β€” "What features would you like to see?" - Experience details β€” "What almost stopped you from buying today?" Keep the question focused so customers know what kind of response you're looking for. Open Ended responses are analyzed in the Analytics section, where UserLoop uses AI to identify common themes across responses. Open Ended settings 1. Sub Heading β€” Add extra text below the question heading to provide instructions or context. 2. Placeholder Text β€” Sets the hint text shown inside the text field before the customer starts typing. Use this to guide customers on what kind of response you're looking for. Number Score Number Score questions let customers select a number on a scale. This is useful for any kind of rating or satisfaction question. Number Score question When to use it: - Overall experience β€” "How would you rate your overall experience?" - Product satisfaction β€” "How would you rate this product?" - Effort score β€” "How easy was it to complete your purchase?" - Any custom rating β€” Use the label fields to define what the low and high ends mean. Number Score settings 1. Scale β€” Choose the range and direction of the number scale: 1–10 (default), 10–1 (reversed), 1–5 (shorter scale), or 5–1 (shorter, reversed). 2. Left Label β€” The label shown at the low end of the scale (e.g. "Least", "Poor", "Very Difficult"). 3. Right Label β€” The label shown at the high end of the scale (e.g. "Most", "Excellent", "Very Easy"). NPS NPS (Net Promoter Score) is a widely used metric for measuring customer loyalty. It asks customers how likely they are to recommend your brand on a scale of 1 to 10. NPS question When to use it: - Post-purchase β€” "How likely are you to recommend us to a friend or family member?" - Ongoing tracking β€” Add an NPS question to your checkout survey to track loyalty over time. Responses are automatically grouped into three categories: - Detractors (1–6) β€” Unhappy customers who are unlikely to recommend you. - Passives (7–8) β€” Satisfied but unenthusiastic customers. - Promoters (9–10) β€” Loyal customers who will actively recommend you. Your NPS score is calculated as: % Promoters - % Detractors, ranging from -100 to 100. Go to Analytics to see your score and trend over time. NPS uses a fixed 1–10 scale with customizable Left Label and Right Label (pre-filled with "Not Likely" and "Very Likely"). Unlike Number Score, the scale cannot be changed. Date Date questions let customers select a specific date using dropdown selectors for day, month, and year. Date question When to use it: - Birthdays β€” "When is your birthday?" Use this to send personalized birthday offers or discounts. - Anniversaries β€” "When did you first start using our products?" - Event dates β€” "When is the event you're shopping for?" Video Video questions let customers record a short video response using their device's camera and microphone, or upload an existing video file. Video question When to use it: - Testimonials β€” "Record a short video telling us about your experience." - Product feedback β€” "Show us how you use our product." - Unboxing reactions β€” "Record your first impressions when opening your order." Customers can record up to 2 minutes of video, review and re-record before submitting, or upload a file instead. Video questions are available in Link and Popup surveys. They are not currently supported in Checkout surveys. CSAT CSAT (Customer Satisfaction) questions measure how satisfied a customer is with a specific experience. Customers choose from a set of satisfaction levels like "Very Satisfied", "Satisfied", "Neutral", etc. CSAT question When to use it: - Post-purchase β€” "How satisfied are you with your purchase?" - Support interactions β€” "How would you rate your experience with our support team?" - Delivery experience β€” "How satisfied were you with the delivery?" CSAT works like a Single Select question with satisfaction-focused answer options. Unlike NPS (which measures loyalty on a 1-10 scale), CSAT measures satisfaction with a specific touchpoint. It supports the Other toggle, Randomize Order, and follow-up questions. Email Email questions let you collect a customer's email address as part of your survey. This is especially useful for App Block and Popup surveys where visitors may not be logged in. Email question When to use it: - Lead capture β€” Collect emails from anonymous visitors. - Identify visitors β€” Link anonymous survey responses to a customer record. - Enable targeting β€” Once an email is collected, subsequent questions can target new or returning customers based on order history. When an email is collected, all previous responses from that survey session are linked to the email address. Info Screen Info Screens display content without asking the customer for an answer. They are useful for introducing a survey, separating it into sections, setting expectations, or explaining a reward before the next question. An Info Screen with a heading, image, explanatory copy, and Continue button An Info Screen can contain a heading, multi-line supporting text, and an optional image. Customers select Continue to move to the next step. - It collects no answer and creates no row in response exports. - It advances the progress bar, but is not numbered as a question. - Required and skip settings do not apply. - It cannot be used as a conditional follow-up question. - Product, new-customer, and returning-customer targeting can still be applied. Info Screens are supported in JavaScript SDK, Shopify Checkout, Popup Survey, and App Block survey experiences. Question Settings Every question type has the following settings, available in the settings panel on the right side when you click a question. Question settings panel 1. Other β€” Available on Single Select and Multi Select questions only. When enabled, an "Other" option is added to the end of the answer list. If a customer selects it, a text field appears where they can type their own answer. 2. Required β€” When enabled, customers must answer the question before they can move to the next one. The submit button stays disabled until they select or type an answer. 3. Allow Skip β€” Shows a Skip button below the submit button, letting customers skip the question without answering. Skipped questions don't record a response. If both Required and Allow Skip are enabled, customers can still skip using the skip button β€” but they can't proceed without answering unless they explicitly choose to skip. 4. Randomize Order β€” Shuffles the order of answer choices each time the survey is shown. This helps reduce bias from answer position. If you have an Other option, it always stays at the end of the list regardless of randomization. This setting applies to Single Select and Multi Select questions. 5. Sub Heading β€” Add extra text below the main question heading. This is useful for providing instructions or additional context without cluttering the main question. Need Help? If you have any questions about question types, reach out to us via live chat and we'll be happy to help.

πŸ’» Developer Documentation

UserLoop SDK

The UserLoop JavaScript SDK lets you embed a survey or quiz in any website, including custom storefronts, landing pages, account areas, and post-purchase experiences. It has no framework dependency and renders a responsive survey inside an element you choose. Quick Start Add this snippet where you want the survey to appear. Replace YOUR_SURVEY_ID with the survey ID from your UserLoop dashboard. <div id="userloop_survey"></div> <script> (function () { var SURVEY_ID = 'YOUR_SURVEY_ID'; var TARGET_ID = 'userloop_survey'; function start() { var target = document.getElementById(TARGET_ID); if (!target) return; UserLoop(SURVEY_ID, target).init(); } if (window.UserLoop) { start(); return; } var script = document.createElement('script'); script.src = 'https://cdn.userloop.io/sdk-2/userloop.js'; script.async = true; script.onload = start; document.head.appendChild(script); })(); </script> The SDK loads the published survey, applies its colours and settings, and handles navigation and response submission. A single-select survey rendered by the UserLoop JavaScript SDK Initialising the SDK If your site manages script loading itself, include the SDK and create an instance directly: <script src="https://cdn.userloop.io/sdk-2/userloop.js"></script> <div id="userloop_survey"></div> <script> const survey = UserLoop( 'YOUR_SURVEY_ID', document.getElementById('userloop_survey'), { email_collection: true, expanded_mode: false, }, { customer: {}, transaction: {}, } ); survey.init(); </script> The initializer is: UserLoop(surveyId, targetElement, config, context) | Argument | Description | |---|---| | surveyId | The survey ID shown in UserLoop. | | targetElement | The DOM element in which the survey should render. | | config | Optional SDK behaviour and preview settings. | | context | Optional customer and transaction data to attach to responses and use for targeting. | Call init() after the target element exists in the page. Common Configuration Options | Option | Type | Description | |---|---|---| | email_collection | boolean | Shows an email field when the survey requires one and the supplied customer has no email address. | | expanded_mode | boolean | Expands the survey to the width of its container. | | preview_mode | boolean | Renders the survey for design and testing without applying live question targeting. | | preview_start_question_id | string | Opens a preview on a particular question. | | preview_thankyou_page | boolean | Opens the thank-you screen directly in preview mode. | | auto_answer_first_question | string | Selects and submits a particular answer ID on the first question. Useful for links from email campaigns. | | allow_restart | boolean | Adds an option to restart the survey after completion. | | surface | string | Identifies a custom integration surface in response metadata. The default is sdk. | | quiz_mode | boolean | Enables quiz behaviour for a survey configured as a quiz. | Branding, colours, progress bars, button text, thank-you content, rewards, and most presentation settings come from the published survey configuration rather than the embed code. Instance Methods UserLoop(...) returns an instance with these methods: | Method | Description | |---|---| | init() | Loads and renders the survey. Returns a promise. | | refresh() | Reloads the current survey configuration. | | refresh(newSurveyId) | Switches the instance to another survey and loads it. | | setSurveyId(newSurveyId) | Alias for switching surveys with refresh(newSurveyId). | | mount() | Mounts the survey into its configured target element. | Supported Question Types The SDK renders the question types configured in UserLoop, including Single Select, Multi Select, Open Ended, Email, Date, Phone, Number Score and NPS, CSAT, Video, and Info Screen. Follow-up questions, optional questions, custom button text, auto-proceed, redirect links, progress indicators, and thank-you rewards are driven by the survey configuration. See Question Types for guidance on choosing and configuring questions. Info Screens An Info Screen is a content-only step. Use one to introduce a survey, explain what will happen next, divide a longer survey into sections, or describe a reward before asking a question. An Info Screen with a heading, image, explanatory copy, and Continue button in the UserLoop SDK An Info Screen can contain: - A heading - Multi-line supporting text - An optional image - The survey's normal Continue button It is placed in the survey like any other question, so you can position it at the beginning, end, or between questions. The same product and customer targeting rules also apply. For API-driven survey configurations, an Info Screen has type: "Info Screen": { "question_id": "intro-screen", "type": "Info Screen", "question": "Before you start", "question_subheading": "This takes about two minutes.\n\nYou'll receive 10% off at the end.", "info_image": "https://example.com/survey-intro.jpg", "required": false, "answers": [] } Info Screen behaviour: - It collects and submits no answer, so it creates no response row and does not appear in response exports. - It advances the progress bar as a survey step, but is not numbered as a question. - Supporting text keeps its line breaks. - A broken optional image is removed without preventing the rest of the screen from loading. - The Continue button is always available. Required and skip settings do not apply. - An Info Screen cannot be used as a conditional follow-up question. - If email collection is enabled, the email field appears on the first answerable question rather than on the Info Screen. - A live survey containing only Info Screens is not shown because there is no response to collect. Preview mode can still render it for design review. Info Screens are supported by the JavaScript SDK and by UserLoop's Shopify Checkout, Popup Survey, and App Block survey experiences. Passing Customer and Transaction Data Pass customer and order context as the fourth argument. All fields are optional; send only the data your site has. const customer = { id: 'customer_123', email: '[email protected]', firstName: 'Alex', lastName: 'Morgan', order_count: 3, }; const transaction = { transaction_id: 'order_456', order_creation_date: '2026-08-07T09:30:00.000Z', total: 89.95, currency: 'GBP', coupon_code: 'WELCOME10', platform: 'Custom Storefront', source: 'website', utm_source: 'newsletter', products: [ { id: 'shopify-product-id', name: 'Example product', product_url: 'https://example.com/products/example-product', }, ], }; const survey = UserLoop( 'YOUR_SURVEY_ID', document.getElementById('userloop_survey'), { email_collection: true }, { customer, transaction } ); await survey.init(); This context enriches the response data and lets UserLoop decide whether targeted questions apply. Question Targeting Questions, including Info Screens, can be limited by product or customer status in the survey configuration: - Product targeting matches against product IDs in the transaction. For Shopify, pass product IDs rather than variant IDs. - New-customer and returning-customer targeting uses customer.order_count or transaction.order_count. - Live embeds hide questions whose targeting rules do not match. - Preview mode intentionally ignores targeting and renders every configured question so the complete design can be reviewed. If targeting removes every answerable question, the live survey container is hidden instead of displaying an empty survey. Refreshing or Switching Surveys Use refresh() when your page gains updated order data or when you want to reload the published survey configuration: await survey.refresh(); To switch the same embed to another survey: await survey.refresh('ANOTHER_SURVEY_ID'); // Equivalent: await survey.setSurveyId('ANOTHER_SURVEY_ID'); Video Responses Video questions let respondents record with their camera or choose an existing file. The SDK handles permission prompts, upload progress, retry messaging, and the normal response submission flow. Whether Video is available and its maximum duration are controlled by the survey configuration. Because camera and microphone access require a secure browser context, serve pages containing Video questions over HTTPS. If a respondent declines camera access, the file-upload option can still be used where supported. Integrations When the survey is connected to integrations such as Shopify, Klaviyo, Slack, gift cards, webhooks, or response notifications, the published survey includes the necessary integration flags. The SDK copies those flags into response payloads automatically; you do not need to duplicate them in your embed configuration. Testing and Troubleshooting Before publishing your integration: 1. Confirm the survey is published and the survey ID is correct. 2. Confirm https://cdn.userloop.io/sdk-2/userloop.js is allowed by your site's Content Security Policy. 3. Make sure the target element exists before calling UserLoop(...). 4. Test at mobile and desktop widths. 5. Pass representative product IDs and order counts when testing targeted questions. 6. Use preview_mode: true to review all questions, including those whose live targeting would not match your test customer. 7. Check the browser console and Network panel if the survey does not load. Do not place private credentials or API keys in browser-side SDK configuration. The survey ID is intended to be used by the browser; secret server-side keys are not. Need Help? If you have questions about an SDK integration, contact us through live chat. Include the page URL, survey ID, browser-console error, and a description of the behaviour you expected so we can investigate quickly.

πŸ’» Developer Documentation

UserLoop API

Getting Started - Base URL: https://api.userloop.io - Generate API keys inside the UserLoop dashboard. Keys are tied to a company, scoped by feature, and shown only once at creation time. - Keep keys private. Rotate immediately if you suspect compromise. Authenticate Every Request Send the full key string exactly as issued using the X-API-Key header: curl https://api.userloop.io/health \ -H "X-API-Key: " The API validates scopes, survey allowlists, and optional origin restrictions before serving any protected data. Date & Pagination Helpers - start_date and end_date must use YYYY-MM-DD format. When omitted, endpoints default to the broadest safe range (from 1970-01-01 through the current day) so analytics always receive explicit boundaries. - limit and offset control pagination. limit defaults to 50 (maximum 200). offset defaults to 0. Endpoint Reference 1. Health Check β€” GET /health Verifies the service is reachable. No authentication required. { "status": "ok" } 2. Survey Catalog β€” GET /surveys Lists surveys accessible to the calling API key. Query parameters: | Name | Type | Description | | ---- | ---- | ---- | | company_id | string (optional) | Validate the company owning the key. If provided it must match the key’s company; otherwise it defaults automatically. | Example request: curl "https://api.userloop.io/surveys" \ -H "X-API-Key: " Example response (trimmed for brevity): { "company_id": "1621...", "count": 2, "surveys": [ { "id": "1624...", "title": "Post Purchase Email Survey", "format": "Email", "status": "active", "question_count": 6, "created_at": "2021-05-19T10:52:50.657Z", "updated_at": "2025-10-10T12:10:34.630Z", "toggles": { "progress_bar": true, "discount_enabled": true }, "schedule": { "post_purchase": "in 2 days" }, "discount": { "header": "Your 10% Coupon Awaits", "shopify_price_rules": [ { "api_c2_id": "1032299675825" } ] } } ] } Response fields | Field | Type | Description | | ---- | ---- | ---- | | company_id | string | Company ID | | count | integer | Number of surveys returned. | | surveys | array | Collection of survey summaries ordered by last update. | | surveys[].id | string | Unique survey identifier. | | surveys[].title | string | Human readable survey name. | | surveys[].format | string | Channel (Email, Checkout, Link, etc.). | | surveys[].status | string | active or archived. | | surveys[].question_count | integer | Number of questions associated with the survey. | | surveys[].question_ids | array | List of question IDs. | | surveys[].created_at / updated_at | ISO 8601 string | Creation and last modification timestamps. | | surveys[].toggles, schedule, triggers, discount, colors, recipients, flags, incentives, sharing, integrations | object | Grouped metadata copied from the survey configuration. Keys are normalized to snake_case. | 3. Survey Metadata β€” GET /surveys/{survey_id} Retrieves the full configuration (questions, answer choices, metadata) for a single survey. curl "https://api.userloop.io/surveys/1710884429805x361876466030084100" \ -H "X-API-Key: " { "survey_id": "1710884429805x361876466030084100", "survey": "Post Purchase Email Survey", "company_id": "1621...", "questions": [ { "question": "How satisfied were you with your recent order?", "question_id": "1710884436289x589566297607766000", "type": "CSAT", "answers": [ { "answer": "1", "answer_id": "1658..." }, { "answer": "2", "answer_id": "1659..." } ] } ] } Response fields | Field | Type | Description | | ---- | ---- | ---- | | survey_id | string | Survey identifier. | | survey | string | Survey title. | | company_id | string | Owning company. | | questions | array | Ordered list of questions in the survey. | | questions[].question_id | string | Question identifier used in analytics queries. | | questions[].question | string | Question text. | | questions[].type | string | Question type (CSAT, NPS, etc.). | | questions[].answers | array | Answer options (when applicable). | | answers[].answer_id | string | Answer identifier used in analytics filters. | | answers[].answer | string | Display text for the answer choice. | 4. Aggregated Analytics β€” GET /responses?view=counts Calculates response counts, percentages, and revenue metrics per answer option. Required query parameters: | Name | Type | Description | | ---- | ---- | ---- | | survey_id | string | Survey to analyze. Must be enabled for the calling key. | | question_id | string | Question to aggregate. | Optional query parameters: | Name | Type | Description | | ---- | ---- | ---- | ---- | ---- | ---- | ---- | | answer_ids | comma-separated strings | Restrict analytics to specific answer IDs. Defaults to all answers in the question. | | start_date, end_date | YYYY-MM-DD | Restrict analytics to a date window. Defaults to full history. | Example request: curl "https://api.userloop.io/responses?view=counts&survey_id=1710884429805x361876466030084100&question_id=1710884436289x589566297607766000&start_date=2025-09-01&end_date=2025-09-30" \ -H "X-API-Key: " Example response (abridged): { "data": [ { "answer_id": "1658...", "answer_text": "1", "count": 75, "percentage": 50, "revenue": 15000, "aov": 200, "currency": "USD" } ], "meta": { "survey_id": "1710884429805x361876466030084100", "survey": "Post Purchase Email Survey", "question": { "id": "1710884436289x589566297607766000", "text": "How satisfied were you with your recent order?", "type": "CSAT" }, "totals": { "responses": 150, "unique_customers": 120, "sum_responses": 150 }, "filters": { "answer_ids": ["1658...", "1659..."], "start_date": "2025-09-01", "end_date": "2025-09-30" } } } Response fields | Field | Type | Description | | ---- | ---- | ---- | | data | array | Aggregated metrics per answer option (sorted by count desc). | | data[].answer_id | string | Answer identifier. | | data[].answer_text | string | Answer label (resolved from survey config when available). | | data[].count | integer | Number of responses recorded for the answer. | | data[].percentage | number | Share of total responses for the answer (0–100). | | data[].revenue | integer | Sum of order_total values associated with the answer (rounded). | | data[].aov | integer | Average order value for the answer (rounded). | | data[].currency | string | Currency code used for revenue metrics. | | meta | object | Contextual metadata for the aggregation. | | meta.survey_id | string | Survey identifier. | | meta.survey | string | Survey title (if available). | | meta.question | object | Question metadata (id, text, type). | | meta.totals.responses | integer | Count of responses returned by analytics (count_survey_responses). | | meta.totals.unique_customers | integer | Unique respondent count. | | meta.totals.sum_responses | integer | Sum of data[].count; falls back to total responses when rows are empty. | | meta.filters | object | Effective filters applied to the analytics call. | | meta.filters.answer_ids | array | Answer IDs used for aggregation. | | meta.filters.start_date / end_date | string | ISO dates bounding the analytics query. | 5. Open-Ended Feedback β€” GET /responses?view=open Fetches paginated free-text responses with associated metadata. Required query parameters: survey_id, question_id Optional query parameters: start_date, end_date, limit, offset Example request: curl "https://api.userloop.io/responses?view=open&survey_id=1710884429805x361876466030084100&question_id=1658178244899x246576140312903680&limit=50" \ -H "X-API-Key: " Example response (first record shown): { "data": [ { "id": "abc123", "unique_id": "abc123", "recipient": "[email protected]", "survey": "1710884429805x361876466030084100", "creation_date": "2025-09-10T12:00:00Z", "open_ended_response": "Great product!", "line_items": ["Product A"], "line_items_count": 1, "order_total": 199.99, "currency": "USD" } ], "pagination": { "total_count": 150, "page_size": 50, "current_page": 1, "total_pages": 3, "has_next_page": true, "has_previous_page": false }, "filters": { "survey_id": "1710884429805x361876466030084100", "question_id": "1658178244899x246576140312903680", "start_date": "1970-01-01", "end_date": "2025-09-30" } } Response fields | Field | Type | Description | | ---- | ---- | ---- | | data | array | Open-text responses ordered by creation_date desc. | | data[].id / unique_id | string | Stable response identifier. | | data[].recipient | string | Email (when captured). | | data[].survey | string | Survey identifier. | | data[].creation_date | string | ISO timestamp of the response. | | data[].open_ended_response | string | Free-text answer content. | | data[].line_items | array | Associated products/items (if present). | | data[].line_items_count | integer | Number of items in line_items. | | data[].order_total | number | Monetary value associated with the response. | | data[].currency | string | Currency code. | | pagination | object | Pagination metadata supplied by the endpoint. | | pagination.total_count | integer | Total open-text responses matching the filters. | | pagination.page_size | integer | Page size applied to the request. | | pagination.current_page | integer | 1-indexed page number based on offset. | | pagination.total_pages | integer | Total calculated pages. | | pagination.has_next_page / has_previous_page | boolean | Convenience flags for pagination UI. | | filters | object | Effective filters included in the request. | | filters.survey_id | string | Survey identifier. | | filters.question_id | string | Question identifier. | | filters.start_date / end_date | string | Date range applied to the query. | 6. Raw Responses β€” GET /responses Provides tabular response data similar to the CSV export. Supports standard pagination and filtering. Sample request: curl "https://api.userloop.io/responses?survey_id=1710884429805x361876466030084100&start_date=2025-09-01&limit=25" \ -H "X-API-Key: " { "responses": [ { "id": "abc123", "unique_id": "abc123", "survey": "1710884429805x361876466030084100", "question_id": "1658...", "question_text": "How satisfied were you with your recent order?", "creation_date": "2025-09-10T12:00:00Z", "answer_id": "1658...", "answer_text": "5", "order_total": 99.99, "currency": "USD" } ], "pagination": { "total_count": 1500, "limit": 25, "offset": 0, "has_next_page": true } } Response fields | Field | Type | Description | | ---- | ---- | ---- | | responses | array | Tabular response data matching the export schema. | | responses[].id / unique_id | string | Response identifier. | | responses[].survey | string | Survey identifier. | | responses[].question_id | string | Question identifier. | | responses[].question_text | string | Question text captured at response time. | | responses[].creation_date | string | ISO timestamp of the response. | | responses[].answer_id | string | Answer identifier (if structured). | | responses[].answer_text | string | Selected answer text (or numeric/NPS value). | | responses[].open_ended_response | string | Free-text answer (when relevant). | | responses[].order_total | number | Order total associated with the response. | | responses[].currency | string | Currency code. | | responses[].utm_*, environment, surface, landing_site, etc. | string | Additional marketing and contextual metadata captured by UserLoop. | | pagination | object | Pagination metadata mirroring the request. | | pagination.total_count | integer | Total number of responses matching filters (may be null when exact count unavailable). | | pagination.limit | integer | Page size used for the query. | | pagination.offset | integer | Offset applied to the query. | | pagination.has_next_page | boolean | Indicates whether more pages are available. | 7. Single Response β€” GET /responses/{response_id} Retrieves one record by its unique ID. Useful when cross-referencing from webhooks or CRM. curl "https://api.userloop.io/responses/abc123" \ -H "X-API-Key: " { "response": { "id": "abc123", "unique_id": "abc123", "survey": "1710884429805x361876466030084100", "question_id": "1658...", "answer_text": "5" } } Response fields | Field | Type | Description | | ---- | ---- | ---- | | response | object | Response record matching the structure in the raw responses endpoint. | | response.id / unique_id | string | Response identifier. | | response.survey | string | Survey identifier. | | response.question_id | string | Question identifier. | | response.answer_text | string | Selected answer. Additional fields (e.g., order_total, utm_*) may be present depending on the record. | Error Handling Errors are returned with a consistent envelope: { "error": "Forbidden", "code": "FORBIDDEN", "detail": "Survey not allowed for this key" } Common error codes: - UNAUTHORIZED – Invalid, revoked, or expired key. - FORBIDDEN – Missing scope, survey not in allowlist, or origin not permitted. - BAD_REQUEST – Invalid parameters (missing IDs, malformed dates, etc.). - NOT_FOUND – Record does not exist or is not accessible to the caller. - INTERNAL – Upstream failure (e.g., Supabase error). Retry or contact support. All errors are safe to expose to clients; sensitive details (such as decrypted tokens) never appear in responses. Best Practices 1. Cache survey metadata when possible; the schema only changes when you update surveys in UserLoop. 2. Respect pagination limits. Use limit/offset for large exports. 3. Filter by date to speed up analytics calls, especially when embedding reporting dashboards. 4. Secure your keys. Store them in encrypted configuration stores and rotate periodically. 5. Monitor rate limits. Contact UserLoop if you expect sustained high throughput so we can tune allocations. Support If you encounter issues: 1. Confirm your key has the correct scopes and survey access inside the dashboard. 2. Double-check parameter spelling and formats (particularly survey_id, question_id, and date strings). 3. Review HTTP status codes and error payloads for hints. 4. Reach out to your UserLoop contact or support team with the request timestamp, key_id, and the full response body for faster troubleshooting. Happy building!

πŸ”Œ Integrations & Shopify

Klaviyo Integration

UserLoop can automatically push survey response data to your Klaviyo account and attach it to customer records. This lets you build targeted email campaigns and segments based on how customers answer your surveys. Watch: Klaviyo x UserLoop Tutorial How to Set Up the Klaviyo Integration 1. In your Klaviyo account, go to Settings > API Keys (or navigate to the Developer Tools section). 2. Click Create Private API Key. 3. Name the key (e.g. "UserLoop Surveys") and set the scope to Events with read and write access. 4. Copy the API key. You'll only see it once. 5. In UserLoop, click Integrations in the left menu. 6. Find the Klaviyo section and paste your API key into the Klaviyo Private API Key field. Integrations page showing Klaviyo, Meta, and Slack Verifying the Integration After saving, send a test survey response and check your Klaviyo account to confirm the event data appears on the customer's profile. Need Help? If you have any questions about the Klaviyo integration, reach out to us via live chat and we'll be happy to help.

πŸ’» Developer Documentation

MCP Server

The UserLoop MCP server connects Claude, ChatGPT, and other compatible AI clients to your UserLoop survey data through the Model Context Protocol. You can explore feedback, compare answer counts, review NPS, find individual responses, and chart response volume by asking questions in natural language. All UserLoop MCP tools are read-only. They can retrieve and analyse survey data, but they cannot create, edit, or delete anything in your UserLoop account. What You Can Ask Try prompts such as: - β€œChart response volume across all surveys for the last 90 days and break it down by survey.” - β€œHow did customers answer β€˜How did you hear about us?’ this month?” - β€œShow the NPS score and promoter, passive, and detractor breakdown for our quarterly survey.” - β€œSummarise the main themes in open-text responses from the post-purchase survey.” - β€œCompare answer counts, revenue, and average order value for this question.” Your AI client chooses the appropriate UserLoop tools automatically. Interactive Charts Clients that support MCP Apps can display UserLoop results as interactive charts directly in the conversation. Clients without MCP Apps support still receive the same data as compact JSON. Response Volume Over Time View response volume for one survey, one question, or every survey combined. Switch between 7 days, 30 days, 90 days, 12 months, or all time, and use automatic, daily, weekly, or monthly grouping. Company-wide results also include a per-survey breakdown and a data-table view. UserLoop MCP response-volume chart showing weekly responses across all surveys Answer Counts Multiple-choice results are shown as a horizontal bar chart with response counts and share. Hover over a result to see more detail, including revenue and average order value when available. UserLoop MCP answer-count chart for a multiple-choice survey question NPS Number Score and NPS questions include the headline NPS score, average score, promoter/passive/detractor split, 0–10 score distribution, and revenue metrics when available. UserLoop MCP NPS chart showing score and promoter, passive, and detractor segments The screenshots above use example data. When connected, the charts use your UserLoop survey data. Available Tools The server currently provides eight tools: | Tool | What it does | |---|---| | userloop_health | Checks the connection and confirms your API key is valid | | userloop_list_surveys | Lists all surveys, including their questions and question types | | userloop_get_survey | Retrieves one survey and its questions | | userloop_analytics_counts | Returns answer counts for a question, including NPS and revenue metrics when available | | userloop_responses_timeseries | Charts response volume by day, week, or month for one survey, one question, or all surveys | | userloop_responses_open | Retrieves paginated open-text responses for a question | | userloop_responses_raw | Retrieves paginated raw responses for a survey | | userloop_get_response | Retrieves one response by its ID | Before You Connect Create a UserLoop API key in your UserLoop account. API keys start with ul_live and are shown only once, so create a new one if you no longer have the full key. See UserLoop API for more information. Your MCP server URL is: https://mcp.userloop.io/mcp?api_key=ul_live_... Replace ul_live_... with your complete API key. Keep this URL private. It contains your UserLoop API key. Do not share it, publish it, or include it in screenshots. Rotate the key immediately if it is exposed. Connect to Claude or Claude Desktop Remote MCP connectors are available on supported Claude plans. 1. Open Settings > Connectors in Claude or Claude Desktop. 2. Select Add custom connector. 3. Name the connector UserLoop. 4. Paste the complete MCP server URL, including ?api_key=.... 5. Select Add. 6. In a conversation, open Search and tools and enable the UserLoop connector. Remote servers in Claude Desktop are configured through Settings > Connectors, not claude_desktop_config.json. See Anthropic’s custom connector guide for current plan and workspace requirements. Connect to ChatGPT Custom MCP apps are available to supported ChatGPT workspace plans and may need to be enabled by a workspace administrator. 1. Enable developer mode if your workspace requires it. 2. Open Settings > Apps > Create, or ask a workspace administrator to open Workspace settings > Apps > Create. 3. Enter the complete MCP server URL, including ?api_key=.... 4. Choose No authentication if prompted. The UserLoop API key is already included in the URL you entered. 5. Scan the available tools and create the app. 6. Enable UserLoop from the app menu in a conversation. See OpenAI’s developer mode and MCP apps guide for current availability and administrator controls. Connect from Another MCP Client Use the complete URL above with a client that supports remote MCP servers over Streamable HTTP. A typical configuration looks like this: { "mcpServers": { "userloop": { "type": "http", "url": "https://mcp.userloop.io/mcp?api_key=ul_live_..." } } } The exact configuration format depends on your MCP client. Privacy and Email Redaction Email redaction is enabled by default for open-text, raw-response, and individual-response tools. For example, [email protected] is returned in a masked form. This helps protect customer privacy when data is sent to a third-party AI service. The response tools support redact_emails: false, but only disable redaction when you have a clear reason and are comfortable sharing those addresses with your AI provider. Response-Volume Notes - Response-volume charts can combine all surveys in a single request and include totals for each survey. - A response-volume row represents an answer given to a question, rather than a completed survey submission. Use a question filter when you want to count answers to one specific question. - The server reads up to 2,000 response rows for a time-series request. If a result is marked as truncated, narrow the date range or select a specific survey. Troubleshooting The connection or health check fails - Confirm that the URL includes the complete ?api_key=ul_live_... query parameter. - The MCP server no longer accepts the old X-UserLoop-Key header method. - Create a new UserLoop API key if the original key has been revoked, lost, or exposed. The new time-series tool is missing Refresh or rescan the connector’s tools. In ChatGPT, newly added tools may need to be enabled by a workspace administrator. If refreshing does not work, remove and re-add the connector using the current URL. Charts are not displayed Your client may not support MCP Apps, or it may need to refresh the UserLoop connector. The tool still returns the underlying data, which the AI assistant can analyse and present as text or a table. Need Help? If you have questions about the UserLoop MCP server, contact us through live chat and we’ll be happy to help.