# Aug 16, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-08-16 Update the external researcher name on published projects (staging). ← Back to Product Updates **Update the external researcher name for published projects (staging)** The update project endpoint now enables API partners to update the `researcherName` after the project is in the published state. # Aug 19, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-08-19 Update the external researcher name on published projects. ← Back to Product Updates **Update the external researcher name for published projects (production)** The update project endpoint now enables API partners to update the `researcherName` after the project is in the published state. # Aug 21, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-08-21 publishedAt added to project responses and bookedDate now marked required for scheduling. ← Back to Product Updates **Updates to schedule screener and project** * Added `publishedAt` to project response once published. * Schedule screener endpoint: `bookedDate` is now marked required. The code was already expecting `bookedDate` to be provided and would fail without it; this is now reflected properly in the documentation. # Aug 28, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-08-28 Screener response rejection webhooks available in staging. ← Back to Product Updates **Screener response webhooks for rejection (staging)** When a screener response is rejected or made visible again, a webhook is sent indicating whether `rejected` is true or false, with a body matching: ```json theme={null} { "event": "SCREENER_RESPONSES.UPDATED", "uuid": "67ec23b8-4016-42cc-933d-073d141d8e76", "created": "2024-08-28T22:20:04.248Z", "payload": { "resource": { "id": "66cf97060888f40f25b78ab4", "parentId": "66cf96ce0888f40f25b78297", "type": "screener-responses", "updatedFields": [ { "name": "rejected", "oldValue": true, "newValue": false } ] } } } ``` # Sep 4, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-09-04 Simulate specific status and rejection events on screener response webhooks. ← Back to Product Updates **Simulate specific webhook events for screener response updates** When simulating a `SCREENER_RESPONSES.UPDATED` webhook, you can now pass `screenerResponseUpdatedType` to indicate which status or rejection state you wish to simulate on the screener webhook response. Options include: * `'STATUS.PAID'` * `'STATUS.CANCELLED'` * `'REJECTED.TRUE'` * `'REJECTED.FALSE'` See the [simulate webhook reference](/webhooks/simulate-webhook-event). # Sep 6, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-09-06 Screener response webhooks fire when a response is rejected or made visible again. ← Back to Product Updates **Screener response webhooks for rejection (production)** When a screener response is rejected or made visible again, a webhook is sent indicating whether `rejected` is true or false, with a body matching: ```json theme={null} { "event": "SCREENER_RESPONSES.UPDATED", "uuid": "67ec23b8-4016-42cc-933d-073d141d8e76", "created": "2024-08-28T22:20:04.248Z", "payload": { "resource": { "id": "66cf97060888f40f25b78ab4", "parentId": "66cf96ce0888f40f25b78297", "type": "screener-responses", "updatedFields": [ { "name": "rejected", "oldValue": true, "newValue": false } ] } } } ``` # Sep 18, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-09-18 New endpoint for deleting draft projects programmatically. ← Back to Product Updates **Delete draft projects endpoint added** Introducing a new endpoint allowing users to delete projects programmatically. Only projects in the status of `DRAFT` can be deleted. # Oct 18, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-10-18 isTopicsMatch attribute now available in screener response matching object. ← Back to Product Updates **Improved screener response matching information** The `isTopicsMatch` attribute is now accessible in the screener responses `matching` object for any screeners submitted after this release. # Oct 24, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-10-24 Projects create and draft update now support setting autopay true or false. ← Back to Product Updates **Projects create or update allows setting autopay true/false** Added Public API project create and draft update to set autopay true or false. Include the following in the payload: ```json theme={null} "automations": { "autopay": true } ``` If the user does not send the preference in the payload, by default project creation/update sets `autopay: true`. Live in both staging and production. # Oct 30, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-10-30 Add targetJobFunctions when creating or updating draft B2B projects. ← Back to Product Updates **Job Function added to B2B projects** You may now add `targetJobFunctions` when creating a B2B project or when updating draft B2B projects. These can be selected instead of `targetJobTitles`, which are now optional. Job Functions are also now available in the lookups. # Nov 13, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-11-13 Use key qualifier signals as filters when creating or updating draft projects. ← Back to Product Updates **Key qualifiers: use signals as filters in the Public API** Key qualifiers is a feature that allows researchers to use signal type audience parameters as filters. The following fields can be used with Key qualifiers only if they are given values in the project: * Topics * Industries * Company size * Seniority * Skills * Household income Can be added or updated during project create and update of draft projects. * [Create project reference](/projects/create-a-project) * [Update project reference](/projects/update-a-project) Key qualifiers can be found in the lookups: `v1/lookups?pick=keyQualifier`. # Nov 14, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-11-14 Project description character limit increased to 800. ← Back to Product Updates **Project description character limit increased to 800** Increased the project description character limit to 800 during project creation and update. Live in both staging and production. # Nov 20, 2024 Source: https://developers.respondent.io/docs/Changelog/2024-11-20 Lookups, Topics, Job-titles, Industries, and Skills now include createdAt and updatedAt timestamps. ← Back to Product Updates **Lookups including createdAt and updatedAt timestamps** Public API Lookups, Topics, Job-titles, Industries, and Skills now include `createdAt` and `updatedAt` timestamps in their responses. Live in both staging and production. # Apr 30, 2025 Source: https://developers.respondent.io/docs/Changelog/2025-04-30 Auto-invite, Completion URLs, audience size estimates, and enhanced participant profiles. ← Back to Product Updates **Auto-invite, Completion URLs, audience size estimates, and enhanced participant profiles** * Added support for auto-invite when creating or editing projects so you can automatically invite participants to a project if they qualify after completing the screener. Only available when `targetResearchMethodology` is `unmoderatedStudy` or `survey`. ```json theme={null} automations: { autoinvite: true / false } ``` * Added `completionUrl` to projects on creation, allowing participants to self-report task completion. Only available when `targetResearchMethodology` is `unmoderatedStudy` or `survey`. * Added an audience size estimate endpoint that provides an estimate of the audience that you can reach with your project. * Added topics and profile picture URLs to profile and screener response endpoints. * Added support to allow `targetMarketType` to be updated for draft projects via API. * Fixed a bug where NDAs could not be removed when updating a published project. # Jun 19, 2025 Source: https://developers.respondent.io/docs/Changelog/2025-06-19 New qualifiedPendingIdVerification field and a fix for non-Latin project titles. ← Back to Product Updates **New screener response field and fix for non-Latin project titles** * Added a new field `qualifiedPendingIdVerification` to the screener response object to identify participants that have met all screener criteria and have been requested to go through, but not completed, ID verification. * Fixed a bug where projects with exclusively non-Latin characters in their titles could not be created. # Aug 5, 2025 Source: https://developers.respondent.io/docs/Changelog/2025-08-05 Quotas generally available, cleaner Job Titles and Skills lookups, higher participant limits, and more. ← Back to Product Updates **Updates to Job Titles, Skills, and Quotas** * Quotas are now generally available to all API partners. * Job Titles and Skills lists cleaned up, with major deduplication and removal of irrelevant or inappropriate entries from the endpoint. * Participant recruitment limit increased from 150 to 500 per project. * Bug fix: for API projects, Completion URLs now auto-mark participants as attended 24 hours after click. * Max job titles per project increased from 10 to 30. # Aug 11, 2025 Source: https://developers.respondent.io/docs/Changelog/2025-08-11 Advance notice of breaking API changes going live in staging Aug 18 and production Sept 15. ← Back to Product Updates **\[Action Required] Breaking changes to Respondent API — live Sept 15** We're rolling out several breaking changes to the API and want to ensure you're fully informed and prepared. ### What's changing? * The `publicDescription` field will be required to publish projects. * We're enabling webhook retries. A 2xx status code will be expected within 3 seconds; if not, we'll retry delivery up to 5 times at 10-minute intervals. * Screener questions will be limited to a maximum of 40 per project when adding single or multiple questions. ### Why are we changing? These updates are designed to: * Improve recruitment efficiency and the quality of participant experiences. * Ensure more reliable event delivery through webhook retries and reduce reliance on frequent polling or manual intervention. ### Key dates * **Aug 18, 2025** — these changes go live in our staging environment. * **Sept 15, 2025** — these changes go live in our production environment. ### Action required * Require your researchers to add a description to projects created on your platform prior to Sept 15, 2025. * If you have a webhook set up with us, ensure it responds with a 2xx response within 3 seconds prior to Sept 15, 2025. * Enforce a maximum screener question limit of 40 questions per project on your platform prior to Sept 15, 2025. ### Need help? If you have any questions or concerns, feel free to reach out to us at [partnersupport@respondent.io](mailto:partnersupport@respondent.io). # Aug 18, 2025 Source: https://developers.respondent.io/docs/Changelog/2025-08-18 Breaking changes now live in staging ahead of the Sept 15 production release. ← Back to Product Updates **Breaking changes — live in staging** * `publicDescription` is now a required field to publish projects. * Webhook retries are now live. A 2xx status code is expected within 3 seconds; if not, we retry delivery up to 5 times at 10-minute intervals. * Screener questions are now limited to a maximum of 40 per project when adding single or multiple questions. These changes will go live in production on Sept 15, 2025 (UTC) as per previous notice. # Sep 15, 2025 Source: https://developers.respondent.io/docs/Changelog/2025-09-15 Breaking changes now live in production: publicDescription required, webhook retries, and screener question limits. ← Back to Product Updates **Breaking changes — live in production** * `publicDescription` is now a required field to publish projects. * Webhook retries are now live. A 2xx status code is expected within 3 seconds; if not, we retry delivery up to 5 times at 10-minute intervals. * Screener questions are now limited to a maximum of 40 per project when adding single or multiple questions. These changes are now live in production. # Dec 11, 2025 Source: https://developers.respondent.io/docs/Changelog/2025-12-11 Messaging webhook events, AI-generated project titles/descriptions, and job title filtering by seniority. ← Back to Product Updates **Messaging webhook events, AI suggestions, and job title endpoint updates** * Messaging events added to our webhook (`MESSAGES.CREATED` and `CONVERSATIONS.CREATED`), live in staging (Jan 14) and production (Feb 11). * AI generated project titles and descriptions now available. * Job title lookup list can now be filtered by seniority. # Feb 3, 2026 Source: https://developers.respondent.io/docs/Changelog/2026-02-03 Screener response webhooks now include projectId; parentId is deprecated. ← Back to Product Updates **Improved clarity for screener response webhooks** **Added:** `projectId` field to `SCREENER_RESPONSES.CREATED` and `SCREENER_RESPONSES.UPDATED` events. This provides a clearer reference than the generic `parentId` field. **Deprecated:** The `parentId` field in screener response events is now deprecated. It will be removed in 12 months. Please migrate your integrations to use `projectId`. # Mar 10, 2026 Source: https://developers.respondent.io/docs/Changelog/2026-03-10 Several participant profile fields will no longer be returned from the /profiles endpoint. ← Back to Product Updates **Participant profile fields removed from /profiles** **Breaking change:** Several fields including `lastName`, `location`, `age`, `gender`, and company details will no longer be returned from the `/profiles` endpoint. # June 8, 2026 Source: https://developers.respondent.io/docs/Changelog/2026-06-08 Expanded B2B targeting limits for industries and job functions. ← Back to Product Updates **Larger B2B targeting limits** B2B project targeting no longer caps how many professional industries or job functions you can select. You can pass more than 5 entries to `targetProfessionalIndustries` and more than 3 entries to `jobFunctions` when creating or updating an Industry Professionals (B2B) project. At least one industry is still required for B2B projects. See [Targeting audiences effectively](/docs/Projects/targeting-audiences-effectively). # June 15, 2026 Source: https://developers.respondent.io/docs/Changelog/2026-06-15 Consistent lower-cased company and work-email domains on participant profiles. ← Back to Product Updates **Lower-cased company and work-email domains** Company domains and work-email domains on participant profiles are now stored and returned in lowercase. Existing profiles have been back-filled, so domain comparisons (for example, matching a participant's work email to a company) behave consistently regardless of the casing the participant originally entered. See [Retrieve a participant's profile](/profiles/retrieve-participant-profile). # 2026-06-22 Source: https://developers.respondent.io/docs/Changelog/2026-06-22 Staging test participants are auto-approved for identity verification, unblocking end-to-end API test flows. ← Back to Product Updates **Auto-approved identity verification for staging test participants** Test participants created in the staging environment are now auto-approved for identity verification. This unblocks end-to-end API test flows so you can move participants through the full screener, invite, and payout lifecycle without a manual verification step. See [Create a test participant (staging only)](/profiles/create-test-participant-staging-only). # September 28, 2026 Source: https://developers.respondent.io/docs/Changelog/2026-09-28 Bug fix: Create Project no longer rejects B2B projects that target job functions without job titles. ← Back to Product Updates **Bug fix: Create Project accepts B2B job functions without job titles** Fixed a bug in Create Project. A B2B project that set `targetJobFunctions` and omitted `targetJobTitles` (or sent an empty array) was rejected with a `400` error: `professionalJobTitles should not be empty`. Job titles have been optional since the [Oct 30, 2024 update](/docs/Changelog/2024-10-30). Until this fix, targeting by job function alone only worked by creating the project with titles and then removing them on a draft update. Create Project now accepts functions-only targeting. When you do send `targetJobTitles`, the maximum of 30 job titles per project still applies. ```json theme={null} { "targetMarketType": "b2b", "targetProfessionalIndustries": [""], "targetJobFunctions": [""] } ``` See [Targeting audiences effectively](/docs/Projects/targeting-audiences-effectively). # September 30, 2026 Source: https://developers.respondent.io/docs/Changelog/2026-09-30 New feature: target a custom age range with targetMinAge and targetMaxAge. ← Back to Product Updates **New feature: Custom age range targeting** Create Project and Update draft project now accept `targetMinAge` and `targetMaxAge` for B2B and B2C projects. Use them to target an exact age range instead of the fixed `targetAgeGroups` buckets. Both values are integers between `18` and `55`, and both bounds are inclusive. Project responses also return `targetMinAge` and `targetMaxAge`. ```json theme={null} { "targetMinAge": 25, "targetMaxAge": 34 } ``` * `targetMinAge`/`targetMaxAge` and `targetAgeGroups` are mutually exclusive. Sending both returns a `400` error. * `targetMinAge` must be less than or equal to `targetMaxAge`. Otherwise the request returns a `400` error. * You can send a single bound for an open-ended range. On Update draft project, send a bound as `null` to clear it. Existing integrations that use `targetAgeGroups` keep working unchanged. See [Age targeting](/docs/Projects/targeting-audiences-effectively#age-targeting). # October 8, 2026 Source: https://developers.respondent.io/docs/Changelog/2026-10-08 Quotas V2 (pre-release): B2B-only attributes are rejected on B2C projects. ← Back to Product Updates **Quotas V2 (pre-release): B2B-only attributes on B2C projects** These changes apply to Quotas V2 (quota bodies with `version: "v2"`), which has not been released yet and is only enabled for organizations testing it. If your organization does not have Quotas V2, nothing changes for you. A quota on a project with `targetMarketType: "b2c"` can no longer use `industry`, `jobTitle`, `companySize`, `seniority` or `jobFunctions`. `militaryServiceStatus` and `workSetting` stay available on B2C projects. * `PUT /v1/projects/{projectId}/quotas`, and `quotas` on Create Project, return a `400` that lists each such condition, for example `groups[0].segments[0].rule.children[0]: companySize is only available on B2B projects; set the project's audience to B2B or remove this condition`. * Changing `targetMarketType` on a draft does not change its quota. A draft switched to `b2c` after such a quota was saved is refused at publish with a `400` starting `Quotas need updating before this project can be published:`, and stays a draft. Any quota request the quota rules reject now returns `error` as a list with one message per problem, the same body as a request with a missing or malformed field. Previously `error` was a single string that did not say what was wrong. ```json theme={null} { "statusCode": 400, "error": [ "groups[0].segments[0].rule.children[0]: companySize is only available on B2B projects; set the project's audience to B2B or remove this condition" ] } ``` See [Audience quotas](/docs/Projects/audience-quotas#conditions). # Product Updates Source: https://developers.respondent.io/docs/Changelog/overview All changes to the Respondent Partner API, newest first. **Legend** * **New feature** — Net-new capability added to the API. * **Improvement** — Enhancement to existing functionality. * **Bug fix** — Correction to unintended behavior. * **Breaking change** — Change that may require updates to your integration.
Oct 8, 2026
Improvement
Quotas V2 (pre-release): B2B-only attributes on B2C projects

Quotas V2 (pre-release): on B2C projects, quotas reject industry, jobTitle, companySize, seniority and jobFunctions, publish refuses a draft whose quota still uses one, and quota errors list each problem.

Sep 30, 2026
New feature
Custom age range targeting

Create Project and Update draft project accept targetMinAge and targetMaxAge (18–55) as an alternative to targetAgeGroups.

Sep 28, 2026
Bug fix
Bug fix: Create Project accepts B2B job functions without job titles

Fixed Create Project rejecting B2B projects that set targetJobFunctions and omit targetJobTitles. The 30-title maximum still applies when titles are sent.

Jun 22, 2026
Improvement
Auto-approved identity verification for staging test participants

Test participants created in the staging environment are now auto-approved for identity verification, unblocking end-to-end API test flows.

Jun 15, 2026
Bug fix
Lower-cased company and work-email domains

Company domains and work-email domains on participant profiles are now stored and returned in lowercase for consistent comparisons.

Jun 8, 2026
Improvement
Larger B2B targeting limits

B2B project targeting no longer caps how many professional industries or job functions you can select.

Mar 10, 2026
Breaking change
Participant profile fields removed from /profiles

Several fields including lastName, location, age, gender, and company details will no longer be returned from the /profiles endpoint.

Feb 3, 2026
Improvement
Improved clarity for screener response webhooks

Added projectId to screener response events. The generic parentId field is now deprecated and will be removed in 12 months.

Dec 11, 2025
New feature
Messaging webhook events, AI suggestions, and job title updates

Messaging events added to webhooks, AI-generated project titles and descriptions, and job title lookups filterable by seniority.

Sep 15, 2025
Breaking change
Breaking changes — live in production

publicDescription is now required to publish projects, webhook retries are enabled, and screener questions are limited to 40 per project.

Aug 18, 2025
Breaking change
Breaking changes — live in staging

The Sept 15 breaking changes are now live in staging: required publicDescription, webhook retries, and 40-question screener limit.

Aug 11, 2025
Breaking change
\[Action required] Breaking changes to Respondent API — live Sept 15

Advance notice of required publicDescription, webhook retries, and a 40-question screener cap. Staging Aug 18, production Sept 15.

Aug 5, 2025
Improvement
Updates to Job Titles, Skills, and Quotas

Quotas generally available, cleaner Job Titles and Skills lookups, participant limit raised to 500, and 30 job titles per project.

Jun 19, 2025
New feature
New screener response field and fix for non-Latin project titles

Added qualifiedPendingIdVerification to screener responses and fixed project creation for titles with only non-Latin characters.

Apr 30, 2025
New feature
Auto-invite, Completion URLs, audience size estimates, and enhanced profiles

Auto-invite qualified participants, Completion URLs, audience size estimate endpoint, and topics and profile pictures in profile responses.

Nov 20, 2024
Improvement
Lookups including createdAt and updatedAt timestamps

Lookups, Topics, Job-titles, Industries, and Skills now include createdAt and updatedAt timestamps in their responses.

Nov 14, 2024
Improvement
Project description character limit increased to 800

The project description character limit was increased to 800 during project creation and update.

Nov 13, 2024
New feature
Key qualifiers: use signals as filters in the Public API

Use signal-type audience parameters (topics, industries, company size, seniority, skills, household income) as filters when creating or updating draft projects.

Oct 30, 2024
New feature
Job Function added to B2B projects

You can now add targetJobFunctions when creating or updating draft B2B projects. Job Functions are also available in the lookups.

Oct 24, 2024
New feature
Projects create or update allows setting autopay true/false

Project create and draft update now accept an automations.autopay field. Defaults to true when omitted.

Oct 18, 2024
Improvement
Improved screener response matching information

The isTopicsMatch attribute is now accessible in the screener responses matching object for screeners submitted after this release.

Sep 18, 2024
New feature
Delete draft projects endpoint added

A new endpoint allows deleting projects programmatically. Only projects in DRAFT status can be deleted.

Sep 6, 2024
New feature
Screener response webhooks for rejection (production)

Webhooks now fire when a screener response is rejected or made visible again, with the rejected field in updatedFields.

Sep 4, 2024
New feature
Simulate specific webhook events for screener response updates

Pass screenerResponseUpdatedType when simulating SCREENER\_RESPONSES.UPDATED to trigger STATUS.PAID, STATUS.CANCELLED, REJECTED.TRUE, or REJECTED.FALSE.

Aug 28, 2024
New feature
Screener response webhooks for rejection (staging)

Screener response rejection webhooks are now available in staging ahead of the production release.

Aug 21, 2024
Improvement
Updates to schedule screener and project

Added publishedAt to project responses once published and marked bookedDate as required on the schedule screener endpoint.

Aug 19, 2024
New feature
Update the external researcher name for published projects (production)

The update project endpoint now allows API partners to update the researcherName after a project is published.

Aug 16, 2024
New feature
Update the external researcher name for published projects (staging)

Update the researcherName on published projects, now available in staging.

