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

# Appending data to a dataset

Once a dataset has batches attached, you often need to add more datapoints — new reviews arrive, a labelling round surfaces extra cases, or you want to grow a live task. The append endpoint adds datapoints to an existing **V4 dataset** synchronously, in a single request, and (when configured) propagates them straight into the tasks of any attached batch.

For the full dataset lifecycle, see [Working with Datasets](/api-reference/ai-task-builder/datasets). To learn how appended datapoints reach existing tasks, see [Syncing a batch](/api-reference/ai-task-builder/syncing-a-batch).

## When to append vs re-upload

Append and file upload solve different problems. Pick based on how much data you're adding and whether the dataset is already in use.

| Use **append** (`POST .../datapoints`) when…                      | Use **file upload** (presigned URL flow) when…  |
| ----------------------------------------------------------------- | ----------------------------------------------- |
| Adding datapoints to a dataset that already has batches attached  | Performing the initial load of a dataset        |
| The addition is small — up to **1000 records** per request        | Loading more than 1000 records at once          |
| You want new datapoints materialised into live tasks via sync     | You don't need incremental, in-request feedback |
| You want per-record results back immediately, with no job to poll | A larger import you're happy to poll for        |

Append is **V4-only** and **JSONL-only**. For the file-upload flow (which also supports CSV and V3 datasets), see [Uploading data](/api-reference/ai-task-builder/datasets#uploading-data).

## Appending datapoints

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

Send your datapoints as JSONL in the request body — one JSON object per line. Field names must match the keys defined in the [dataset schema](/api-reference/ai-task-builder/dataset-schema), exactly as for a JSONL file upload.

```bash
curl -X POST \
  -H "Authorization: Token <API_TOKEN>" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @new-reviews.jsonl \
  "https://api.prolific.com/api/v1/data-collection/datasets/<dataset-id>/datapoints"
```

Where `new-reviews.jsonl` contains:

```
{"review_text": "Fast delivery, works great", "product_name": "Widget Pro", "source": "amazon"}
{"review_text": "Stopped working after a week", "product_name": "Widget Pro", "source": "trustpilot"}
{"review_text": "Exactly as described", "product_name": "Basic Widget", "source": "amazon"}
```

This endpoint accepts **JSONL only**. CSV is rejected with a `400` — a CSV body needs a header row per request, which doesn't fit a record-by-record append. To append from a CSV, convert it to JSONL first, or use the [file-upload flow](/api-reference/ai-task-builder/datasets#uploading-data).

### Requirements

The append is rejected before any writes if:

| Condition                                         | Response                                            |
| ------------------------------------------------- | --------------------------------------------------- |
| Dataset is not V4                                 | `400` — appending is only supported for V4 datasets |
| Dataset has no schema                             | `400` — define a schema before appending            |
| More than 1000 records in one request             | `400` — split the payload, or use a file upload     |
| An import is already in progress for the dataset  | `409` — retry once it completes                     |
| A schema migration is in progress for the dataset | `409` — retry once it completes                     |

## Response

The append is **synchronous**. The response reports the outcome directly:

```json
{
  "accepted": 3,
  "rejected": 0,
  "errors": []
}
```

| Field      | Type   | Description                                                                   |
| ---------- | ------ | ----------------------------------------------------------------------------- |
| `accepted` | number | Records that passed validation — newly written **plus** idempotent duplicates |
| `rejected` | number | Records that failed parsing or schema validation                              |
| `errors`   | array  | Per-record error detail (empty when `rejected` is 0)                          |

Accepted datapoints are persisted immediately and sort **after** every existing datapoint in the dataset.

## Handling partial outcomes

Valid records are written even if others in the same request fail. When any record is rejected, the successful ones are still accepted and the response lists what went wrong per record:

```json
{
  "accepted": 2,
  "rejected": 1,
  "errors": [
    { "record_index": 2, "field": "rating", "reason": "Value is not a valid number" }
  ]
}
```

| Error field    | Meaning                                                                                         |
| -------------- | ----------------------------------------------------------------------------------------------- |
| `record_index` | 1-based line number in the JSONL body (blank lines are skipped)                                 |
| `field`        | The schema field that failed, or `_raw` for malformed JSON, or `_write` for a persistence error |
| `reason`       | Human-readable explanation                                                                      |

To recover, fix the rejected lines and re-send them. Appends are **idempotent per row**: a datapoint identical to one already stored is counted as a duplicate (included in `accepted`), not written twice — so it's safe to re-send a corrected batch that overlaps rows you already appended. Key order within each JSON object doesn't affect deduplication.

## Where appended data appears

Each append is also recorded as an `api_append` import job so it shows up alongside file uploads in the dataset's imports projection:

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

Unlike a file upload, this job is created already in its terminal state (`complete` or `partial`) — you don't need to poll it, since the append response already told you the outcome. See [Monitoring an import](/api-reference/ai-task-builder/datasets#monitoring-an-import) for the job shape.

## Propagating appends to live tasks

Appending adds datapoints to the **dataset**. Batches built from that dataset don't automatically gain tasks for them unless a **sync** runs.

* If an attached batch has **auto-sync enabled**, a successful append automatically triggers a sync for it — the new datapoints become tasks with no further action.
* Otherwise, trigger a sync manually once you've finished appending.

See [Syncing a batch](/api-reference/ai-task-builder/syncing-a-batch) for both flows.

***

By using AI Task Builder, you agree to our [AI Task Builder Terms](https://prolific.notion.site/Researcher-Terms-7787f102f0c541bdbe2c04b5d3285acb).