> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.prolific.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.prolific.com/_mcp/server.

# Defining a Dataset Schema

A dataset schema lets you describe the structure of your data before uploading it. Schemas are a feature of **V4 datasets** and enable:

* Named, typed fields that drive what participants see
* `dataset_field` items in `batch_items` that pull dataset values directly into your task layout
* Per-record validation during import (with strict mode)
* Structured metadata and custom task grouping

## Creating a dataset with a schema

Pass a `schema` object when creating a dataset.

```bash
POST /api/v1/data-collection/datasets
```

```json
{
  "name": "AI response evaluation",
  "workspace_id": "6278acb09062db3b35bcbeb0",
  "schema": {
    "strict": true,
    "fields": {
      "prompt": { "type": "text", "label": "Prompt" },
      "source_audio": { "type": "audio_url", "label": "Audio clip" },
      "source_video": { "type": "video_url", "label": "Video clip" },
      "response_a": { "type": "text", "label": "Response A" },
      "response_b": { "type": "text", "label": "Response B" },
      "category": { "type": "metadata" }
    }
  }
}
```

## Field types

| Type            | Description                                                                                                                                                                                                                                                                                                                                                                   |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`          | A text value displayed to participants. Can be referenced by `dataset_field` items in `batch_items`.                                                                                                                                                                                                                                                                          |
| `image_url`     | A URL pointing to an image displayed to participants. Can be referenced by `dataset_field` items in `batch_items`.                                                                                                                                                                                                                                                            |
| `audio_url`     | A URL pointing to an audio file displayed to participants in Prolific's audio player. Can be referenced by `dataset_field` items in `batch_items`.                                                                                                                                                                                                                            |
| `video_url`     | A URL pointing to a video file displayed to participants in Prolific's video player. Can be referenced by `dataset_field` items in `batch_items`. If your task could benefit from participants citing specific frame numbers, you can turn on frame controls by supplying a `frame_rate` attribute — see [Frame controls for video fields](#frame-controls-for-video-fields). |
| `metadata`      | An internal value included in exports but not shown to participants. Equivalent to the `META_` column prefix in V3 CSV datasets.                                                                                                                                                                                                                                              |
| `task_group_id` | Groups datapoints into task groups. Rows with the same value are assigned to the same participant in one submission. At most one field per schema may have this type.                                                                                                                                                                                                         |

## Audio URL fields

Use `audio_url` for externally hosted source audio that participants should listen to during a Batch task.

* **Prolific does not host source media.** `audio_url` fields must point to audio files hosted outside Prolific.
* **URLs must remain valid for the full study duration.** The audio URL should stay reachable and accessible from dataset import through participant completion.
* **Use direct-file HTTPS URLs only.** Manifest-based streaming formats such as `.m3u8` and `.mpd` are not supported.
* **Supported audio formats:** `.mp3`, `.wav`, `.aac`, and `.m4a`.
* **`Access-Control-Allow-Origin` (CORS):** without it the audio can still play, but the player shows a generic placeholder waveform instead of the decoded waveform preview because waveform generation uses a cross-origin fetch.
* **`Cache-Control` and/or `ETag`:** without cache headers the same file may be downloaded twice — once to decode the waveform and once again for playback.

## Video URL fields

Use `video_url` for externally hosted source video that participants should watch during a Batch task.

* **Prolific does not host source media.** `video_url` fields must point to video files hosted outside Prolific.
* **URLs must remain valid for the full study duration.** The video URL should stay reachable and accessible from dataset import through participant completion.
* **Use direct-file HTTPS URLs only.** Manifest-based streaming formats such as `.m3u8` and `.mpd` are not supported and are rejected during dataset validation.
* **Supported video formats:** `.mp4`, `.mov`, `.webm`, and `.m4v`.
* **`Access-Control-Allow-Origin` (CORS):** without it the video can still play, but the player shows a generic placeholder poster frame instead of the real one, because the poster frame is decoded via a cross-origin `fetch`.
* **`Cache-Control` and/or `ETag`:** without cache headers the same file may be downloaded twice — once to decode the poster frame and once again for playback.

> **Note**
>
> If your task could benefit from participants citing specific frame numbers, you can turn on frame controls by supplying a `frame_rate` attribute for each video. See [Frame controls for video fields](#frame-controls-for-video-fields) for details.

## Frame controls for video fields

To let participants refer to a specific frame in a video, give each video its frame rate in your dataset. A video with a frame rate gets a frame counter and frame-step buttons in the participant's video player.

> **Note**
>
> **There is no layout setting for frame controls, and no default frame rate.** A video with a frame rate gets frame controls automatically. A video without one gets the standard player with no counter. Prolific never guesses a frame rate, because a wrong one records wrong frame numbers.

The frame rate belongs to each video file, so it goes in your data rather than in the task layout, and different rows can hold videos at different rates.

1. Add a column for the rate and declare it in the schema as a `metadata` field. You can name it anything.
2. On the `video_url` field, bind the `frame_rate` attribute to that column with `attributes`.

```json
{
  "fields": {
    "video_a":     { "type": "video_url", "attributes": { "frame_rate": "video_a_fps" } },
    "video_a_fps": { "type": "metadata" },
    "prompt":      { "type": "text" }
  }
}
```

Each row then supplies its own rate, for example `{ "prompt": "…", "video_a": "https://…/clip.mp4", "video_a_fps": 25 }`.

* `frame_rate` is the attribute name Prolific defines. The column name is yours.
* The rate is a number from 1 to 480 (frames per second). Fractional rates such as `29.97` are allowed, and a numeric string is accepted.
* A row with an **invalid** rate is rejected at import, and the error names the rate column. A **missing or blank** rate is not an error: that video simply has no frame controls. In a strict schema the column must still be present.
* The rate column is `metadata`, so participants do not see it. Several video fields can share one column.
* `attributes` is only allowed for supported attributes on the field types that support them, and each column must be a declared `metadata` field in the same schema. Anything else is rejected with `400`.
* Frames are numbered from 1.
* To add, change or remove a binding on a dataset that already has data, update the schema: existing rows are reprocessed from the original upload, so the column must have been in that upload. Uploading the same file again does not apply the change.

## Strict mode

The `strict` flag controls how missing fields are handled during import.

| Mode              | Behaviour                                                                                 |
| ----------------- | ----------------------------------------------------------------------------------------- |
| `"strict": true`  | Records missing any schema field are **rejected**. Use this to enforce data completeness. |
| `"strict": false` | Records with missing fields are **accepted**. Missing field values are treated as absent. |

## Schema constraints

* Maximum **200 fields** per schema.
* Field keys: **1–128 characters**.
* Field labels: maximum **255 characters**.
* At most **one field** of type `task_group_id` per schema.

## Referencing schema fields in the batch layout

Once a dataset with a schema is attached to a batch, you can use `dataset_field` items in `batch_items` to display dataset values to participants.

```json
{
  "batch_items": [
    {
      "rows": [
        {
          "columns": [
            {
              "items": [
                { "type": "dataset_field", "field": "prompt" },
                { "type": "dataset_field", "field": "response_a" },
                { "type": "dataset_field", "field": "response_b" },
                {
                  "type": "multiple_choice",
                  "description": "Which response is more helpful?",
                  "answer_limit": 1,
                  "options": [
                    { "label": "Response A", "value": "a" },
                    { "label": "Response B", "value": "b" },
                    { "label": "Neither", "value": "neither" }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

> **Note**
>
> Only fields of type `text`, `image_url`, `audio_url`, or `video_url` can be referenced by `dataset_field` items. `metadata` and `task_group_id` fields are not displayed to participants.

> **Note**
>
> Frame controls on a video are not set in the layout. A `video_url` field shows them whenever the dataset supplies a frame rate for that video. See [Frame controls for video fields](#frame-controls-for-video-fields).

## Retrieving a dataset with its schema

```bash
GET /api/v1/data-collection/datasets/{dataset_id}
```

The response includes the current schema and all import jobs:

```json
{
  "id": "0192a3b5-e8f9-7a0b-1c2d-3e4f5a6b7c8d",
  "name": "AI response evaluation",
  "schema_version": 4,
  "total_datapoint_count": 1000,
  "schema": {
    "strict": true,
    "fields": {
      "prompt": { "type": "text", "label": "Prompt" },
      "source_audio": { "type": "audio_url", "label": "Audio clip" },
      "source_video": { "type": "video_url", "label": "Video clip" },
      "response_a": { "type": "text", "label": "Response A" },
      "response_b": { "type": "text", "label": "Response B" },
      "category": { "type": "metadata" }
    }
  },
  "imports": [
    {
      "import_id": "01935c2d-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
      "status": "complete",
      "accepted_count": 1000
    }
  ]
}
```