API documentation
Everything needed to integrate with ResearchDart as a buyer or a supplier: link formats, status codes, signatures, postbacks, pre-survey screening, GDQ-coded reversals and the REST API.
Concepts
- Project: one buyer survey, identified by a code such as
RDK7M2QX. - Allocation: one supplier's access to one project, with its own entry link, payout and optional quota.
- Transaction: one respondent session, identified by a 26-character lowercase
rdid. - RID: the supplier's own respondent ID. Opaque to ResearchDart, up to 128 characters, never shown to buyers.
Entry links
Suppliers send respondents to:
https://researchdart.com/s/entry/{allocation_code}?rid={RID}
The response is a 302 redirect into the buyer survey, or an outcome page when the router closes the session immediately (see quality controls). Projects with pre-survey screening return a holding page first, described below. URL-encode the RID if it contains reserved characters.
Endlinks
Buyers put the session ID into their survey URL with the {RDID} placeholder. If the survey URL has no placeholder, rdid is appended as a query parameter. At the end of the survey, redirect to one of:
https://researchdart.com/s/end/complete?rdid={RDID}
https://researchdart.com/s/end/terminate?rdid={RDID}
https://researchdart.com/s/end/overquota?rdid={RDID}
https://researchdart.com/s/end/quality?rdid={RDID}
Numeric alternative for platforms that can only change a single value:
https://researchdart.com/s/end?st=10&rdid={RDID}
The first outcome recorded for a session is final. Endlinks and the status API both follow this rule.
Status codes
| Status | Code | Meaning |
|---|---|---|
complete | 10 | Qualified and finished |
terminate | 20 | Did not qualify |
overquota | 30 | Qualified but the quota was full |
quality | 40 | Failed a quality check |
in_progress | 0 | Entered, no outcome yet (API only) |
Router reasons, with their GDQ code where the reason is a quality removal:
| Reason | Outcome | GDQ code |
|---|---|---|
project_closed | Over quota | None |
quota_full | Over quota | None |
duplicate_respondent | Quality terminate | 4 |
duplicate_device | Quality terminate | 4 |
geo_mismatch | Quality terminate | 3 |
speeder | Quality terminate | 6 |
automation | Quality terminate | 1 |
anonymized_network | Quality terminate | 2 |
fraud_tool | Quality terminate | 2 |
Signatures
Entry links (when the supplier enables signing) and endlinks (when the project requires it) carry an HMAC-SHA256 signature in a final h parameter.
- Build the full URL with every parameter except
h. - Take the part starting at the path: everything from the first
/after the host. Example:/s/end/complete?rdid=01k8zq4r2mxv6c3e9ab7t5wq1n. - Compute HMAC-SHA256 of that string with your secret, as lowercase hex.
- Append
&h=and the signature. It must be the last parameter.
Scheme and host are not signed, so http, https and www variants all verify. Buyers use the callback secret shown on their account; suppliers use their entry secret.
Only compute signatures on a server. Never put a secret in JavaScript that runs in the respondent's browser. If your survey platform cannot sign server-side, report outcomes with the status API instead.
PHP
$path = '/s/end/complete?rdid=' . $rdid;
$url = 'https://researchdart.com' . $path . '&h=' . hash_hmac('sha256', $path, $secret);
Python
import hashlib, hmac
path = f"/s/end/complete?rdid={rdid}"
signature = hmac.new(secret.encode(), path.encode(), hashlib.sha256).hexdigest()
url = f"https://researchdart.com{path}&h={signature}"
Node.js
const crypto = require('crypto');
const path = `/s/end/complete?rdid=${rdid}`;
const signature = crypto.createHmac('sha256', secret).update(path).digest('hex');
const url = `https://researchdart.com${path}&h=${signature}`;
Supplier redirects
Suppliers configure one redirect URL per outcome. These placeholders are replaced and URL-encoded:
{RID} | Your respondent ID, exactly as sent on the entry link |
{RDID} | ResearchDart transaction ID |
{STATUS} | Outcome slug: complete, terminate, overquota or quality |
{CODE} | Outcome code: 10, 20, 30 or 40 |
{PAYOUT} | Payout in USD for completes, otherwise 0.00 |
{PROJECT} | ResearchDart project code |
{LOI} | Length of interview in seconds |
{TEST} | 1 for sandbox sessions, otherwise 0 |
Respondents see a short outcome page first and are returned after a few seconds, with a button to return immediately.
Postbacks
If a supplier sets a postback URL, ResearchDart sends an HTTP GET to it as soon as a session has an outcome, with the same placeholders. Any 2xx response counts as delivered. Other responses and timeouts (5 seconds) are retried after 1, 5, 30, 120 and 720 minutes, then marked failed. The request uses the user agent ResearchDart-Postback/1.0.
Make your endpoint idempotent on {RDID}: a retry after a slow success can deliver the same outcome twice.
Pre-survey screening
When a project's screening_mode is monitor or enforce, the entry link does not redirect straight to the survey. It returns a short holding page (HTTP 200, noindex) that loads the screening provider's browser script, then posts the provider session to ResearchDart:
POST https://researchdart.com/s/screen/{rdid}
{ "token": "…", "session_id": "…" }
{ "next": "https://survey.example.com/s/abc?rdid=…" }
The browser follows next: the buyer survey when the session passes, or a signed outcome page when it is blocked. If the script cannot load within six seconds the page reports that instead, and the installation's outage policy decides whether the respondent continues. Browsers without JavaScript fall back to a plain GET on the same path.
| Screening status | Meaning |
|---|---|
passed | No risk found; survey opened |
flagged | Risk found; allowed because the project monitors, or a suspicious session in enforce mode without suspicious blocking |
blocked | Stopped before the survey; the session is a quality terminate with a router reason |
unavailable | The provider could not answer; allowed under the fail-open policy |
Nothing changes for suppliers or buyers: entry links, survey URLs and endlinks stay the same. The screening status appears on every transaction in the API.
Verisoul configuration
Set these on the server. VERISOUL_SDK_URL should point at your first-party custom hostname in production so ad blockers do not stop the script.
VERISOUL_ENABLED=true
VERISOUL_ENV=prod
VERISOUL_PROJECT_ID=…
VERISOUL_API_KEY=…
VERISOUL_SDK_URL=https://js.researchdart.com/bundle.js
RD_SCREENING_FAIL_OPEN=true
GDQ removal codes
Reversals use the Global Data Quality feedback loop code frame. Send the numeric code as gdq_code.
| Code | Reason |
|---|---|
| 1 | Bot detection |
| 2 | Third party fraud tool failure |
| 3 | Geo-location check |
| 4 | Participant duplication |
| 5 | Suspicious survey entry time |
| 6 | Speeding / racing |
| 7 | Excessive interview length |
| 8 | Straight lining / flat lining |
| 9 | Inconsistent / contradictory answers |
| 10 | Red herring / explicit trap question |
| 11 | Knowledge question |
| 12 | Over-qualification / over-claiming |
| 13 | Open end: poor quality |
| 14 | Open end: duplicate |
| 15 | Open end: AI completed |
| 16 | Duplicate responses in closed questions |
| 17 | Ghost complete |
| 18 | Unspecified issue / other |
API authentication
Each buyer and supplier account has one API key, issued in the console and shown once. Send it as a bearer token. Base URL: https://researchdart.com/api/v1.
curl https://researchdart.com/api/v1/supplier/surveys \
-H "Authorization: Bearer rd_your_api_key" \
-H "Accept: application/json"
Rotating a key invalidates the previous one immediately.
Supplier API
List live surveys
GET /api/v1/supplier/surveys returns every live project allocated to you.
{
"data": [
{
"project_code": "RDK7M2QX",
"country": "US",
"language": "en",
"loi_minutes": 12,
"incidence_rate": 34,
"payout": "1.75",
"completes_remaining": 588,
"entry_link": "https://researchdart.com/s/entry/k3m9q2xa7hpd?rid={RID}",
"entry_signature_required": false,
"updated_at": "2026-09-24T14:31:07+00:00"
}
]
}
Get one survey
GET /api/v1/supplier/surveys/{project_code}. Returns 404 once the survey is no longer live for you.
Reconcile sessions
GET /api/v1/supplier/transactions with optional since (ISO 8601, matches last update), status, project_code and per_page (max 500). Results are cursor-paginated; follow links.next.
{
"data": [
{
"rdid": "01k8zq4r2mxv6c3e9ab7t5wq1n",
"rid": "4471",
"project_code": "RDK7M2QX",
"status": "complete",
"status_code": 10,
"reason": null,
"payout": "1.75",
"loi_seconds": 684,
"is_test": false,
"entered_at": "2026-09-24T14:22:39+00:00",
"ended_at": "2026-09-24T14:34:03+00:00",
"gdq_code": null,
"screening_status": "passed",
"reversal": null
}
],
"links": { "next": null }
}
GET /api/v1/supplier/transactions/{rdid} returns a single session.
Buyer API
Create a project
POST /api/v1/buyer/projects. Projects are created as draft unless you send a status.
curl -X POST https://researchdart.com/api/v1/buyer/projects \
-H "Authorization: Bearer rd_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Snack concept test",
"survey_url": "https://survey.example.com/s/abc?id={RDID}",
"country": "GB",
"language": "en",
"loi_minutes": 10,
"incidence_rate": 35,
"cpi": 3.20,
"completes_target": 400,
"min_loi_seconds": 180,
"require_callback_hash": true
}'
The response includes the project code and its four endlinks.
Read and update projects
GET /api/v1/buyer/projects: paginated list.GET /api/v1/buyer/projects/{code}: includes livestats(entries, completes, terminates, over quotas, quality terminates, incidence, conversion, average LOI).PATCH /api/v1/buyer/projects/{code}: changename,survey_url,loi_minutes,incidence_rate,completes_target,min_loi_seconds, signing and duplicate settings, orstatus(draft,live,paused,closed). Price, country and language are fixed after creation.GET /api/v1/buyer/projects/{code}/transactions: cursor-paginated sessions.
Report an outcome server-to-server
POST /api/v1/buyer/transactions/{rdid}/status with status as a name or code.
curl -X POST https://researchdart.com/api/v1/buyer/transactions/01k8zq4r2mxv6c3e9ab7t5wq1n/status \
-H "Authorization: Bearer rd_your_api_key" \
-H "Accept: application/json" \
-d status=complete
Returns 200 with "recorded": true when this call set the outcome, or 409 with "recorded": false and the existing outcome when the session had already ended.
Reversals
POST /api/v1/buyer/transactions/{rdid}/reversal reverses a recorded complete after data cleaning.
curl -X POST https://researchdart.com/api/v1/buyer/transactions/01k8zq4r2mxv6c3e9ab7t5wq1n/reversal -H "Authorization: Bearer rd_your_api_key" -H "Accept: application/json" -d gdq_code=15 -d note="OE2 copied from a chatbot"
Returns 200 with "reversed": true, or 409 with "reversed": false when the session is not a complete or was already reversed. The transaction's reversal object carries gdq_code, gdq_label, note and reversed_at. Reversed completes stop counting toward quotas, and the supplier payout for the session becomes 0.00.
Errors and limits
| HTTP status | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 403 | Account paused |
| 404 | Resource not found or not owned by your account |
| 409 | Session already has an outcome |
| 422 | Validation failed; see errors |
| 429 | Rate limit reached: 300 requests per minute per key |
Respondent-facing links are limited to 120 requests per minute per IP address.