Researcher API
Automate studies with the Populiva API: create and launch studies, review and bonus submissions, manage groups, message participants, and get signed webhooks when things happen. Create a token from your lab's Developer page.
Authentication
Send your lab's token as a Bearer token. Tokens have scopes: read (every GET), studies (create and run studies, review submissions, groups, and messages), and money (bonuses and invoices). Each token can make 600 requests a minute. Participants appear only as PARTICIPANT_IDs for your lab, and amounts are US dollars as strings.
curl https://populiva.com/api/v1/studies \
-H "Authorization: Bearer pv_..."
curl -X POST https://populiva.com/api/v1/submissions/123/approve \
-H "Authorization: Bearer pv_..."
Endpoints
| Method | Path | What it does |
|---|---|---|
| get | /api/v1/studies | List your lab's studies (newest first |
| post | /api/v1/studies | Create a draft study (title, description, language, external_url, estimated_minutes, max_minutes, reward, places, screeners, ...) |
| get | /api/v1/studies/{id} | A study |
| patch | /api/v1/studies/{id} | Update a draft study |
| post | /api/v1/studies/{id}/launch | Launch a draft (reserves its cost from your balance) |
| post | /api/v1/studies/{id}/pause | Pause a running study |
| post | /api/v1/studies/{id}/resume | Resume a paused study |
| post | /api/v1/studies/{id}/close | Close a study (releases unused reserve) |
| post | /api/v1/studies/{id}/add_places | Add places to a running study (count) |
| get | /api/v1/studies/{study_id}/submissions | A study's submissions (optional status filter) |
| get | /api/v1/studies/{study_id}/provenance_reports | A study's provenance reports |
| post | /api/v1/studies/{study_id}/provenance_reports | Issue a new provenance report version |
| get | /api/v1/submissions/{id} | A submission |
| post | /api/v1/submissions/{id}/approve | Approve a submission awaiting review |
| post | /api/v1/submissions/{id}/reject | Reject with a reason (no_completion, failed_attention_checks, did_not_follow_instructions, low_effort, duplicate_or_fraud) |
| post | /api/v1/submissions/{id}/bonus | Bonus an approved submission (amount, up to 100.00; money scope) |
| get | /api/v1/groups | Your lab's participant groups |
| post | /api/v1/groups | Create a group (name) |
| get | /api/v1/groups/{id} | A group and its PARTICIPANT_IDs |
| post | /api/v1/groups/{id}/members | Add participant_ids, or remove them with remove=true |
| post | /api/v1/messages | Message one participant (submission_id, body) or a study audience (study_id, audience, group_id, body) |
| get | /api/v1/balance | Your lab's available and reserved balance |
| get | /api/v1/invoices | Your lab's invoices |
| post | /api/v1/invoices | Request an invoice (amount, billing_name, billing_email, billing_address, po_number; money scope) |
Webhooks
Lab → Developer → Webhooks. Each delivery is a POST with a JSON body {id, event, created_at, data} and a `Populiva-Signature: t=<unix time>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your endpoint secret>` header. Reject deliveries whose signature doesn't match or whose t is more than 5 minutes old. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours.
Events: study.launched, study.paused, study.resumed, study.closed, submission.created, submission.reviewed.
# Ruby: verify a delivery
timestamp, signature = request.headers["Populiva-Signature"].scan(/(?:t|v1)=([^,]+)/).flatten
expected = OpenSSL::HMAC.hexdigest("SHA256", ENDPOINT_SECRET, "#{timestamp}.#{request.raw_post}")
valid = ActiveSupport::SecurityUtils.secure_compare(expected, signature) && Time.now.to_i - timestamp.to_i < 300