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'll need an experiment ID, which DataPipe assigns when you create your experiment. Code examples for jsPsych and JavaScript are 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 can be changed per experiment.

  • 32 MB per request. Enforced by the server infrastructure and not adjustable. A typical jsPsych dataset is 50 KB to 5 MB, so this mainly matters for base64 media. Gzipped request bodies are decompressed transparently, which in practice raises the ceiling for text data.
  • 60 seconds per request. Every /api/* path runs behind a hosting layer with a hard 60-second ceiling. That's why /api/finalize returns immediately and finishes its work in the background instead of waiting for the job to end.
  • JSON bodies only. Send Content-Type: application/json. The three participant endpoints don't 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

The trial size, session, abandonment, and file-size limits that apply only to incremental sessions. Save-as-you-go 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 set up, 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, or the request fails.
data

string

The file contents as a string.
sessionId

string (optional)

The session returned by /api/session/, if this experiment staged its trials as it went. It carries no data of its own. The data field above is still the submission. It only tells DataPipe which staged copy this request replaces, so DataPipe can discard it.

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 happen, rather than only at the end. A participant who abandons the experiment partway through then leaves behind a recoverable partial session instead of nothing at all.

You won't usually call this yourself. The @jspsych/extension-pipe extension calls it by default, along with the staging writes that follow, and datapipe-client does the same when a plain JavaScript experiment starts a session with it. It's documented here because those writes go to a Firebase Realtime Database rather than to this API, and this response tells a client where to send them.

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. 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 use up one of those sessions. The count is still taken when a submission completes. A 503 with SESSION_START_ERROR means incremental upload is unavailable, whether because the service is unreachable, because an experiment already has an unusually large number of sessions open at once, or because it has been switched off entirely. The experiment should submit at the end, as it would without streaming.

Example response

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

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 DataPipe tolerates a missing number rather than treating it 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 in order (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 couldn't 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.
400Rejected. 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

FILE_EXISTS, UPLOAD_ERROR, and UPLOAD_EXCEPTION are returned for every storage provider. INVALID_OSF_TOKEN occurs only on experiments still collecting to OSF, which is why it still names it.

INVALID_OSF_TOKEN, INVALID_REFRESH_TOKEN, and PROVIDER_TOKEN_EXPIRED no longer reject a submission. If a connected account's credential has expired, been revoked, or gone invalid, DataPipe queues the submission for retry (202, error: null), the same as a provider outage, because reconnecting the account fixes it. They are listed below because a queued entry's failureReason and the failure-notification email still name them. PROVIDER_NOT_CONNECTED is the one credential code still rejected outright: with no connection at all, a retry has nothing to succeed against.

Renamed in September 2026

Three codes dropped their OSF_ prefix: OSF_FILE_EXISTSFILE_EXISTS, OSF_UPLOAD_ERRORUPLOAD_ERROR, and OSF_UPLOAD_EXCEPTIONUPLOAD_EXCEPTION. They are returned on every provider, so the old names described nothing. If your experiment compares error against one of the old strings, that comparison no longer matches and the branch stops running — most often a retry that regenerates a filename after OSF_FILE_EXISTS. Match the new names, or both while you roll experiments over. The HTTP status codes are unchanged, so anything branching on those is unaffected. See What's changed for the rest of the September 2026 update.

The message text is human-readable only, and is reworded without notice. When writing code, match on the error code, not the message.

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 DataPipe could not read its configuration.
USER_DATA_NOT_FOUND

400

DataPipe could not read the account that owns the experiment.
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 set for this experiment.
INVALID_BASE64_DATA

400

The data is not valid base64.
METADATA_ERROR

400

DataPipe could not produce Psych-DS metadata from this submission, so it did not store the data. It keeps the submission and recovers it automatically in the background.
FILE_EXISTS

400

A file with this name already exists in the experiment's storage. Filenames must be unique.
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

202 (queued)

The API token for the storage provider has expired. The submission is queued and retried automatically rather than rejected. The owner must still create a new token and reconnect it, since retrying alone cannot fix an expired static token, but no participant sees an error for it.
INVALID_OSF_TOKEN

202 (queued)

The OSF token for this account is invalid or expired. Queued and retried automatically; reconnecting the account is what lets a later retry succeed.
INVALID_REFRESH_TOKEN

202 (queued)

The owner's refresh token is no longer valid (OSF, Google Drive, or Zenodo). Queued and retried automatically; reconnecting the account is what lets a later retry succeed.
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.
UPLOAD_EXCEPTION

500

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

500

DataPipe could not save the data, 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 hasn't 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 gets 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.
500DataPipe could not read a queued upload. Nothing has been deleted. Try again, or fetch the files one at a time.

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 gets 405.

FieldTypeDescription
experimentID

string

The experiment to finalize. Required.

The response doesn't 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.
500DataPipe could not schedule the background job. Nothing has been merged or deleted.

Statuses the dashboard reports

The outcome lands on the experiment record. These are all the possible statuses:

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 work around. 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. DataPipe deletes files only after verifying the archive that replaces them.

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