API reference

Every endpoint your experiment can call, what it returns, and the limits every request runs under.

All endpoints accept JSON request bodies with Content-Type: application/json. You will need an experiment ID, which you get when you create an experiment on DataPipe. Code examples for jsPsych and JavaScript are available on each experiment's dashboard.

The API is the same whichever storage provider an experiment uses. DataPipe routes each submission to that experiment's own destination — a Google Drive folder, a Dataverse dataset, or a Zenodo deposition — so your experiment code never names a provider.

Limits

Three limits apply to every request, and none of them is configurable per experiment.

  • 32 MB per request. Enforced by the server infrastructure and not raisable. A typical jsPsych dataset is 50 KB to 5 MB, so this bites mainly on base64 media. Gzipped request bodies are decompressed transparently, which effectively raises the ceiling for text data.
  • 60 seconds per request. Every /api/* path runs behind a hosting layer with a hard 60-second ceiling. It is why /api/finalize returns immediately and does its work in the background rather than answering when the job is done.
  • JSON bodies only. Send Content-Type: application/json. The three participant endpoints do not check the HTTP method, so a request sent with the wrong verb arrives with an empty body and comes back as MISSING_PARAMETER rather than 405. The two authenticated endpoints below do check, and answer 405.

Compressing a request body yourself, and what the size limit means in practice. Request size limits

Save text data

Save text data

POSThttps://pipe.jspsych.org/api/data/

Save a text file (CSV, JSON, etc.) to your experiment's storage. If you have validation rules configured, DataPipe checks the data before sending it on.

FieldTypeDescription
experimentID

string

Your experiment ID, found on the experiment dashboard.
filename

string

Name for the stored file (e.g., subject01.csv). Must be unique — the request will fail if a file with this name already exists.
data

string

The file contents as a string.
metadataOptions

object (optional)

Extra Psych-DS metadata to merge into this experiment's dataset description. Ignored unless metadata is switched on for the experiment.
sessionId

string (optional)

The session returned by /api/session/, if this experiment staged its trials as it went. It carries no data — the data field above is still the submission — and only tells DataPipe which staged copy this request supersedes, so it can be discarded.

Example request body

{
  "experimentID": "abc123",
  "filename": "subject01.csv",
  "data": "rt,response\n204,1\n389,0"
}

Start an incremental session

Start an incremental session

POSThttps://pipe.jspsych.org/api/session/

Open a session so an experiment can send trials as they are produced, rather than only at the end. A participant who abandons the experiment partway then leaves behind a recoverable partial session instead of nothing at all.

You will not usually call this directly — the jsPsychPipe plugin does it for you, along with the staging writes that follow. It is documented because those writes go to a Firebase Realtime Database rather than to this API, and this response is what tells a client where.

FieldTypeDescription
experimentID

string

Your experiment ID, found on the experiment dashboard.
filename

string (optional)

The name this participant will submit under. Used only to name a recovered partial session, so an abandoned run is identifiable rather than opaque. A completed submission always uses the filename sent to /api/data/.

The same checks as /api/data/ run here, with the same error codes: the experiment must exist, not be finalized, be accepting data, and be under its session limit. Starting a session does not consume one from that limit — the count is still taken when a submission completes. A 503 with SESSION_START_ERROR means incremental upload is unavailable and the experiment should simply submit at the end, as it would otherwise.

Example response

{
  "sessionId": "8fKq2mXpR7vNwLzB4cTy1dHs",
  "databaseURL": "https://<project>-default-rtdb.firebaseio.com",
  "maxTrialBytes": 65536,
  "maxTrials": 10000,
  "flushIntervalMs": 10000,
  "flushEveryNTrials": 10
}

Trials are then written to staging/<sessionId>/trials/<n> in that database, each one a JSON string, numbered from zero and never rewritten. The session is write-only: nothing can read it back, and a missing number is tolerated rather than treated as an error. Send sessionId with the final /api/data/ request to close it.

Save base64-encoded data

Save base64-encoded data

POSThttps://pipe.jspsych.org/api/base64/

Save a binary file (audio, video, images) encoded as a base64 string. DataPipe decodes the string and stores the resulting file alongside the experiment's other data.

FieldTypeDescription
experimentID

string

Your experiment ID.
filename

string

Name for the decoded file (e.g., recording_01.webm). Must be unique.
data

string

The base64-encoded file contents.

Get condition assignment

Get condition assignment

POSThttps://pipe.jspsych.org/api/condition/

Get the next condition number for balanced assignment. Returns a value from 0 to n−1, cycling sequentially (0, 1, 2, ..., 0, 1, 2, ...).

FieldTypeDescription
experimentID

string

Your experiment ID.

Example response

{
  "message": "Success",
  "condition": 2
}

Responses

All responses are JSON. On failure, the body carries an error code from the table below and a message describing the problem. When metadata is on, write responses also include a metadataMessage field reporting what happened to the metadata file; it never affects whether the data itself was stored.

StatusMeaning
201Stored. The body is { "message": "Success" }. The condition endpoint returns 200 with a condition field instead.
202Accepted and queued. DataPipe has your data safely but could not reach your storage provider yet, so it will retry automatically. error is null. Treat this as success and do not resubmit — retrying would store the participant's data twice.
400The request was rejected and the data was not stored.
500Something failed on our side. See the individual codes below for whether the data was stored.

What DataPipe does with a queued submission, and how to get it back. When an upload fails

Error codes

Codes beginning OSF_ are historical names kept for backward compatibility. OSF_FILE_EXISTS, OSF_UPLOAD_ERROR and OSF_UPLOAD_EXCEPTION are returned for every storage provider, not only OSF. INVALID_OSF_TOKEN and INVALID_REFRESH_TOKEN occur only on experiments still collecting to OSF.

The same applies to the message text: several messages still name OSF whatever provider an experiment actually uses — a queued upload reports “Data received. OSF upload will be retried automatically” on Google Drive, Dataverse, and Zenodo alike. Read “OSF” in a message as “your storage provider”, and match on the error code rather than the message when you are writing code.

Error codeStatusMeaning
MISSING_PARAMETER

400

One or more required fields are missing from the request body.
EXPERIMENT_NOT_FOUND

400

No experiment matches the provided ID.
EXPERIMENT_DATA_NOT_FOUND

400

The experiment exists but its configuration could not be read.
USER_DATA_NOT_FOUND

400

The account that owns the experiment could not be read.
INVALID_OWNER

400

The experiment owner does not match a valid user account.
EXPERIMENT_FINALIZED

400

The experiment has been finalized and no longer accepts submissions.
DATA_COLLECTION_NOT_ACTIVE

400

Data collection is not enabled for this experiment.
BASE64DATA_COLLECTION_NOT_ACTIVE

400

Base64 data collection is not enabled for this experiment.
CONDITION_ASSIGNMENT_NOT_ACTIVE

400

Condition assignment is not enabled for this experiment.
SESSION_LIMIT_REACHED

400

The experiment has reached its session limit. Raise the limit in the dashboard.
INVALID_DATA

400

The data did not pass the validation rules configured for this experiment.
INVALID_BASE64_DATA

400

The data is not valid base64.
METADATA_ERROR

400

Psych-DS metadata could not be produced from this submission, so the data was not stored. The submission is kept and recovered automatically in the background.
OSF_FILE_EXISTS

400

A file with this name already exists in the experiment's storage. Filenames must be unique.
OSF_UPLOAD_ERROR

400

The storage provider rejected the upload.
PROVIDER_NOT_CONNECTED

400

The owner has not connected an account for this experiment's storage provider.
PROVIDER_TOKEN_EXPIRED

400

The API token for the storage provider has expired. The owner must create a new one and reconnect it.
INVALID_OSF_TOKEN

400

The OSF token for this account is invalid or expired.
INVALID_REFRESH_TOKEN

400

The owner's OSF refresh token is no longer valid.
UNKNOWN_ERROR_GETTING_CONDITION

400

An unexpected error occurred while assigning a condition.
TOKEN_RESOLUTION_ERROR

500

DataPipe could not resolve the owner's storage credentials.
OSF_UPLOAD_EXCEPTION

500

An unexpected error occurred while uploading to the storage provider.
DATA_PERSIST_ERROR

500

DataPipe could not save the data. It was not stored, and a live participant may need to resubmit.

Queue status

Queue status

GEThttps://pipe.jspsych.org/api/queuestatus

List the queued uploads DataPipe is holding for an experiment, or download them. This is the endpoint behind the queued files panel on the dashboard, and the scriptable way to recover data that has not reached your storage provider.

Unlike the three participant endpoints, this one is authenticated: send a Firebase ID token for the account that owns the experiment as Authorization: Bearer <token>. Anything other than GET is answered 405.

FieldTypeDescription
experimentID

query string

The experiment whose queue you want. Required.
download

query string (optional)

The id of a single queue entry. Responds with that file's contents as an attachment, decoded back to the original bytes for base64 submissions.
downloadAll

query string (optional)

Set to true to receive every waiting, in-flight and failed file for the experiment as one ZIP.

With no download or downloadAll, the response is 200 with an entries array and a count, newest first. Each entry carries id, filename, dataType, status, errorCode, retryCount, maxRetries, createdAt, lastAttemptAt, nextRetryAt and failureReason. Only entries that are pending, processing or failed are listed — a completed upload leaves the queue.

Example response

{
  "entries": [
    {
      "id": "abc123_subject01.csv",
      "filename": "subject01.csv",
      "dataType": "data",
      "status": "pending",
      "errorCode": 503,
      "retryCount": 2,
      "maxRetries": 5,
      "createdAt": "2026-08-22T14:03:11.000Z",
      "lastAttemptAt": "2026-08-22T17:03:44.000Z",
      "nextRetryAt": "2026-08-22T21:03:44.000Z",
      "failureReason": "Provider error 503: Service Unavailable"
    }
  ],
  "count": 1
}
StatusMeaning
400No experimentID query parameter.
401No Authorization header, or the token could not be verified.
403You do not own that experiment. A nonexistent experiment gets the same answer, so this endpoint never reveals which experiment IDs exist.
404The requested queue entry does not belong to that experiment, or downloadAll found nothing queued.
405The request was not a GET.
500A queued upload could not be read. Nothing has been deleted — try again, or fetch the files individually.

Finalize

Finalize

POSThttps://pipe.jspsych.org/api/finalize

Start finalizing an experiment: merge everything in its storage into one archive and permanently stop accepting submissions. This is the endpoint behind the Finalize control on the dashboard.

Authenticated the same way as queue status — Authorization: Bearer <token> for the owning account. Anything other than POST is answered 405.

FieldTypeDescription
experimentID

string

The experiment to finalize. Required.

The response does not tell you the outcome. Merging a whole study takes longer than the 60-second request ceiling, so a successful call returns 202 with { "status": "queued" } and the work runs in the background. Watch the experiment's dashboard, which reports queued, then running, then the result. Calling again while a pass is in flight returns 202 with the current status rather than starting a second one.

StatusMeaning
202Accepted. Body is { "status": "queued" }, or queued/running if a pass was already under way.
200{ "status": "already-finalized" }. Nothing to do; finalizing is permanent.
400No experimentID, or the experiment predates the per-experiment storage DataPipe now creates and carries { "status": "not-eligible" } with a detail.
401Missing or unverifiable bearer token.
403You do not own that experiment, or it does not exist.
405The request was not a POST.
500The background job could not be scheduled. Nothing has been merged or deleted.

Statuses the dashboard reports

The outcome lands on the experiment record. The full vocabulary is:

StatusMeaning
queued

in flight

The background job has been scheduled.
running

in flight

The merge is under way.
finalized

done

One archive now holds the whole dataset and the experiment accepts no further submissions.
already-finalized

done

It had already been finalized.
not-eligible

refused

This storage provider has no file-count ceiling to relieve — today that means anything other than Zenodo.
queued-uploads-pending

refused

Uploads are still waiting to be stored, and they belong inside the archive. Let the upload queue drain and try again.
nothing-to-archive

refused

The experiment has never received any data.
leased-elsewhere

refused

Another finalizing pass or archive merge is already running for this experiment. Try again shortly.
archive-too-large

refused

The merged archive would exceed the provider's per-file limit. Nothing was uploaded or deleted.
failed

error

Something went wrong during the pass. Files are only ever deleted after the archive that replaces them is verified.

What finalizing does to your files, and which providers support it. Finishing a study

Created by the developers of jsPsych ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Test environment. Data sent here is not preserved. Do not sign in with production credentials.