Sign in Get started

Exports

Build and download dataset exports in 12 formats, including COCO, YOLO, Pascal VOC, CVAT and canonical Pictograph JSON.

View as Markdown

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
Copied to clipboard