Exports
Build and download dataset exports in 12 formats, including COCO, YOLO, Pascal VOC, CVAT and canonical Pictograph JSON.
An export is a ZIP of an annotated dataset in a chosen format, optionally embedding the original image files. Builds run server-side: a few seconds for hundreds of images, longer for tens of thousands.
Addressing. /exports/{export} accepts either the export name or its
UUID. Names are unique per dataset, not per organization, so if the same export
name exists under two datasets add ?dataset={name-or-uuid} to disambiguate; a
UUID never needs it.
Formats
format |
Notes |
|---|---|
pictograph (default) |
Canonical Pictograph JSON - the wire format the SDK consumes |
darwin |
Darwin V7 JSON 2.0, one file per image - drop-in for V7 dataset imports |
coco |
COCO detection / instance segmentation (Darwin-consistent dialect: polygon holes export as RLE) |
yolo |
YOLO darknet .txt, one file per image |
yolo_obb |
Oriented boxes - Ultralytics YOLO-OBB: class x1 y1 x2 y2 x3 y3 x4 y4, corners normalized to [0,1]. What yolo obb trains on. Every annotation type exports: a box is a rotated box at angle 0, a polygon reduces to its minimum-area rotated rectangle |
dota |
Oriented boxes - the aerial/satellite standard (labelTxt/): four corners in absolute pixels plus the class name, so it needs no image dimensions and no class-index ordering |
cvat |
CVAT XML - a rotated box exports natively as <box rotation="…">, an exact round-trip |
pascal_voc |
Pascal VOC XML, one file per image |
datumaro |
Intel Datumaro / CVAT’s native JSON dataset format |
labelme |
LabelMe JSON, one file per image |
yolo_pose |
Multi-joint pose - Ultralytics YOLO-Pose: a box plus per-joint x y v triplets, joints grouped by instance_id |
csv |
Flat CSV - bbox annotations only |
Every format understands an oriented (rotated) box. Formats with a native
rotated representation (yolo_obb, dota, cvat) keep the angle exactly; the
rest receive the box’s four rotated corners as a polygon, so the shape survives
losslessly even where the parameterization cannot be expressed.
create
Build a new export. Blocks until the ZIP is ready unless you pass wait=False.
Source: Exports.create
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str |
required | Dataset name. |
name |
str |
required | Unique within the dataset, max 100 chars |
format |
ExportFormat |
"pictograph" |
See table above |
include_images |
bool |
False |
Bundle the original image bytes too |
class_filter |
list[str] | None |
None |
Limit to these class names |
status_filter |
str | None |
None |
Image status filter, e.g. "complete" |
organize_by_split |
bool |
False |
Group into train/ / valid/ / test/ - see split layout |
wait |
bool |
True |
Block until terminal status |
poll_interval |
float |
2.0 |
Seconds between status checks |
timeout |
float |
300.0 |
Max seconds to wait |
export = client.exports.create(
dataset_name="my-dataset",
name="for-yolov8",
format="yolo",
include_images=True,
class_filter=["car", "truck"],
status_filter="complete",
timeout=600.0,
)
print(export.id, export.status, export.image_count, export.annotation_count)
pictograph exports create my-dataset \
--name for-yolov8 \
--format yolo \
--include-images \
--class-filter car,truck \
--status-filter complete \
--timeout 600
curl -s -X POST "https://api.pictograph.io/api/v1/developer/exports/" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dataset_name": "my-dataset", "name": "for-yolov8", "format": "yolo",
"include_images": true, "class_filter": ["car", "truck"],
"status_filter": "complete"}'
Returns Export
Export · 17 fields
class Export(BaseModel):
"""A dataset export - produced asynchronously, downloaded as a ZIP."""
id: str
dataset_id: str
dataset_name: str
name: str
format: Literal['pictograph', 'darwin', 'coco', 'yolo', 'yolo_obb', 'yolo_pose', 'dota', 'pascal_voc', 'cvat', 'datumaro', 'labelme', 'csv']
include_images: bool = False
class_filter: list[str] | None = None
status_filter: str | None = None
status: Literal['pending', 'processing', 'completed', 'failed']
error_message: str | None = None
file_size: int | None = None
image_count: int | None = None
annotation_count: int | None = None
created_at: datetime
expires_at: datetime | None = None
download_url: str | None = None
organization_id: str | None = None
wait=False (CLI --no-wait, REST always) returns a pending export. Poll it
with get.
list
One page of exports for the organization. Optional dataset_name and status
(pending / processing / completed / failed) filters. limit defaults to
100, backend cap 1000.
Source: Exports.list
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str | None |
None |
Dataset name. |
status |
str | None |
None |
Only exports in this state, e.g. completed. |
limit |
int |
100 |
Page size. |
offset |
int |
0 |
Page offset. |
for e in client.exports.list(limit=20):
print(e.dataset_name, e.name, e.status)
pictograph exports list --dataset my-dataset --status completed -n 20
curl -s "https://api.pictograph.io/api/v1/developer/exports/?dataset_name=my-dataset&status=completed&limit=20" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns list[Export]
Export · 17 fields
class Export(BaseModel):
"""A dataset export - produced asynchronously, downloaded as a ZIP."""
id: str
dataset_id: str
dataset_name: str
name: str
format: Literal['pictograph', 'darwin', 'coco', 'yolo', 'yolo_obb', 'yolo_pose', 'dota', 'pascal_voc', 'cvat', 'datumaro', 'labelme', 'csv']
include_images: bool = False
class_filter: list[str] | None = None
status_filter: str | None = None
status: Literal['pending', 'processing', 'completed', 'failed']
error_message: str | None = None
file_size: int | None = None
image_count: int | None = None
annotation_count: int | None = None
created_at: datetime
expires_at: datetime | None = None
download_url: str | None = None
organization_id: str | None = None
iter
Auto-paging iterator over every export. No CLI equivalent - use
pictograph exports list -n <n>, or page REST manually with limit + offset.
Source: Exports.iter
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str | None |
None |
Dataset name. |
status |
str | None |
None |
Only exports in this state, e.g. completed. |
page_size |
int |
100 |
Rows fetched per underlying request. Tuning only - the iterator yields every item either way. |
max_total |
int | None |
None |
Stop after this many items. None walks everything. |
for e in client.exports.iter(page_size=50):
print(e.dataset_name, e.name, e.status)
# The CLI does not auto-page; this is a single page.
pictograph exports list --limit 100
curl -s "https://api.pictograph.io/api/v1/developer/exports/?limit=100&offset=100" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns OffsetPager[Export]
Export · 17 fields
class Export(BaseModel):
"""A dataset export - produced asynchronously, downloaded as a ZIP."""
id: str
dataset_id: str
dataset_name: str
name: str
format: Literal['pictograph', 'darwin', 'coco', 'yolo', 'yolo_obb', 'yolo_pose', 'dota', 'pascal_voc', 'cvat', 'datumaro', 'labelme', 'csv']
include_images: bool = False
class_filter: list[str] | None = None
status_filter: str | None = None
status: Literal['pending', 'processing', 'completed', 'failed']
error_message: str | None = None
file_size: int | None = None
image_count: int | None = None
annotation_count: int | None = None
created_at: datetime
expires_at: datetime | None = None
download_url: str | None = None
organization_id: str | None = None
get
Fetch one export by (dataset_name, export_name). Returns Export.
Source: Exports.get
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str |
required | Dataset name. |
export_name |
str |
required | Export name. |
export = client.exports.get(
dataset_name="my-dataset",
export_name="for-yolov8",
)
print(export.status, export.image_count)
pictograph exports get my-dataset for-yolov8
curl -s "https://api.pictograph.io/api/v1/developer/exports/for-yolov8?dataset=my-dataset" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns Export
Export · 17 fields
class Export(BaseModel):
"""A dataset export - produced asynchronously, downloaded as a ZIP."""
id: str
dataset_id: str
dataset_name: str
name: str
format: Literal['pictograph', 'darwin', 'coco', 'yolo', 'yolo_obb', 'yolo_pose', 'dota', 'pascal_voc', 'cvat', 'datumaro', 'labelme', 'csv']
include_images: bool = False
class_filter: list[str] | None = None
status_filter: str | None = None
status: Literal['pending', 'processing', 'completed', 'failed']
error_message: str | None = None
file_size: int | None = None
image_count: int | None = None
annotation_count: int | None = None
created_at: datetime
expires_at: datetime | None = None
download_url: str | None = None
organization_id: str | None = None
get_by_id
The by-UUID complement to get. A cross-org or missing id is a 404.
Source: Exports.get_by_id
| Arg | Type | Default | Notes |
|---|---|---|---|
export_id |
str |
required | Export id. |
export = client.exports.get_by_id(
export_id="b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c",
)
pictograph exports get-by-id b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c
curl -s "https://api.pictograph.io/api/v1/developer/exports/b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns Export
Export · 17 fields
class Export(BaseModel):
"""A dataset export - produced asynchronously, downloaded as a ZIP."""
id: str
dataset_id: str
dataset_name: str
name: str
format: Literal['pictograph', 'darwin', 'coco', 'yolo', 'yolo_obb', 'yolo_pose', 'dota', 'pascal_voc', 'cvat', 'datumaro', 'labelme', 'csv']
include_images: bool = False
class_filter: list[str] | None = None
status_filter: str | None = None
status: Literal['pending', 'processing', 'completed', 'failed']
error_message: str | None = None
file_size: int | None = None
image_count: int | None = None
annotation_count: int | None = None
created_at: datetime
expires_at: datetime | None = None
download_url: str | None = None
organization_id: str | None = None
download
Stream the ZIP to a local file. The SDK fetches a signed URL, then streams the
bytes into a sibling .part file and renames it atomically on success. Accepts
chunk_size (8 MB default) and a progress(sent, total) callback.
Source: Exports.download
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str |
required | Dataset name. |
export_name |
str |
required | Export name. |
output_path |
str | Path |
required | Local destination. Parent dirs created if missing. |
chunk_size |
int |
8388608 |
Streaming chunk size; 8 MB by default, tuned for the storage transfer. |
progress |
Callable[[int, int], None] | None |
None |
Optional (bytes_so_far, total_bytes) callback. total_bytes is 0 if the server did not provide Content-Length. |
from pathlib import Path
client.exports.download(
dataset_name="my-dataset",
export_name="for-yolov8",
output_path=Path("./my-dataset.zip"),
)
pictograph exports download my-dataset for-yolov8 -o ./my-dataset.zip
# The endpoint returns a signed download_url valid for 60 minutes, generated
# fresh on every call; fetch the ZIP from it.
curl -s "https://api.pictograph.io/api/v1/developer/exports/for-yolov8/download?dataset=my-dataset" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns Path
download_by_id
The by-UUID complement to download, same streaming behavior. Returns the
output Path.
Source: Exports.download_by_id
| Arg | Type | Default | Notes |
|---|---|---|---|
export_id |
str |
required | Export id. |
output_path |
str | Path |
required | Where to write the ZIP. |
chunk_size |
int |
8388608 |
Bytes per streamed chunk while downloading. |
progress |
Callable[[int, int], None] | None |
None |
Called with (bytes_done, bytes_total) as the download streams. |
path = client.exports.download_by_id(
export_id="b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c",
output_path="./out.zip",
)
pictograph exports download-by-id b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c -o ./out.zip
curl -s "https://api.pictograph.io/api/v1/developer/exports/b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c/download" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns Path
wait_for_completion
Poll an export created with wait=False until it is completed or failed.
No CLI equivalent - re-run pictograph exports get until status settles.
Source: Exports.wait_for_completion
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str |
required | Dataset name. |
export_name |
str |
required | Export name. |
poll_interval |
float |
2.0 |
Seconds between status checks. |
timeout |
float |
300.0 |
Maximum seconds to wait. |
export = client.exports.wait_for_completion(
dataset_name="my-dataset",
export_name="nightly",
timeout=300.0,
)
# create already waits; --no-wait opts out.
pictograph exports create road-signs --name v1 --format coco
# Poll until status is completed or failed.
curl -s "https://api.pictograph.io/api/v1/developer/exports/v1?dataset=road-signs" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns Export
Export · 17 fields
class Export(BaseModel):
"""A dataset export - produced asynchronously, downloaded as a ZIP."""
id: str
dataset_id: str
dataset_name: str
name: str
format: Literal['pictograph', 'darwin', 'coco', 'yolo', 'yolo_obb', 'yolo_pose', 'dota', 'pascal_voc', 'cvat', 'datumaro', 'labelme', 'csv']
include_images: bool = False
class_filter: list[str] | None = None
status_filter: str | None = None
status: Literal['pending', 'processing', 'completed', 'failed']
error_message: str | None = None
file_size: int | None = None
image_count: int | None = None
annotation_count: int | None = None
created_at: datetime
expires_at: datetime | None = None
download_url: str | None = None
organization_id: str | None = None
delete
Removes the export record and its stored ZIP. Requires admin or owner.
Downloads already in flight fail mid-stream.
Source: Exports.delete
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name |
str |
required | Dataset name. |
export_name |
str |
required | Export name. |
client.exports.delete(
dataset_name="my-dataset",
export_name="for-yolov8",
)
pictograph exports delete my-dataset for-yolov8 --yes
curl -s -X DELETE "https://api.pictograph.io/api/v1/developer/exports/for-yolov8?dataset=my-dataset" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns None
bulk_delete
Delete up to 10,000 exports by UUID in one server-side call, instead of fanning
out N requests. Requires admin or owner. Ids that do not resolve in your
organization land in not_found rather than raising, so a re-run still succeeds.
Source: Exports.bulk_delete
| Arg | Type | Default | Notes |
|---|---|---|---|
export_ids |
Sequence[str] |
required | UUIDs of the exports to delete (from list / Export.id). Duplicates are ignored; ids that don’t resolve in your organization are reported in BulkDeleteResult.not_found rather than raising, so a re-run still succeeds. |
res = client.exports.bulk_delete(
export_ids=[
"b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c",
"c5e2d8f3-4a69-4b01-9d7c-2e3f4a5b6c7d",
],
)
print(res.succeeded, res.not_found, res.count)
pictograph exports bulk-delete \
b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c \
c5e2d8f3-4a69-4b01-9d7c-2e3f4a5b6c7d --yes
curl -s -X POST "https://api.pictograph.io/api/v1/developer/exports/bulk-delete" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"export_ids": ["b4d1c7e2-3f58-4a90-8c6b-1d2e3f4a5b6c",
"c5e2d8f3-4a69-4b01-9d7c-2e3f4a5b6c7d"]}'
Returns BulkDeleteResult
BulkDeleteResult · 3 fields
class BulkDeleteResult(BaseModel):
"""Result of a server-side bulk delete (one chunked, org-scoped call)."""
succeeded: list[str] = []
not_found: list[str] = []
count: int = 0
Train/valid/test split layout
Assign each image a split with
client.images.set_split, then export with
organize_by_split=True for a directly-trainable ZIP: images and their
annotation files grouped into top-level train/ / valid/ / test/
directories, with unassigned images going to train. Training pipelines honor
the same assignments, so one curation pass drives both.
export = client.exports.create(
dataset_name="my-dataset",
name="yolo-split",
format="yolo",
include_images=True,
organize_by_split=True,
)
pictograph exports create my-dataset --name yolo-split \
--format yolo --include-images --organize-by-split
The resulting YOLO ZIP has one root data.yaml pointing at train: train/images,
val: valid/images, test: test/images plus a root classes.txt, so it trains
with Ultralytics as-is. COCO writes its manifest per split
(train/annotations.json, and so on); every per-image format nests its files
under the split directory. A dataset with no assigned splits keeps the flat
layout.
Class filtering
class_filter keeps only annotations whose class name matches. Images with no
surviving annotations are still included if their status matches
status_filter - they get an empty annotation list. The default None keeps
every annotation.
Common errors
| Status | Exception | Cause |
|---|---|---|
| 404 | NotFoundError |
Dataset or export missing |
| 409 | ConflictError |
Export name already exists in this dataset, or a bare name is ambiguous across datasets - add dataset= |
| 422 | ValidationError |
Unknown format or status_filter |
| 403 | ForbiddenError |
delete / bulk_delete require admin+ |
| 408 | PollTimeoutError |
wait=True timed out - the export keeps building |