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.

Status codes

StatusCodeMeaning
complete10Qualified and finished
terminate20Did not qualify
overquota30Qualified but the quota was full
quality40Failed a quality check
in_progress0Entered, no outcome yet (API only)

Router reasons, with their GDQ code where the reason is a quality removal:

ReasonOutcomeGDQ code
project_closedOver quotaNone
quota_fullOver quotaNone
duplicate_respondentQuality terminate4
duplicate_deviceQuality terminate4
geo_mismatchQuality terminate3
speederQuality terminate6
automationQuality terminate1
anonymized_networkQuality terminate2
fraud_toolQuality terminate2

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.

  1. Build the full URL with every parameter except h.
  2. Take the part starting at the path: everything from the first / after the host. Example: /s/end/complete?rdid=01k8zq4r2mxv6c3e9ab7t5wq1n.
  3. Compute HMAC-SHA256 of that string with your secret, as lowercase hex.
  4. 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 statusMeaning
passedNo risk found; survey opened
flaggedRisk found; allowed because the project monitors, or a suspicious session in enforce mode without suspicious blocking
blockedStopped before the survey; the session is a quality terminate with a router reason
unavailableThe 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.

CodeReason
1Bot detection
2Third party fraud tool failure
3Geo-location check
4Participant duplication
5Suspicious survey entry time
6Speeding / racing
7Excessive interview length
8Straight lining / flat lining
9Inconsistent / contradictory answers
10Red herring / explicit trap question
11Knowledge question
12Over-qualification / over-claiming
13Open end: poor quality
14Open end: duplicate
15Open end: AI completed
16Duplicate responses in closed questions
17Ghost complete
18Unspecified 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 live stats (entries, completes, terminates, over quotas, quality terminates, incidence, conversion, average LOI).
  • PATCH /api/v1/buyer/projects/{code}: change name, survey_url, loi_minutes, incidence_rate, completes_target, min_loi_seconds, signing and duplicate settings, or status (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 statusMeaning
401Missing or invalid API key
403Account paused
404Resource not found or not owned by your account
409Session already has an outcome
422Validation failed; see errors
429Rate limit reached: 300 requests per minute per key

Respondent-facing links are limited to 120 requests per minute per IP address.