# Quickstart Source: https://developers.respondent.io/docs/Getting-started/quickstart Find needed resources and keep in touch. 1. Review [API Endpoints](/reference/introduction) to get an overview of available endpoints you'll have available to build with. Endpoints will continue to expand and update as we improve the API and add functionality. 2. [Book a time](https://calendar.google.com/calendar/appointments/schedules/AcZssZ2fFnsWs3qjiZYB0k9j6fALt1dw4rS8RHTyXQOKOOeCnsscoL0YRm4K1P6CrgVAF7HnTk0DIGP2) with our Partner team to further discuss API implementation needs. When you're ready to start work on the integration, we engage partners in the following way: 1. Provide [API Staging](/docs/Getting-started/staging-environment) credentials to allow for building and end-to-end testing 2. Support integration partners throughout their development work via email, Slack, and regular calls (as needed by the partner) 3. Finalize partner agreement 4. Conduct pre-launch end-to-end testing to ensure a seamless participant and researcher experience through the API. This testing **must meet the [Requirements for Production Credentials](/docs/Getting-started/requirements-for-production-credentials)** and include a demo video that follows the [Staging Demo Video Checklist](/docs/Getting-started/staging-demo-video-checklist). 5. Deliver API Production credentials and support final testing and launch activities 6. Provide ongoing partner account management and support # Requirements for Production Credentials Source: https://developers.respondent.io/docs/Getting-started/requirements-for-production-credentials Before your integration can move from Staging to Production, please complete the following steps: ## 1. Staging Implementation Review Provide a comprehensive screen-recording demo of your integration in our Staging environment. We review this demo to confirm that your integration implements the required flows and meets our quality standards. We want to confirm your **product** can drive the Respondent API end-to-end. Every required action should be demonstrated by: 1. **Triggering it from your product UI** — the same surface your end users would see. 2. **Showing the resulting state in your product UI** — so we can see your integration handled the response. 3. **Showing the same state change in Respondent's UI** — so we can confirm the API call had the intended downstream effect. For step-by-step instructions on what to record and how to submit, see the [Demo video checklist](/docs/Getting-started/staging-demo-video-checklist). ### Recording your demo * **Tool** — any screen recorder is fine (Loom, QuickTime, OBS, Drive upload). Narration is encouraged but not required. * **Test participants** — seed participants in staging via `POST /v1/profiles`; see [Staging Environment](/docs/Getting-started/staging-environment). You can [sign in as those participants here](https://website.staging.respondent.io/next/participants/login) to apply to your own project and reply to messages. * **Some items don't need video** — confirming auto-pay is on, listing which study types and screener question types your integration supports, and sharing your staging organization, team, and project IDs can all go in the email body. We'll cross-check against your staging org. * **Token hygiene** — keep API tokens and secrets off-screen. Staging tokens are still credentials. Once these flows are validated, you'll be cleared for Production credentials. *** ## 2. Execute the API Partner Master Services Agreement (MSA) * Review and sign the MSA to formalize our partnership and credits * Return the fully executed document to our Sales team. *** ## 3. Production Organization, Team and API User Setup Production credentials belong to one researcher account (your **API user**) on one team in your production organization. Every project you create through the API is created by that user, in that team. We can only issue credentials once the account exists and has the right access, so complete the steps below first. Staging and production are completely separate. Accounts, organizations, teams and credentials in staging do not carry over, so you set these up again in production, even if you want an API user with the same email address. ### 3a. Set up your production organization and team * If your company doesn't have a production organization yet, [sign up as a researcher](https://app.respondent.io/signup/?signupAs=researcher) and create one. If it already has one, use that. * Consider a dedicated team for API projects. Projects created through the API, and your [webhook](/docs/Webhooks/overview), belong to the team your credentials are issued for, so a dedicated team keeps them apart from any studies your researchers run by hand. Note that separate teams don't share credits, but you can transfer them between teams as needed. ### 3b. Create your API user and add it to the team Use a shared inbox or group address for the API user, such as `api@yourcompany.com`, rather than a person's own email. * The account isn't tied to one person, so your integration keeps working if they leave or change roles. * The API user receives the [transactional emails](/docs/Guides/automated-emails) for every project created through the API. That is a high volume of email, and it belongs in an inbox your team can share and filter. 1. An owner or admin of your production organization invites the API user's email address from the invite page in your organization settings. In the invitation, set the role to **Admin** and add the API team. 2. Open the invitation email sent to that address and follow its link to create the account (or to sign in, if that address already has a Respondent researcher account). Use the exact address that was invited; a different address won't accept the invitation. Using the invite link to sign up avoids the usual researcher onboarding that asks you to create a new organization. 3. Check your organization's member list. The API user should appear as an **Admin** of the organization and as a member of the API team. If your organization uses SSO, there's no invite form. Create the API user by signing in through your SSO provider with its address. An owner or admin then sets its role to **Admin** in the member list and adds it to the API team from the unassigned users list. If you get stuck, contact [partnersupport@respondent.io](mailto:partnersupport@respondent.io). ### 3c. Request your production credentials Email [partnersupport@respondent.io](mailto:partnersupport@respondent.io) with: * your production organization and API team (their IDs if you have them, otherwise their names) * the API user's email address We'll issue the production credentials for that user and team, and fund the account with incentive and recruiting fee credits appropriate for the volume of recruiting you intend to conduct. **Questions?** Reach out to [partnersupport@respondent.io](mailto:partnersupport@respondent.io) *** When migrating your integration, please note the following: * **Lookup ID Differences:** Lookup IDs for industries, skills, topics and jobTitles are different between staging and production. You will need to update these IDs when moving to production * **Auto-pause of Projects:** All newly created production projects will be paused by default. You can request [partnersupport@respondent.io](mailto:partnersupport@respondent.io) to have this removed from your integration once ready * **Internal Testing in Production:** You can test your project by inviting internal participants via the Share link. These are accessible in the Respondent platform by clicking into the project and selecting the "Share" dropdown (top right). Participants must have a valid Respondent account and match the project's demographic filters * **Webhook Setup:** Webhooks configured in staging do not carry over to production # Demo video checklist Source: https://developers.respondent.io/docs/Getting-started/staging-demo-video-checklist Step-by-step instructions for recording your staging implementation review demo Before we issue production API credentials we ask you to record a short screen-capture walking through your integration in our **Staging environment**. This guide expands the [Staging Implementation Review section of our production requirements page](/docs/Getting-started/requirements-for-production-credentials#1-staging-implementation-review) with concrete instructions on what we want to see. We're looking for evidence that your **product** can drive the Respondent API end-to-end. For every required action below, we want to see it trigger from your own product UI and then see the resulting state in both your product UI and Respondent's UI. *** ## Before you record * **Staging researcher login** — have your Respondent staging researcher account ready in another tab. You'll switch to it to show state changes. * **Test participants** — seed at least a few using `POST /v1/profiles` (the same endpoint you've been using to test). See [Staging Environment](/docs/Getting-started/staging-environment) if you need a refresher. You can [sign in as those participants here](https://website.staging.respondent.io/next/participants/login) to apply to your own project and to reply to messages. * **Recording tool** — any screen recorder is fine (Loom, QuickTime, OBS, Drive upload, etc.). We just need to be able to play it and reference timestamps. Narration is encouraged but not required — if you record silently, please use on-screen labels. * **One or two videos?** — your call. If you support both human-moderated and unmoderated studies, you can put both in one video with chapters, or send two shorter videos. Whichever is easier. * **Token hygiene** — keep API tokens, secrets, and any real user PII off-screen. Staging tokens are still credentials. *** ## Recommended structure This is a suggested order — feel free to deviate if your integration's natural flow is different. What matters is that every checklist item below is covered somewhere in the recording or written notes. 1. Intro (\~30–60s) 2. Project setup (per study type you support) 3. Happy-path lifecycle — Invite → Mark as attended 4. Disqualification, quality rejection, and reporting 5. Human-moderated flows (if applicable): scheduling + messaging *** ## Checklist > If a checklist item doesn't apply to your integration (e.g. you don't support unmoderated studies, or your flow skips a particular action), include a brief written note explaining why. This helps us review faster without coming back with clarifying questions. ### Written items > Include these in the message when you send the video. * [ ] Confirm **auto-pay is enabled** on the projects your integration creates. * [ ] List which **study types** your integration supports (human-moderated, unmoderated, or both). * [ ] List which **screener question types** your integration supports (e.g. multi-choice, free-text, number range, etc). ### 1. Intro — orient us * [ ] State who you are and what your product does in 1–2 sentences. * [ ] Tell us which study types your integration supports — **human-moderated**, **unmoderated**, or **both**. * [ ] Briefly outline what we're about to see (which flows, which study type first, etc). ### 2. Project setup > Demonstrate once per study type you support. * [ ] From your product UI, create and publish a Respondent project that includes **at least one screener question**. * [ ] Show the published project in your product UI. * [ ] Switch to [Respondent's UI](https://website.staging.respondent.io/) and show the same project, published. ### 3. Happy-path lifecycle > Demonstrate once per study type you support. > > Take **one** screener response — one that arrived qualified from screening — all the way through. You can use a test participant you've seeded via `POST /v1/profiles` and applied to your project. * [ ] Show the qualified screener response appearing in your product UI (the list/inbox/whatever your users see). * [ ] **Invitation** — two paths depending on your integration: * If your integration uses **auto-invite**, just show the response landing at `Invited` status in your product UI and in Respondent's UI. * Otherwise, trigger [**Invite participant**](/screener-responses/invite-participant) from your product UI and show your UI and Respondent's UI reflecting the invite. * [ ] **For unmoderated studies only** — demonstrate the participant going to the task (e.g. sign in as the test participant and follow the task link from their dashboard), finishing it, and being redirected through the project's [`completionUrl`](/docs/Screener-responses/completion-url). Show the task showing as complete in your product UI and in Respondent's UI. * [ ] Trigger [**Mark as attended**](/screener-responses/mark-as-attended) once you're satisfied with the response — this can be manual or automated. For moderated studies it's typically triggered after the session ends (calendar callback, researcher action, etc.); for unmoderated studies it's the point at which you accept the completed task and start the payout. Show your UI and Respondent's UI reflecting it. (If a response isn't acceptable you'd call Mark as rejected instead — that's covered in the next section.) ### 4. Disqualification, quality rejection, and reporting > Doesn't need to be the same participant — pick any screener response(s). > > For each item, explain or demonstrate when this is triggered in your flow (manually or automated). * [ ] Trigger [**Disqualify**](/screener-responses/qualify-participant) from your product UI — a Qualify call with `qualifyStatus: false`. Show your UI and Respondent's UI reflecting it. * [ ] Trigger [**Mark as rejected**](/screener-responses/mark-as-rejected) on a response (this is the post-invitation "quality didn't meet expectations" action, distinct from disqualifying at entry). Show your UI and Respondent's UI reflecting it. * [ ] Trigger [**Report participant**](/screener-responses/report-participant) on a response. Show your UI and Respondent's UI reflecting it. ### 5. Human-moderated studies — scheduling and messaging > Only required if you support human-moderated studies. * [ ] **Scheduling** — handle scheduling on your side. After a participant books a time, call [**Schedule participant**](/screener-responses/schedule-participant) to write the booking back to Respondent. Demonstrate by signing in as the test participant and showing the booked time on their Respondent dashboard. * [ ] Show a **two-way** message exchange using the [Messaging API](/messaging/create-a-conversation): * Send a message to a participant from your product UI. * Sign in as that test participant and send a reply. * Show the reply appearing in your product UI. *** ## When you're ready Send the recording (or a shareable link) plus the written items above to **[partnersupport@respondent.io](mailto:partnersupport@respondent.io)**. We'll get back to you with either approval to proceed to production credentials or any follow-up questions. If anything in this checklist is ambiguous for your integration's shape, just ask — it's much faster than re-recording. # Staging environment Source: https://developers.respondent.io/docs/Getting-started/staging-environment Learn and test in staging We currently use a staging environment to allow developers to set up their integration in a risk free manner. The environment is free and fully functional. Participant API developers should build their integration in staging and then switch over to the production environment when they are ready to go live. | Environment | URL | | - | - | | Staging | [https://api-staging.respondent.io](https://api-staging.respondent.io) | | Production | [https://api.respondent.io](https://api.respondent.io) | See [Staging vs Production](/reference/staging-vs-production) for the differences between the two. ## Getting staging credentials Staging API credentials (`x-api-key` and `x-api-secret`) are issued by the Respondent Partner team — they are not self-serve. Follow the [Quickstart](/docs/Getting-started/quickstart) to book an intro call; the Partner team will then provision a staging Researcher account and API credentials for your organization. See the [API FAQ](/docs/Guides/api-faq) for additional context. An example call to the API would look like: ```bash theme={null} curl --location 'https://api-staging.respondent.io/v1/skills?query=comp' \ --header 'x-api-key: MY_ID' \ --header 'x-api-secret: MY_SECRET' ``` ## Testing We will provide credentials to you to log in as a Researcher in Staging. To test end to end in the staging environment you will need to add a test credit card and create staging participants using the API endpoint described below. ### Card information | Field | Value | | - | - | | Full name | John Doe | | Billing street address | 23 Respondent Ave | | Zipcode | 12345 | | Card number | 4111111111111111 | | Expiry date | 09 / 2036 | | Security code | 902 | ### Participant creation POST /v1/profiles You can now create participants in staging environment via our API for easy testing using the profiles POST endpoint. Below is an example payload to create a participant profile. * Participant email and work email will be auto verified * All other mandatory profile info is set as random data * You can use this [link](https://website.staging.respondent.io/next/participants/login) to sign in as the participants you create to view projects from the participant point of view, and update profile fields to test multiple personas. ```json theme={null} { "email": "my-unique-email@email.com", "firstName": "Sarah", "lastName": "Smith", "password": "test1234", "gender": "female", "workEmail": "my-unique-email@email.com" } ``` # API FAQ Source: https://developers.respondent.io/docs/Guides/api-faq Common questions and tips for maximizing your integration 1. ### How do I test the respondent flow in staging without recruiting real participants? You create a participant profile in staging via our [profile creation endpoint](/profiles/create-test-participant-staging-only). You can then invite that profile by following these steps: 1. Log into the staging participant profile in one browser tab. 2. In another tab (recommend opening in incognito), log into the researcher side. 3. Open a published project and look for the **Share** button in the top-right corner. 4. Copy the project link and switch back to the staging participant profile tab. 5. Paste the link into the search bar of the staging participant profile. The staging participant should then be able to access the screener. 2. ### What is an externalResearcherId? An externalResearcherId is a custom identifier that the API partner assigns to researchers when creating projects via our API. 3. ### What parameters are appended to the task URL? * `respondent_profile_id` * `respondent_project_id` * `respondent_screener_response_id` * `respondent_user_id` * `month` 4. ### How do I get staging API credentials? Staging credentials (`x-api-key` and `x-api-secret`) are issued by the Respondent Partner team — they are not self-serve. To request them: 1. Follow the [Quickstart](/docs/Getting-started/quickstart) and book an intro call with the Partner team. 2. After the intro call, Respondent will provision a staging Researcher account and API credentials for your organization. 3. Treat staging tokens as real credentials and keep them out of logs, screenshots, and recordings. See [Staging environment](/docs/Getting-started/staging-environment) for the staging base URL and test data once you have credentials. 5. ### What does `qualifiedOverriden` mean on a screener response? `qualifiedOverriden` is a boolean on the [Qualify Participant](/screener-responses/qualify-participant) request body and on the screener response itself. It indicates whether the participant's `qualified` status was set manually by a researcher (via the qualify endpoint or the researcher UI) rather than by the automatic screener evaluation. * `false` — the `qualified` status reflects the participant's screener answers as evaluated by Respondent. * `true` — a researcher overrode the automatic result, for example to manually qualify a participant whose screener marked them as `qualified: false`. Use this field to distinguish system-qualified participants from manually qualified ones when auditing screener decisions. 6. ### How do I retrieve participants I've reported? The [Report Participant](/screener-responses/report-participant) endpoint dismisses the response — its `status` becomes `CANCELLED` and its `visibility.status` becomes `DISMISSED` — after which no further actions are available on it. Default [List Screener Responses](/screener-responses/list-screener-responses) results use `visibilityStatus=VISIBLE`, so reported participants do not appear unless you pass `visibilityStatus=DISMISSED`. Filtering by `status=CANCELLED` alone is not enough under that default. `DISMISSED` can also include responses dismissed for reasons other than a report. The most reliable audit trail is to persist the `screenerResponseId` (and any other identifiers you need) on your side at the time you call Report Participant. Reported participants are also prevented from applying to your organization's future projects automatically — see [Response statuses](/docs/Screener-responses/working-with-responses) for the full status lifecycle. 7. ### What are some tips and tricks to maximise your integration? * `autopay` is set to `true`. If set to false, you will need to manually login onto our DIY platform to process payment or use our [manual payout endpoint](/screener-responses/trigger-manual-payout) with payoutCount: 1. * Use the [Retrieve Audience Size Estimate](/projects/retrieve-audience-size-estimate) endpoint to test feasibility — it can be called multiple times as you adjust filters and signals. * Use the [Qualify Participant](/screener-responses/qualify-participant) endpoint: For qualifying or disqualifying participants based on their screener responses—any disqualified participant will automatically be backfilled. * Use the [Mark As Rejected](/screener-responses/mark-as-rejected)endpoint: For when the participant response to the study does not meet researcher’s criteria. * Use the [Report Participant](/screener-responses/report-participant) endpoint: For flagging participants for inappropriate behavior. Reported participants won’t appear in future projects. * Enable messaging with participants through our [messaging flow](/messaging/create-a-conversation) to boost show and task completion rates. * Enable the past participant database feature to unlock the ability to filter out participants who’ve taken part in previous studies, or to selectively re-invite high-quality (favorite) participants. * When enough qualified participants apply to meet the project's target, it is automatically paused. Pausing stops sourcing new applicants, but already-qualified applicants continue to receive auto-invitations until the target is reached. * Our recruiting dynamically sources up to 3x the target to ensure you can reach your target even if some participants do not complete the study or provide poor quality answers and need to be replaced. # API vs Researcher Platform Source: https://developers.respondent.io/docs/Guides/api-vs-researcher-platform Using the API and the Researcher Platform? This page is for you. Our API is built with an end to end solution in mind. It is designed to manage recruiting projects from creation to participant payment. With that in mind we've found some use cases where partners still elect to take some actions via API and others through our Researcher Platform UI. While the goal of this API is to provide very similar capabilities to our Researcher Platform UI, there are some differences in behaviors that limit what actions can happen across API and our platform UI. These limitations are largely dictated by the methodology type given the different functions each has. ## Moderated projects Projects where methodology = `oneOnOne`, `focusGroup`, `inRespondentHome`, or `inRespondentOffice` are considered moderated projects and require scheduling to interact with a participant. These project types have the most restrictive in their interoperability between API and Researcher Platform: * Moderated projects drafted or published via public API **cannot be edited** on the Researcher Platform. * The API required a bookingURL that is not compatible with the native booking system and instead requires API users to supply their own means of [scheduling](/docs/Screener-responses/scheduling). * Moderated projects drafted or published via public API **will not have access to scheduling** on the Researcher Platform. * API create projects require their own scheduling tool * Moderated projects drafted or published via public API **cannot invite or otherwise status participants** on the Researcher Platform. * Since our Researcher Platform is depended on the native scheduling functionality at this time, it will override API designated `bookingURLs`. ## Unmoderated projects Projects where methodology = `unmoderatedStudy`, `survey`, or `diaryStudy` are considered unmoderated projects and have fewer dependencies across API and the Researcher Platform. * Unmoderated projects drafted or published via public API **can be edited** on the Researcher Platform. ## All methodologies While we recommend using `/v1/projects/{projectId}/copy` when needing to copy projects, it is technically possible to copy any API created projects on the Researcher Platform, they do however remain "API" projects with an `externalResearcher` object. *** # Automated Researcher Emails Source: https://developers.respondent.io/docs/Guides/automated-emails Overview of transactional emails sent by Respondent to researchers As projects are published and recruited through the Respondent API, you will receive transactional emails for the researcher sent to the email address for your API researcher account. This page outlines what emails are sent so that you can align with emails that you may want to send/trigger to your researcher customers, if desired. ### Project Notifications | Email | Description | Environment | | - | - | - | | Project Saved | Notifies that project has been created | All | | Publish Reminder | Reminds a researcher to publish project if it has been in Draft status for >3 days | All | | Recruiting Started | Notifies researcher that recruiting has launched for a project | All | | 1st Qualified Participant | Notifies that the first participant has been qualified for a project | All | | Project Filled | Notifies that the project has reached its target number for qualified participants | All | | New Message | Notifies that a participant has sent a message about a particular project | All | | Pay Participant Reminder | Prompts researcher to pay participants that have been marked as attended *(uncommon because API will auto-pay participants who are marked as attended)* | All | | Auto-Pause Warning | Notifies researcher on 27th day that project will automatically pause on Day 28 (if it is not already paused because of hitting 3x recruiting target) | All | | Project Recruiting Stopped | Notifies researcher that project has stopped recruiting after 30 days, if project has not been paused | All | ### Scheduling & Booking Notifications Respondent does **not** send the researcher any scheduling or booking-lifecycle emails for projects managed through the API. When a participant is scheduled, rescheduled, or cancelled via the [scheduling endpoints](/docs/Screener-responses/scheduling), only the **participant** is notified. Moderator-side notifications and reminders are the responsibility of your own platform. See [Scheduling](/docs/Screener-responses/scheduling) for the participant emails Respondent sends automatically. ### Account/Organization Management Notifications | Email | Description | Environment | | - | - | - | | New Researcher | Notifies that new researcher has been added to the organization | Prod only | | Join Request | Notifies that a researcher has requested to join the organization | Prod only | | Researcher Invite | Notifies a new researcher that they have been invited to join the organization | Prod only | | Reset Password | Provides password reset link if requested for account | Prod only | ### *** # Defining Qualified Participants Source: https://developers.respondent.io/docs/Guides/b2b-participant-qualification-and-filters When we talk about a participant being qualified, whether they are B2B or B2C, we mean: * They are 100% qualified based on their responses to screener question logic * They are matched on the Filter criteria outlined in our [targeting overview](/docs/Projects/targeting-audiences-effectively) * They have agreed to the NDA (if you require NDAs) Beyond these definitions, there is no hard filter for qualified participants, such as household income or company or industry unless you have set those as key qualifiers when available. Instead these other parameters are used as part of the matching sort we do when promoting projects to participants. To apply to B2B projects, participants will still need to have a verified work email, but participants can have a different industry, job title, and company size and they will still be considered qualified if all the qualification criteria above are met. We have found that job titles, industries, and other company information used in targeting can be very different across different roles, companies, and countries. As a result, in order to determine whether the participant has the requisite knowledge and experience, an effective screener does a better job than simply targeting by those employment criteria. ## How to Qualify B2B Participants Partners handle B2B participant qualification in a number of different ways: * They set expectations that the researcher needs to create a good screener that encompasses all role and functional requirements, even if they are covered by the B2B project audience criteria ([see our guide](https://respondent.io/blog/screener-survey-guide-how-to-screen-for-unmoderated-studies)) * They use Job Function, Seniority, and key qualifiers to create hard match filters that determine qualification and either don't use job title or use it for additional signal value when we match participants * They do text matching on the job title within each profile to decide who to invite and who to disqualify * They use the matching object in the [Screener Response endpoint](/screener-responses/list-screener-responses) response to exact match against any of the B2B targeting fields and disqualify using the [Screener Response Qualify endpoint](/screener-responses/qualify-participant) if there is not an exact match (so that the recruiting will continue) * Note that the exact match provided in the matching object is very specific in comparing the job title requested to the job title provided by the participant. For example, "Product Manager" and "Senior Product Manager" would not be a match, and "IT Director" and "Director, IT" would not be a match. *As a result, it is recommended that if you use the matching object to disqualify participants, you also provide as many relevant job titles as possible in the project setup.* If you are using the API to disqualify participants, be sure to also check that the project has not paused automatically, especially if your requested number of participants is small. Respondent will auto-pause projects when the number of qualified participants equals 3x the number of participants requested. However, we will not un-pause a project if you disqualify participants after we have already auto-paused it. In that case, you will need to unpause the project using the [Project Pause endpoint](/projects/pause-a-project). Auto-pause only stops **sourcing** — no new applicants enter the funnel and matching emails are not sent. Applicants who were already qualified before the pause continue to receive auto-invitations up to the project's invite cap (or completes cap for invite-to-complete flows). If you rely on invite-to-complete delivery, an auto-paused project will still keep inviting qualified respondents; you only need to unpause when you want to source more applicants. *** # Conditionals Source: https://developers.respondent.io/docs/Guides/conditional-and-dependency-reference Learn what field are available based on different conditions ## Target Market / Audience `targetMarketType` conditional Please see [Targeting audiences effectively](/docs/Projects/targeting-audiences-effectively) for a full list of optional and required fields for B2B and B2C projects. *** ## In person vs remote research `typeOfResearch` conditional ## In Person Research | Selection | Conditionally available field | Details | | - | - | - | | inPerson | `targetLocationRespondentInPerson` becomes a required field | This field determines where the researcher is and determines if participants are close enough | If a researcher chooses to conduct in person research they are required to supply a country, street address, city, and zip code. In the United States, a state entry is also required. ### Impacts on Available Methodologies: In person `targetResearchMethodology` conditional dependent of `typeOfResearch` When inPerson is selected, the following options are available for `targetResearchMethodology` * One on One * Focus Group * In Respondent Home * In Respondent Office ## Remote research | Selection | Conditionally available field | Details | | - | - | - | | remote | `targetCountry` is required and `targetCities` becomes an optional | This allows researcher to determine where they would like their participants to come from | If a researcher is conducting research remotely, only `targetCountry` is required. When a target country is selected, `targetCities` becomes an optional targeting parameter. ### Impacts on Available Methodologies: Remote `targetResearchMethodology` conditional dependent of `typeOfResearch` When remote is selected, the following options are available for `targetResearchMethodology` * One on One * Focus Group * Unmoderated Study * Survey * Diary Study *** ## Recruiting for surveys ### `targetResearchMethodology` conditional | Selection | Conditionally available field | Details | | - | - | - | | survey | `task`becomes an optional field | Researchers can enter a custom URL and instructions for the survey a participant will need to complete - if this is not completed now it will need to be included in the participant invite | | unmoderatedStudy | `task`becomes an optional field | Researchers can enter a custom URL and instructions for the task a participant will need to complete - if this is not completed now it will need to be included in the participant invite | *** ### `targetCountry` conditional | Selection | Conditionally available field | Details | | - | - | - | | Any individual counts | `targetCities` becomes a required field | This field allows researcher to target a more specific location for participants | *** # Creating conversations Source: https://developers.respondent.io/docs/Messaging/creating-conversations Putting together the pieces of a conversation # Create the conversation POST: /v1/messaging/conversations A conversation can be created with 1 or more participants IDs. In most cases messages are used for 1:1 conversations, but a group chat can be facilitated by adding multiple participant IDs ```json Create conversation theme={null} { "participantUserIds": [ "PARTICIPANT_ID_1", "PARTICIPANT_ID_2" ], "projectId": "PROJECT_ID", "externalResearcherId": "RESEARCHER_ID" } ``` # Add messages POST: /v1/messaging/messages A conversation ID is returned in the response and is required to add a new message to the conversation. Messages will go to all participants in a conversation. Responses from participants will be visible to all participants in the conversation. ```json Create message theme={null} { "conversationUid": "CONVERSATION_ID", "body": "Hi there!" } ``` # Adding new participants POST: /v1/messaging/conversations/\{uid}/participants/\{participantUserId} As noted above, additional participants can be added to a conversation to facilitate a group chat environment. Responses from participants will be visible to all participants in the conversation. ```json json theme={null} { "uid": "CONVERSATION_ID", "participantUserId": "PARTICIPANT_ID" } ``` *** # Messaging Overview Source: https://developers.respondent.io/docs/Messaging/messaging-overview Streamlining communication between researchers and participants # What can you do with messaging? Messaging endpoints allow you to set up a two way conversation between researchers and participants. On our platform messaging is often used to clarify study requirements, aid in rescheduling, and follow up on screener answers. We've found that this functionality greatly benefits both researchers and participants and ultimately helps facilitate smoother end to end research. Setting up messaging first requires the [creation of a conversation](/messaging/create-a-conversation) with participant ID before messages can be created. After a conversation is created additional participants can be added if a group chat environment is desired. ## Supported project methodologies The messaging endpoints are not restricted by `targetResearchMethodology`. As long as you have a `projectId` and one or more participant user IDs (typically from screener responses), you can open a conversation and exchange messages. This includes: * Moderated methodologies (for example one-on-one interviews and focus groups), where messaging is most commonly used to clarify screener answers, coordinate scheduling, and follow up after a session. * Unmoderated methodologies (`unmoderatedStudy`, `survey`, and `diaryStudy`), where messaging can still be used to clarify screener answers, send task instructions or reminders, and follow up on submissions. See [Moderated vs unmoderated methodologies](/docs/Projects/project-deep-dive) for the methodology field reference. # Why use messages? Messaging is a powerful tool for researchers that helps facilitate pre-screening, booking clarifications, and lightweight study follow-up. ## API endpoints Start a conversation linked to a project. Send messages to participants in a conversation. # Advanced targeting attributes Source: https://developers.respondent.io/docs/Projects/advanced-targeting-attributes Target participants on lifestyle, professional, technology, and product attributes using the advancedTargeting field ## Overview Advanced targeting attributes let you target participants on a broader set of profile characteristics — languages spoken, employment, technology and devices, products and services used, and more — beyond the core audience fields in [Targeting audiences effectively](/docs/Projects/targeting-audiences-effectively). These attributes are **additive**. Every existing targeting field (`targetCountries`, `targetGenders`, `targetHouseholdIncome`, the B2B firmographic fields, and so on) is unchanged and continues to work exactly as before. Advanced attributes are set through a single new object, `advancedTargeting`, when you create or update a project, and are available identically to **B2C** and **B2B** projects. ## How it works `advancedTargeting` is a flat object whose keys are the attributes you want to target and whose values are the criteria a participant should meet: ```json theme={null} { "advancedTargeting": { "languages": ["en", "es"], "streamingServices": ["netflix", "disneyplus"], "homeOwner": true } } ``` ### Filters vs. signals Advanced attributes use the same **filter vs. signal** model as the rest of targeting. For a refresher, see [Targeting audiences effectively](/docs/Projects/targeting-audiences-effectively) and [participant qualification and filters](/docs/Guides/b2b-participant-qualification-and-filters). **We strongly recommend making an attribute a hard filter whenever it genuinely needs to be met** — add its key to `keyQualifiers`. Keep it a signal only when you want a soft preference and broader reach. ### Participants who haven't set an attribute yet Targeting an advanced attribute does **not** shrink your reachable audience to only participants who have already answered it. Participants who haven't yet set that attribute can still match on your other criteria and be contacted; when they apply, they're asked to complete the missing attribute and only continue if they qualify. ## Available attributes All attributes below are available on **both B2C and B2B** projects. **Behavior** shows whether an attribute is a soft signal by default (promotable to a hard filter via `keyQualifiers`) or always a hard filter. For each attribute's allowed values, see the `advancedTargeting`[ schema in the Create a project reference](/projects/create-a-project#body-one-of-0-advanced-targeting) — it's generated from the API, so it's always current. ### Personal | Attribute | Type | Behavior | | - | - | - | | `relationshipStatus` | string | Signal · promotable | | `languages` | string\[] | Signal · promotable | | `nationality` | string\[] | Signal · promotable | | `numberOfChildren` | number | Signal · promotable | | `numberOfChildrenLiveAtHome` | number | Signal · promotable | | `childrenBirthYears` | string\[] | Signal · promotable | | `livingSituation` | string | Signal · promotable | | `homeOwner` | boolean | Signal · promotable | | `pets` | string\[] | Signal · promotable | | `areaType` | string | **Always hard filter** | | `onlineShoppingFrequency` | string | Signal · promotable | | `militaryServiceStatus` | string | Signal · promotable | ### Professional | Attribute | Type | Behavior | | - | - | - | | `workSetting` | string | **Always hard filter** | | `employmentStatuses` | string\[] | Signal · promotable | | `cloudPlatforms` | string\[] | Signal · promotable | | `codingAITools` | string\[] | Signal · promotable | | `designPrototypingTools` | string\[] | Signal · promotable | | `productivitySoftware` | string\[] | Signal · promotable | ### Technology & devices | Attribute | Type | Behavior | | - | - | - | | `browsers` | string\[] | **Always hard filter** | | `computerOS` | string\[] | **Always hard filter** | | `programmingLanguages` | string\[] | **Always hard filter** | | `tabletOS` | string\[] | **Always hard filter** | | `smartphoneOS` | string\[] | **Always hard filter** | | `webcam` | boolean | **Always hard filter** | | `mobilePhoneWithInternet` | boolean | **Always hard filter** | | `headsetWithMicrophone` | boolean | **Always hard filter** | ### Products & services | Attribute | Type | Behavior | | - | - | - | | `streamingServices` | string\[] | Signal · promotable | | `foodDeliveryServices` | string\[] | Signal · promotable | | `onlineBankingTools` | string\[] | Signal · promotable | | `onlineLearningPlatforms` | string\[] | Signal · promotable | | `onlineMarketplacePlatforms` | string\[] | Signal · promotable | | `onlineTravelBooking` | string\[] | Signal · promotable | | `smartHomeDevices` | string\[] | Signal · promotable | | `socialNetworkPlatforms` | string\[] | Signal · promotable | | `vehicleBrands` | string\[] | Signal · promotable | ### Health | Attribute | Type | Behavior | | - | - | - | | `healthWellnessApps` | string\[] | Signal · promotable | ## Setting advanced attributes Set `advancedTargeting` when you create a project (`POST /v1/projects`) or update a draft (`PATCH /v1/projects/{projectId}`). Add any attribute keys you want promoted to hard filters to `keyQualifiers`. In the create examples below, `languages` (B2C) / `cloudPlatforms` (B2B) are promoted to hard filters via `keyQualifiers`; the other set attributes act as soft signals; and the always-hard attributes (`webcam`, `workSetting`) filter automatically without appearing in `keyQualifiers`. ```json B2C create theme={null} // POST /v1/projects { // … required project fields — see "Create a project" in the API reference "publicTitle": "Streaming habits study", "targetMarketType": "b2c", "advancedTargeting": { "languages": ["en", "es"], "streamingServices": ["netflix", "disneyplus"], "homeOwner": true, "webcam": true }, "keyQualifiers": ["languages"] } ``` ```json B2B create theme={null} // POST /v1/projects { // … required project fields — see "Create a project" in the API reference "publicTitle": "Cloud engineering panel", "targetMarketType": "b2b", "advancedTargeting": { "employmentStatuses": ["employedfulltime"], "cloudPlatforms": ["aws", "googlecloud"], "workSetting": "remote" }, "keyQualifiers": ["cloudPlatforms"] } ``` ```json Update a draft theme={null} // PATCH /v1/projects/{projectId} { "advancedTargeting": { "streamingServices": ["netflix", "hulu", "max"] }, "keyQualifiers": ["languages", "streamingServices"] } ``` ### Updating and clearing attributes On `PATCH /v1/projects/{projectId}`, `advancedTargeting` is **merged per attribute** — send only the attributes you want to change: | Goal | What to send | | - | - | | **Set / change** an attribute | Include the key with its new value | | **Clear** an attribute | Send the key with the value `null` | | **Clear an array attribute** | Send `[]` — it's stored as an empty list and treated as *no target* (no filter, no signal) | | **Leave an attribute unchanged** | Omit the key | `keyQualifiers` is **not** merged — it's replaced as a whole, so always send the complete set of hard filters you want in effect (send `[]` to remove all of them). ```json Clearing examples theme={null} // PATCH /v1/projects/{projectId} { "advancedTargeting": { "languages": ["en"], // set / change "relationshipStatus": null, // clear (remove the target) "streamingServices": [] // clear an array attribute (no target) // workSetting omitted // left unchanged }, "keyQualifiers": ["languages"] // replaces the whole hard-filter set } ``` ## See also * [Targeting audiences effectively](/docs/Projects/targeting-audiences-effectively) * [Participant qualification and filters](/docs/Guides/b2b-participant-qualification-and-filters) * [Audience quotas](/docs/Projects/audience-quotas) # Audience Quotas Source: https://developers.respondent.io/docs/Projects/audience-quotas Use quota endpoints to cap how many participants you invite from each audience segment. A quota is optional. One quota per project. Set the configuration with `PUT /v1/projects/{projectId}/quotas`. You can also send the same `{ version: "v2", groups[] }` body as `quotas` on [POST /v1/projects](/projects/create-a-project). `/v1/projects/{projectId}/quota` is an alias of `/quotas`. A project with no quota returns `200` and `{ version: "v2", groups: [] }`. `404` is only for a project that does not exist. A quota holds at most 20 groups, and a group at most 50 segments. Rule groups may nest at most 10 levels deep, counting the segment's root group as depth 1. A configuration over any of these caps is rejected with a `400`. A request the quota rules reject returns a `400` whose `error` is a list with one message per problem, the same body as a request with a missing or malformed field. Each message starts with where the problem is: a path into the quota, such as `groups[0].segments[0].rule.children[0]`, or `groupId/segmentId` for a [target](/quotas/update-quota) or [external fill](/quotas/set-external-fills) entry that names a bucket the quota does not have, or names one twice. This applies to `PUT /v1/projects/{projectId}/quotas`, `PATCH /v1/projects/{projectId}/quotas/targets`, `PUT /v1/projects/{projectId}/quotas/external-fills` and `quotas` on [POST /v1/projects](/projects/create-a-project). ```json theme={null} { "statusCode": 400, "error": [ "groups[0].segments[0].rule.children[0]: companySize is only available on B2B projects; set the project's audience to B2B or remove this condition" ] } ``` ## Conditions Each segment has a rule tree. Nodes are `kind: "group"` (`AND` / `OR` children) or `kind: "condition"`. An attribute condition can read any participant attribute a project can target, except employment status, the children attributes and disability type / assistive technologies. Age is a numeric attribute, not age-band enums. | Source | Operators | Fields | | - | - | - | | Age (`age`, whole years) | `range`, `gt`, `lt` | `range`: inclusive `min` and/or `max`. `gt` / `lt`: `value` (strict). `in` / `notIn` is rejected (`in is not available for age; it takes range, gt, lt`). | | Single-value attributes: `gender`, `ethnicity`, `country`, `education`, `householdIncome`, `companySize`, `seniority`, `relationshipStatus`, `livingSituation`, `areaType`, `onlineShoppingFrequency`, `militaryServiceStatus`, `workSetting` | `in`, `notIn` | `values` from the attribute's vocabulary (the same values the profile stores, e.g. `gender: ["female"]`). "Prefer not to say" sentinels are not accepted and count as unanswered. | | Multi-value attributes: `industry`, `jobFunctions`, `languages`, `pets`, `nationality`, `cloudPlatforms`, `codingAITools`, `designPrototypingTools`, `foodDeliveryServices`, `healthWellnessApps`, `onlineBankingTools`, `onlineLearningPlatforms`, `onlineMarketplacePlatforms`, `onlineTravelBooking`, `productivitySoftware`, `smartHomeDevices`, `socialNetworkPlatforms`, `streamingServices`, `vehicleBrands`, `programmingLanguages`, `browsers`, `smartphoneOS`, `tabletOS`, `computerOS` | `in`, `notIn` | `values`. The participant holds several values; `in` matches when any of them is listed and `notIn` when none is. `industry` values are industry names; the rest use the attribute's vocabulary. | | Boolean attributes: `homeOwner`, `webcam`, `mobilePhoneWithInternet`, `headsetWithMicrophone` | `in`, `notIn` | `values` of `"true"` / `"false"`. | | Job title (`jobTitle`) | `in`, `notIn` | `values` are job title ids, resolved to title families on save. | | Screener RADIO / CHECKBOX | `any`, `all`, `none` | `questionUid`, `answerUids`. `all` is CHECKBOX only. | | Screener CHECKBOX | `count` | `questionUid`, `op` (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`), `value` (a non-negative integer). Compares how many options the participant selected with `value`; `gt` and `lt` are strict, `gte` and `lte` inclusive. | | External screener RADIO / CHECKBOX | `any`, `all`, `none` | `questionId`, `optionIds` — the `id`s of the question's declared `options`. `all` is CHECKBOX only. | | External screener CHECKBOX | `count` | `questionId`, `op`, `value`, as above. | A participant who skipped a question matches no condition on it, `none` and `count` included; a CHECKBOX answered with nothing selected counts as `0`. A participant with no value for an attribute matches no condition on it, `notIn` included; when a project's quota reads an attribute the participant has not filled in, the platform asks for it before the screener. `industry`, `jobTitle`, `companySize`, `seniority` and `jobFunctions` are B2B only. On a project with `targetMarketType: "b2c"`, a condition on any of them is rejected with a `400` that lists each one, for example `groups[0].segments[0].rule.children[0]: companySize is only available on B2B projects; set the project's audience to B2B or remove this condition`. This applies to `PUT /v1/projects/{projectId}/quotas` and to `quotas` on [POST /v1/projects](/projects/create-a-project). `militaryServiceStatus` and `workSetting` are available on B2B and B2C projects. Targets are `{ op: "min" }`, `{ op: "max" }`, `{ op: "exact" }` (each with `value`) or `{ op: "track" }` for counted-but-uncapped buckets. Address a group's implicit leftover bucket with `segmentId: "no-match"`. ## Sample workflow 1. Create a project via [POST /v1/projects](/projects/create-a-project), optionally with `quotas`. 2. Create or replace the quota with PUT /v1/projects/\{projectId}/quotas (allowed on draft and published projects). 3. Publish. The quota is validated again against the project: if it no longer passes, publish returns a `400` starting `Quotas need updating before this project can be published:` followed by each error, and the project stays a draft. Changing `targetMarketType` on a draft leaves its quota as it is, so a draft switched to `b2c` after a quota on a B2B-only attribute was saved is refused here; remove the condition or set `targetMarketType` back to `b2b`. Matching and invites consume the quota. A public [invite](/screener-responses/invite-participant) is always admitted: the invite succeeds, consumption is recorded, and the bucket may go over target. There is no 409 on this path. 4. Read fill with GET /v1/projects/\{projectId}/quotas. Each screener response that occupies a quota carries `quotaAssignment` (`segments[]`, `tier`, `revision`, `assignedAt`, `tierChangedAt`). 5. Subscribe to `QUOTAS.SEGMENT_FILLED` and `QUOTAS.FILLED` on your [webhook](/docs/Webhooks/overview). ## Create or replace PUT /v1/projects/\{projectId}/quotas A group or segment whose `id` matches an existing one keeps its progress. An omitted or new id is a new bucket at zero. The response includes `changes` (`created`, `preserved`, `removed`, `rebucketed`, `externalFillsDropped`, `overTarget`) and `warnings`. ```Text Example request theme={null} { "version": "v2", "groups": [ { "id": "gender", "name": "Gender", "segments": [ { "id": "female", "name": "Female", "rule": { "kind": "group", "connector": "AND", "children": [ { "kind": "condition", "condition": { "source": "attribute", "attribute": "gender", "op": "in", "values": ["female"] } } ] }, "target": { "op": "max", "value": 10 } }, { "id": "male", "name": "Male", "rule": { "kind": "group", "connector": "AND", "children": [ { "kind": "condition", "condition": { "source": "attribute", "attribute": "gender", "op": "in", "values": ["male"] } } ] }, "target": { "op": "max", "value": 10 } } ] } ] } ``` ```Text Example response theme={null} { "id": "68086546545de68c1bc1bc1b", "projectId": "68084868666de68c1b4f3412", "version": "v2", "revision": 1, "filled": false, "closed": false, "groups": [ { "id": "gender", "name": "Gender", "filled": false, "closed": false, "minProtected": false, "segments": [ { "id": "female", "name": "Female", "rule": { "kind": "group", "connector": "AND", "children": [ { "kind": "condition", "condition": { "source": "attribute", "attribute": "gender", "op": "in", "values": ["female"] } } ] }, "target": { "op": "max", "value": 10 }, "progress": { "pending": 0, "qualified": 0, "invited": 0, "attended": 0, "externalFills": 0 }, "filled": false, "closed": false, "overTarget": false }, { "id": "male", "name": "Male", "rule": { "kind": "group", "connector": "AND", "children": [ { "kind": "condition", "condition": { "source": "attribute", "attribute": "gender", "op": "in", "values": ["male"] } } ] }, "target": { "op": "max", "value": 10 }, "progress": { "pending": 0, "qualified": 0, "invited": 0, "attended": 0, "externalFills": 0 }, "filled": false, "closed": false, "overTarget": false } ], "noMatch": { "target": { "op": "track" }, "progress": { "pending": 0, "qualified": 0, "invited": 0, "attended": 0, "externalFills": 0 }, "filled": false, "closed": false, "overTarget": false } } ], "changes": { "created": [ { "groupId": "gender", "segmentId": "female" }, { "groupId": "gender", "segmentId": "male" } ], "preserved": [], "removed": [], "rebucketed": 0, "externalFillsDropped": [], "overTarget": [] }, "warnings": [], "createdAt": "2026-09-08T03:44:44.482Z", "updatedAt": "2026-09-08T03:44:44.482Z" } ``` ## Update targets PATCH /v1/projects/\{projectId}/quotas/targets Sparse array. One entry per bucket; a duplicate `(groupId, segmentId)` is `400`. Omitted buckets are untouched. Tracked-only is `{ "op": "track" }`. Rules and progress are not rewritten. ## Read GET /v1/projects/\{projectId}/quotas Config and progress together, plus derived `filled`, `closed`, and per-group `minProtected`. `version` discriminates the body: `"v2"` is the document above; `"v1"` is a legacy `{ criteria, progress, totalTarget, status }` shape still returned for projects that have not been moved. ## External fills PUT /v1/projects/\{projectId}/quotas/external-fills Bare array of `{ groupId, segmentId, count }`. Absolute counts, applied atomically. ## Delete DELETE /v1/projects/\{projectId}/quotas Returns `204`. The next GET is the empty V2 configuration. Assignments on existing responses are not released by delete. ## References Example of how quotas are created via the Respondent Researcher Platform Example of how fill progress is presented in the Respondent Researcher Platform # NDAs Source: https://developers.respondent.io/docs/Projects/ndas Require participants to sign an NDA before applying # How do NDAs work on Respondent? When an NDA is added to a project the participant must first view the document and express their agreement or disagreements to the document before submitting their application to a study. * Participants who do not agree to an NDA are still delivered as available screener responses, but ineligible for a "qualified" status. Read more about [project statuses](/docs/Projects/project-statuses). ## Self hosted NDAs Self hosted NDAs allow for a URL to be passed to Respondent via the `ndaLink` field in project creation (POST: /v1/projects) * This field is commonly made available for end user researchers to provide a link to their preferred document handler * It can also be used to pass an internal link if you provide your own document hosting functionality, once the researcher uploads an NDA file to you, the corresponding public link can be made available via `ndaLink`. *** # Create a project Source: https://developers.respondent.io/docs/Projects/project-creation-json Putting project requirements to use # Defining a Project The following fields represent the most basic necessities a researcher will need to define a project. There are many additional dependencies and conditional values that will be covered in the [Conditionals page](/docs/Guides/conditional-and-dependency-reference). | Feature name | Filed name | Use case | | - | - | - | | Internal name | `publicInternalName` | This allows researchers to apply a project name unseen by participants - often used for internal tracking | | Project title | `publicTitle` | A project name will catch participants' attention | | Project description | `publicDescription` | Additional information about what will be required of the participant or why the study is being run | | Time required | `participantTimeRequiredMinutes` | This is the amount of time a researcher needs from a participant | | Incentive | `incentiveAmount` | The amount of money in US dollars being paid to the participant for joining the study. Minimum 5, maximum 1000. Zero is not accepted. | | Type of research | `typeOfResearch` | This field determines whether research is remote or in-person | | Research methodology | `targetResearchMethodology` | This allows the researcher to select the methodology of their study - this will enable different features based on the selection. [Learn more about research method types ↗️.](https://help.respondent.io/en/articles/5489302-research-method-and-types-of-research-projects) | | Target audience | `targetMarketType` | Allows targeting of business professionals (B2B) or consumers (B2C) | | NDA | `ndaLink` | Allows researcher to link to a document handler that will be presented to the participant during application to a study | | Researcher | `externalResearcher` | Allows Respondent to understand who is communicating with a participant | We also collect an `externalResearcher` object to aid in features like messaging, past participation tracking, and more. | Field name | Use case | Required | | - | - | - | | `researcherId` | Participant and researcher reference | Y | | `researcherName` | Show researcher name to participant in chat | N | | `teamId` | Supplemental information for better matching | N | | `companyName` | Supplemental information for better matching | N | | `companyId` | Supplemental information for better matching | N | | `bookingUrl` | Direct a participant to your booking page | N | # Targeting an Audience Researchers want to target a specific group of people to eventually take their screener. This first layer of targeting material is what we use to begin our matching process of bringing highly qualified participants to research projects. | Feature name | Field name | Required (Y/N) | | - | - | - | | Number of participants | `targetNumberOfParticipants` | Y | | Target audience | `targetMarketType` | Y | | Industries (B2B) | `targetProfessionalIndustries` | Y for B2B | | Topics (B2C) | `targetProjectTopics` | Y for B2C | | External researcher | `externalResearcher` | Y | | Location | `targetCountries`, `targetCities` | N | | Household income | `targetHouseholdIncome` | N | | Gender | `targetGenders` | N | | Education | `targetEducation` | N | | Ethnicity | `targetEthnicities` | N | | Age range | `targetAgeGroups` | N | | Custom age range | `targetMinAge`, `targetMaxAge` | N | Conditionally available fields are not shown here. Read through Conditions, linked below, for more information. # Project creation POST: /v1/projects B2B and B2C projects differ in requirements and have different configurations. See Conditionals page linked below. ```json B2B Project theme={null} { "publicTitle": "Seeking business professionals!", "publicInternalName": "B2B Project Example", "publicDescription": "We're looking for skilled business professionals in a particular industry.", "typeOfResearch": "remote", "targetResearchMethodology": "oneOnOne", "participantTimeRequiredMinutes": 30, "incentiveAmount": 80, "targetNumberOfParticipants": 5, "targetMarketType": "b2b", "targetProfessionalIndustries": [ "INDUSTRY_ID_1", "INDUSTRY_ID_2" ], "targetJobTitles": [ "JOB_TITLE_ID_1", "JOB_TITLE_ID_1" ], "targetAudienceSkills": [ "SKILL_ID_1", "SKILL_ID_2", "SKILL_ID_3" ], "targetCountries": ["all"], "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ```json B2C Project theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "oneOnOne", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountries": ["all"], "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` Once you are able to create projects successfully you should be able to view the projects by logging into Respondent’s [staging](https://website.staging.respondent.io/login) dashboard with your credentials: # Project deep dive Source: https://developers.respondent.io/docs/Projects/project-deep-dive Creating more complex projects # B2B vs B2C POST: /v1/projects ```json B2B theme={null} { "publicTitle": "Seeking business professionals!", "publicInternalName": "B2B Project Example", "publicDescription": "We're looking for skilled business professionals in a particular industry.", "typeOfResearch": "remote", "targetResearchMethodology": "oneOnOne", "participantTimeRequiredMinutes": 30, "incentiveAmount": 80, "targetNumberOfParticipants": 5, "targetMarketType": "b2b", "targetProfessionalIndustries": [ "INDUSTRY_ID_1", "INDUSTRY_ID_2" ], "targetJobTitles": [ "JOB_TITLE_ID_1", "JOB_TITLE_ID_1" ], "targetJobFunctions": [ "JOB_FUNCTION_STRING_1", "JOB_FUNCTION_STRING_2" ], "targetAudienceSkills": [ "SKILL_ID_1", "SKILL_ID_2", "SKILL_ID_3" ], "targetCountry": "all", "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ```json B2C theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "oneOnOne", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "all", "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ## Required fields for B2B vs B2C projects Please see [Targeting audiences effectively](/docs/Projects/targeting-audiences-effectively) for a full list of optional and required fields for B2B and B2C projects. # Remote vs in person POST: /v1/projects ```json Remote theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "oneOnOne", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "targetCities": [ { "location": { "country": "US", "location": { "longitude": 42.22788, "latitude": -71.191113 }, "city": "Boston", "state": "MA", "googleId": "ChIJGzE9DS1l44kRoOhiASS_fHg" } } ], "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ```json In Person theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "inPerson", "targetResearchMethodology": "oneOnOne", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "targetLocationRespondentInPerson": { "country": "US", "address1": "700 Atlantic Avenue", "city": "Boston", "latitude": 42.351005, "longitude": -71.05662, "state": "MA", "formattedAddress": "700 Atlantic Avenue, Boston, Ma 02205, USA" }, "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ## Remote When `remote` is selected for `typeOfResearch` a project will recruit participants world wide by default. `targetCountries` can be added to optionally target one or more countries * If a country is targeted, one or more cities can also be targeted in that country via `targetCities`. * Participants will be recruited within 250km (155 miles) for remote projects selecting a city. We recommend using [Google Place Autocomplete](https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete) to supply the required information for city targeting. We expect the a post including city targeting to include something similar to the code block below. It is important to note that cities cannot be added targeting worldwide, and only cities in targeted countries can be added. ```Text targetCities theme={null} targetCities: [ { location: { city: 'Seattle', state: 'WA', country: 'US', location: { longitude: -122.3328481, latitude: 47.6061389, }, }, }, { location: { city: 'Portland', state: 'OR', country: 'US', location: { longitude: -122.6783853, latitude: 45.515232, }, }, }, ], ``` #### Methodologies When running remote projects the following `targetResearchMethodology` are valid. * One on One * Focus Group * Unmoderated Study * Survey * Diary Study ## In person When `inperson` is selected for `typeOfResearch` a project must supply `targetCountry` and `targetLocationRespondentInPerson` fields. * Participants will be recruited within 100km (62 miles) for in person projects. We recommend using [Google Place Autocomplete](https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete) to supply the required information for city targeting. #### Methodologies When running in person projects the following `targetResearchMethodology` are valid. * One on One * Focus Group * In Respondent Home * In Respondent Office # Moderated vs unmoderated methodologies POST: /v1/projects ```json oneOnOne theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "oneOnOne", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ```json focusGroup theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "focusGroup", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ```json unmoderatedStudy theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "unmoderatedStudy", "unmoderatedStudyOrSurvey": { "remoteCommunicationDetails": "https://www.myunmoderatedstudylink.com", "specialInstructions": "Please open the link to walk through the same UI and give your feedback!" }, "participantTimeRequiredMinutes": 20, "incentiveAmount": 40, "targetNumberOfParticipants": 10, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", } } ``` ```json survey theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "survey", "unmoderatedStudyOrSurvey": { "remoteCommunicationDetails": "https://www.mysurveylink.com", "specialInstructions": "Please fill out this survey!" }, "participantTimeRequiredMinutes": 10, "incentiveAmount": 20, "targetNumberOfParticipants": 10, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", } } ``` ```json diaryStudy theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "remote", "targetResearchMethodology": "diaryStudy", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ```json inRespondentHome theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "inPerson", "targetResearchMethodology": "inRespondentHome", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "targetLocationRespondentInPerson": { "country": "US", "address1": "700 Atlantic Avenue", "city": "Boston", "latitude": 42.351005, "longitude": -71.05662, "state": "MA", "formattedAddress": "700 Atlantic Avenue, Boston, Ma 02205, USA" }, "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ```json inRespondentOffice theme={null} { "publicTitle": "Seeking savvy consumers!", "publicInternalName": "B2C Project Example", "publicDescription": "We're seeking people who own this product and want to share their opinions.", "typeOfResearch": "inPerson", "targetResearchMethodology": "inRespondentOffice", "participantTimeRequiredMinutes": 30, "incentiveAmount": 60, "targetNumberOfParticipants": 5, "targetMarketType": "b2c", "targetProjectTopics": [ "TOPIC_ID_1", "TOPIC_ID_2" ], "targetCountry": "US", "targetLocationRespondentInPerson": { "country": "US", "address1": "700 Atlantic Avenue", "city": "Boston", "latitude": 42.351005, "longitude": -71.05662, "state": "MA", "formattedAddress": "700 Atlantic Avenue, Boston, Ma 02205, USA" }, "externalResearcher": { "researcherId": "PARTNER_SUPPLIED_ID", "researcherName": "Jane Doe", "bookingUrl": "https://bookingurl.com" } } ``` ## Moderated Methodologies that are considered moderated must use the `bookingUrl` field in the `externalResearcher` object to pass back a URL where the participant can book a meeting time with the researcher. * oneOnOne * focusGroup * inRespondentHome * inRespondentOffice * diaryStudy ## Unmoderated Methodologies that are considered unmoderated must use a `unmoderatedStudyOrSurvey` object to pass through a URL (`remoteCommunicationDetails`) and instructions (`specialInstructions`) for participants. * survey * unmoderatedStudy ### remoteCommunicationDetails URLs that are places in this field will be appended with URL parameters on redirect to allow partners to collect participant, project, and screener response information. These fields can help confirm which participant relates to a particular survey response. Please see the [URL Parameters guide](/docs/Screener-responses/URL-Parameters-for-Project-Link) for more information. ### completionUrl Available for projects where `targetResearchMethodology` is `unmoderatedStudy` or `survey`. The `completionUrl` is the link a researcher redirects participants to at the end of the survey or task in order for Respondent to record task completion. * When a participant accesses the `completionUrl`, the screener response's `task.completedAt` timestamp is set automatically. * Task completion does **not** trigger payout at that moment. Use [Mark as Attended](/screener-responses/mark-as-attended) to mark the participant as complete and kick off the incentive payout, or [Mark as Rejected](/screener-responses/mark-as-rejected) if the response did not meet quality criteria. * It does, however, start a **24-hour clock**: if you take no action, Respondent marks the response `ATTENDED` automatically and autopay fires. Reject inside that window if you want to withhold the incentive — see [your review window](/docs/Screener-responses/completion-url#your-review-window-is-24-hours). * The `completionUrl` is appended with the same identifying URL parameters as `remoteCommunicationDetails` — see the [URL Parameters guide](/docs/Screener-responses/URL-Parameters-for-Project-Link) for the full list and examples. See [Completion URL](/docs/Screener-responses/completion-url) for more details on completion URLs and how they interacts with [Mark as attended](/screener-responses/mark-as-attended). *** # Project statuses Source: https://developers.respondent.io/docs/Projects/project-statuses Manage project lifecycle from draft to close # Managing project status Each project status PATCH endpoint updates two key fields that control what a project is doing: * **projectStatus**: `DRAFT`, `PAID`, `ARCHIVED`, `CLOSED` * **projectRecruitingStatus**: `RECRUITING`, `RECRUITED`, `PAUSED` ## Status transitions | Action | Endpoint | projectStatus | projectRecruitingStatus | Requirements | | - | - | - | - | - | | **Publish** | [PATCH .../publish](/projects/publish-a-project) | `PAID` | `RECRUITING` | Must be `DRAFT`, must have 2+ screener questions, and its [quota](/docs/Projects/audience-quotas), if any, must be valid | | **Pause** | [PATCH .../pause](/projects/pause-a-project) | `PAID` | `PAUSED` | Must currently be `PAID` | | **Close** | [PATCH .../close](/projects/close-a-project) | `CLOSED` | `RECRUITED` | No unpaid participants | There is no dedicated "stop" endpoint. To stop sourcing new applicants temporarily, pause the project. To end recruitment permanently, close the project. ### Publish details When published, the following fields are set automatically: * `publishedAt` = current time * `expireAt` = 1 month from now ### Project expiry `expireAt` is the point at which Respondent stops recruiting on the project on its own. Approaching that date, two emails go to the researcher account: an **Auto-Pause Warning** on day 27, and **Project Recruiting Stopped** if the project reaches day 30 without being paused (see [Automated researcher emails](/docs/Guides/automated-emails)). At day 28 the project auto-pauses and after day 30 no new applicants are sourced. Expiry stops sourcing; it does not close the project or lock participant actions. On an expired project you can still invite qualified applicants who were already sourced, schedule them, mark them attended or no-show, pay them, and close the project — the same participant endpoints continue to work. There is no `expireAt` extension endpoint. To keep recruiting past 30 days, contact partner support. Although in most cases we recommend that you close the current project and create a new one with the same targeting instead of extending. ### Pause details A project's `projectRecruitingStatus` becomes `PAUSED` in two ways: * **Manually**, via [PATCH .../pause](/projects/pause-a-project). * **Automatically**, When enough qualified participants to meet the project's `targetNumberOfParticipants` have applied to the project. Our recruiting algorithm dynamically sources up to 3x the target to ensure you can reach your target even if some participants do not complete the study or provide poor quality answers and need to be replaced While a project is `PAUSED`: * **Sourcing stops.** New applicants can no longer apply through matching, and matching/recruitment emails are not sent. * **Auto-invites keep flowing.** Applicants who were already qualified before the pause continue to receive invitations on their normal `automations.invite` schedule, up to the project's invite cap (or completes cap for invite-to-complete flows). Participants who have already been invited can still schedule, attend, and be paid. In other words, `PAUSED` means *"stop sourcing new applicants"* — not *"stop inviting."* This matters most for invite-to-complete integrations, where completion volume, not invite volume, determines when recruitment is finished; the project will keep auto-inviting qualified applicants (subject to eligibility and quota checks) even while paused. To resume sourcing after an auto-pause, unpause the project with [PATCH .../pause](/projects/pause-a-project) (the same endpoint toggles the status). **There is no separate response-count limit for resuming.** Unpausing is available on any `PAID` project. The only caps that govern recruitment are `targetNumberOfParticipants` itself (min 1, max 500) and the 3x sourcing buffer above it. Disqualifying a participant via [Qualify participant](/screener-responses/qualify-participant) frees their spot, and Respondent automatically backfills disqualified participants. ### Close details Closing is permanent. Once a project is closed, you cannot contact participants or change any participant's status. Pay and resolve all participants before you close. When closed, the following fields are populated: * `closedDetails.closed` = `true` * `closedDetails.closedMessage` = message passed in the request * `closedDetails.closedByUserId` = current user ID * `closedDetails.closedOnDate` = current time If your project has screener responses in `SCHEDULED` or `ATTENDED` status, you will receive a **400 error** with the message "Project has unpaid participants and cannot be closed". Ensure all participants are paid before closing. ## Editing a project after publish `PATCH /v1/projects/{projectId}` accepts different fields depending on the project's current status. Once a project is live (`projectStatus: PAID`), it is treated as a published project and only a limited set of fields can be modified. ### Fields you can update on a live (published) project * `publicTitle` * `publicInternalName` * `publicDescription` * `participantTimeRequiredMinutes` * `incentiveAmount` * `ndaDetails` * `externalResearcher` * `externalScreenerLink` (external-screener organizations only; see [External screeners](/docs/Screener-responses/external-screeners#changing-the-screener-after-publish)) * `unmoderatedStudyOrSurvey.deadline` (Unmoderated Study and Survey projects only; the new deadline also applies to participants with an open invitation) ### Fields that cannot be changed on a live project Targeting, methodology, and recruitment-shape fields are locked once a project is published. This includes (but is not limited to): * `targetNumberOfParticipants` * `targetResearchMethodology`, `typeOfResearch` * `targetMarketType`, `targetCountry`, `targetCities`, `targetLocationRespondentInPerson` * `targetProjectTopics`, `targetProfessionalIndustries`, `targetJobTitles`, `targetJobFunctions`, `targetAudienceSkills` * `automations` (including `autoinvite` and `autopay`) If you need to change any of these on a live project, close or archive the project and create a new one. Screener questions can still be edited after publish through the screener question endpoints, including qualifying and skip logic. Editing effectively replaces the screener: responses collected before the edit remain qualified against the original version, and only new responses are evaluated against the update. See [Creating questions](/docs/Screener-responses/screener-creation) for the caveats. See [Update a project](/projects/update-a-project) for the full request reference. # Projects overview Source: https://developers.respondent.io/docs/Projects/projects-overview Recruiting the participants your researchers need # What is a project? A project is essentially the request to Respondent to recruit participants. It allows a researcher to define targeting for which participants they are looking for and apply some basic screening questions to filter our the broad matches. At its most basic a project will include: 1. A name and description 2. A target population of consumers or business professionals 3. A research methodology 4. A location and other personal attributes 5. Screener questions with qualifying logic to narrow down the perfect fit For the purposes of documentation and API organization, screeners will be covered in their own section, but are very much a part of a project. ## API endpoints Set up a new B2B or B2C research project. Make a draft project live and start recruiting. # Targeting audiences effectively Source: https://developers.respondent.io/docs/Projects/targeting-audiences-effectively Configure filters and signals to reach the right participants Need to target lifestyle, professional, technology, or product attributes — languages, employment, devices, software used, and more? See [Advanced targeting attributes](/docs/Projects/advanced-targeting-attributes). ## Determining how many participants to target `targetNumberOfParticipants` sets how many participants a researcher plans to interview. Respondent attempts to deliver 3× `targetNumberOfParticipants` qualified applicants; once that target is reached, recruitment is paused and new applicants are no longer sourced — though participants with your direct invite link can still apply. We also do not restrict inviting or paying more participants if a researcher's needs grow during the course of research. ## Audience features Audience features have 2 different implementations to ensure projects can be filled appropriately. Some features are used as filters while others are used as signals. Most features are available to both B2C and B2B projects — see [B2C-only and B2B-only features](#b2c-only-and-b2b-only-features) for the exceptions. **Filters** Filter-type audience features restrict who can apply — a participant can only apply if their profile matches the selected feature. Filters give you the most accurate, on-target sample, so reach for a filter whenever a criterion genuinely needs to be met. **Signals** Signal-type audience features influence which participants Respondent contacts via a fuzzy match, but **do not stop off-target participants from applying**. Exact-match participants are still the first we contact, before recruiting expands to fuzzier matches over time until your project is filled. Promote a signal to a hard filter by adding its key to your project's `keyQualifiers`. The one exception is `targetJobTitles`, which is handled differently from both filters and signals — see [Job title targeting](#job-title-targeting). **We strongly recommend making an attribute a hard filter whenever it genuinely needs to be met** — add its key to [`keyQualifiers`](/docs/Guides/b2b-participant-qualification-and-filters). Leave it as a signal only when you want a soft preference and broader reach. The table below covers every audience feature and whether it acts as a **filter** or a **signal** by default. The **Personal** through **Health** groups are advanced attributes set via the `advancedTargeting` object, shown in the same groups as the researcher dashboard — see [Advanced targeting attributes](/docs/Projects/advanced-targeting-attributes) for the allowed values on each. | Feature | Filter | Signal | | - | - | - | | **Standard audience** | | | | targetCountries | ✅ | | | targetCities (used with countries only) | ✅ | | | targetLocationRespondentInPerson (in person project only) | ✅ | | | targetGenders | ✅ | | | targetAgeGroups | ✅ | | | targetMinAge, targetMaxAge (see [Age targeting](#age-targeting)) | ✅ | | | targetEthnicities | ✅ | | | filterPastParticipation | ✅ | | | targetEducation | ✅ | | | targetHouseholdIncome | | ✅ | | targetProjectTopics | | ✅ | | targetProfessionalIndustries | | ✅ | | targetCompanySize | | ✅ | | seniority | ✅ | | | targetJobFunctions | ✅ | | | targetJobTitles (special — see [Job title targeting](#job-title-targeting)) | | | | targetAudienceSkills | | ✅ | | **Personal** | | | | relationshipStatus | | ✅ | | languages | | ✅ | | numberOfChildren | | ✅ | | numberOfChildrenLiveAtHome | | ✅ | | childrenBirthYears | | ✅ | | livingSituation | | ✅ | | pets | | ✅ | | homeOwner | | ✅ | | nationality | | ✅ | | areaType | ✅ | | | onlineShoppingFrequency | | ✅ | | militaryServiceStatus | | ✅ | | **Professional** | | | | workSetting | ✅ | | | employmentStatuses | | ✅ | | cloudPlatforms | | ✅ | | codingAITools | | ✅ | | designPrototypingTools | | ✅ | | productivitySoftware | | ✅ | | **Technology & Devices** | | | | browsers | ✅ | | | computerOS | ✅ | | | programmingLanguages | ✅ | | | tabletOS | ✅ | | | smartphoneOS | ✅ | | | webcam | ✅ | | | mobilePhoneWithInternet | ✅ | | | headsetWithMicrophone | ✅ | | | **Products & Services** | | | | streamingServices | | ✅ | | foodDeliveryServices | | ✅ | | onlineBankingTools | | ✅ | | onlineLearningPlatforms | | ✅ | | onlineMarketplacePlatforms | | ✅ | | onlineTravelBooking | | ✅ | | smartHomeDevices | | ✅ | | socialNetworkPlatforms | | ✅ | | vehicleBrands | | ✅ | | **Health** | | | | healthWellnessApps | | ✅ | ## Age targeting You can target participants by age in one of two ways. Both act as a filter. * **Fixed age groups:** set `targetAgeGroups` to one or more of these enum values: `"18-20"`, `"21-29"`, `"30-39"`, `"40-54"`, `"55+"`, `"N/A"`. Use `"N/A"` for no age restriction. * **Custom age range:** set `targetMinAge` and/or `targetMaxAge` to integers between `18` and `55`. Both bounds are inclusive. Use this when the fixed groups don't fit your study, for example participants aged 25 to 34. Both options are available on [Create Project](/docs/Projects/project-creation-json) and Update draft project, for B2B and B2C projects. Project responses return `targetMinAge` and `targetMaxAge` alongside `targetAgeGroups`. ```json theme={null} { "targetMinAge": 25, "targetMaxAge": 34 } ``` You can send only one bound to leave the range open-ended. For example, `"targetMinAge": 40` with no `targetMaxAge` matches participants aged 40 and older. Setting `targetMaxAge` to `55` excludes participants older than 55. To include them, omit `targetMaxAge`. Respondent validates age targeting with these rules: * `targetMinAge`/`targetMaxAge` and `targetAgeGroups` are mutually exclusive. Sending both in the same request returns a `400` error: `Use either minAge/maxAge or ageGroups, not both`. * `targetMinAge` must be less than or equal to `targetMaxAge`. Otherwise the request returns a `400` error: `minAge must be less than or equal to maxAge`. On Update draft project, Respondent checks the bound you send against the bound already saved on the project. * Values outside `18`–`55`, or non-integer values, return a `400` error. On Update draft project, switching between the two options replaces the previous one. Setting `targetMinAge` or `targetMaxAge` clears `targetAgeGroups`, and setting `targetAgeGroups` clears both numeric bounds. To remove a single bound, send it as `null`. The API does not expose a participant's precise age. If you need exact age data, ask for it via a screener question. ## Job title targeting `targetJobTitles` differs from other attributes because job title data is often ambiguous, leading to variations and differing interpretations across companies. A strict filter may disqualify many qualified participants, while a pure signal could allow too many off-target applicants. Because of this special handling: * `targetJobTitles` is **not** promotable through `keyQualifiers` — there is no way to restrict it to only the exact titles you provided. If you need to narrow further, use a screener question to confirm role specifics. * To restrict by broader role category instead of by title, use `targetJobFunctions` (a hard filter) or `seniority` (a hard filter). * Listing more representative titles helps Respondent infer the right job family. If the titles you list span multiple families, participants from any of those families may match. ## B2C-only and B2B-only features Most audience features — including every [advancedTargeting attribute](/docs/Projects/advanced-targeting-attributes) — are available to **both** B2C and B2B projects. Only these are specific to one audience: **B2C only** * `targetProjectTopics` — required for B2C. Pass a minimum of 1 and a maximum of 2 topic IDs. Topic IDs differ between staging and production. **B2B only** * `targetProfessionalIndustries` — required for B2B. Pass at least 1 industry ID. There is no maximum as of the [2026-06-08 changelog](/docs/Changelog/2026-06-08). * `targetCompanySize` * `seniority` * `targetJobFunctions` * `targetJobTitles` — maximum 30 entries * `targetAudienceSkills` — maximum 25 entries ## Filter past participants The `filterPastParticipation` field allows you to control whether participants who have previously participated in your research can apply to new projects. This is a filter-type audience feature that restricts participant access based on their participation history. ### How it works When `filterPastParticipation` is set, the system checks if a participant has previously been paid for research conducted by the same organisation or based on the `externalResearcher` object for API. ### Using filterPastParticipation via API Include the `filterPastParticipation` field in your [project creation request](/projects/create-a-project): ```json theme={null} { "publicTitle": "New Product Feedback Study", "targetMarketType": "b2c", "filterPastParticipation": { "hasNeverParticipated": true, "months": "3"} "externalResearcher": { "researcherId": "researcher_123", "teamId": "team_456", "companyId": "company_789" }, // ... other project fields } ``` ### Using filterPastParticipation on the DIY platform When creating a project through the Respondent platform: 1. Navigate to the project creation flow 2. In the **Audience Targeting** section, locate the **Past Participation** filter 3. Select your preference: * **Has never participated in a project** - Only new participants can apply * **Has never participated in a project in the last X months** - Only new participants can apply within X months * **N/A** - No restriction Past participation is tracked at the payment level. A participant is only considered a "past participant" if they were marked as attended and paid for a previous project associated with the organisation. ### Working with your participant database If your organisation has the participant database feature enabled, you can also: * Search past participants using the [Search past participants](/team-respondents/search-past-participants) endpoint * Retrieve specific participant profiles with [Retrieve past participant profile](/team-respondents/retrieve-past-participant-profile) * Invite multiple past participants directly using [Invite multiple past participants](/team-respondents/invite-multiple-past-participants) ## Audience qualification Screener responses are qualified across the participant's profile and their actual responses to screener questions to be determined qualified or not. Answering all screener questions with qualifying answers makes them qualified on a screener, but profile qualification can be achieve by matching all filter type audiences features. Respondent will treat participants that match all audience features with preferential treatment when actively recruiting, but will qualify participants that are not direct matches for signal type audience features. **Example** If a participants qualified via the screener AND they match selected `targetAgeGroups` and `targetCountries` but not `targetHouseholdIncome`, their response will be considered qualified by default. Partners or researchers can choose to disqualify these responses to narrow the number of participants and keep recruiting open. *** # URL Parameters for Project Link Source: https://developers.respondent.io/docs/Screener-responses/URL-Parameters-for-Project-Link Identify participants arriving at your project or scheduling tool When a participant is invited and clicks through on the scheduling or project link that you provide, Respondent will append multiple URL parameters to that link so that you can associate the participant arriving to your project or scheduling tool with the participant that you invited without having to request their email address or other PII. As a reminder, requesting PII of participants sourced through the Respondent API is in violation of our terms of service. We recommend that you extract all parameters as they are used in different API endpoints. Those parameters include: * `respondent_profile_id` -- set to the `profileId` provided in the [GET Screener Response](/screener-responses/list-screener-responses) response and used in the [GET Profiles](/profiles/retrieve-participant-profile) endpoint call and other endpoints that require profileId * `respondent_user_id` -- set to the `userId` provided in the [GET Screener Response](/screener-responses/list-screener-responses) response and used in the Messaging API endpoints if needed * `respondent_project_id` -- set to the `id` provided in the [POST Projects Response](/projects/create-a-project) and used throughout the API to update and status the project, get and status screener responses, and in multiple other endpoints * `respondent_screener_response_id` -- set to the `id` provided in the [GET Screener Response](/screener-responses/list-screener-responses) endpoints and used throughout the API to get and status screener responses In some cases, we will also append duplicate parameters `participant_id` and `project_id`. These are equivalent to `respondent_user_id` and `respondent_project_id` respectively but may not always be present in the link when the participant arrives at your project or scheduling tool. All four parameters (`respondent_profile_id`, `respondent_user_id`, `respondent_project_id`, and `respondent_screener_response_id`) are always appended together — you cannot configure Respondent to send only a subset. If you only need one identifier (for example, a participant ID), extract the parameter you care about (typically `respondent_user_id` or `respondent_profile_id`) and ignore the rest. Your own platform's parameter names (such as `rid`, `pid`, or `participant_id` in your URL) are not recognized or remapped by Respondent — they are treated as opaque query string and preserved as‑is. If you have added your own URL parameters into the project or scheduling link, we will simply append the above parameters to the end of that URL. Respondent detects whether your link already contains a `?` and joins with `&` accordingly, so pre-existing parameters like `?src=respondent` are preserved. Example with a pre-existing parameter: * Project link: `https://www.researchplatform.com/123456?src=respondent` * URL when participant arrives: `https://www.researchplatform.com/123456?src=respondent&respondent_profile_id=123456789abcdefghij&respondent_project_id=987654321jihgfedcba&respondent_screener_response_id=abcdefghij123456789&respondent_user_id=jihgfedcba987654321` For unmoderated studies and surveys, Respondent also generates a separate **`completionUrl`** that you can redirect participants to at the end of the task to mark it as complete. The `cc` parameter on that URL is unrelated to the parameters above. See [Completion URL](/docs/Screener-responses/completion-url) for details. Examples: * Project link: [https://www.researchplatform.com/123456](https://www.researchplatform.com/123456) * URL when participant arrives at your project: [https://www.researchplatform.com/123456?respondent\\\_profile\\\_id=123456789abcdefghij\\\&respondent\\\_project\\\_id=987654321jihgfedcba\\\&respondent\\\_screener\\\_response\\\_id=abcdefghij123456789\\\&respondent\\\_user\\\_id=jihgfedcba987654321](https://www.researchplatform.com/123456?respondent\\_profile\\_id=123456789abcdefghij\\\&respondent\\_project\\_id=987654321jihgfedcba\\\&respondent\\_screener\\_response\\_id=abcdefghij123456789\\\&respondent\\_user\\_id=jihgfedcba987654321) # Collecting responses Source: https://developers.respondent.io/docs/Screener-responses/collecting-responses This will outline how to surface participants to your researchers # Response formatting When collecting screener responses there may be one or multiple responses but generally the content of each response has a predictable set of information returned ## Basic Screener response GET: /v1/projects/\{projectId}/screener-responses The example below shows a `screener-questions` POST and subsequent `screener-responses` GET. The response itself has information relating to the answered questions themselves as well as the participant. Most of the participant information can be found via a second call using the profile ID. ## Answer shapes by question type Each item in `responses[]` has an `answers` field that is **always an array**. The type of the array's elements depends on `questionType`: | `questionType` | `answers` contains | Example | | - | - | - | | `radio` | A single answer object | `[{ "id": "…", "uid": "…", "text": "West Coast", "answerValue": 1 }]` | | `checkbox` | One answer object per selected option | `[{ …option 1… }, { …option 2… }]` | | `singleLineTextbox` | A single string with the participant's raw input | `["Green"]` | | `multiLineTextbox` | A single string with the participant's raw input | `["My favorite hobby is riding motorcycles."]` | | `numericBox` | A single string (numeric input is **not** converted to a number) | `["36"]` | | `sliderScale` | A single string with the selected value | `["9"]` | | `rank` | Empty — see the `ranking` field instead | `[]` | | `matrix` | Empty — see the `selections` field instead | `[]` | | `fileUpload` | Empty — see the `files` field instead | `[]` | Responses recorded before 2024 may contain bare numbers instead of strings for `numericBox`/`sliderScale` answers. Parsers should accept both. `fileUpload` answers carry a `files[]` array instead. Each file has `name`, `size`, `mime`, `uploadedAt`, `scanStatus` (`pending`, `clean`, `infected`, `unscanned`) and, only once `scanStatus` is `clean`, a temporary download `url`. If `scanStatus` is `pending`, refetch the response later. When displaying the responses section of the screener-response it is recommended to share: * Time the participant took to complete the screener * `"totalTime":` * The incentive the participant took the screener at * `"project.incentive":` * Time screener was completed * `"createdAt":` * Question, response, and qualify status * `"questionText":` * `"text":` * `"qualify":` ```json screener-questions POST theme={null} "projectID": "PROJECT_ID" { "questionType": "radio", "text": "What is your favorite coastline in the United States?", "answers": [ { "text": "East Coast", "answerValue": 2 }, { "text": "West Coast", "answerValue": 1 }, { "isOther": true, "text": "Other", "answerValue": 2 } ], "skipLogic": false, "includeOtherOption": true, "isRequired": true }, { "questionType": "checkbox", "text": "What animals would you like to spend time with?", "answers": [ { "text": "Cat", "answerValue": 1 }, { "text": "Dog", "answerValue": 1 }, { "text": "Giraffe", "answerValue": 1 }, { "text": "Pineapple", "answerValue": 3 } ], "isRequired": true, "skipLogic": false, "includeOtherOption": false }, { "questionType": "multiLineTextbox", "text": "In your own words, tell us what your favorite hobby is.", "skipLogic": false, "isRequired": true }, { "questionType": "singleLineTextbox", "text": "What is your favorite color?", "skipLogic": false, "isRequired": true }, { "questionType": "numericBox", "text": "How old are you?", "skipLogic": false, "isRequired": true }, { "questionType": "sliderScale", "text": "On a scale from 1 to 10 how confident are you building an API?", "isRequired": true, "skipLogic": false, "minValue": 1, "maxValue": 10, "step": 1 } ``` ```json screener-responses GET theme={null} { "id": "RESPONSE_ID", "projectId": "PROJECT_ID", "qualified": true, "status": "PENDING", "favorite": false, "hidden": false, "responses": [ { "questionId": "6526e85f08fa017ec030ec93", "questionText": "What is your favorite coastline in the United States?", "order": 0, "type": "screener", "questionType": "radio", "answers": [ { "id": "6526e85f08fa017ec030ec94", "uid": "b80c8be4-5abc-45c0-a92b-24552095ac9d", "text": "West Coast", "answerValue": 1, "goToQuestionUid": null } ], "qualify": true }, { "questionId": "6526e85f08fa017ec030ec96", "questionText": "What animals would you like to spend time with?", "order": 1, "type": "screener", "questionType": "checkbox", "qualify": true, "answers": [ { "id": "6526e85f08fa017ec030ec98", "uid": "c84ba5eb-a487-43b0-8196-aa5f538f4bc9", "text": "Pineapple", "answerValue": 3, "goToQuestionUid": null, "checked": true } ] }, { "questionId": "6526e85f08fa017ec030ec9a", "questionText": "In your own words, tell us what your favorite hobby is.", "order": 2, "type": "screener", "questionType": "multiLineTextbox", "answers": ["My favorite hobby is riding motorcycles."], "qualify": true }, { "questionId": "6526e85f08fa017ec030ec9b", "questionText": "What is your favorite color?", "order": 3, "type": "screener", "questionType": "singleLineTextbox", "answers": ["Green"], "qualify": true }, { "questionId": "6526e85f08fa017ec030ec9c", "questionText": "How old are you?", "order": 4, "type": "screener", "questionType": "numericBox", "answers": ["36"], "qualify": true }, { "questionId": "6526e85f08fa017ec030ec9d", "questionText": "On a scale from 1 to 10 how confident are you building an API?", "order": 5, "type": "screener", "questionType": "sliderScale", "answers": ["9"], "qualify": true } ], "totalTime": 33, "isNew": false, "visibility": { "name": "VISIBLE", "status": "VISIBLE", "code": "OK" }, "statusUpdatedAt": null, "project": { "id": "PROJECT_ID", "name": "Test Screener Answer Values", "incentive": 125, "organizationId": "ORG_ID", "teamId": "TEAM_ID" }, "profile": { "id": "PROFILE_ID", "firstName": "Bob", "lastName": "H", "skills": [ { "createdAt": "2023-10-11T18:27:24.260Z", "validated": true, "type": "certification", "count": 0, "slug": "typescript", "id": "SKILL_ID", "name": "TypeScript" } ] } } ``` ## Viewing the profile /v1/profiles/\{profileId} ```json json theme={null} { "userId": "string", "companySize": "selfemployed", "gender": "male", "educationLevel": "somehighschool", "ethnicity": "americanindianalaskannative", "householdIncome": "<30000", "firstName": "Bob", "lastInitial": "H", "jobTitle": "PM", "jobFunctions": [ "informationTechnology" ], "linkedinUrl": "URL", "skills": [ { "createdAt": "2023-10-11T18:27:24.260Z", "validated": true, "type": "certification", "count": 0, "slug": "typescript", "id": "SKILL_ID", "name": "TypeScript" } ], "isEmailVerified": true, "location": { "country": "US", "location": { "longitude": 42.22788, "latitude": -71.191113 }, "city": "Boston", "state": "MA", }, "age": "string", "socialLinks": {}, "industry": [ "string" ], "seniorityLevel": "senior", "id": "string", "createdAt": "2023-10-11T18:27:24.260Z", "updatedAt": "2023-10-15T13:24:35.260Z" } ``` ## Understanding qualified responses `"qualified": true,` implies: * NDA is agree to if present * 100% percentage on screener questions * Matched on location, age, gender, education, and ethnicity ## The "End Of Survey" sentinel Every screener's question list ends with an auto-generated sentinel entry marked `isEnd: true`: ```json theme={null} { "id": "6864ea79744b87a65743b880", "uid": null, "text": "End Of Survey", "isEnd": true, "answers": [] } ``` You will see it as the **last element** of `screenerQuestions` on the project payload and on the screener-questions endpoints. It is always present and always last, but it is **not a real question**: it has no `questionType`, its `uid` and `order` are `null` or absent, participants never answer it, and it **never appears in screener-response `responses[]`** — every item there corresponds to a question the participant actually answered (or, with the skipped-questions option below, explicitly skipped). Skip the sentinel when rendering or counting questions. `isEnd` is present on **every** screener question — `false` on real questions, `true` on this sentinel — so you can discriminate on it directly rather than treating an absent field as `false`. Note that the sentinel's `answers` field, unlike a real question's, is sometimes absent rather than an empty array, so do not call `.length` on it without checking. Older versions of this API also appended an "End Of Survey" trailer entry to `responses[]` itself (with `null` values for `order` and `answers`), and earlier versions of this guide showed it in the example payload. The current API never does; no special handling is required in response parsing. ## Skipped questions in `responses[]` By default, questions that a participant did not answer (because they were skipped over by skip logic, or because the participant chose to skip an optional question) are omitted from the `responses[]` array. Organizations that opt in to including skipped questions in screener responses (contact Respondent to enable) receive an entry for every screener question, with skipped questions marked explicitly. Each item in `responses[]` then includes: | Field | Type | Description | | - | - | - | | `skipped` | `boolean` | `true` if the participant did not provide an answer to this question. Absent or `false` for answered questions. | | `skippedBy` | `'user' \| 'skipLogic'` | Why the question was skipped. `'user'` means the participant chose to skip an optional question; `'skipLogic'` means skip logic on a previous answer jumped over this question. Present only when `skipped` is `true`. | | `shown` | `boolean` | `true` if the question was displayed to the participant. Equivalent to `!skipped \|\| skippedBy === 'user'` — i.e. answered questions and user-skipped optional questions were shown; skip-logic-skipped questions were not. | Example skipped entries: ```json theme={null} { "questionId": "6526e85f08fa017ec030ec9b", "questionText": "What is your favorite color?", "order": 3, "type": "screener", "questionType": "singleLineTextbox", "answers": [], "qualify": true, "skipped": true, "skippedBy": "user", "shown": true }, { "questionId": "6526e85f08fa017ec030ec9c", "questionText": "How old are you?", "order": 4, "type": "screener", "questionType": "numericBox", "answers": [], "qualify": true, "skipped": true, "skippedBy": "skipLogic", "shown": false } ``` Skipped entries still carry `answers`, as an empty array — the field is present on every item in `responses[]`. *** # Completion URL Source: https://developers.respondent.io/docs/Screener-responses/completion-url Track task completion for unmoderated studies and surveys without triggering payment For unmoderated studies and surveys, researchers usually point participants to an external survey, prototype, or task. The **`completionUrl`** is how you get an authoritative signal that a participant finished that task — **without** moving them to `ATTENDED` and triggering payout. This separation matters for API partners. Because the API requires autopay to be enabled, [Mark as attended](/screener-responses/mark-as-attended) immediately starts the incentive payment. Most partners want to inspect the quality of a response (manually or via automated checks) before paying. `completionUrl` gives you a clean "task is done, participant is ready for review" signal in between those two states, and it's visible to both you and the researcher on the screener response. That review period lasts **24 hours** — see [Your review window is 24 hours](#your-review-window-is-24-hours) — after which Respondent marks the participant attended for you and autopay fires. This page covers: * What `completionUrl` is and what the `cc` parameter means * All the ways a task can get marked complete (and what each one is for) * How task completion relates to marking a participant as **attended** * How long you have to review before payment happens automatically ## What is `completionUrl`? When you create a project where `targetResearchMethodology` is `unmoderatedStudy` or `survey`, the [project response](/projects/retrieve-a-specific-project) includes a `completionUrl` field. It looks like this: ``` https://app.respondent.io/complete?cc= ``` * `completionUrl` is generated by Respondent. You cannot set it on create or update — it comes back on the project response. * The `cc` query parameter is the **project completion ID**: a unique identifier that lets Respondent associate the incoming participant with the right screener response on the right project. It is not a participant identifier on its own — Respondent also requires the participant to be signed in to their Respondent account in the same browser session. You should: 1. Place the URL of the external survey, study, or task in `unmoderatedStudyOrSurvey.remoteCommunicationDetails` when [creating the project](/projects/create-a-project) (see [Project deep dive](/docs/Projects/project-deep-dive#unmoderated)). 2. Configure the survey or task tool to redirect to `completionUrl` once the participant finishes. When the participant lands on `completionUrl`, Respondent: * Sets `task.status` to `complete` on their screener response * Sets `task.completedAt` to the current timestamp (this appears on the screener response object — see [Mark as attended](/screener-responses/mark-as-attended) response schema) * Stops any pending task-completion reminder emails for that participant ## How tasks get marked complete There are three ways `task.status` becomes `complete` on a screener response. Only the first one is something you, as an API partner, plug into directly. | Path | Who triggers it | When to use | | - | - | - | | Participant hits `completionUrl` | The participant, redirected from your task tool | The standard path for API partners. The participant finishes the external survey or study and your tool redirects them to `completionUrl`. | | First-party integrations (SurveyMonkey, Lookback) | Respondent, server-side | Only relevant when a researcher connects a SurveyMonkey or Lookback task on the Researcher Platform. Respondent receives a webhook from the third party and marks the task complete with the response payload. Not available over the public API. | | Inferred from "Mark as attended" | You, via the API | If you skip task completion entirely and just call [Mark as attended](/screener-responses/mark-as-attended), the participant moves to `ATTENDED` and payout starts. This does not flip `task.status` to `complete` — the two flags are independent — but for projects driven entirely from the API it's a valid shape if you don't need the intermediate "task done, review pending" state. | There is no public API endpoint to mark a task complete directly. If you want Respondent's UI and the screener response object to reflect "task done" before you decide on payment, the only lever is to send the participant through `completionUrl` at the end of your external task. ## Task complete vs. attended `task.status` and `screenerResponse.status` are independent. Hitting `completionUrl` only touches `task.status`; calling Mark as attended only touches `screenerResponse.status`. Neither one implies the other. For API partners, that gives you a three-stage lifecycle for unmoderated work: 1. **Invited** — you've sent the participant to the task. 2. **Task complete** — they finished the task and were redirected through `completionUrl`. `task.status = complete`, `task.completedAt` is set, reminder emails stop. The participant has not been paid. 3. **Attended** — you've reviewed the response and called [Mark as attended](/screener-responses/mark-as-attended). Status moves to `ATTENDED` and autopay fires. A typical lifecycle: 1. Researcher creates the project — `completionUrl` is returned on the response. 2. Participant qualifies, you invite them, they complete the external task. 3. The external tool redirects the participant to `completionUrl`. Respondent marks the task complete and stops reminders. 4. Your system (or a reviewer) inspects the response. 5. Once you're happy, your system calls [Mark as attended](/screener-responses/mark-as-attended) — `ATTENDED` + autopay. 6. If the response is unacceptable, call [Mark as rejected](/screener-responses/mark-as-rejected) or [Mark as no show](/screener-responses/mark-as-no-show) instead. ## Your review window is 24 hours Stage 2 is not open-ended. For `unmoderatedStudy` and `survey` projects, task completion also schedules an **automatic completion 24 hours later**. If you have not acted by then, Respondent moves the response to `ATTENDED` on your behalf and autopay fires — the same outcome as if you had called Mark as attended yourself. This is deliberate: it means a participant who did the work still gets paid if your review never happens. But it does mean **the review window has a deadline**, and the practical consequence is about *ordering*: | You call | Within 24h of task completion | After autopay has fired | | - | - | - | | [Mark as rejected](/screener-responses/mark-as-rejected) | No payout is created, and the participant's recruitment slot is freed so a replacement can be invited. | The response is marked rejected, but **a payout has already been created.** Rejecting does not cancel it through the API. | | [Mark as no show](/screener-responses/mark-as-no-show) | Same effect on payment — no payout is created. Use this when the task wasn't done, rather than done badly. | As above: the rejection is recorded, the payout is not cancelled. | | [Mark as attended](/screener-responses/mark-as-attended) | Payout starts immediately — you are giving up the rest of your review window. | N/A | Two things follow from this: * **Reject or mark no-show inside the window if you want to withhold payment.** Both prevent a payout from being created; pick by reason — reject for a quality failure, no-show for a task that wasn't completed. * **Don't mark attended just to record that the task is done.** `completionUrl` already records that, and the 24-hour timer already handles "approved by default". Calling Mark as attended early converts your remaining review time into an immediate payout. If you miss the window and need a payment stopped, contact partner support promptly — a created payout stays pending for a short period before it is disbursed, and there is no partner-facing endpoint to cancel it. The automatic completion only applies when the participant actually went through `completionUrl` and the response is still `INVITED`. If you skip the completion URL, nothing auto-advances and you must call Mark as attended yourself. Moderated methodologies have no equivalent — there is no automatic completion for `oneOnOne`, `focusGroup`, or in-person studies. Don't treat `completionUrl` as a payment trigger — it's a "ready for review" signal, and payment does not start at that moment. It does start **24 hours later** if you take no action, so treat task completion as the start of a review clock rather than an indefinite hold. ## How `cc` interacts with the other URL parameters The `cc` parameter on `completionUrl` is separate from the [URL parameters](/docs/Screener-responses/URL-Parameters-for-Project-Link) that Respondent appends to your `remoteCommunicationDetails` link (`respondent_profile_id`, `respondent_screener_response_id`, and so on). * The **outbound** link from Respondent to your task tool carries the `respondent_*` parameters so your tool knows which participant and screener response it is dealing with. * The **return** link back to Respondent (`completionUrl`) carries `cc` so Respondent can identify which project's task is being marked complete. The participant identity comes from the signed-in Respondent session, not from `cc`. You can pass `cc` through your survey tool as a hidden field and use it as part of the final redirect, or you can hard-code the project's `completionUrl` as the redirect target — both work. ## When `completionUrl` is and isn't returned | Methodology | `completionUrl` returned? | | - | - | | `unmoderatedStudy` | Yes | | `survey` | Yes | | `diaryStudy` | No | | `oneOnOne`, `focusGroup`, `inRespondentHome`, `inRespondentOffice` | No | For moderated methodologies, task completion is determined by the meeting actually happening — there is no equivalent self-reported completion link. # External screeners Source: https://developers.respondent.io/docs/Screener-responses/external-screeners Run your own screener on your platform and post the results back to Respondent Some partners run their own screening step — proprietary questions, an internal qualification model, or compliance checks that have to happen on their own platform. **External screeners** let you do that without losing Respondent's recruitment funnel: participants qualify on the Respondent screener first, then complete *your* screener on *your* site, and only then does their application present to the researcher as submitted and invitable. This page covers: * How the flow works end to end, and the two response statuses it adds * Setting up a project: the screener link and the declared question catalog * Receiving participants (the `respondent_*` correlation parameters) * Posting answers back, corrections, and how the qualification verdict is derived * The return contract — the two rules your redirect must follow * Webhooks and status handling ## How the flow works 1. A participant applies to your project and completes the **Respondent screener** as usual. 2. If the answers to the **Respondent screener** qualifies them for the study, the participant is shown a handoff page explaining they'll finish the screener on your platform. When they continue, they arrive at your `externalScreenerLink` with correlation parameters appended. 3. The participant completes your screener on your site. 4. You **post their answers back** with [Post external screener answers](/screener-responses/post-external-screener-answers), then redirect them to the Respondent page they came from. You must ensure all answers are posted before the user can be redirected back. 5. Once the answers cover your declared question catalog (or you signal early completion due to skip logic, disqualification etc.), the response moves to **`PENDING`** — the same status a classic application reaches when it is submitted — **provided nothing else is outstanding on our side**. The participant may be required to complete identity verification steps on the Respondent platform, so the response stays `EXTERNAL_SCREENER_IN_PROGRESS` until they complete it, and moves to `PENDING` on its own afterwards. Do not treat a response that has not yet reached `PENDING` as an error. From `PENDING` it behaves like any classic application: one that passed (`qualified: true`) and is not held is visible to the researcher, counts toward the project's applications, and can be invited — manually or by auto-invite. A failed verdict stays `PENDING` too but is never auto-invited, and a held response is not visible to the researcher until its hold resolves. One status carries the flow, and it behaves like a status you already know: | Status | Behaves like | Meaning | | - | - | - | | `EXTERNAL_SCREENER_IN_PROGRESS` | `DRAFT` | Qualified on the Respondent screener and not finished: in your screener, or back from it with a Respondent verification step still outstanding. Not visible to the researcher, not invitable, holds no recruitment slot. Participants may sit here indefinitely. | Reaching `PENDING` does not mean the participant *passed* your screener either — as with a classic submission, the pass/fail verdict lives in the `qualified` flag, so a participant who fails your screener also settles on `PENDING`, with `qualified: false`. ## Setting up a project An external-screener project declares two things: * **`externalScreenerLink`** — the URL of your screener, as an absolute `https://` URL (`http://` and bare hostnames are rejected). Participants are sent here with correlation parameters appended, so the URL may include its own query string. * **`externalQuestions`** — the declared catalog of your screener's questions. Answer rows you post back are validated against these `questionId`s, and the catalog defines when a response counts as complete. You can set both on [Create a project](/projects/create-a-project), or create the project with just the link and declare the catalog later with [Replace external screener questions](/projects/replace-external-screener-questions). Neither has to exist before you publish: `externalScreenerLink` can be set or changed on a live project with [Update a project](/projects/update-a-project), and the catalog endpoint works on live projects too — see [Changing the screener after publish](#changing-the-screener-after-publish). ```json theme={null} PUT /v1/projects/{projectId}/external-screener-questions/bulk [ { "questionId": "team-size", "questionType": "RADIO", "text": "How many people do you manage?", "options": [ { "id": "none", "text": "None" }, { "id": "1-5", "text": "1-5" }, { "id": "6-20", "text": "6-20" }, { "id": "20-plus", "text": "More than 20" } ] }, { "questionId": "tooling", "questionType": "TEXT", "text": "Which tools does your team use day to day?" } ] ``` * The endpoint **replaces the whole catalog** (like the [screener questions bulk endpoint](/screener-questions/create-multiple-screener-questions)). Send the full list every time. * `questionId` is **your** stable identifier — answer rows reference it, and completion is counted against it. Duplicate `questionId`s are rejected. * `options` is required on `RADIO` and `CHECKBOX` questions and not accepted on the others. Each option carries **your** stable `id` (unique within the question) and the `text` shown to the participant. Answer rows and quota conditions reference the `id`, so you can reword an option without changing who a quota segment captures. * `questionType` is one of `RADIO`, `CHECKBOX`, `TEXT`, `NUMBER`, or `OTHER`. Use `OTHER` for anything not modelled — the `text` keeps the answers legible to researchers either way. * Don't change `questionId`s while a project is live: answers already posted are keyed by them, and completion counting depends on the declared set. ### Publishing preconditions [Publish a project](/projects/publish-a-project) checks that the external screener config is valid before an external-screener project can go live: **The pair must be complete.**\ A project must have both `externalQuestions` and an `externalScreenerLink` configured.\ A draft may hold just the link (or just the catalog) while you're setting up, but publishing rejects a half-configured pair with a `400`. **At least 2 questions in total.**\ Every project needs [at least 2 screener questions](/docs/Screener-responses/screener-creation) to publish. On an external-screener project the two catalogs count together: two Respondent screener questions, two external questions, or one of each all satisfy it. A project with a single question across both is rejected with a `400` (`Project must have at least 2 screener questions`). The same total applies once the project is live: replacing the external catalog with fewer questions, or removing Respondent screener questions, is rejected if it would leave the published project below 2. Organizations enabled for screener-less projects have no minimum: their projects publish with no questions in either catalog, and a live project's screener may be emptied (including with an empty list on the bulk endpoints). ### Changing the screener after publish Your screener does not have to exist when the project goes live. On a published project (`projectStatus: PAID`): * Set or change **`externalScreenerLink`** with [Update a project](/projects/update-a-project) — it is one of the fields accepted on a live project. While `externalQuestions` are declared the link is required, so a `null` is rejected with a `400`; send the new URL instead. * Set or replace **`externalQuestions`** with [Replace external screener questions](/projects/replace-external-screener-questions) — the same replace-all endpoint you use on a draft. The flow switches on for new applicants as soon as **both** are configured (and the feature is enabled for your organization). What that means for participants already in the funnel: * Participants who applied **before** both were configured went through the classic flow and are already `PENDING` — they never saw your screener, and they stay invitable. If you want their answers too, screen them on your side after inviting them. * Participants who were **already handed off** to your screener keep the URL they were sent to when they resume from the Respondent apply page, so a partner session they started is not broken by a link change. New applicants get the new link. * Removing the link is only possible while no catalog is declared. Answers you already posted stay on their responses either way. ## Receiving participants When a qualified participant continues to your screener, Respondent appends these query parameters to your `externalScreenerLink`: | Parameter | What it is | | - | - | | `respondent_screener_response_id` | The screener response this participant's answers belong to. **This is the id you post answers back with.** | | `respondent_project_id` | The project the participant applied to. Used in the answers URL, and to build the return URL. | | `respondent_user_id` | The participant's user id. | | `respondent_profile_id` | The participant's profile id. | Persist at least `respondent_screener_response_id` and `respondent_project_id` for the session — you need both to post answers and to send the participant back. Ask participants **only your screener questions**. Never ask for their Respondent password, a sign-in code, or contact details such as email or phone — Respondent tells participants to close the tab and report any site that does. ## Posting answers back Post the participant's answers — and your per-question verdicts — with [Post external screener answers](/screener-responses/post-external-screener-answers): ```json theme={null} POST /v1/projects/{respondent_project_id}/screener-responses/{respondent_screener_response_id}/external-screener-answers { "answers": [ { "questionId": "team-size", "answerValues": ["6-20"], "qualifies": true }, { "questionId": "tooling", "answerText": "Jira, Figma, and an internal planning tool", "qualifies": true } ] } ``` How it behaves: * **Rows upsert idempotently per `questionId`.** Re-sending a row replaces the earlier one, so a correction is just another POST — including flipping an earlier `qualifies` verdict. Respondent re-derives the response's state from the stored rows on every write. * Each row must reference a declared `questionId` (undeclared ids are rejected) and must carry content: at least one of `answerValues` (a non-empty array — multi-select friendly) or `answerText` (a non-empty string). * On a `RADIO` or `CHECKBOX` question, `answerValues` holds the chosen option **ids** from the declared `options`, exactly one for `RADIO`. An id you did not declare, or `answerText` in place of ids, is rejected with a 400 and nothing is stored. * **Completion**: your side of the screener is complete once the stored rows cover **every** declared question. The response then moves to `PENDING`, unless a Respondent verification step is still outstanding — see step 5. If participants may legitimately skip questions on your side, send `"complete": true` alongside the answers to settle the response early — you can post in as many batches as you like before that. * **The verdict is derived**: the response ends up `qualified` only if *every* stored row has `qualifies: true`. A failed verdict settles the response on `PENDING` with `qualified: false` — visible to the researcher, but excluded from qualified counts and auto-invite. * You can keep posting corrections while the response is in `EXTERNAL_SCREENER_IN_PROGRESS` or `PENDING`. Once the participant has been invited, the response is closed to answer writes. * For an overall disqualification decision that isn't tied to answer rows, use [Qualify participant](/screener-responses/qualify-participant) with `qualifyStatus: false` — same as on classic projects. Note that a later answers re-post re-derives the verdict from the rows, which can undo a manual flip. ## The return contract Two rules, both load-bearing: Send the answers request and wait for the response **before** sending the participant back. If the participant lands back on Respondent before the answers arrive, they see a waiting state instead of a submitted application — the page recovers on its own once your POST lands, but posting first makes the handoff seamless. Send the participant back to the project's apply page: ```text theme={null} https://app.respondent.io/next/participants/projects/{respondent_project_id}/apply ``` Do **not** redirect through the project's [`completionUrl`](/docs/Screener-responses/completion-url). The completion URL marks the *study task* complete — on an unmoderated study or survey it starts the review-then-autopay clock. A screener is not the study; routing screener traffic through it would pay participants for applying. Respondent never auto-forwards the participant in either direction: the handoff to your screener and the return are both explicit pages, so a participant who navigates back early can always resume from where they left off. ## Webhooks and statuses If you use [webhooks](/docs/Webhooks/overview), the moment your answers settle the response — `EXTERNAL_SCREENER_IN_PROGRESS` → `PENDING` — is the application's submission moment, so it arrives as a **`SCREENER_RESPONSES.CREATED`** event, exactly as a classic application's submission does. There is no separate external-screener submission event to subscribe to. There is **no webhook for the handoff** into your screener. You learn a participant has arrived when they land on your `externalScreenerLink` with the `respondent_*` correlation parameters — the same moment a webhook would tell you, without the round trip. A few status behaviors worth knowing: * **A `PENDING` response may still be held** — in an identity-verification or review hold, it is not yet visible to the researcher. The status does not change when the hold is released. Your answer posts are accepted throughout. * **[Cancel invite](/screener-responses/cancel-invite) reverts to `PENDING`** — the same as on a classic project. The participant's answers are already stored, so withdrawing an invitation does not send them through your screener again. * [**List screener responses**](/screener-responses/list-screener-responses) accepts `EXTERNAL_SCREENER_IN_PROGRESS` in its `statuses` filter. ## Quick reference | I want to… | Do this | | - | - | | Turn the flow on | Contact Respondent to enable it for your organization | | Configure a project | Set `externalScreenerLink` + `externalQuestions` on create, or link first and catalog via the bulk endpoint — both required by publish, and Respondent + external questions must total at least 2 | | Identify an arriving participant | Read `respondent_screener_response_id` + `respondent_project_id` from the query string | | Report answers | `POST …/external-screener-answers` — before redirecting back | | Let participants skip questions | Send `"complete": true` with the final batch | | Fix a wrong answer or verdict | Re-post the corrected row — rows upsert per `questionId` | | Fail a participant | Per-question: `qualifies: false` rows. Overall: the qualify endpoint | | Send the participant back | The project's apply page — never `completionUrl` | # Multiple payments Source: https://developers.respondent.io/docs/Screener-responses/multiple-payments Pay participants multiple times for multi-stage studies We provide 2 `screener-responses` endpoints to allow invited participants to be paid the projects incentive amount up to 5 times. For instances where the second, third, forth, and fifth payments may need to be higher than the initial incentive, there is a `tip` field that allows for a payment to a participant to exceed the incentive defined in the project. Payouts cannot be lower than the incentive defined on the project. It is important to keep the project incentive the lowest dollar amount a participant may get paid. If researchers are running a multi-stage project and want to advertise the total possible incentive in a research project, this should be included in the project title or description as a callout for "up to \$XX incentive". To maintain trust with participants, the incentive defined in the project is the lowest amount a participant will get paid if they complete the project. If researchers plan to have variable incentives based on quality or time of a study that they will determine after interacting with a participant, the incentive set at the project should be the lowest possible payment. ## How to use multiple payments The first payment behavior depends on the project's `automations.autopay` setting: * **`autopay: true`** (the default when omitted at project creation): the first payment fires automatically when the participant is marked as Attended/Complete via [Mark as attended](/screener-responses/mark-as-attended). Do **not** also call the payout endpoint with `payoutCount: 1` — that produces a duplicate first payment. * **`autopay: false`**: [Mark as attended](/screener-responses/mark-as-attended) records attendance but does not pay the participant. Issue the first payment yourself with `POST /v1/projects/{projectId}/screener-responses/{screenerResponseId}/payouts` and `payoutCount: 1` (or process it from the Respondent dashboard). For any subsequent payment (payments 2–5), use the endpoints below regardless of the autopay setting. * /v1/projects/\{projectId}/screener-responses/payouts * /v1/projects/\{projectId}/screener-responses/\{screenerResponseId}/payouts ### /v1/projects/\{projectId}/screener-responses/payouts * To begin a followup payment, you must first call the payouts GET with the desired project ID * You should receive a response like this: ```Text After 1st payment theme={null} [ { "userId": "string", "profileId": "string", "screenerResponseId": "string", "numberOfPayouts": 1, "payouts": [ { "incentive": 50, "tip": 0, "totalIncentive": 0, "createdAt": "string" } ] } ] ``` ```Text After 2nd payment theme={null} [ { "userId": "string", "profileId": "string", "screenerResponseId": "string", "numberOfPayouts": 2, "payouts": [ { "incentive": 50, "tip": 0, "totalIncentive": 0, "createdAt": "string" } { "incentive": 50, "tip": 10, "totalIncentive": 60, "createdAt": "string" } ] } ] ``` ### /v1/projects/\{projectId}/screener-responses/\{screenerResponseId}/payouts * After confirming the current number of payments for a profile ID, you can POST the next payment ```Text 3rd payment theme={null} { "payoutCount": 3, "tip": 20 } ``` Notice, this is no option to input incentive. Incentive is fixed during project creation and can only be "increased" by adding a tip. *** # Scheduling Source: https://developers.respondent.io/docs/Screener-responses/scheduling How to schedule for moderated projects # When and how to use scheduling Scheduling endpoints are applicable for the following methodologies: `oneOnOne`, `focusGroup`, `inRespondentHome`, `inRespondentOffice`. ## Where to start Scheduling begins with [project creation](/projects/create-a-project). When one of the above methodologies is select for a project, the `bookingURL` field in the `externalResearcher` object is required to schedule with participants. The link provided should lead participants to a page where they can select a time to participate in the study. The booking page should not require a participant to enter email or phone number and should instead rely on the data passed in the redirect URL parameters to associate the schedule with the appropriate participant: `?respondent_profile_id={{%PROFILE_ID%}}&respondent_project_id={{%PROJECT_ID%}}&respondent_screener_response_id={{%SCREENER_RESPONSE_ID%}}` ## Inviting participants Before a participant has the opportunity to book time they must first be invited to the project via: [`/v1/projects/{projectId}/screener-responses/{screenerResponseId}/invite`](/screener-responses/invite-participant) ## Scheduling participants Once a participant has been invited, they will be able to follow the "Book a Time" button to scheduled a time on your platform. Once the participant has confirmed a time the schedule endpoint,[`/v1/projects/{projectId}/screener-responses/{screenerResponseId}/schedule`](/screener-responses/schedule-participant), should be utilized to send Respondent the booking information to keep the participant informed. **Between invite and booking, the invitation does not expire on its own and Respondent does not nudge the participant to book.** The response stays `INVITED` until you call [Schedule](/screener-responses/schedule-participant), [Cancel invite](/screener-responses/cancel-invite). Automated participant reminders begin **after** a booking is written back — they are 5 days, 1 day, and 4 hours before the booked time — so booking-nag emails to unbooked participants are the responsibility of your own platform. See [Automated researcher emails](/docs/Guides/automated-emails) for the researcher-side emails Respondent does and does not send. Scheduling is also required before a moderated session can settle. [Mark as attended](/screener-responses/mark-as-attended) and [Mark as no-show](/screener-responses/mark-as-no-show) are next actions on a `SCHEDULED` response — see the [Response statuses table](/docs/Screener-responses/working-with-responses#statuses) — so an `INVITED` response that never booked cannot skip straight to `ATTENDED` or `NOSHOW`. If a session happens without a prior booking, call [Schedule](/screener-responses/schedule-participant) with the actual session start as `bookedDate` first, then mark attended or no-show. **Set `bookingLink` to your *reschedule* URL when calling the schedule endpoint.** The `bookingLink` you pass to `/schedule` becomes the participant's **reschedule link** (surfaced in their booking confirmation and in the "Reschedule" action on their dashboard). If your platform uses a *different* URL for rescheduling than for the initial booking, pass your **reschedule** URL as `bookingLink` on the schedule call — the initial booking link was already used at invite time to get them booked. Related fields on `/schedule`: `meetingLink` is the session/meeting URL, and `meetingCancelLink` is the cancel link. If `meetingCancelLink` is omitted it falls back to `bookingLink` (then to the project's `externalResearcher.bookingURL`), so pass it explicitly if your cancel URL differs from your reschedule URL. ## Who cancels matters Researcher cancel and reschedule use a 4 hour window before start. Participant cancel and no-show do not. | Who | Call | 4 hours or more before start | Less than 4 hours before start | | - | - | - | - | | Researcher cancels, no re-invite | [Cancel booking](/screener-responses/cancel-booking) | `CANCELLED`. Not paid. | `ATTENDED`. Full incentive and service fee charged. | | Researcher cancels and wants a new time | [Cancel & re-invite](/screener-responses/cancel-booking-&-re-invite) | Back to `INVITED`. | Request is rejected. | | Participant asks to cancel | [Cancel booking (participant request)](/screener-responses/cancel-booking-participant-request) | Always `CANCELLED`. Not paid. | Same. No lead-time check. | No-show is after the session starts. On the Partner API, wait more than 15 minutes after start. A call at exactly 15 minutes returns 400. Status is `NOSHOW`. The participant is not paid. See [No-shows](#no-shows). Do not use [Cancel booking](/screener-responses/cancel-booking) for a participant cancel. Inside 4 hours it marks them `ATTENDED` and charges you. Use [Cancel booking (participant request)](/screener-responses/cancel-booking-participant-request) instead. Respondent does not enforce participant reschedule or cancel windows on your calendar. If you need a cutoff, enforce it before you call [Schedule](/screener-responses/schedule-participant). ## Rescheduling (researcher) Call [`/v1/projects/{projectId}/screener-responses/{screenerResponseId}/cancel-booking-reinvite`](/screener-responses/cancel-booking-&-re-invite) to cancel the booking and let the participant pick a new time. They return to `INVITED`. The API rejects the request less than 4 hours before start, for both remote (`oneOnOne`, `focusGroup`) and in-person (`inRespondentHome`, `inRespondentOffice`). There is no public API equivalent of the platform "Enable reschedule" flag. ## Cancelling (researcher) When the researcher cannot make the session and is not re-inviting now, call [`/v1/projects/{projectId}/screener-responses/{screenerResponseId}/cancel-booking`](/screener-responses/cancel-booking). * 4 hours or more before start: `CANCELLED`. Not paid. Recruitment slot released. * Less than 4 hours before start, or after start: `ATTENDED`. Autopay. Full incentive and service fee charged. This 4 hour charge window is the same for remote and in-person. It is not the reinvite lead time. ## Cancelling on behalf of a participant When the participant asked to cancel, call [`/v1/projects/{projectId}/screener-responses/{screenerResponseId}/participant-cancel-booking`](/screener-responses/cancel-booking-participant-request). Always `CANCELLED`. Never paid, no matter how close the session is. If you send `shortNoticeCancel`, it is stored. It does not start autopay. On the native Respondent scheduler, participants are warned if they cancel within 12 hours of a remote session or 24 hours of an in-person session. They are not blocked. That warning is UI only. The public API does not apply it. ## No-shows If the participant does not attend, call [Mark as no-show](/screener-responses/mark-as-no-show). They are not paid. Status is `NOSHOW`. * Partner API and external bookings: more than 15 minutes after start. A call at exactly 15 minutes returns 400. * Unmoderated and survey Invalid Response: [Response statuses](/docs/Screener-responses/working-with-responses). * Clears pending pay. Does not clear `externalProject.bookedDate` or free your calendar slot. Free that slot on your side. * Respondent emails the participant. Talk to them first. This cannot be undone. Do not use [Cancel booking](/screener-responses/cancel-booking) as a no-show. After start that path marks `ATTENDED` and pays. ### Replacing a no-show Respondent does not automatically schedule a standby participant for the missed slot. To fill the session, invite another qualified applicant from your project via [`/v1/projects/{projectId}/screener-responses/{screenerResponseId}/invite`](/screener-responses/invite-participant) and let them book a time through your `bookingURL`. Respondent's recruiting algorithm sources up to 3x the project's `targetNumberOfParticipants`, so a project usually has qualified, uninvited applicants available as replacements. Use [List screener responses](/screener-responses/list-screener-responses) to find them. See [Project statuses](/docs/Projects/project-statuses) for how sourcing and auto-pause interact with the 3x buffer. *** # Creating questions Source: https://developers.respondent.io/docs/Screener-responses/screener-creation Understanding question and logic types to configure questions Screener questions can now be edited after publishing Screener questions can now be edited any time after a project is published. It is important to note there are some catches. * When updated screener questions after publishing, the screener is effectively replaced * This means that if answers have already been collected, they are still qualified on the original screener * Only new screener responses will be qualified based on screener updates We generally recommend using this only as a fallback if there was an error in screener setup or if no screener responses have been submitted. If you choose to accept edits after responses have been collected, you can use the `createdAt` field in the screener response to cross reference with the screener update time to manage different qualifications. # Question types and logic The screener `questionType` can be selected from a set list of strings. Radio and checkbox answers can have additional answer logic and skip logic applied. All projects require at least 2 screener questions to publish. On an [external-screener](/docs/Screener-responses/external-screeners) project, the questions declared in `externalQuestions` count towards that minimum, so one screener question plus one external question is enough. Organizations enabled for screener-less projects are exempt: their projects publish with no screener questions at all, and participants apply without answering any. Ask your Respondent contact if you need this enabled. Question and answer `text` fields accept plain strings only. HTML, Markdown, and other rich text formatting are not rendered — participants see the raw characters. | questionType | Use case | `answerValue` Schema | Skip logic | | - | - | - | - | | `radio` | Single select | 1 = Qualify 2 = Disqualify | Yes | | `checkbox` | Multi select | 1 = May Select 2 = Must Select 3 = Disqualify (see [Checkbox qualification](#checkbox-qualification)) | Yes | | `singleLineTextbox` | Short form text box | n/a | No | | `multiLineTextbox` | Long form text box | n/a | No | | `numericBox` | Single entry number | n/a | No | | `sliderScale` | Number selector | n/a | No | | `fileUpload` | File attachment upload | n/a | No | | `rank` | Rank/order a set of options | n/a | No | | `matrix` | Grid of rows scored against shared columns | Per-row via `matrixRows` (column `answerValue` unused) | Yes | `fileUpload`, `rank`, and `matrix` are enabled per organization. Creating one of these question types without the corresponding feature enabled returns `403 Forbidden`. # Basic screener question setup POST /v1/projects/\{projectId}/screener-questions The examples below show a basic setup for each question type. * For all examples `isRequired` is set to `true` and `skipLogic` is set to `false` * All question types can be required or not * Only `radio` and `checkbox` questions can include skip logic * These question types can also include an `other` option, but do not require it * The `radio` example shows the inclusion of an `other` option while `checkbox` example shows no `other` option * Each example shows the minimum fields required, but other fields may be available. ## Checkbox qualification A `checkbox` question qualifies a participant when both of these hold: * No selected answer is a **Disqualify** (`answerValue: 3`). * The **Must Select** answers (`answerValue: 2`) satisfy the question's `matchType`: * `"all"` (the default when `matchType` is omitted): every Must Select answer must be selected. * `"any"`: at least one Must Select answer must be selected. **May Select** answers (`answerValue: 1`) never affect qualification. Selecting or skipping them changes nothing. A question with no Must Select answers qualifies on any selection that does not include a Disqualify answer. `matchType` is optional and only applies to `checkbox` questions. Questions created before the field existed have no `matchType` and behave as `"all"`. ```json Checkbox with matchType "any" theme={null} { "questionType": "checkbox", "text": "Which of these tools do you use during handoff?", "answers": [ { "text": "Figma", "answerValue": 2 }, { "text": "Jira", "answerValue": 2 }, { "text": "Zeplin", "answerValue": 1 }, { "text": "Storybook", "answerValue": 3 } ], "matchType": "any", "isRequired": true, "skipLogic": false, "includeOtherOption": false } ``` With `"matchType": "any"`, selecting Figma alone qualifies. With `"all"` (or no `matchType`), the participant must select both Figma and Jira. Selecting Storybook disqualifies in either case. ```json Radio theme={null} { "questionType": "radio", "text": "What is your favorite coastline in the United States?", "answers": [ { "text": "East Coast", "answerValue": 2 }, { "text": "West Coast", "answerValue": 1 }, { "isOther": true, "text": "Other", "answerValue": 2 } ], "skipLogic": false, "includeOtherOption": true, "isRequired": true } ``` ```json Checkbox theme={null} { "questionType": "checkbox", "text": "What animals would you like to spend time with?", "answers": [ { "text": "Cat", "answerValue": 1 }, { "text": "Dog", "answerValue": 1 }, { "text": "Giraffe", "answerValue": 1 }, { "text": "Pineapple", "answerValue": 3 } ], "isRequired": true, "skipLogic": false, "includeOtherOption": false } ``` ```json SingleLineTextbox theme={null} { "questionType": "singleLineTextbox", "text": "What is your favorite color?", "skipLogic": false, "isRequired": true } ``` ```json multiLineTextbox theme={null} { "questionType": "multiLineTextbox", "text": "In your own words, tell us what your favorite hobby is.", "skipLogic": false, "isRequired": true } ``` ```json numericBox theme={null} { "questionType": "numericBox", "text": "How old are you?", "skipLogic": false, "isRequired": true } ``` ```json sliderScale theme={null} { "questionType": "sliderScale", "text": "On a scale from 1 to 10 how confident are you building an API?", "isRequired": true, "skipLogic": false, "minValue": 1, "maxValue": 10, "step": 1 } ``` ```json fileUpload theme={null} { "questionType": "fileUpload", "text": "Please upload a screenshot of your current dashboard.", "isRequired": true } ``` ```json rank theme={null} { "questionType": "rank", "text": "Rank these product features from most to least important to you.", "answers": [ { "text": "Price" }, { "text": "Battery life" }, { "text": "Camera quality" }, { "text": "Screen size" } ], "randomizeOrder": true, "isRequired": true } ``` ```json matrix theme={null} { "questionType": "matrix", "text": "How familiar are you with each of these brands?", "matrixSelectionMode": "single", "matrixMatchType": "all", "answers": [ { "uid": "a1111111-1111-4111-8111-111111111111", "text": "Never heard of it" }, { "uid": "a2222222-2222-4222-8222-222222222222", "text": "Heard of it" }, { "uid": "a3333333-3333-4333-8333-333333333333", "text": "Use it regularly" } ], "matrixRows": [ { "uid": "b1111111-1111-4111-8111-111111111111", "text": "Figma", "qualifyingAnswerUids": ["a3333333-3333-4333-8333-333333333333"] }, { "uid": "b2222222-2222-4222-8222-222222222222", "text": "Sketch", "qualifyingAnswerUids": [ "a2222222-2222-4222-8222-222222222222", "a3333333-3333-4333-8333-333333333333" ] } ], "isRequired": true } ``` *** # Screener overview Source: https://developers.respondent.io/docs/Screener-responses/screener-overview Finding just the right participant for that study # What are screener questions? Screener questions serve as a brief survey to narrow down participants that match targeting parameters in a project. It is best practice to use between 5 and 10 questions - at least 2 are required (the maximum is 40 for a project). We currently offer the following screener question types: * Radio * Checkbox * Single line text box * Multi line text box * Number box * Slider * File upload * Matrix * Ranking ## Basic qualifying logic Single answer or multiple choice question types can have qualifying logic applied directly to each answer. For example: Do you own any pets? 1. Yes (qualify) 2. No (disqualify) What pets do you own? 1. Cat (may select) 2. Dog (must select) 3. Fish (may select) 4. Raccoon (disqualify) For checkbox questions, **may select** answers never affect qualification, a **disqualify** answer always disqualifies, and the **must select** answers combine according to the question's optional `matchType`: `"all"` (the default) requires every must select answer, `"any"` requires at least one. See [Checkbox qualification](/docs/Screener-responses/screener-creation#checkbox-qualification). ## Skip logic These question types can also have skip logic applied where each possible answer selection can lead the participant directly to a specific question. Continuing the above example (Question 1) Do you own any pets? 1. Yes (qualify) 2. No (disqualify) 1. If selected go to **end of screener** (Question 2) What pets do you own? 1. Cat (may select) 2. Dog (must select) 1. If **not select** go to **end of screener** 3. Fish (may select) 4. Raccoon (disqualify) 1. If **selected** go to **end of screener** (Question 3) Tell us about where you got your first pet. (End of screener) ## API endpoints Add screening questions to your project. Add up to 40 questions in a single request. # Applying skip logic Source: https://developers.respondent.io/docs/Screener-responses/skip-logic Using logic options with radio and checkbox questions Both `radio` and `checkbox` questions can include skip logic, but each question utilizes a slightly different means of achieving the logic given the granularity of logic available. For skip logic to be applied, other questions must already be created or you can pass your own UUID-format uid in the PUT Bulk endpoint * Question IDs are returned when a screener question is created. Once created, the question `uid` can be used to determine skip logic. End of survey will create its own `uid` automatically when you set the `goToQuestionUid` as `null` in the skip logic object. # Radio skip logic PATCH: /v1/projects/\{projectId}/screener-questions/\{screenerQuestionId} Radio skip logic allows for one additional layer of logic to be applied to each answer beyond Qualified (1) or Disqualified (2); the additional logic layer can designate which question a participant moves to based on the answer. For this example we'll use the sample question from basic screener question setup and apply skip logic. * The applied skip logic dictates that if East Coast or Other are selected, the participant moves to the end of the screener. If West Coast is selected, the participant moves to another question. * This logic is applied by marking `skipLogic` = `true` and utilizing the `goToQuestionUid` field in the answer object. Setting the `goToQuestionUid` to `null` will automatically apply the End of Screener uid that we generate. ```json Radio with skip logic theme={null} { "questionType": "radio", "text": "What is your favorite coastline in the United States?", "answers": [ { "text": "East Coast", "answerValue": 2, "goToQuestionUid": null }, { "text": "West Coast", "answerValue": 1, "goToQuestionUid": "DISTINCT_QUESTION_UID" }, { "isOther": true, "text": "Other", "answerValue": 2, "goToQuestionUid": null } ], "skipLogic": true, "includeOtherOption": true, "isRequired": true } ``` *** # Checkbox skip logic PATCH: /v1/projects/\{projectId}/screener-questions/\{screenerQuestionId} Checkbox skip logic allows for one or multiple layers of logic to be applied using AND/OR booleans beyond the answer level logic of May Select (1), Must Select (2), Disqualify (3). For both examples we'll use the sample question from basic screener question setup and apply skip logic. ## Single logic type * The applied skip logic dictates that if Dog is selected (`answerUid`) to skip to a particular question via the `goToQuestionUid` in the logic object. ```json skipLogicType theme={null} { "questionType": "checkbox", "text": "What animals would you like to spend time with?", "answers": [ { "text": "Cat", "answerValue": 1 }, { "text": "Dog", "answerValue": 1 }, { "text": "Giraffe", "answerValue": 1 }, { "text": "Pineapple", "answerValue": 3 } ], "isRequired": true, "skipLogic": true, "includeOtherOption": false, "logic": { "answerUid": "DOG_ANSWER_ID", "goToQuestionUid": "DISTINCT_QUESTION_UID", "skipLogicType": { "text": "Selected", "id": 1 }, "multipleAnswers": null, "multipleAnswersBoolType": null } } ``` ## Multiple answer logic * The applied logic dictates that if Dog is Selected (`answerUid`) AND Cat is Not Selected AND Giraffe is Not Selected AND Pineapple is Not Selected to skip to a particular question via the `goToQuestionUid` in the logic object. * Either AND/OR logic can be applied for a string of multiple answers, but only one at a time can be applied via `multipleAnswersBoolType`. `skipLogicType.id` and each `multipleAnswers[].logicType.id` are numbers: * Selected = 1, Not Selected = 2 `multipleAnswersBoolType.id` is a number: * AND = 1, OR = 2 For `matrix` questions, set `rowUid` on the logic object (and on each `multipleAnswers[]` entry) to the row the answer belongs to. ```json multipleAnswers theme={null} { "questionType": "checkbox", "text": "What animals would you like to spend time with?", "answers": [ { "text": "Cat", "answerValue": 1 }, { "text": "Dog", "answerValue": 1 }, { "text": "Giraffe", "answerValue": 1 }, { "text": "Pineapple", "answerValue": 3 } ], "isRequired": true, "skipLogic": true, "includeOtherOption": false, "logic": { "answerUid": "DOG_ANSWER_ID", "goToQuestionUid": "DISTINCT_QUESTION_UID", "skipLogicType": { "text": "Selected", "id": 1 }, "multipleAnswersBoolType": { "text": "AND", "id": 1 }, "multipleAnswers": [ { "answer": "CAT_ANSWER_ID", "logicType": { "text": "Not Selected", "id": 2 } }, { "answer": "GIRAFFE_ANSWER_ID", "logicType": { "text": "Not Selected", "id": 2 } }, { "answer": "PINEAPPLE_ANSWER_ID", "logicType": { "text": "Not Selected", "id": 2 } } ] } } ``` *** # Response statuses Source: https://developers.respondent.io/docs/Screener-responses/working-with-responses Understand participant statuses and the actions available at each stage ## Statuses **/v1/projects/\{projectId}/screener-responses/\{screenerResponseId}/** Updating a participant status in a study happens through screener response endpoints. | Call | Status change | Next actions | | - | - | - | | [Qualify](/screener-responses/qualify-participant) | N/A (`qualified` flag) | Invite, Disqualify, Hide, Report | | [Disqualify](/screener-responses/qualify-participant) | N/A (`qualified` flag) | Qualify, Hide, Report | | [Hide](/screener-responses/hide-participant) | N/A (flag only) | N/A | | [Invite](/screener-responses/invite-participant) | Invited | Attended, Cancel invite, Reject, Report | | [Cancel Invite](/screener-responses/cancel-invite) | Reverts to previous | Invite, Disqualify, Hide, Report | | [Schedule](/screener-responses/schedule-participant) | Scheduled | Attended, Cancel booking, Participant cancel, Cancel & reinvite, Reject, No Show, Report | | [Cancel booking](/screener-responses/cancel-booking) | `CANCELLED` 4 hours or more before, unpaid. `ATTENDED` less than 4 hours, paid. Researcher only. See [Scheduling](/docs/Screener-responses/scheduling#who-cancels-matters). | `CANCELLED`: Re-invite, Report. `ATTENDED`: No show, Report | | [Cancel booking (participant request)](/screener-responses/cancel-booking-participant-request) | Always `CANCELLED`. Never paid. | Re-invite, Report | | [Cancel & reinvite](/screener-responses/cancel-booking-&-re-invite) | Invited | Attended, Cancel invite, Reject, Report | | [Attended](/screener-responses/mark-as-attended) | Attended | No show, Report | | [Manual payout](/screener-responses/trigger-manual-payout) | Paid | Manual payout again (see [Multiple payments](/docs/Screener-responses/multiple-payments)) | | [Rejected](/screener-responses/mark-as-rejected) | Rejected Quality | N/A | | [No show](/screener-responses/mark-as-no-show) | `NOSHOW` / `INVALIDRESPONSE` | N/A | | [Favorite](/screener-responses/mark-as-favorite) | N/A (flag only) | N/A | | [Report](/screener-responses/report-participant) | Cancelled (response is dismissed) | N/A | Screener responses arrive with a `qualified` status of `true` or `false` based on predetermined criteria. If Respondent passes a participant with `qualified` = `false`, your researchers can manually qualify them before inviting. Likewise, disqualify participants your own screening rules out **before inviting them**. The body requires all of `qualifyStatus: false`, `qualifiedOverriden`, `disqualifyReasons` (an array of `MISREPRESENTED`, `CONTEXT`, `SCREENER`, `OTHER`) and `message`. Qualification is a flag, separate from the response lifecycle `status`. Disqualifying does not withdraw an invitation that has already been sent — the response stays `INVITED` and the participant can still take part. To withdraw an invitation, use [Cancel invite](/screener-responses/cancel-invite); after participation, use [Reject](/screener-responses/mark-as-rejected). Hide a participant from the project view. This signals that there is nothing wrong with the participant — they are simply not right for this project. Pass along booking or task data to the participant. For moderated studies, provide a `bookingLink`. For unmoderated, provide a `meetingLink`. When the project has a quota, the invite is always admitted: it does not return 409, consumption is recorded, and the bucket may go over target. The response includes `quotaAssignment` (`segments[]`, `tier`, `revision`, `assignedAt`, `tierChangedAt`) so you can see which buckets the participant occupies. Revert an accidental invitation. The participant loses the ability to schedule a meeting or complete a linked study. **Moderated projects only.** After a participant books a time on your platform, send the `bookedDate` and `timezone` so their dashboard reflects when they should attend. You can also pass an updated `bookingLink` with a meeting link. **Moderated. Researcher-initiated.** If the researcher cannot make a booked meeting and is not rescheduling now, call [Cancel booking](/screener-responses/cancel-booking). Less than 4 hours before start, or after start, the response becomes `ATTENDED` and they are paid. 4 hours or more before, status is `CANCELLED` and they are not paid. Same 4 hour charge window for remote and in-person. Do not use this when the participant asked to cancel. That pays them inside 4 hours. Use [Cancel booking (participant request)](/screener-responses/cancel-booking-participant-request) instead. See [Scheduling](/docs/Screener-responses/scheduling#who-cancels-matters). **Moderated. Participant-initiated.** Always `CANCELLED`. Never paid. **Moderated. Researcher-initiated.** Cancel the booking and re-invite them to pick a new time. Rejected less than 4 hours before start, remote and in-person. Mark a participant as "attended" (moderated) or "complete" (unmoderated). **This kicks off incentive payout to the participant.** For unmoderated studies and surveys, you can track that a participant finished the task — without triggering payment — by redirecting them through the project's [`completionUrl`](/docs/Screener-responses/completion-url) at the end of the task. `task.status` and `screenerResponse.status` are independent flags: hitting the completion URL only sets `task.status = complete`, and calling this endpoint only moves `screenerResponse.status` to `ATTENDED`. Use the completion URL as a "ready for review" signal and call this endpoint once you've cleared the response for payout. That review window is **24 hours**: if you have not acted by then, Respondent marks the response `ATTENDED` automatically and autopay fires. Calling this endpoint early gives up the remainder of the window. See [Completion URL](/docs/Screener-responses/completion-url#your-review-window-is-24-hours). Indicates the quality of the response did not meet expectations. Stops a pending incentive payment, marks the participant as Rejected Quality, and sends them a notification email. It only stops a payment that is still pending. On an unmoderated study or survey that means **within 24 hours of task completion**, before the automatic completion marks the participant attended and autopay fires. Rejecting after that records the rejection but does not cancel a payout that has already been created — contact partner support promptly if the payment needs stopping. On moderated Partner API and external bookings, wait more than 15 minutes after start. Status is `NOSHOW`. They are not paid. Do not use [Cancel booking](/screener-responses/cancel-booking) as a no-show. After start that path still pays. On unmoderated studies and surveys, status is `INVALIDRESPONSE` when the participant did not complete the task. Stops a pending incentive payment and sends a notification email. The same pending-payment limit applies as for Rejected: on an unmoderated study or survey, use it **within 24 hours of task completion**. After autopay has created a payout, marking no-show does not cancel it. Flag a participant as a favorite. Useful for identifying high-quality participants you may want to work with again. Report a suspicious or fraudulent participant. The response's `status` becomes `CANCELLED` and its `visibility.status` becomes `DISMISSED` — it is removed from the researcher UI and from default listings (`visibilityStatus` defaults to `VISIBLE`). Pass `visibilityStatus=DISMISSED` on [List screener responses](/screener-responses/list-screener-responses) if you need them back (that filter may include non-report dismissals), and prefer persisting the `screenerResponseId` on your side before reporting. Reported participants cannot apply to future studies from your organization, and the Respondent team investigates whether they should be removed from the platform. ## Skipped questions Organizations that opt in to including skipped questions in screener responses receive an entry in `responses[]` for every screener question, with `skipped`, `skippedBy`, and `shown` flags marking questions the participant did not answer. See [Collecting responses](/docs/Screener-responses/collecting-responses#skipped-questions-in-responses) for the field reference and examples. ## Example status flows * Invite → Schedule → Attended / No show / Reject * Qualify → Invite → Schedule → Attended / No show / Reject * Invite → Schedule → Cancel booking & reinvite → Schedule → Attended / No show / Reject * Invite → Schedule → Participant cancel (never paid) * Invite → Schedule → Researcher cancel (4 hours or more: cancelled; less than 4 hours: attended and paid) * Invite → Attended / No show / Reject * Invite → Cancel invite * Qualify → Invite → Attended / No show → Favorite # Webhooks Overview Source: https://developers.respondent.io/docs/Webhooks/overview Webhooks are available to send updates to a specified url when changes take place. Webhook events are sent only for projects created via API Each team can have a single active webhook, which receives all events for projects created under that team. (A team can have multiple sets of API credentials; they all share the same webhook.) ### Available Webhook Event Types | Webhook Event | Description | | - | - | | `PROJECTS.UPDATED` | When the `recruitingStatus` of the project changes. | | `SCREENER_RESPONSES.CREATED` | When a new screener response has been submitted by a participant. | | `SCREENER_RESPONSES.UPDATED` | When a screener response has been updated by a participant. Non-researcher actions. Includes status change to `PAID` or `CANCELLED` and when a screener is rejected and no longer visible, or approved and made visible again. | | `MESSAGES.CREATED` | Any new messages from participants. | | `CONVERSATIONS.CREATED` | Any new conversation created. | | `QUOTAS.SEGMENT_FILLED` | When a quota segment (or a group's No match bucket) reaches its `min`, `max` or `exact` target. | | `QUOTAS.FILLED` | When every targeted bucket of a project's quota has reached its target. | ### Webhook Body Examples #### Project updated ``` { "event": "PROJECTS.UPDATED", "uuid": "4aae25da-dd7b-4e2d-bb6a-9177dec83baa", "created": "2026-01-08T23:09:42.677Z", "payload": { "resource": { "id": "69603934cd7d6158385a200c", "type": "projects", "updatedFields": [ { "name": "recruitingStatus", "oldValue": "RECRUITING", "newValue": "RECRUITED" } ] } } } ``` #### Screener response created **Deprecation Notice:** The `parentId` field in screener response created webhook events is deprecated. Please use `projectId` instead. `parentId` will be removed in February 2027. ``` { "event": "SCREENER_RESPONSES.CREATED", "uuid": "006a4577-43d6-448a-a3d6-1a63581ca217", "created": "2026-01-08T23:09:08.239Z", "payload": { "resource": { "id": "69603913cd7d6158385a1ff7", "projectId": "69603913cd7d6158385a1ff6", "type": "screener-responses" } } } ``` #### Screener response status updated **Deprecation Notice:** The `parentId` field in screener response webhook updated events is deprecated. Please use `projectId` instead. `parentId` will be removed in February 2027. ``` { "event": "SCREENER_RESPONSES.UPDATED", "uuid": "b7d8bc55-28dd-4319-b25a-84a7558afccf", "created": "2026-01-08T23:09:22.735Z", "payload": { "resource": { "id": "69603922bd5465b2dd6bd6e7", "projectId": "69603922bd5465b2dd6bd6e6", "type": "screener-responses", "updatedFields": [ { "name": "status", "oldValue": "PENDING", "newValue": "CANCELLED" } ] } } } ``` `SCREENER_RESPONSES.CREATED` webhook payloads carry only resource IDs. `SCREENER_RESPONSES.UPDATED` webhook payloads carry resource IDs plus the changed fields under `updatedFields`. To read the full screener response, fetch it via `GET /v1/projects/{projectId}/screener-responses/{screenerResponseId}`. ### Messaging Webhooks Messaging webhooks notify you when participants send messages or when new conversations are created. These events are only triggered for messages sent by participants, not by researchers. #### Message created Triggered when a participant sends a message in a conversation. ``` { "event": "MESSAGES.CREATED", "uuid": "4e5aa6de-70f9-4973-b50b-a05b9b79dfdb", "created": "2026-01-08T23:07:25.251Z", "payload": { "resource": { "messageId": "fe8edd40-92c8-4feb-bc71-8aea41a7de1e", "messageBody": "Test Message", "attachments": [ { "uid": "a1f7b0bb-b1ef-4110-84d5-aaedbcdb5abd", "url": "https://example.com/attachments/file_name.pdf", "contentType": "application/pdf" } ], "projectId": "6927a28e6f6ab200a4c358a7", "conversationId": "04576ba9-2309-4f3b-82b4-2771fe737085", "senderType": "participant", "profileId": "67d763cfa53ea24550dc52b2", "organizationId": "67c4f87adbb9ff9dc5322fc8", "teamId": "67c4f87adbb9ff9dc5322fe0", "webhookId": "6960367acd7d61583859e0e0", "createdAt": "2026-01-08T23:06:08.603Z", "updatedAt": "2026-01-08T23:06:08.603Z" } } } ``` Attachment `url` values are short-lived signed links. Download the file promptly and do not store the URL; request the message again if you need a fresh link. #### Conversation created Triggered when a new conversation is created with a participant. Includes the full conversation object, participant profiles, and the initial message if present. ``` { "event": "CONVERSATIONS.CREATED", "uuid": "ede25382-a2e4-4a81-b591-a311ec302e4f", "created": "2026-01-08T23:08:28.975Z", "payload": { "resource": { "conversation": { "uid": "04576ba9-2309-4f3b-82b4-2771fe737085", "name": "Conversation Test", "deleted": false, "locked": false, "read": false, "createdAt": "2025-12-01T02:34:41.004Z", "updatedAt": "2025-12-01T02:34:41.008Z", "metadata": { "projectId": "6927a28e6f6ab200a4c358a7", "researcherUserId": "67c4f86584754023ca2ec3ec", "externalResearcherId": "externalResearcherId" }, "user": { "uid": "2bc828db-e6f6-481e-af8d-2860153a31b0", "foreignId": "67d763ceb7852780e7fbb68e", "firstName": "John", "lastName": "S" }, "participants": [ { "uid": "fe799a09-08ac-4486-8dee-4f69dc1b5153", "foreignId": "67c4f86584754023ca2ec3ec", "firstName": "Sam", "lastName": "D", "read": true, "deleted": false }, { "uid": "2bc828db-e6f6-481e-af8d-2860153a31b0", "foreignId": "67d763ceb7852780e7fbb68e", "firstName": "John", "lastName": "S", "read": true, "deleted": false } ] }, "organizationId": "67c4f87adbb9ff9dc5322fc8", "teamId": "67c4f87adbb9ff9dc5322fe0", "webhookId": "6960367acd7d61583859e0e0" } } } ``` ### Quota Webhooks Quota webhooks notify you when a project's quota reaches its targets. They fire once per crossing: when a bucket reaches its target (its count rises to it, or its target is lowered to the count), and when the last targeted bucket does. Lowering a count below the target (a released participant, a lowered external fill) and reaching it again fires again. Buckets with a tracked-only target never fire. Read the current progress of every bucket from `GET /v1/projects/{projectId}/quotas`. #### Quota segment filled Triggered when one bucket reaches its target. `segmentId` is `no-match` for a group's No match bucket. `progress` holds the counts at the moment of filling: `invited`, `attended` and `externalFills` are compared against the target, and `qualified` is the pool of qualified applicants not yet invited. ``` { "event": "QUOTAS.SEGMENT_FILLED", "uuid": "1b7f3d2e-6a4c-4f0e-9c1d-2e3f4a5b6c7d", "created": "2026-09-07T12:00:00.000Z", "payload": { "resource": { "projectId": "6927a28e6f6ab200a4c358a7", "quotaId": "68bd5e0c2f1a4b3c9d8e7f60", "groupId": "gender", "segmentId": "women", "target": { "op": "max", "value": 20 }, "progress": { "qualified": 7, "invited": 12, "attended": 8, "externalFills": 0 }, "organizationId": "67c4f87adbb9ff9dc5322fc8", "teamId": "67c4f87adbb9ff9dc5322fe0" } } } ``` #### Quota filled Triggered when every bucket with a `min`, `max` or `exact` target has reached it. ``` { "event": "QUOTAS.FILLED", "uuid": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b", "created": "2026-09-07T12:00:00.000Z", "payload": { "resource": { "projectId": "6927a28e6f6ab200a4c358a7", "quotaId": "68bd5e0c2f1a4b3c9d8e7f60", "organizationId": "67c4f87adbb9ff9dc5322fc8", "teamId": "67c4f87adbb9ff9dc5322fe0" } } } ``` ### To set up a webhook * Create a `POST` to `/v1/webhooks` with the desired url for webhooks to be sent to. ``` {"url": "https://my-webhooks.com"} ``` * This will return a body with your organizationId, teamId, url, privateKey, and the webhook id to be used to validate webhook responses ``` { "organizationId": "67c4f87adbb9ff9dc5322fc8", "teamId": "67c4f87adbb9ff9dc5322fe0", "url": "https://example.com/webhooks/respondent", "privateKey": "44450f9c-f43c-4c26-98cb-53bdc0f49fde", "id": "6960367acd7d61583859e0e0" } ``` ### To Validate a webhook signature ```node node theme={null} const signatureHeader = request.headers['Respondent-Webhook-Signature']; const body = request.body; const algorithm = signatureHeader.substring(0, signatureHeader.indexOf('=')); const receivedSignature = signatureHeader.replace(algorithm + '=', ''); const privateKey = ''; const hmacBodyDigest = createHmac(algorithm, privateKey) .update(JSON.stringify(body)) .digest('base64'); if (hmacBodyDigest === receivedSignature) { // Handle webhook events return HttpStatusCode.Ok; } else { return HttpStatusCode.Forbidden; } ``` ### Webhook responses and retries Partners must return a 2xx status code within 3 seconds; otherwise, we’ll retry up to 5 times in 10-minute intervals. ### Isolating your own environments on staging Because each team has its own independent webhook, you can use separate teams to represent your own development and staging environments against the Respondent staging environment. Create a team for each environment (for example, one per developer's local stack or a shared CI environment), each with its own API credentials, and register a webhook on each. Deliveries for one environment then never collide with another's, so you can test webhook handling in isolation. *** # Retrieve industry list Source: https://developers.respondent.io/industries/retrieve-industry-list get /v1/industries Retrieve the full list of industries used for B2B project targeting. Use the returned IDs when setting `targetProfessionalIndustries` on a project. **Note:** Industry IDs differ between staging and production — always fetch dynamically rather than hardcoding. # Retrieve job title list Source: https://developers.respondent.io/job-titles/retrieve-job-title-list get /v1/job-titles Search and retrieve job titles used for B2B project targeting. Supports pagination — use `pageSize=50000` to fetch the full list. Use the returned IDs when setting `jobTitleIds` on a project. **Note:** IDs differ between staging and production. # Retrieve lookup values Source: https://developers.respondent.io/lookups/retrieve-lookup-values get /v1/lookups Retrieve all enumerated lookup values (age groups, education levels, genders, income brackets, and more). Use these values when configuring demographic targeting criteria on projects. # Add participant to conversation Source: https://developers.respondent.io/messaging/add-participant-to-conversation post /v1/messaging/conversations/{conversationUid}/participants Add a participant to an existing conversation so they can receive and send messages. # Create a conversation Source: https://developers.respondent.io/messaging/create-a-conversation post /v1/messaging/conversations Create a new conversation linked to a specific project. You must create a conversation before sending messages to participants. ## Next steps Use the conversation UID to send messages to participants. Read the full messaging guide for best practices. # Create a message Source: https://developers.respondent.io/messaging/create-a-message post /v1/messaging/conversations/{conversationUid}/messages Send a message within an existing conversation. The message is delivered to all participants in the conversation. # Mark conversation as read Source: https://developers.respondent.io/messaging/mark-conversation-as-read patch /v1/messaging/conversations/{conversationUid}/read Mark all messages in a conversation as read for your organization. # Remove participant from conversation Source: https://developers.respondent.io/messaging/remove-participant-from-conversation delete /v1/messaging/conversations/{conversationUid}/participants/{participantUserId} Remove a participant from a conversation. They will no longer receive new messages in this thread. # Retrieve all conversations Source: https://developers.respondent.io/messaging/retrieve-all-conversations get /v1/messaging/conversations List all conversations for your organization, with optional filters by project or participant. # Retrieve all messages Source: https://developers.respondent.io/messaging/retrieve-all-messages get /v1/messaging/messages List messages across conversations with optional filters for conversation, researcher, or date range. Supports pagination. # Retrieve specific conversation Source: https://developers.respondent.io/messaging/retrieve-specific-conversation get /v1/messaging/conversations/{conversationUid} Retrieve a single conversation by UID, including its participants and metadata. # Retrieve specific message Source: https://developers.respondent.io/messaging/retrieve-specific-message get /v1/messaging/messages/{messageUid} Retrieve a single message by its UID, including sender info and timestamps. # Retrieve unread conversations Source: https://developers.respondent.io/messaging/retrieve-unread-conversations get /v1/messaging/messages/inbox Retrieve conversations that have unread messages. Use this to build inbox-style notification flows. # Update a conversation Source: https://developers.respondent.io/messaging/update-a-conversation patch /v1/messaging/conversations/{conversationUid} Update a conversation's properties such as its title or metadata. # View credit and incentive balance Source: https://developers.respondent.io/pricing/view-credit-and-incentive-balance get /v1/pricing/balances/summary View your organization's current credit and incentive balance. Use this to check available funds before creating or publishing projects. # Create test participant (Staging only) Source: https://developers.respondent.io/profiles/create-test-participant-staging-only post /v1/profiles Create a test participant profile in the **staging environment only**. Use this to simulate screener responses and test your integration end-to-end without real participants. # Retrieve participant profile Source: https://developers.respondent.io/profiles/retrieve-participant-profile get /v1/profiles/{profileId} Retrieve a participant's profile including engagement history and available profile fields. Note: some demographic and professional fields are being deprecated — refer to the changelog for details. # Close a project Source: https://developers.respondent.io/projects/close-a-project patch /v1/projects/{projectId}/close Permanently close a project and stop all recruitment. **This action cannot be undone.** Ensure all participants have been paid before closing. # Copy a project Source: https://developers.respondent.io/projects/copy-a-project post /v1/projects/{projectId}/copy Duplicate an existing project as a new draft, including its screener questions and targeting configuration. Useful for running follow-up studies with similar criteria. # Create a project Source: https://developers.respondent.io/projects/create-a-project post /v1/projects Create a new B2B or B2C research project. Projects start in **DRAFT** status — add screener questions and then call [Publish](/projects/publish-a-project) to go live. ## Quick start Use this endpoint with your project configuration. The project starts in **DRAFT** status. Use [Create Screener Questions](/screener-questions/create-a-screener-question) to add screening criteria. Call [Publish Project](/projects/publish-a-project) to make it live. The `publicDescription` field is required at publish time. ## Completion deadlines For **Unmoderated Study** and **Survey** projects you can optionally set a completion deadline via `unmoderatedStudyOrSurvey.deadline`. Pass `endsAt` (an ISO 8601 date-time in the future) and, optionally, an IANA `timezone` (e.g. `America/New_York`) used when displaying the deadline to participants. The deadline is returned in project responses and can be changed later with [Update Project](/projects/update-a-project). # Delete a project Source: https://developers.respondent.io/projects/delete-a-project delete /v1/projects/{projectId} Permanently delete a project. Only projects in **DRAFT** status can be deleted — published or closed projects cannot be removed. # Generate title & description Source: https://developers.respondent.io/projects/generate-title-and-description post /v1/projects/suggestions Generate AI-suggested titles and descriptions based on your project configuration. Returns multiple options to choose from. # Pause a project Source: https://developers.respondent.io/projects/pause-a-project patch /v1/projects/{projectId}/pause Pause or unpause an active project. While paused, the project stops accepting new applicants but existing participants are unaffected. # Publish a project Source: https://developers.respondent.io/projects/publish-a-project patch /v1/projects/{projectId}/publish Publish a draft project to make it live and begin collecting responses. The `publicDescription` field must be set before publishing. # Replace external screener questions Source: https://developers.respondent.io/projects/replace-external-screener-questions put /v1/projects/{projectId}/external-screener-questions/bulk Replace the project's declared external screener question catalog. Works on draft and published projects. Pairs with `externalScreenerLink`: both are required to publish an external-screener project, and on a live project the flow starts for new applicants once both are set. # Retrieve a specific project Source: https://developers.respondent.io/projects/retrieve-a-specific-project get /v1/projects/{projectId} Retrieve full details for a single project, including configuration, targeting criteria, and current status. # Retrieve all projects Source: https://developers.respondent.io/projects/retrieve-all-projects get /v1/projects List all projects in your organization. Closed projects are excluded by default — use the `status` filter to include them. # Retrieve audience size estimate Source: https://developers.respondent.io/projects/retrieve-audience-size-estimate get /v1/projects/{projectId}/feasibility/audience-size-estimate Estimate how many participants in Respondent's panel match your project's targeting criteria. Use this to validate your audience configuration before publishing. # Update a project Source: https://developers.respondent.io/projects/update-a-project patch /v1/projects/{projectId} Update one or more fields on an existing project. Only the fields you include in the request body are modified — omitted fields are left unchanged. The set of editable fields depends on the project's status: draft B2B, draft B2C, and published projects each accept different request bodies. Once a project is live (projectStatus: PAID), only publicTitle, publicInternalName, publicDescription, participantTimeRequiredMinutes, incentiveAmount, ndaDetails, externalResearcher, externalScreenerLink, and unmoderatedStudyOrSurvey.deadline can be changed — targeting and recruitment fields such as targetNumberOfParticipants are locked. See Editing a project after publish in Project statuses for the full list. ## Completion deadlines For **Unmoderated Study** and **Survey** projects, `unmoderatedStudyOrSurvey.deadline` (`endsAt`, optional IANA `timezone`) can be changed on both draft and published projects. Only the fields you send are changed — omitting `timezone` keeps the stored one. On a published project, the new deadline also applies to participants with an open invitation (not to those who have already completed the task or whose invitation was cancelled). ## External screener link For external-screener organizations, `externalScreenerLink` can be set or changed on both draft and published projects. It is required while `externalQuestions` are declared, so on a published project a `null` is rejected with a `400` in that case; otherwise `null` removes it. Participants already handed off to your screener keep the URL they were sent to; new applicants get the new one. See [External screeners](/docs/Screener-responses/external-screeners#changing-the-screener-after-publish). ## Changing the audience On a draft, `targetMarketType` can be switched between `b2b` and `b2c`. The project's quota is left as it is, so a quota condition on a B2B-only attribute blocks publish once the draft is `b2c`. See [Audience quotas](/docs/Projects/audience-quotas#sample-workflow). # Upload NDA file Source: https://developers.respondent.io/projects/upload-nda-file put /v1/projects/{projectId}/files/form-data Upload an NDA file for participants to sign before accessing the study. Accepts PDF files via multipart/form-data. # Create or replace a project's quota Source: https://developers.respondent.io/quotas/create-quota put /v1/projects/{projectId}/quotas Create or replace a project's quota. Id-preserving: matching group and segment ids keep their progress. The response includes changes and warnings. # Delete a project's quota Source: https://developers.respondent.io/quotas/delete-quota delete /v1/projects/{projectId}/quotas Delete a project's quota. Returns 204. The next GET is an empty v2 configuration. Assignments on existing responses are not released. # Retrieve a project's quota Source: https://developers.respondent.io/quotas/retrieve-quota get /v1/projects/{projectId}/quotas Retrieve the quota configuration and progress. A project with no quota returns 200 with version v2 and an empty groups array. version discriminates a legacy v1 body when present. # Set external fills Source: https://developers.respondent.io/quotas/set-external-fills put /v1/projects/{projectId}/quotas/external-fills Set absolute external fill counts for quota buckets. The whole array is applied atomically. # Update quota targets Source: https://developers.respondent.io/quotas/update-quota patch /v1/projects/{projectId}/quotas/targets Sparse target edit. One entry per bucket; omitted buckets are untouched. Tracked-only is { op: track }. Rules and progress are not rewritten. # Introduction Source: https://developers.respondent.io/reference/introduction Get started with the Respondent Partner API Respondent's API gives your development team the tools to deliver seamless, high-quality participant recruitment—right from your own platform. By integrating our API, you can give your researchers access to Respondent's deeply profiled, highly qualified participant pool without ever leaving your environment. Set up research projects and screeners programmatically with full targeting control. Retrieve profiles, qualify, invite, and track participant status in real-time. Communicate with participants throughout the study lifecycle via the messaging API. Receive real-time notifications for participant status changes and project events. Your developers can integrate these endpoints to build custom UI for project creation and participant management and embed Respondent workflows directly into your platform. Whether you're building new tools or enhancing existing ones, the Respondent API enables you to streamline study setup and deliver exceptional participant experiences. To begin, you'll need a Client ID and Client Secret. Visit our [Quickstart Guide](/docs/Getting-started/quickstart) to learn the steps to receiving credentials and start integrating. ## Authentication All API requests require two headers: `x-api-key` (your Client ID) and `x-api-secret` (your Client Secret). ```bash theme={null} curl --request GET \ --url 'https://api-staging.respondent.io/v1/skills?query=comp' \ --header 'x-api-key: YOUR_API_KEY' \ --header 'x-api-secret: YOUR_API_SECRET' ``` ## Base URLs | Environment | Base URL | | - | - | | **Staging** | `https://api-staging.respondent.io` | | **Production** | `https://api.respondent.io` | Build and test your integration in staging first, then switch to production when ready. See [Staging vs Production](/reference/staging-vs-production) for important differences. ## Rate limiting To ensure stability and fair usage across clients, our API enforces the following rate limits: | Environment | Requests per Second | Burst Capacity | Daily Quota | | - | - | - | - | | Production | 250 RPS | 500 requests | 1,000,000 requests/day | | Staging | 50 RPS | 100 requests | 200,000 requests/day | # Staging vs production Source: https://developers.respondent.io/reference/staging-vs-production Respondent provides both Staging and Production environments to support safe development and reliable deployment of your API integration. ## Staging The Staging environment is free to use and fully functional, allowing developers to build and test integrations in a risk-free environment. To learn more, see: [Staging Environment](/docs/Getting-started/staging-environment) * **Base URL:** [https://api-staging.respondent.io](https://api-staging.respondent.io) ## Production Once your integration is ready, switch to the Production environment. To request access, see: [Requirements for Production Credentials](/docs/Getting-started/requirements-for-production-credentials) * **Base URL**: [https://api.respondent.io](https://api.respondent.io) # Create a screener question Source: https://developers.respondent.io/screener-questions/create-a-screener-question post /v1/projects/{projectId}/screener-questions Add a screener question to a project. Supports up to 40 questions per project. Available types: `radio`, `checkbox`, `singleLineTextbox`, `multiLineTextbox`, `numericBox`, `sliderScale`, `fileUpload`, `rank`, and `matrix`. ## Supported question types | Type | Description | Requires `answers` | | - | - | - | | `radio` | Single-choice selection | Yes | | `checkbox` | Multi-choice selection | Yes | | `singleLineTextbox` | Short text input | No | | `multiLineTextbox` | Long text input | No | | `numericBox` | Numeric input | No | | `sliderScale` | Slider with min/max | No | | `fileUpload` | File attachment | No | | `rank` | Ordered ranking of the provided `answers` | Yes | | `matrix` | Grid of rows scored against `answers` columns; also requires `matrixRows` | Yes | # Create multiple screener questions Source: https://developers.respondent.io/screener-questions/create-multiple-screener-questions put /v1/projects/{projectId}/screener-questions/bulk Add up to 40 screener questions in a single request. This replaces any existing questions — use it for initial screener setup rather than incremental additions. # Delete screener question Source: https://developers.respondent.io/screener-questions/delete-screener-question delete /v1/projects/{projectId}/screener-questions/{screenerQuestionId} Remove a screener question from a project. Any existing responses to this question are preserved but the question will no longer be shown to new participants. # Reorder screener questions Source: https://developers.respondent.io/screener-questions/reorder-screener-questions patch /v1/projects/{projectId}/screener-questions/order Reorder screener questions to control the sequence in which they are presented to participants. # Retrieve screener questions Source: https://developers.respondent.io/screener-questions/retrieve-screener-questions get /v1/projects/{projectId}/screener-questions List all screener questions for a project, returned in display order. # Retrieve specific screener question Source: https://developers.respondent.io/screener-questions/retrieve-specific-screener-question get /v1/projects/{projectId}/screener-questions/{screenerQuestionId} Retrieve a single screener question by ID, including its answer options and configuration. # Update screener question Source: https://developers.respondent.io/screener-questions/update-screener-question patch /v1/projects/{projectId}/screener-questions/{screenerQuestionId} Update a screener question's text, type, or answer values. Only the fields you include are modified. # Cancel booking Source: https://developers.respondent.io/screener-responses/cancel-booking patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/cancel-booking Researcher-initiated cancel. Within 4 hours of the booked start, or after it has passed, the participant is marked attended and you are charged the full incentive and service fee. Use [Cancel booking (participant request)](/screener-responses/cancel-booking-participant-request) when the participant asked to cancel. To cancel and re-invite in one call, use [Cancel & Re-invite](/screener-responses/cancel-booking-&-re-invite). Researcher cancel only. If the participant asked to cancel, use [Cancel booking (participant request)](/screener-responses/cancel-booking-participant-request). This endpoint pays them if the session is less than 4 hours away. The participant is not re-invited. Use [Cancel booking & re-invite](/screener-responses/cancel-booking-&-re-invite) to cancel and reinvite in one call. # Cancel booking & re-invite Source: https://developers.respondent.io/screener-responses/cancel-booking-&-re-invite patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/cancel-booking-reinvite Researcher-initiated cancel and re-invite. Rejected less than 4 hours before the booked start, for both remote and in-person sessions. Combines two actions into a single API call. Reinvite is 4 hours for remote and in-person. That lead time is separate from the researcher-cancel charge window on [Cancel booking](/screener-responses/cancel-booking). If they need to cancel instead of picking a new time, use [Cancel booking (participant request)](/screener-responses/cancel-booking-participant-request), not [Cancel booking](/screener-responses/cancel-booking). Cancel booking pays them inside 4 hours. # Cancel booking (participant request) Source: https://developers.respondent.io/screener-responses/cancel-booking-participant-request patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/participant-cancel-booking Participant-initiated cancel. Always CANCELLED. The participant is not paid. No lead-time window. Do not use [Cancel booking](/screener-responses/cancel-booking). That path pays them if the session is less than 4 hours away. Use this for participant cancels. Do not call [Cancel booking](/screener-responses/cancel-booking). That pays them if the session is less than 4 hours away. Always `CANCELLED`, never paid. If you send `shortNoticeCancel`, it is stored. It does not start autopay. # Cancel invite Source: https://developers.respondent.io/screener-responses/cancel-invite patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/cancel-invite Cancel a pending invitation before the participant has accepted or scheduled. The participant is returned to their previous qualified state. # Hide participant Source: https://developers.respondent.io/screener-responses/hide-participant patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/hide Hide a participant from the default response list. Hidden participants can still be accessed by filtering explicitly. # Invite participant Source: https://developers.respondent.io/screener-responses/invite-participant patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/invite Invite a qualified participant to your study. For unmoderated studies, provide a `meetingLink`. For moderated studies, provide a `bookingLink` so the participant can schedule a session. While the participant is still in `INVITED` status, the invitation can be withdrawn with [Cancel invite](/screener-responses/cancel-invite). [Disqualifying](/screener-responses/qualify-participant) a participant does **not** withdraw an invitation that has already been sent. To withdraw an invitation, use [Cancel invite](/screener-responses/cancel-invite) while the response is still `INVITED`. ## Moderated vs unmoderated studies For unmoderated studies or surveys, provide a `meetingLink` — this URL is included in the participant's invitation email as the study link. For moderated studies, provide a `bookingLink` — the participant uses this to schedule a time with the researcher. # List screener responses Source: https://developers.respondent.io/screener-responses/list-screener-responses get /v1/projects/{projectId}/screener-responses List all screener responses for a project with filtering and pagination. Use query parameters to filter by status, qualification state, or date range. ## Typical workflow Retrieve screener responses and review participant answers. Use [Qualify Participant](/screener-responses/qualify-participant) to approve or reject based on screener answers. Send invitations via [Invite Participant](/screener-responses/invite-participant). After the session, use [Mark as Attended](/screener-responses/mark-as-attended) to trigger payment. ## File upload answers Answers to `fileUpload` questions carry a `files` array and an optional `note` the participant left with their upload. Each file includes its metadata (`name`, `size`, `mime`, `uploadedAt`, `scanStatus`) and a temporary `url` to download it. The internal storage key is not exposed on responses. Download URLs expire. Every URL is valid for at least one hour from the moment the response is fetched, but not indefinitely. Do not cache or store the URL. The recommended pattern is a download button in your UI backed by the most recently fetched URL. When a URL has expired, refetch either this list or the [specific screener response](/screener-responses/view-a-specific-response). Each fetch issues fresh URLs for every file. A file only carries a `url` once its virus scan has passed. Until then `scanStatus` is `pending` and the `url` property is absent. Refetch the response later to get the URL. A file whose `scanStatus` is `infected` is never downloadable. # Mark as attended Source: https://developers.respondent.io/screener-responses/mark-as-attended patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/attended Mark a participant as having attended the study session. **This triggers the incentive payment process** — ensure the session is complete before calling. ## Attendance and the first payment Whether marking a participant as attended fires the first payment depends on the project's `automations.autopay` setting (see [Create a project](/projects/create-a-project) or read it back with [Get a project](/projects/retrieve-a-specific-project)). * **`autopay: true`** (the default when omitted at project creation): calling this endpoint automatically charges the first incentive payment to your team's default payment card and pays the participant. Do **not** also call [Trigger manual payout](/screener-responses/trigger-manual-payout) with `payoutCount: 1` for the same participant — that would issue a second, duplicate payment. * **`autopay: false`**: this endpoint marks the participant as attended but does **not** issue any payment. You must send the first payment yourself by calling [Trigger manual payout](/screener-responses/trigger-manual-payout) with `payoutCount: 1`, or by processing the payment from the Respondent dashboard. There is no request-body option on this endpoint to mark a participant attended without paying when `autopay` is `true`. To decouple attendance from payment for a project, set `automations.autopay` to `false` at project creation (or via [Update a project](/projects/update-a-project)) and drive all payments through [Trigger manual payout](/screener-responses/trigger-manual-payout). For projects that pay participants more than once, see [How to use multiple payments](/docs/Screener-responses/multiple-payments). # Mark as favorite Source: https://developers.respondent.io/screener-responses/mark-as-favorite patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/favorite Toggle a participant's favorite status. Favorited participants can be easily filtered when reviewing responses. # Mark as no-show Source: https://developers.respondent.io/screener-responses/mark-as-no-show patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/no-show Mark a participant as a no-show. They are not paid. For moderated Partner API and external bookings, call this more than 15 minutes after the scheduled start. Do not use Cancel booking to record a no-show. A no-show is not paid. Do not use [Cancel booking](/screener-responses/cancel-booking) as a no-show. After start that path marks `ATTENDED` and pays. On the Partner API, call this more than 15 minutes after start. A call at exactly 15 minutes returns 400. Status is `NOSHOW`. On unmoderated studies, status is `INVALIDRESPONSE`. See [Response statuses](/docs/Screener-responses/working-with-responses). # Mark as rejected Source: https://developers.respondent.io/screener-responses/mark-as-rejected patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/reject Mark a participant as rejected for this project. The participant will not be paid and cannot be re-invited. # Post External Screener Answers Source: https://developers.respondent.io/screener-responses/post-external-screener-answers post /v1/projects/{projectId}/screener-responses/{screenerResponseId}/external-screener-answers Post a participant's answers from your own screener — external-screener organizations only. Rows upsert idempotently per questionId and must reference the project's declared `externalQuestions`. The application reads as submitted once every declared question has an answer, or earlier when the payload sets `complete: true`; the overall verdict is derived from the per-row `qualifies` flags. Post the answers BEFORE redirecting the participant back to the Respondent apply page they came from. Corrections are just another POST with the corrected rows. # Qualify participant Source: https://developers.respondent.io/screener-responses/qualify-participant patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/qualify Qualify or disqualify a participant based on their screener responses, before inviting them. When disqualifying (`qualifyStatus: false`), include a reason code: `MISREPRESENTED`, `CONTEXT`, `SCREENER`, or `OTHER`. Qualification is separate from the response lifecycle: this endpoint only updates the `qualified` flag (and the `disqualification` details) — it never changes the response `status`, and there is no `DISQUALIFIED` status. In particular, disqualifying a participant who has **already been invited** does not withdraw the invitation — the response stays `INVITED` and the participant can still start the study. To withdraw an invitation, use [Cancel invite](/screener-responses/cancel-invite); to act on a response after participation, use [Mark as rejected](/screener-responses/mark-as-rejected). ## Disqualification reason codes When disqualifying a participant (`qualifyStatus: false`), provide one or more reason codes: | Code | Description | | - | - | | `MISREPRESENTED` | False or misleading information provided by participant | | `CONTEXT` | Not enough information about participant | | `SCREENER` | Answers to screener questions not a match | | `OTHER` | Other reason (provide details in `message`) | # Report participant Source: https://developers.respondent.io/screener-responses/report-participant patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/report Report a participant for policy violations or quality issues. The Respondent team will review the report and take appropriate action. Reporting immediately dismisses the response: its `status` becomes `CANCELLED` and its `visibility.status` becomes `DISMISSED`. Dismissed responses are removed from the researcher UI and from default [List screener responses](/screener-responses/list-screener-responses) results (the list defaults to `visibilityStatus=VISIBLE`). To retrieve them later, pass `visibilityStatus=DISMISSED` — note that filter also includes other dismissed responses, not only reports, so prefer persisting the `screenerResponseId` (and any other identifiers you need) on your side before reporting. The reported participant also cannot apply to future studies from your organization. # Retrieve payout counts Source: https://developers.respondent.io/screener-responses/retrieve-payout-counts get /v1/projects/{projectId}/screener-responses/payouts Retrieve aggregated payout statistics for a project, including total paid, pending, and failed payment counts. # Schedule participant Source: https://developers.respondent.io/screener-responses/schedule-participant patch /v1/projects/{projectId}/screener-responses/{screenerResponseId}/schedule Schedule a participant for a moderated study session at a specific date and time. # Trigger manual payout Source: https://developers.respondent.io/screener-responses/trigger-manual-payout post /v1/projects/{projectId}/screener-responses/{screenerResponseId}/payouts Trigger an incentive payout to a participant. Use this for the first payment on projects with `autopay: false`, and for any subsequent payments (payments 2–5) on any project. ## When to call this endpoint Which call you make depends on the project's `automations.autopay` setting. Read it with [Get a project](/projects/retrieve-a-specific-project) if you are unsure. * **First payment, `autopay: true`**: do **not** call this endpoint. [Mark as attended](/screener-responses/mark-as-attended) already fires the first payment. Calling this endpoint with `payoutCount: 1` on top of that produces a duplicate payment. * **First payment, `autopay: false`**: call this endpoint with `payoutCount: 1` to issue the first payment. [Mark as attended](/screener-responses/mark-as-attended) does not pay the participant when autopay is off. * **Payments 2–5 (any project)**: call this endpoint with `payoutCount` set to the intended payment number (`2`, `3`, `4`, or `5`). Use [Retrieve payout counts](/screener-responses/retrieve-payout-counts) first to confirm how many payments the participant has already received. `payoutCount` is required and is the sequence number of the payment being made, not a delta. See [How to use multiple payments](/docs/Screener-responses/multiple-payments) for the full flow and the optional `tip` field. **`402 Payment Required` — no default payment card on file.** Payouts are charged to the team's default payment card. If the team has no default card configured, this endpoint returns a `402` with an explanatory `error` message instead of completing the payout. This is **not** a transient error — retrying will keep failing until a default payment card is added to the team in Respondent. Add a default card, then re-run the payout. # View a specific response Source: https://developers.respondent.io/screener-responses/view-a-specific-response get /v1/projects/{projectId}/screener-responses/{screenerResponseId} Retrieve a single screener response including the participant's answers, profile data, and current status. ## File upload answers Answers to `fileUpload` questions carry a `files` array and an optional `note` the participant left with their upload. Each file includes its metadata (`name`, `size`, `mime`, `uploadedAt`, `scanStatus`) and a temporary `url` to download it. The internal storage key is not exposed on responses. Download URLs expire. Every URL is valid for at least one hour from the moment the response is fetched, but not indefinitely. Do not cache or store the URL. The recommended pattern is a download button in your UI backed by the most recently fetched URL. When a URL has expired, refetch this screener response. Each fetch issues fresh URLs for every file. A file only carries a `url` once its virus scan has passed. Until then `scanStatus` is `pending` and the `url` property is absent. Refetch the response later to get the URL. A file whose `scanStatus` is `infected` is never downloadable. # Retrieve skill list Source: https://developers.respondent.io/skills/retrieve-skill-list get /v1/skills Search and retrieve skills used for project targeting. Use the returned IDs when configuring skill-based targeting on a project. **Note:** Skill IDs differ between staging and production. # Invite multiple past participants Source: https://developers.respondent.io/team-respondents/invite-multiple-past-participants put /v1/team-respondents/batch-invite Batch invite past participants to a new project. Send invitations to multiple participants in a single request for efficient re-engagement. # Retrieve past participant profile Source: https://developers.respondent.io/team-respondents/retrieve-past-participant-profile get /v1/team-respondents/profiles/{profileId} Retrieve a past participant's profile along with their engagement history across your organization's projects. # Search past participants Source: https://developers.respondent.io/team-respondents/search-past-participants get /v1/team-respondents Search across participants from your organization's previous projects. Filter by name, project, or engagement status to find candidates for new studies. # Retrieve topics list Source: https://developers.respondent.io/topics/retrieve-topics-list get /v1/topics Search and retrieve topics used for B2C project targeting. Use the returned IDs when setting `topicIds` on a project. **Note:** Topic IDs differ between staging and production. # Create webhook Source: https://developers.respondent.io/webhooks/create-webhook post /v1/webhooks Register a webhook URL to receive real-time event notifications. Each team can have **one active webhook**, which receives all events; registering a new one deactivates the previous webhook. Failed deliveries are retried up to 5 times at 10-minute intervals. ## Webhook behavior Failed deliveries are retried **up to 5 times** at 10-minute intervals. Your endpoint must respond within 3 seconds. Use [Retrieve Event Types](/webhooks/retrieve-webhook-event-types) to see all available events, then [Simulate](/webhooks/simulate-webhook-event) to test your handler. # Deactivate webhook Source: https://developers.respondent.io/webhooks/deactivate-webhook delete /v1/webhooks/{webhookId} Deactivate a webhook and permanently stop all event deliveries to its URL. You can register a new webhook afterward. # Retrieve specific webhook Source: https://developers.respondent.io/webhooks/retrieve-specific-webhook get /v1/webhooks/{webhookId} Retrieve a specific webhook by ID, including its URL, status, and configured event types. # Retrieve webhook Source: https://developers.respondent.io/webhooks/retrieve-webhook get /v1/webhooks Retrieve the currently configured webhook for your organization. # Retrieve webhook event types Source: https://developers.respondent.io/webhooks/retrieve-webhook-event-types get /v1/webhooks/{webhookId}/event-types List all available webhook event types that can be delivered to your endpoint. Use this to understand which events you can subscribe to. # Simulate webhook event Source: https://developers.respondent.io/webhooks/simulate-webhook-event post /v1/webhooks/{webhookId}/simulate Send a simulated test event to your webhook URL. Use this to verify your handler is correctly receiving and processing webhook payloads before going live.