> For the complete documentation index, see [llms.txt](https://docs.roboflow.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.roboflow.com/datasets/create-and-upload/upload-a-dataset.md).

# Upload a Dataset

This page covers bulk import of an existing labeled dataset - images plus their annotations - using the Python SDK, the REST zip endpoint, or the CLI. To upload and manage individual images through the API, see [Manage Images](/datasets/manage/manage-images.md).

## Python SDK

`Workspace.upload_dataset()` uploads a structured dataset (images + matching annotations) to a Roboflow project. The project will be created if it doesn't exist; otherwise the new images get added to the existing project.

```python
import roboflow

rf = roboflow.Roboflow(api_key="YOUR_API_KEY")
workspace = rf.workspace()

workspace.upload_dataset(
    "./dataset/",                  # path to a structured dataset directory
    "my-detector",                 # project id (created if it doesn't exist)
    num_workers=10,
    project_license="MIT",
    project_type="object-detection",
    batch_name=None,
    num_retries=0,
    is_prediction=False,           # True for model-generated annotations awaiting review
)
```

### Parameters

* `dataset_path` (str) - path to the dataset root.
* `project_name` (str) - destination project's id. Created if it doesn't exist.
* `num_workers` (int, default `10`) - concurrent uploads. We recommend not exceeding 25.
* `project_license` (str, default `"MIT"`) - license for a newly-created project. Set to `"Private"` for private projects (paid plans only).
* `project_type` (str, default `"object-detection"`) - type for a newly-created project. Ignored if the project already exists.
* `batch_name` (str, optional) - group these uploads under a named batch. Useful for tracking the source of a labeling round.
* `num_retries` (int, default `0`) - retry transient upload failures.
* `is_prediction` (bool, default `False`) - set to `True` to upload annotations as model predictions awaiting review rather than ground truth.

### Where predictions land

When you save a prediction on an image that is still in an upload batch, Roboflow moves that image into a review job for the batch. Open the job to approve or fix the labels before they become ground truth.

If the image is already assigned to an annotation job, it stays in that job.

### Expected directory layout

For a COCO dataset:

```
my_dataset/
├── train/
│   ├── image1.jpg
│   └── _annotations.coco.json
├── valid/
│   ├── image2.jpg
│   └── _annotations.coco.json
└── test/
    ├── image3.jpg
    └── _annotations.coco.json
```

For VOC, drop matching `.xml` files alongside each image. For YOLO, drop matching `.txt` files plus a `data.yaml` describing the class list.

### Note on SHA-256 dedup (v1.3.6+)

As of `roboflow` 1.3.6, the SDK uploads original image bytes rather than re-encoding via Pillow. This brings parity with the web uploader and lets the Roboflow server deduplicate uploads by SHA-256 - re-uploading the same image (e.g. into a different batch) succeeds without consuming additional storage credits.

## REST API

### Upload a Dataset Zip

Upload a dataset as a single zip archive (up to 2 GB, 10,000 files) using an async task. Unlike the standard image upload endpoint, you do not hold an HTTP connection open while the zip is processed. The API returns a signed URL that you PUT the zip to, and a `taskId` you poll for status.

This endpoint accepts zips containing images and annotations in any of the formats supported by the Roboflow dataset upload tools (COCO, YOLO, Pascal VOC, etc.). Folder names are used as class labels for classification datasets.

#### Flow

1. `POST /:workspace/:project/upload/zip` returns a signed URL and a `taskId`.
2. `PUT` the zip directly to the signed URL.
3. `GET /:workspace/upload/zip/:taskId` to poll until the task completes.

#### Initiate the Upload

Send a `POST` to `/:workspace/:project/upload/zip`. The response includes a GCS signed URL and a `taskId`.

```bash
curl -X POST "https://api.roboflow.com/my-workspace/my-project/upload/zip?api_key=$ROBOFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"split": "train", "batchName": "my-batch"}'
```

**Body Parameters**

```
- split (string, optional) - One of train, valid, or test. Defaults to train.
- batchName (string, optional) - Group uploaded images under a batch with this name.
```

Zips that contain annotations land in one annotation job named after `batchName`, or "Uploaded via API" if you leave it out. Every upload gets its own job, even when you reuse a name. If the workspace has [Review Mode](/datasets/annotate/annotate/team-collaboration.md#review-mode) on, the images wait in Review and join the dataset once a reviewer approves them.

**Response**

```json
{
    "taskId": "abc123",
    "signedUrl": "https://storage.googleapis.com/...",
    "url": "https://api.roboflow.com/my-workspace/upload/zip/abc123"
}
```

#### Upload the Zip

`PUT` the zip file to the returned `signedUrl`. The Content-Type must be `application/zip`.

```bash
curl -X PUT "$SIGNED_URL" \
  -H "Content-Type: application/zip" \
  --upload-file ./my-dataset.zip
```

Processing starts automatically once the upload completes.

#### Poll Task Status

Send a `GET` to `/:workspace/upload/zip/:taskId`.

```bash
curl "https://api.roboflow.com/my-workspace/upload/zip/abc123?api_key=$ROBOFLOW_API_KEY"
```

The response follows the standard [Async Tasks](https://docs.roboflow.com/reference/platform/rest-api/async-tasks) shape. When the task completes, `result` includes a per-image summary plus any warnings or errors encountered during parsing.

```json
{
    "taskId": "abc123",
    "status": "completed",
    "progress": { "current": 250, "total": 250 },
    "result": {
        "uploaded": 248,
        "failed": 2,
        "warnings": [],
        "errors": []
    }
}
```

Up to 100 per-image errors and 100 warnings are reported. Videos and PDFs inside the zip are surfaced as unsupported-format warnings and skipped.

#### Limits

* Maximum zip size: 2 GB
* Maximum files per zip: 10,000

#### Errors

```
- 400 - The zip is malformed or exceeds the size or file count limits.
- 401 - Missing or invalid API key.
- 404 - The workspace, project, or task does not exist, or belongs to another workspace.
```

## CLI

You can upload datasets with images and/or annotations using the Roboflow CLI.

We have prepared a video that walks through how to upload a dataset:

{% embed url="<https://www.loom.com/share/19637984033a466b831af56f9404fa89>" %}

### Upload a Directory

Use `roboflow image upload` with a directory path to bulk-upload a dataset with parallel per-image uploads:

```bash
roboflow image upload /path/to/dataset/folder -p PROJECT_ID
```

Or use the shorthand alias:

```bash
roboflow upload /path/to/dataset/folder -p PROJECT_ID
```

The CLI auto-detects whether the path is a file, directory, or `.zip` file. A directory triggers a bulk import with parallel per-image uploads unless you use `--zip-upload`.

#### Options

| Flag                  | Description                               |
| --------------------- | ----------------------------------------- |
| `-p`, `--project`     | Project ID (required)                     |
| `-c`, `--concurrency` | Number of parallel uploads (default: 10)  |
| `-b`, `--batch`       | Batch name for grouping uploads           |
| `-r`, `--retries`     | Retry failed uploads N times (default: 0) |
| `-s`, `--split`       | Override split for all uploaded images    |

Example with options:

```bash
roboflow upload ./my-dataset -p my-project -c 20 -b "april-batch" -r 3
```

### Upload a Zip File

Use zip uploads for larger datasets or when your dataset is already packaged as a `.zip` file. Zip uploads use Roboflow's asynchronous zip upload flow. By default, the CLI uploads the zip file and waits for processing to finish.

This is the same flow documented in [REST API](#rest-api) above; the CLI handles the signed-URL upload and task polling for you.

To upload an existing zip file:

```bash
roboflow image upload /path/to/dataset.zip -p PROJECT_ID
```

To zip a local directory client-side and upload it with the async zip flow, use `--zip-upload` with the primary command:

```bash
roboflow image upload /path/to/dataset/folder -p PROJECT_ID --zip-upload
```

#### Options for Zip Uploads

| Flag              | Description                                                                              |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `-p`, `--project` | Project ID (required)                                                                    |
| `--zip-upload`    | Zip a directory client-side and upload it with the async zip upload flow                 |
| `--no-wait`       | Return immediately after the zip is uploaded instead of waiting for processing to finish |
| `-b`, `--batch`   | Batch name for grouping uploads                                                          |
| `-s`, `--split`   | Split set: train, valid, or test                                                         |
| `-t`, `--tag`     | Comma-separated tag names                                                                |

Example with zip upload options:

```bash
roboflow image upload ./my-dataset.zip -p my-project -s train -t "outdoor,daytime" -b "april-batch"
```

To start processing and return a task ID immediately:

```bash
roboflow image upload ./my-dataset.zip -p my-project --no-wait --json
```

The JSON response includes the asynchronous task ID:

```json
{
  "status": "pending",
  "task_id": "task-123",
  "path": "./my-dataset.zip",
  "project": "my-project",
  "result": {
    "task_id": "task-123",
    "status": "pending"
  }
}
```

Zip uploads do not support `--is-prediction`. Use the regular per-image upload flow for prediction uploads.

### Supported Project Types

You can upload data for the following project types:

* Object Detection
* Single-Label Classification
* Multi-Label Classification
* Instance Segmentation
* Semantic Segmentation
* Keypoint Detection

### Supported data.yaml Formats

The CLI supports both list-style and key-value pair formatted class name mappings in `data.yaml` files during dataset uploads:

```yaml
nc: 3
names: ['Paper', 'Rock', 'Scissors']
```

OR

```yaml
nc: 3
names:
  0: Paper
  1: Rock
  2: Scissors
```

### JSON Output

For automation, use `--json`:

```bash
roboflow upload ./my-dataset -p my-project --json
```

```json
{"status": "uploaded", "path": "./my-dataset", "project": "my-project"}
```

## MCP Server

Connect your AI agent to the [MCP Server](https://docs.roboflow.com/agents/mcp-server) and it can upload a dataset with these tools: The zip holds images only, so labels are saved in a second step.

<table data-search="false"><thead><tr><th width="290">Tool</th><th>Description</th></tr></thead><tbody><tr><td><code>image_upload</code></td><td>Upload local image files to a project via a zip. Images only, no annotation files.</td></tr><tr><td><code>image_upload_status</code></td><td>Check the status of an image zip upload task.</td></tr><tr><td><code>annotations_save</code></td><td>Save an annotation for an image that is already uploaded.</td></tr></tbody></table>

## Next steps

* Label any unannotated images so they can be used for training. See [Introduction to Roboflow Annotate](/datasets/annotate/annotate/annotation-tools.md).
* Turn your images into a trainable snapshot. See [Create a Dataset Version](/datasets/versions/dataset-versions/create-a-dataset-version.md).
