Upload a Dataset

Upload a structured dataset of images and annotations to a Roboflow project with the Python SDK.

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.

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.

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.

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 on, the images wait in Review and join the dataset once a reviewer approves them.

Response
{
    "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.

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.

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

The response follows the standard Async Tasks shape. When the task completes, result includes a per-image summary plus any warnings or errors encountered during parsing.

{
    "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:

Upload a Directory

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

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

Or use the shorthand alias:

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

FlagDescription
-p, --projectProject ID (required)
-c, --concurrencyNumber of parallel uploads (default: 10)
-b, --batchBatch name for grouping uploads
-r, --retriesRetry failed uploads N times (default: 0)
-s, --splitOverride split for all uploaded images

Example with options:

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 above; the CLI handles the signed-URL upload and task polling for you.

To upload an existing zip file:

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:

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

Options for Zip Uploads

FlagDescription
-p, --projectProject ID (required)
--zip-uploadZip a directory client-side and upload it with the async zip upload flow
--no-waitReturn immediately after the zip is uploaded instead of waiting for processing to finish
-b, --batchBatch name for grouping uploads
-s, --splitSplit set: train, valid, or test
-t, --tagComma-separated tag names

Example with zip upload options:

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:

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

The JSON response includes the asynchronous task ID:

{
  "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:

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

OR

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

JSON Output

For automation, use --json:

roboflow upload ./my-dataset -p my-project --json
{"status": "uploaded", "path": "./my-dataset", "project": "my-project"}

MCP Server

Connect your AI agent to the MCP Server and it can upload a dataset with these tools: The zip holds images only, so labels are saved in a second step.

ToolDescription
image_uploadUpload local image files to a project via a zip. Images only, no annotation files.
image_upload_statusCheck the status of an image zip upload task.
annotations_saveSave an annotation for an image that is already uploaded.

Next steps