Project recipes
Five recipes cover the project lifecycle: project_create_and_list.py for the
common CRUD path, project_add_labels.py for extending an existing project’s
label schema, project_backup.py and project_restore.py for portable
copies, and project_export_dataset.py for dataset export (local + cloud).
For a CSV overview of a project’s jobs, see job_list.py --project-id --csv
in the job recipes.
Create, list, filter, retrieve, rename
Creates a project with labels, then lists all projects, filters by name,
retrieves by id, and renames it. Pass --cleanup to delete it at the end.
| Flag | Required | Meaning |
|---|---|---|
--host |
yes | Server URL, e.g. 'https://app.cvat.ai' |
--token |
yes | Personal Access Token |
--name |
no | Project name (default 'Example project') |
--labels |
no | Label names, space-separated (default car person) |
--cleanup |
no | Delete the created project at the end |
python project_create_and_list.py --host 'https://app.cvat.ai' --token '<your token>' \
--name 'My project' --labels car person
The script
# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
"""Create a project with labels, then list, filter, retrieve, and rename it.
Steps:
1. Create a project with a simple label schema.
2. List all projects visible to you (pagination is handled by the SDK).
3. Filter projects by a name substring.
4. Retrieve one project by id and read its labels.
5. Rename it.
6. Optionally delete it (--cleanup).
Usage (run ``python project_create_and_list.py --help`` for the full list of options):
python project_create_and_list.py --host 'https://app.cvat.ai' --token '<your token>' \\
--name 'My project' --labels car person
"""
import argparse
from cvat_sdk import make_client, models
from cvat_sdk.core.filters import F
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--host", required=True, help="CVAT server URL, e.g. 'https://app.cvat.ai'")
parser.add_argument(
"--token",
required=True,
help="Personal Access Token (CVAT UI: Profile -> Security)",
)
parser.add_argument(
"--name", default="Example project", help="project name (default: '%(default)s')"
)
parser.add_argument(
"--labels",
nargs="+",
default=["car", "person"],
help="label names (default: %(default)s)",
)
parser.add_argument(
"--cleanup", action="store_true", help="delete the created project at the end"
)
return parser.parse_args()
def main() -> None:
args = parse_args()
with make_client(args.host, access_token=args.token) as client:
# 1. Create a project with labels
project = client.projects.create(
models.ProjectWriteRequest(
name=args.name,
labels=[models.PatchedLabelRequest(name=name) for name in args.labels],
)
)
print(f"Created project {project.id}: {args.host}/projects/{project.id}")
# 2. List all projects
projects = client.projects.list()
print(f"Projects visible to you: {len(projects)}")
# 3. Filter by name substring
matches = client.projects.list(filter=F.name.contains(args.name))
print(f"Projects with {args.name!r} in the name: {[p.id for p in matches]}")
# 4. Retrieve by id
fetched = client.projects.retrieve(project.id)
print(f"Project {fetched.id} labels: {[label.name for label in fetched.get_labels()]}")
# 5. Rename
renamed = fetched.update(models.PatchedProjectWriteRequest(name=f"{args.name} (renamed)"))
print(f"Renamed to: {renamed.name}")
# 6. Opt-in cleanup
if args.cleanup:
renamed.remove()
print(f"Deleted project {project.id}")
else:
print("Keeping the project; pass --cleanup to delete it")
if __name__ == "__main__":
main()
Add labels to an existing project
Adds labels — optionally with selectable attributes — to a project that already exists. Labels that are already there are skipped, so the recipe is safe to re-run. The tasks inside the project take their labels from the project itself, so they all pick up the change.
| Flag | Required | Meaning |
|---|---|---|
--host |
yes | Server URL |
--token |
yes | Personal Access Token |
--project-id |
yes | Id of the project to extend |
--labels |
yes | Label names to add, space-separated |
--attr LABEL NAME VALUE [...] |
no | Selectable attribute for one of the --labels; repeat for more |
python project_add_labels.py --host 'https://app.cvat.ai' --token '<your token>' \
--project-id 7 --labels car person
python project_add_labels.py --host 'https://app.cvat.ai' --token '<your token>' \
--project-id 7 --labels car --attr car color red green blue
The script
# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
"""Add labels, optionally with selectable attributes, to an existing project.
Steps:
1. Retrieve the project and read the labels it already has; the requested
labels that already exist are skipped, so the script is safe to re-run.
2. Attach the --attr definitions to their new labels.
3. Send one project update with the new labels. Labels of the tasks inside
the project come from the project itself, so they all pick up the change.
Usage (run ``python project_add_labels.py --help`` for the full list of options):
python project_add_labels.py --host 'https://app.cvat.ai' --token '<your token>' \\
--project-id 7 --labels car person
python project_add_labels.py --host 'https://app.cvat.ai' --token '<your token>' \\
--project-id 7 --labels car --attr car color red green blue
"""
import argparse
from cvat_sdk import make_client, models
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--host", required=True, help="CVAT server URL, e.g. 'https://app.cvat.ai'")
parser.add_argument(
"--token",
required=True,
help="Personal Access Token (CVAT UI: Profile -> Security)",
)
parser.add_argument(
"--project-id", type=int, required=True, help="id of an existing project, e.g. 7"
)
parser.add_argument(
"--labels", nargs="+", metavar="NAME", required=True, help="label names to add"
)
parser.add_argument(
"--attr",
nargs="+",
action="append",
default=[],
metavar=("LABEL NAME", "VALUE"),
help="selectable attribute for one of the --labels: label name, attribute "
"name, then its values (repeat --attr for more attributes)",
)
args = parser.parse_args()
for attr in args.attr:
if len(attr) < 3:
parser.error("--attr needs a label, an attribute name, and at least one value")
if attr[0] not in args.labels:
parser.error(f"--attr refers to label {attr[0]!r}, which is not in --labels")
return args
def main() -> None:
args = parse_args()
attributes_per_label: dict[str, list[models.AttributeRequest]] = {}
for label_name, attribute_name, *values in args.attr:
attributes_per_label.setdefault(label_name, []).append(
models.AttributeRequest(
name=attribute_name,
input_type=models.InputTypeEnum("select"),
values=values,
default_value=values[0],
mutable=True,
)
)
with make_client(args.host, access_token=args.token) as client:
project = client.projects.retrieve(args.project_id)
existing = {label.name for label in project.get_labels()}
new_labels = []
for name in args.labels:
if name in existing:
print(f"Label {name!r} already exists, skipping")
continue
new_labels.append(
models.PatchedLabelRequest(name=name, attributes=attributes_per_label.get(name, []))
)
if new_labels:
project.update(models.PatchedProjectWriteRequest(labels=new_labels))
print(
f"Added {len(new_labels)} labels to project {project.id}: "
f"{', '.join(label.name for label in new_labels) or '-'}"
)
print(f"Project {project.id} labels: {[label.name for label in project.get_labels()]}")
if __name__ == "__main__":
main()
Back up a project
Downloads a full project backup zip — tasks, jobs, annotations, and settings.
Pair with project_restore.py to migrate or clone.
| Flag | Required | Meaning |
|---|---|---|
--host |
yes | Server URL |
--token |
yes | Personal Access Token |
--project-id |
yes | Id of the project to back up |
--output |
no | Destination file (default project_<id>_backup.zip) |
python project_backup.py --host 'https://app.cvat.ai' --token '<your token>' \
--project-id 42
The script
# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
"""Download a backup zip of an existing project.
A backup contains the project's tasks, jobs, annotations, and settings. Pair
this recipe with project_restore.py to migrate or clone a project.
Steps:
1. Retrieve the project by id.
2. Download its backup to --output (default: project_<id>_backup.zip).
Usage (run ``python project_backup.py --help`` for the full list of options):
python project_backup.py --host 'https://app.cvat.ai' --token '<your token>' \\
--project-id 42
"""
import argparse
from pathlib import Path
from cvat_sdk import make_client
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--host", required=True, help="CVAT server URL, e.g. 'https://app.cvat.ai'")
parser.add_argument(
"--token",
required=True,
help="Personal Access Token (CVAT UI: Profile -> Security)",
)
parser.add_argument(
"--project-id", type=int, required=True, help="id of an existing project, e.g. 42"
)
parser.add_argument(
"--output",
type=Path,
help="destination file path (default: project_<id>_backup.zip)",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
with make_client(args.host, access_token=args.token) as client:
project = client.projects.retrieve(args.project_id)
output = args.output or Path(f"project_{project.id}_backup.zip")
project.download_backup(output)
print(f"Backed up project {project.id} to {output.resolve()}")
if __name__ == "__main__":
main()
Restore a project
Restores a project from a backup zip as a brand-new project. Pass --cleanup
to delete the restored copy afterwards — useful when validating a backup file.
| Flag | Required | Meaning |
|---|---|---|
--host |
yes | Server URL |
--token |
yes | Personal Access Token |
--backup |
yes | Path to a project backup zip |
--cleanup |
no | Delete the restored copy (never touches the backup file) |
python project_restore.py --host 'https://app.cvat.ai' --token '<your token>' \
--backup './project_42_backup.zip'
The script
# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
"""Restore a project from a backup zip as a new project.
Pair with project_backup.py to migrate or clone a project.
Steps:
1. Restore --backup as a brand-new project.
2. Optionally delete the restored copy (--cleanup) — useful when testing a
backup file.
Usage (run ``python project_restore.py --help`` for the full list of options):
python project_restore.py --host 'https://app.cvat.ai' --token '<your token>' \\
--backup './project_42_backup.zip'
"""
import argparse
import sys
from pathlib import Path
from cvat_sdk import make_client
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--host", required=True, help="CVAT server URL, e.g. 'https://app.cvat.ai'")
parser.add_argument(
"--token",
required=True,
help="Personal Access Token (CVAT UI: Profile -> Security)",
)
parser.add_argument("--backup", type=Path, required=True, help="path to a project backup zip")
parser.add_argument(
"--cleanup",
action="store_true",
help="delete the restored project at the end (never touches the source backup)",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
if not args.backup.is_file():
sys.exit(f"--backup {args.backup} does not exist")
with make_client(args.host, access_token=args.token) as client:
restored = client.projects.create_from_backup(args.backup)
print(f"Restored a copy as project {restored.id}: {args.host}/projects/{restored.id}")
if args.cleanup:
restored.remove()
print(f"Deleted restored project {restored.id}")
else:
print("Keeping the restored project; pass --cleanup to delete it")
if __name__ == "__main__":
main()
Export a project’s tasks individually (local + cloud)
Exports each task in a project as its own dataset, both to a local zip and
straight to a registered cloud storage. By default every task is exported;
pass --task-id to export only a specific subset. Validates the format name
against the server’s list before starting.
| Flag | Required | Meaning |
|---|---|---|
--host |
yes | Server URL |
--token |
yes | Personal Access Token |
--project-id |
yes | Id of the project to export |
--cloud-storage-id |
yes | Registered cloud storage id (see cloud_storage_register.py) |
--export-format |
no | Exporter name (default 'COCO 1.0') |
--task-id |
no | Task ids to export, space-separated (default: every task in the project) |
python project_export_dataset.py --host 'https://app.cvat.ai' --token '<your token>' \
--project-id 42 --cloud-storage-id 7 --export-format 'COCO 1.0'
The script
# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
"""Export a project's tasks individually, without images, to local zips AND to
a registered cloud storage.
By default every task in the project is exported; pass --task-id to export
only a specific subset. This is the SDK-only stand-in for what could become a
bulk per-task export command in cvat-cli.
Steps:
1. Fetch the server's export format list and validate --export-format.
2. Resolve which tasks to export: --task-id filters to a subset of the
project's tasks; omit it to export every task in the project.
3. For each task: export to task_<id>_dataset.zip in the current directory,
then export the same dataset straight to the cloud storage (no local
download).
Usage (run ``python project_export_dataset.py --help`` for the full list of options):
# every task in the project
python project_export_dataset.py --host 'https://app.cvat.ai' --token '<your token>' \\
--project-id 42 --cloud-storage-id 7 --export-format 'COCO 1.0'
# only tasks 10 and 11
python project_export_dataset.py --host 'https://app.cvat.ai' --token '<your token>' \\
--project-id 42 --cloud-storage-id 7 --task-id 10 11
"""
import argparse
import sys
from pathlib import Path
from cvat_sdk import make_client
from cvat_sdk.core.proxies.types import Location
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--host", required=True, help="CVAT server URL, e.g. 'https://app.cvat.ai'")
parser.add_argument(
"--token",
required=True,
help="Personal Access Token (CVAT UI: Profile -> Security)",
)
parser.add_argument(
"--project-id", type=int, required=True, help="id of an existing project, e.g. 42"
)
parser.add_argument(
"--cloud-storage-id",
type=int,
required=True,
help="a registered cloud storage id (see cloud_storage_register.py)",
)
parser.add_argument(
"--export-format",
default="COCO 1.0",
help="exporter name, e.g. 'COCO 1.0' (default: '%(default)s')",
)
parser.add_argument(
"--task-id",
type=int,
nargs="+",
metavar="ID",
help="export only these task ids (must belong to the project); "
"omit to export every task in the project",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
with make_client(args.host, access_token=args.token) as client:
# 1. Validate the format against the server's list.
# Low-level API: there is no high-level proxy for the format list yet.
formats, _ = client.api_client.server_api.retrieve_annotation_formats()
names = [f.name for f in formats.exporters]
if args.export_format not in names:
sys.exit(
f"Unknown export format {args.export_format!r}. Choose one of: {', '.join(names)}"
)
# 2. Resolve which tasks to export.
project = client.projects.retrieve(args.project_id)
tasks_by_id = {task.id: task for task in project.get_tasks()}
if args.task_id:
missing = [str(tid) for tid in args.task_id if tid not in tasks_by_id]
if missing:
sys.exit(f"Task id(s) {', '.join(missing)} not found in project {project.id}")
tasks = [tasks_by_id[tid] for tid in args.task_id]
else:
tasks = list(tasks_by_id.values())
if not tasks:
sys.exit(f"Project {project.id} has no tasks to export")
# 3. Export each task individually: a local zip AND straight to the cloud storage.
for task in tasks:
local_path = Path(f"task_{task.id}_dataset.zip")
task.export_dataset(
args.export_format, local_path, include_images=False, location=Location.LOCAL
)
print(f"Exported {local_path.resolve()}")
remote_name = f"task_{task.id}_dataset.zip"
task.export_dataset(
args.export_format,
remote_name,
include_images=False,
location=Location.CLOUD_STORAGE,
cloud_storage_id=args.cloud_storage_id,
)
print(f"Exported {remote_name} to cloud storage {args.cloud_storage_id}")
print(f"Exported {len(tasks)} task dataset(s) from project {project.id}")
if __name__ == "__main__":
main()
Other SDK options:
| SDK method / parameter | What it adds |
|---|---|
Project.download_backup(..., lightweight=True) |
Produce a smaller backup that omits media. |
client.projects.create_from_dataset(...) |
Create a project directly from a dataset archive. |
Project.import_dataset(format_name, path) |
Import annotations/data into an existing project - the import counterpart of export_dataset. |
Project.get_annotations() |
Fetch the project’s labeled data. |
Notes:
list()returns the whole collection; pagination is handled for you.- A project backup captures tasks, jobs, users, and settings in a single zip -
but no raw media beyond what
export_datasetwould include. - For a CSV overview of a project’s jobs (no annotation geometry), use
job_list.py --project-id <id> --csv. For an actual dataset export, useproject_export_dataset.py. include_images=Falseexports annotations only and is much smaller.- Full recipes:
project_create_and_list.py,project_add_labels.py,project_backup.py,project_restore.py,project_export_dataset.py.