API Reference
Programmatically create surveys, launch them to targeted audiences, and export responses.
Base URL
https://api.chorusresearch.ioThe Chorus Research API provides 12 endpoints across 6 groups:
- Audiences — Browse available respondent panels
- Surveys — Create and manage survey projects
- Pricing — Estimate launch costs before committing
- Launches — Two-step launch workflow with cost confirmation
- Responses — Export collected survey data
Authentication
All API requests (except /health) require a Bearer token in the Authorization header. API keys are 40+ character strings with a 16-character prefix.
curl -X GET https://api.chorusresearch.io/surveys \
-H 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.chorusresearch.io/surveys', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();You can obtain an API key from the API Keys section in the left navigation menu of the Chorus Research app. Keep your key secret — do not expose it in client-side code or public repositories.
Rate Limiting
Requests are rate-limited per API key. Exceeding your limit returns a 429 Too Many Requests response.
- Standard endpoints: 60 requests per minute
- Launch endpoints: 10 requests per minute
Rate limit info is returned in response headers:
| Header | Description |
|---|---|
RateLimit-Limit | Maximum requests per window |
RateLimit-Remaining | Requests remaining in current window |
RateLimit-Reset | Unix timestamp when the window resets |
When rate limited, wait until the reset time before retrying:
{
"error": "Too Many Requests",
"message": "Rate limit exceeded (60 req/min)",
"code": "RATE_LIMIT_EXCEEDED",
"limit": 60,
"windowMs": 60000,
"retryAfter": 60
}Errors
The API returns consistent JSON error responses. Every error includes an error label, a human-readable message, and a machine-readable code.
{
"error": "Bad Request",
"message": "Survey name is required",
"code": "VALIDATION_ERROR",
"field": "name",
"details": [
{
"field": "name",
"message": "Survey name is required"
}
]
}Error Codes
| HTTP Status | Meaning |
|---|---|
400 | Bad Request — invalid parameters or body |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — token does not belong to you |
404 | Not Found — resource does not exist |
409 | Conflict — confirmation token already used |
413 | Payload Too Large — request exceeds 10 MB |
429 | Too Many Requests — rate limit exceeded |
500 | Internal Server Error — something went wrong on our end |
Audiences
Audiences represent respondent panels you can target when launching a survey. Each audience has its own cost-per-response, completion limits, and estimated fielding times.
/audiencesReturns all available audiences with targeting details, cost information, and estimated fielding times.
Requires authentication
curl https://api.chorusresearch.io/audiences \
-H 'Authorization: Bearer YOUR_API_KEY'{
"audiences": [
{
"id": "genpop",
"name": "General Population",
"description": "Adults 18+ in the United States",
"costPerResponse": 1,
"minTargetCompletes": 5,
"maxTargetCompletes": 5000,
"averageLengthOfInterview": 10,
"targeting": {
"age_min": 18,
"age_max": 99,
"country": "US"
},
"estimatedFieldingTime": {
"50": "1-2 hours",
"100": "2-4 hours",
"500": "1-2 days",
"1000": "2-3 days",
"5000": "1-2 weeks"
}
}
],
"count": 1
}/audiences/{id}Returns a single audience by its identifier.
Requires authentication
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Required | Audience identifier (e.g., "genpop") |
curl https://api.chorusresearch.io/audiences/genpop \
-H 'Authorization: Bearer YOUR_API_KEY'{
"error": "Not Found",
"message": "Audience not found: invalid-id",
"code": "AUDIENCE_NOT_FOUND"
}Surveys
Create and manage survey projects. Surveys use the Chorus Research survey format for question definitions.
/surveysCreates a new survey project. The survey body must follow the Chorus Research survey format.
Requires authentication
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Required | Survey name (1-255 characters) |
survey | object | Required | Survey definition in Chorus Research format (see Survey Format) |
targetCompletes | integer | Optional | Target number of completed responses (min: 5, $5 minimum purchase) |
description | string | Optional | Survey description (max 1000 characters) |
{
"name": "Customer Satisfaction Survey",
"survey": {
"title": "Customer Satisfaction Survey",
"pages": [
{
"name": "page1",
"elements": [
{
"type": "rating",
"name": "satisfaction",
"title": "How satisfied are you with our service?",
"rateMax": 5
}
]
}
]
},
"targetCompletes": 100,
"description": "Q1 2026 customer feedback survey"
}{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Customer Satisfaction Survey",
"survey": {
"title": "Customer Satisfaction Survey",
"pages": [
"..."
]
},
"targetCompletes": 100,
"description": "Q1 2026 customer feedback survey",
"status": "draft",
"createdAt": "2026-01-03T10:00:00.000Z",
"updatedAt": "2026-01-03T10:00:00.000Z"
}/surveysLists all surveys with pagination and optional filtering by status.
Requires authentication
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | Optional | Filter by status: draft, live, complete, cancelled |
page | integer | Optional | Page number Default: 1 |
pageSize | integer | Optional | Results per page (max 100) Default: 20 |
sortBy | string | Optional | Sort field: createdAt, updatedAt, name, status Default: createdAt |
sortOrder | string | Optional | Sort direction: asc or desc Default: desc |
curl 'https://api.chorusresearch.io/surveys?status=live&page=1&pageSize=10' \
-H 'Authorization: Bearer YOUR_API_KEY'{
"surveys": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Customer Satisfaction Survey",
"targetCompletes": 100,
"description": "Q1 2026 customer feedback",
"status": "draft",
"createdAt": "2026-01-03T10:00:00.000Z",
"updatedAt": "2026-01-03T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}/surveys/{id}Returns full survey details including the complete survey definition.
Requires authentication
| Parameter | Type | Required | Description |
|---|---|---|---|
id | uuid | Required | Survey UUID |
curl https://api.chorusresearch.io/surveys/550e8400-e29b-41d4-a716-446655440000 \
-H 'Authorization: Bearer YOUR_API_KEY'Survey Format
The survey field in POST /surveys accepts a JSON object describing your survey's pages and questions. This section documents the full structure, all 10 supported question types, and the platform policies that Chorus Research applies automatically.
Platform Policies
- All input questions are automatically set to
isRequired: trueat publish time (image elements are exempt). - Maximum 10 questions per survey (across all pages).
- Maximum 100 pages per survey.
- Maximum 50 elements per page.
- Maximum 10 levels of nesting depth.
- Maximum 5 MB total survey definition size.
- All URLs must use HTTP or HTTPS schemes.
Survey Root Object
The survey object contains a pages array — an ordered list of survey pages, each containing question elements.
Page Object
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Required | Unique page identifier within the survey |
title | string | Optional | Page title displayed to respondents |
description | string | Optional | Page description displayed below the title |
elements | array | Required | Ordered list of question elements on this page (max 50) |
visibleIf | string | Optional | Expression controlling page visibility, e.g. "{age} >= 18" |
Common Question Properties
All question types share these base properties:
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Required | Question type: text, comment, radiogroup, checkbox, dropdown, tagbox, boolean, rating, ranking, image |
name | string | Required | Unique identifier within the survey — used as the key in response data |
title | string | Optional | Question text displayed to respondents. If omitted, name is displayed |
description | string | Optional | Help text displayed below the question title |
isRequired | boolean | Optional | Whether the question requires an answer (auto-set to true at publish time) Default: true |
visible | boolean | Optional | Whether the question is visible Default: true |
visibleIf | string | Optional | Expression controlling visibility, e.g. "{satisfaction} < 3" |
enableIf | string | Optional | Expression controlling whether the question is editable |
defaultValue | any | Optional | Default value pre-filled for this question |
validators | array | Optional | Validation rules applied to the response (see Validators) |
text — Single-Line Input
Response format: string
| Parameter | Type | Required | Description |
|---|---|---|---|
inputType | string | Optional | HTML input type: text, date, datetime-local, email, number, range, url Default: text |
placeholder | string | Optional | Placeholder text shown when the input is empty |
maxLength | integer | Optional | Maximum character length |
min | any | Optional | Minimum value (for number, range, date types) |
max | any | Optional | Maximum value (for number, range, date types) |
comment — Multi-Line Text Area
Response format: string
| Parameter | Type | Required | Description |
|---|---|---|---|
placeholder | string | Optional | Placeholder text shown when the input is empty |
rows | integer | Optional | Number of visible text rows Default: 4 |
maxLength | integer | Optional | Maximum character length |
radiogroup — Single Select
Response format: string (selected choice value)
| Parameter | Type | Required | Description |
|---|---|---|---|
choices | array | Required | List of answer options (strings or {value, text} objects) |
choicesOrder | string | Optional | Display order: none, asc, desc, random Default: none |
showOtherItem | boolean | Optional | Show an "Other" option with free-text input Default: false |
otherText | string | Optional | Label for the "Other" option |
checkbox — Multi Select
Response format: array of strings
| Parameter | Type | Required | Description |
|---|---|---|---|
choices | array | Required | List of answer options (strings or {value, text} objects) |
choicesOrder | string | Optional | Display order: none, asc, desc, random Default: none |
showOtherItem | boolean | Optional | Show an "Other" option with free-text input Default: false |
showNoneItem | boolean | Optional | Show a "None of the above" option that deselects all others Default: false |
dropdown — Single Select Dropdown
Response format: string (selected choice value)
| Parameter | Type | Required | Description |
|---|---|---|---|
choices | array | Required | List of answer options (strings or {value, text} objects) |
placeholder | string | Optional | Placeholder text shown before selection Default: Select... |
showOtherItem | boolean | Optional | Show an "Other" option with free-text input Default: false |
showNoneItem | boolean | Optional | Show a "None" option Default: false |
tagbox — Multi-Select Dropdown
Response format: array of strings
| Parameter | Type | Required | Description |
|---|---|---|---|
choices | array | Required | List of answer options (strings or {value, text} objects) |
placeholder | string | Optional | Placeholder text shown before selection |
choicesOrder | string | Optional | Display order: none, asc, desc, random Default: none |
boolean — Yes / No Toggle
Response format: boolean
| Parameter | Type | Required | Description |
|---|---|---|---|
labelTrue | string | Optional | Label for the "true" option Default: Yes |
labelFalse | string | Optional | Label for the "false" option Default: No |
rating — Rating Scale
Response format: integer
| Parameter | Type | Required | Description |
|---|---|---|---|
rateType | string | Optional | Display type: labels (numeric scale) or stars (star icons) Default: labels |
rateMin | integer | Optional | Minimum rating value Default: 1 |
rateMax | integer | Optional | Maximum rating value Default: 5 |
minRateDescription | string | Optional | Label at the minimum end of the scale |
maxRateDescription | string | Optional | Label at the maximum end of the scale |
ranking — Drag-and-Drop Ranking
Response format: array of strings (ranked order, first = highest)
| Parameter | Type | Required | Description |
|---|---|---|---|
choices | array | Required | Items to rank (min 2). Strings or {value, text} objects |
image — Display-Only Image
No response collected. Exempt from isRequired auto-enforcement.
| Parameter | Type | Required | Description |
|---|---|---|---|
imageLink | string | Optional | URL of the image to display. Must use HTTP or HTTPS |
Choice Items
Choices can be simple strings (value and display text are the same) or objects with separate value and text fields.
// Simple strings — value and display text are the same
["Very Satisfied", "Satisfied", "Neutral", "Dissatisfied"]
// Objects — separate stored value and display text
[
{ "value": "very_satisfied", "text": "Very Satisfied" },
{ "value": "satisfied", "text": "Satisfied" },
{ "value": "neutral", "text": "Neutral" },
{ "value": "dissatisfied", "text": "Dissatisfied" }
]Validators
Optional validation rules that can be attached to any question via the validators array:
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Required | Validator type: numeric, text, regex, email, expression |
text | string | Optional | Error message displayed when validation fails |
minValue | number | Optional | Minimum allowed value (numeric validator) |
maxValue | number | Optional | Maximum allowed value (numeric validator) |
minLength | integer | Optional | Minimum text length (text validator) |
maxLength | integer | Optional | Maximum text length (text validator) |
regex | string | Optional | Regular expression pattern (regex validator) |
Complete Example
A multi-page survey demonstrating several question types with conditional logic:
{
"pages": [
{
"name": "experience",
"elements": [
{
"type": "rating",
"name": "overall_rating",
"title": "How would you rate your overall experience?",
"rateMin": 1,
"rateMax": 5,
"minRateDescription": "Very Poor",
"maxRateDescription": "Excellent"
},
{
"type": "dropdown",
"name": "usage_frequency",
"title": "How often do you use our product?",
"choices": [
"Daily",
"Weekly",
"Monthly",
"Rarely"
]
}
]
},
{
"name": "details",
"elements": [
{
"type": "checkbox",
"name": "liked_features",
"title": "Which features do you value most?",
"choices": [
"Speed",
"Reliability",
"Design",
"Support",
"Pricing"
],
"showOtherItem": true
},
{
"type": "ranking",
"name": "improvement_priorities",
"title": "Rank these areas by improvement priority",
"choices": [
"Performance",
"Documentation",
"Onboarding",
"Pricing"
]
}
]
},
{
"name": "followup",
"visibleIf": "{overall_rating} <= 3",
"elements": [
{
"type": "comment",
"name": "improvement_feedback",
"title": "What could we do better?",
"rows": 4,
"maxLength": 2000
},
{
"type": "boolean",
"name": "contact_ok",
"title": "May we follow up with you?",
"labelTrue": "Yes, contact me",
"labelFalse": "No thanks"
}
]
}
]
}Pricing
Estimate survey costs before launching. Pricing is simple: $1.00 per completed response × target completes.
/pricing/estimateCalculates the estimated cost to launch a survey based on audience and target completes.
Requires authentication
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
audienceId | string | Required | Target audience identifier |
targetCompletes | integer | Required | Desired number of completed responses (min: 5, $5 minimum purchase) |
projectId | uuid | Required | Survey project ID for the estimate |
{
"audienceId": "genpop",
"targetCompletes": 100,
"projectId": "550e8400-e29b-41d4-a716-446655440000"
}{
"audienceId": "genpop",
"targetCompletes": 100,
"baseCostPerComplete": 1,
"subtotal": 100,
"total": 100,
"currency": "USD"
}/pricing/configReturns the current pricing configuration including cost per response and currency.
Requires authentication
curl https://api.chorusresearch.io/pricing/config \
-H 'Authorization: Bearer YOUR_API_KEY'{
"costPerResponse": 1,
"currency": "USD"
}Launches
Launching a survey is a two-step process to prevent accidental charges. First, request a cost estimate and receive a confirmation token. Then, confirm the launch with the token to execute payment and begin fielding.
Two-Step Launch Workflow
- Step 1:
POST /surveys/{'{id}'}/launch→ Get cost estimate + confirmation token (valid 5 min) - Step 2:
POST /surveys/{'{id}'}/launch/confirm→ Execute launch with token + payment
/surveys/{id}/launch10 req/minStep 1: Get a cost estimate and confirmation token for launching a survey. The token is valid for 5 minutes.
Requires authentication
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
id | uuid | Required | Survey UUID (path parameter) |
audienceId | string | Required | Target audience identifier |
targetCompletes | integer | Required | Desired number of completed responses (min: 5, $5 minimum purchase) |
{
"audienceId": "genpop",
"targetCompletes": 100
}{
"estimatedCost": 100,
"breakdown": {
"audienceId": "genpop",
"targetCompletes": 100,
"baseCostPerComplete": 1,
"subtotal": 100,
"total": 100,
"currency": "USD"
},
"confirmToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2026-01-04T10:05:00.000Z"
}/surveys/{id}/launch/confirm10 req/minStep 2: Execute the launch using the confirmation token from Step 1. Charges payment and begins fielding.
Requires authentication
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
id | uuid | Required | Survey UUID (path parameter) |
confirmToken | string | Required | Token received from Step 1 (valid 5 minutes) |
paymentMethodId | string | Optional | Stripe payment method ID (uses default if omitted) |
{
"confirmToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"paymentMethodId": "pm_1234567890abcdef"
}{
"success": true,
"publishedSurveyId": "660e8400-e29b-41d4-a716-446655440001",
"workflowId": "launch-550e8400-...-1735992000000",
"paymentIntentId": "pi_1234567890abcdef",
"amountCharged": 100,
"currency": "usd",
"targetCompletes": 100
}{
"error": "Bad Request",
"message": "Confirmation token has expired. Please request a new estimate.",
"code": "INVALID_CONFIRM_TOKEN"
}{
"error": "Conflict",
"message": "This confirmation token has already been consumed",
"code": "TOKEN_ALREADY_CONSUMED"
}Automatic refunds: If a launch fails after payment is charged, the charge is automatically refunded.
/surveys/{id}/statusReturns real-time fielding status including completion progress. Results are cached for 30 seconds.
Requires authentication
| Parameter | Type | Required | Description |
|---|---|---|---|
id | uuid | Required | Survey UUID |
curl https://api.chorusresearch.io/surveys/550e8400-e29b-41d4-a716-446655440000/status \
-H 'Authorization: Bearer YOUR_API_KEY'{
"projectId": "550e8400-e29b-41d4-a716-446655440000",
"publishedSurveyId": "660e8400-e29b-41d4-a716-446655440001",
"status": "live",
"completes": 45,
"target": 100,
"progress": 45,
"lastUpdated": "2026-01-04T10:00:00.000Z",
"estimatedCompletion": "2026-01-05T10:00:00.000Z"
}Responses
Export collected survey responses with pagination. Only responses from published (launched) surveys are returned. By default, only completed responses are included.
/surveys/{id}/responsesExports survey responses with pagination. Returns response data, metadata, and completion status.
Requires authentication
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | uuid | Required | Survey UUID (path parameter) |
page | integer | Optional | Page number Default: 1 |
pageSize | integer | Optional | Results per page (max 1000) Default: 100 |
status | string | Optional | Filter by: complete, partial, incomplete Default: complete |
curl 'https://api.chorusresearch.io/surveys/550e8400-.../responses?page=1&pageSize=50' \
-H 'Authorization: Bearer YOUR_API_KEY'{
"responses": [
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"publishedSurveyId": "660e8400-e29b-41d4-a716-446655440001",
"responseData": {
"satisfaction": "Very satisfied",
"recommend": "Yes"
},
"metadata": {
"rdud": "respondent-uuid-12345",
"completedAt": "2026-01-04T09:30:00.000Z"
},
"status": "complete",
"supplier": "cint",
"createdAt": "2026-01-04T09:25:00.000Z",
"completedAt": "2026-01-04T09:30:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 100,
"total": 45,
"totalPages": 1
}
}Pagination Example
For large datasets, iterate through pages until all responses are collected:
async function getAllResponses(surveyId, apiKey) {
const responses = [];
let page = 1;
while (true) {
const res = await fetch(
`https://api.chorusresearch.io/surveys/${surveyId}/responses?page=${page}&pageSize=1000`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
const data = await res.json();
responses.push(...data.responses);
if (page >= data.pagination.totalPages) break;
page++;
}
return responses;
}Health Check
The health endpoint is public and does not require authentication. Use it to verify API availability.
/healthReturns the service health status.
curl https://api.chorusresearch.io/health{
"status": "healthy",
"service": "public-api",
"timestamp": "2026-01-04T10:00:00.000Z"
}