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

# Syncing a batch

When you [append datapoints](/api-reference/ai-task-builder/appending-data) to a dataset, the datapoints land on the **dataset** — but a batch built from that dataset already has its tasks materialised from setup. A **sync** reconciles the two: it creates tasks for the datapoints added to the batch's dataset since setup or the last sync, so participants can start annotating them.

This guide covers what a sync does, how to trigger one manually, how to poll its progress, and how to have syncs run automatically.

## What a sync does

At setup, a batch turns every datapoint in its dataset into a task and organises those tasks into task groups. Appending adds datapoints to the dataset but leaves the batch's tasks untouched. A sync closes that gap:

* It finds datapoints that don't yet have a task in the batch (the delta since the last sync).
* It creates one task per new datapoint and slots them into task groups, following the batch's existing grouping:
  * **Fixed-size grouping** (`tasks_per_group`): the open, partial group is topped up first, then any remaining datapoints are chunked into new groups.
  * **Predetermined grouping** (`task_group_id` in the schema): datapoints join the existing group that carries their group key, or start a new keyed group.
* It updates the batch's task and task-group counts.

Syncs are **V4-only** (the dataset must be a V4/schema dataset), and the batch must be **READY** — a batch that hasn't been set up yet has no tasks to extend and must go through [setup](/api-reference/ai-task-builder/batches#setting-up-the-batch) instead.

A sync only ever **adds** tasks for new datapoints. It never removes tasks, and no participant is assigned the same task group twice — including groups that grew during a sync.

## Syncing manually

Trigger a sync with an empty `POST`. The batch's attached dataset is synced.

```bash
POST /api/v1/data-collection/batches/{batch_id}/sync
```

The request returns `202 Accepted` immediately with a **sync job** — the actual task creation happens asynchronously.

```json
{
  "batch_id": "0192a3b4-d6e7-7f8a-0b1c-2d3e4f5a6b7c",
  "dataset_id": "0192a3b5-e8f9-7a0b-1c2d-3e4f5a6b7c8d",
  "sync_id": "01935c2d-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
  "status": "queued",
  "created_at": "2026-07-15T10:30:00.000Z",
  "updated_at": "2026-07-15T10:30:00.000Z"
}
```

Keep the `sync_id` — you'll use it to poll for completion.

The request is rejected with a `400` if the batch has no dataset attached, or if the batch isn't in `READY` status.

## Polling a sync

Poll the sync job with the `batch_id` and the `sync_id` from the trigger response.

```bash
GET /api/v1/data-collection/batches/{batch_id}/syncs/{sync_id}
```

### Sync status

| Status       | Description                                             |
| ------------ | ------------------------------------------------------- |
| `queued`     | Job created and enqueued; task creation not started yet |
| `processing` | Tasks are being created                                 |
| `complete`   | Sync finished — result counts are populated             |
| `failed`     | Sync could not complete — see `reason`                  |

A `complete` job includes counts describing what the sync did:

```json
{
  "batch_id": "0192a3b4-d6e7-7f8a-0b1c-2d3e4f5a6b7c",
  "dataset_id": "0192a3b5-e8f9-7a0b-1c2d-3e4f5a6b7c8d",
  "sync_id": "01935c2d-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
  "status": "complete",
  "created_at": "2026-07-15T10:30:00.000Z",
  "updated_at": "2026-07-15T10:30:04.000Z",
  "tasks_created": 3,
  "datapoints_processed": 3,
  "groups_created": 1,
  "groups_expanded": 0
}
```

| Field                  | Description                                                                                                                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tasks_created`        | Number of new tasks materialised this sync                                                                                                                                                   |
| `datapoints_processed` | Number of appended datapoints handled                                                                                                                                                        |
| `groups_created`       | New task groups created                                                                                                                                                                      |
| `groups_expanded`      | Existing task groups grown — only non-zero for predetermined grouping, where appended datapoints join a group that carries their key. Always `0` for fixed-size (`tasks_per_group`) batches. |

If nothing has changed since the last sync, the job still completes — with all counts at `0`.

A `failed` job carries a `reason`, for example when an import is in progress on the dataset while the sync runs:

```json
{
  "status": "failed",
  "reason": "Dataset 0192a3b5-e8f9-7a0b-1c2d-3e4f5a6b7c8d has an import in progress. Retry the sync once it completes."
}
```

## Auto-sync

Rather than triggering a sync after every append, you can have a batch sync itself whenever datapoints are added to its dataset. Enable it by patching the batch:

```bash
PATCH /api/v1/data-collection/batches/{batch_id}
```

```json
{
  "auto_sync_enabled": true
}
```

With auto-sync on, every successful [append](/api-reference/ai-task-builder/appending-data) to the dataset automatically enqueues a sync for the batch — no separate `POST .../sync` call needed. The append response returns as soon as the datapoints are written; the sync then runs asynchronously, exactly as a manual one does, and you can poll it the same way.

Enabling auto-sync on a batch that is already `READY` triggers an immediate **catch-up sync**, so any datapoints appended while it was off are materialised right away rather than waiting for the next append.

To turn it off, patch `auto_sync_enabled` back to `false`.

### Recipe: append with auto-sync

Enable auto-sync on the batch: `PATCH /batches/{batch_id}` with `{ "auto_sync_enabled": true }`.

Append datapoints: `POST /datasets/{dataset_id}/datapoints`. Check the `accepted` / `rejected` counts in the response.

A sync is enqueued automatically. Read the batch (`GET /batches/{batch_id}`) — while a sync is running, the `active_sync` field carries its `sync_id` and start time.

Poll `GET /batches/{batch_id}/syncs/{sync_id}` until it reaches `complete`, then confirm the new `total_task_count` on the batch.

## Concurrency

Syncs for a single batch run one at a time — they're serialised, so you never need to space out your requests. If a sync is already in flight when another is triggered (including an auto-sync fired by a fresh append), the in-flight `active_sync` on the batch record tells you so, and the redundant trigger is coalesced away. Because task creation is deterministic, retries and redeliveries converge on the same result rather than double-creating tasks.

***

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