James

James

Last updated on Jul 23, 2026


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

  1. Respect pagination limits. Use limit/offset for large exports.
  2. Filter by date to speed up analytics calls, especially when embedding reporting dashboards.
  3. Secure your keys. Store them in encrypted configuration stores and rotate periodically.
  4. 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!