Batch
Bulk move, copy, delete, and update across many images in one round-trip.
Bulk image operations on a single dataset. Each call takes a dataset_name
plus a list of image IDs and returns a BatchResult with a processed count
and per-item failure context. Partial success does not raise. The backend caps
each request at 100,000 images.
Every example below shows the Python SDK call and the equivalent raw REST
request. The REST examples authenticate with an X-API-Key header; set
PICTOGRAPH_API_KEY in your shell to copy-and-run them.
from pictograph import Client
client = Client() # reads PICTOGRAPH_API_KEY
move
Move images to a different virtual folder within the same dataset. Storage
paths are immutable: “move” updates virtual_folder_path; the underlying
image bytes don’t move.
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name | str | required | Project name within your org |
image_ids | Sequence[str] | required | Image UUIDs (1 to 100000) |
target_folder_path | str | "/" | Destination virtual folder |
result = client.batch.move(
dataset_name="my-dataset",
image_ids=["img-1", "img-2", "img-3"],
target_folder_path="/sorted/cars",
)
print(result.processed, result.failed)
curl -s -X POST "https://api.pictograph.io/api/v1/developer/batch/images/move" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dataset_name": "my-dataset",
"image_ids": ["img-1", "img-2", "img-3"],
"target_folder_path": "/sorted/cars"
}'
Requires member+ role.
copy
Copy images to a different folder. Server-side copy of the underlying bytes
(instant, zero data transfer); new project_images rows point at the copied
blobs.
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name | str | required | |
image_ids | Sequence[str] | required | |
target_folder_path | str | "/" | Destination virtual folder |
duplicate_handling | Literal["rename", "skip", "overwrite"] | "rename" | How to handle filename collisions |
copy_annotations | bool | False | When True, copy annotations_json and status too |
result = client.batch.copy(
dataset_name="my-dataset",
image_ids=["img-1", "img-2"],
target_folder_path="/cars-copy",
duplicate_handling="rename", # collision policy in the destination
copy_annotations=False, # destination images start without annotations
)
print(result.processed, result.failed)
curl -s -X POST "https://api.pictograph.io/api/v1/developer/batch/images/copy" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dataset_name": "my-dataset",
"image_ids": ["img-1", "img-2"],
"target_folder_path": "/cars-copy",
"duplicate_handling": "rename",
"copy_annotations": false
}'
Requires member+ role.
delete
Soft-archive by default; permanent on request. permanent=True purges the
stored bytes plus cached thumbnails. This is irreversible and requires admin+
role.
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name | str | required | |
image_ids | Sequence[str] | required | |
permanent | bool | False | True hard-deletes from storage (admin+) |
result = client.batch.delete(
dataset_name="my-dataset",
image_ids=["img-1", "img-2", "img-3"],
permanent=False, # archive (recoverable)
)
print(result.processed, result.failed)
curl -s -X POST "https://api.pictograph.io/api/v1/developer/batch/images/delete" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dataset_name": "my-dataset",
"image_ids": ["img-1", "img-2", "img-3"],
"permanent": false
}'
update
Update metadata fields on a batch of images. Pass exactly the fields you want
to change: None is omitted from the request. Note this is a PATCH, and the
fields are nested under updates on the wire.
| Arg | Type | Default | Notes |
|---|---|---|---|
dataset_name | str | required | |
image_ids | Sequence[str] | required | |
status | str | None | None | "new", "annotate", "review", "complete" |
display_name | str | None | None | Display override |
is_archived | bool | None | None | True archives; False restores |
result = client.batch.update(
dataset_name="my-dataset",
image_ids=["img-1", "img-2"],
status="complete",
is_archived=False,
)
print(result.processed, result.failed)
curl -s -X PATCH "https://api.pictograph.io/api/v1/developer/batch/images/update" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dataset_name": "my-dataset",
"image_ids": ["img-1", "img-2"],
"updates": {"status": "complete", "is_archived": false}
}'
The SDK raises ValidationError if every field is None (the update would be
a no-op). Requires member+ role.
BatchResult
| Attribute | Type | Notes |
|---|---|---|
success | bool | Whether the call completed |
processed | int | Count of images the op landed for |
failed | list[BatchFailure] | {id, reason} per failed image |
affected_folders | list[str] | Virtual folders touched by the op |
Errors
| Status | Exception | Cause |
|---|---|---|
| 403 | ForbiddenError | permanent=True requires admin+ role; all writes require member+ |
| 404 | NotFoundError | Dataset missing, or no matching image_id |
| 400 / 422 | ValidationError | Invalid field value or empty update |
Why batch over loops
Reorganizing 10K images is one round-trip with batch.move() versus 10K with
images.update(). Bulk operations are implemented server-side as single
statements, not loops.