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, default10) - 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, default0) - retry transient upload failures.is_prediction(bool, defaultFalse) - set toTrueto 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.jsonFor 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
POST /:workspace/:project/upload/zipreturns a signed URL and ataskId.PUTthe zip directly to the signed URL.GET /:workspace/upload/zip/:taskIdto 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.zipProcessing 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_IDOr use the shorthand alias:
roboflow upload /path/to/dataset/folder -p PROJECT_IDThe 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:
roboflow upload ./my-dataset -p my-project -c 20 -b "april-batch" -r 3Upload 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_IDTo 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-uploadOptions 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:
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 --jsonThe 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: ScissorsJSON 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.
| Tool | Description |
|---|---|
image_upload | Upload local image files to a project via a zip. Images only, no annotation files. |
image_upload_status | Check the status of an image zip upload task. |
annotations_save | Save an annotation for an image that is already uploaded. |
Next steps
- Label any unannotated images so they can be used for training. See Introduction to Roboflow Annotate.
- Turn your images into a trainable snapshot. See Create a Dataset Version.