Connectors
Import datasets from V7 (Darwin) and Roboflow into Pictograph. Annotations are converted to canonical Pictograph JSON automatically.
Import remote datasets in two steps: validate the source API key, then kick off the import. The import runs as an async job; the SDK polls until terminal status by default.
On the CLI, pass the source key through the PICTOGRAPH_SOURCE_KEY environment
variable rather than --key: a key on the command line lands in shell history
and is readable by any user on the machine.
The source provider’s key (V7 token or Roboflow key) is a separate value sent in the request body. It is used only for the call that needs it and is never persisted.
Supported providers
provider |
Source | Notes |
|---|---|---|
v7 |
V7 Darwin | Polygon paths, bboxes, polylines, keypoints, image tags |
roboflow |
Roboflow | COCO export converted to Pictograph JSON |
validate
Verify the source API key and list the remote datasets available to it. No quota is consumed, and the source API key is sent only on this call.
Source: Connectors.validate
| Arg | Type | Default | Notes |
|---|---|---|---|
provider |
ConnectorProvider |
required | "v7" or "roboflow" (positional) |
api_key |
str |
required | The source provider’s API key |
result = client.connectors.validate(
provider="v7",
api_key="v7_api_token_example",
)
if result.valid:
for ds in result.datasets:
print(ds.id, ds.name, ds.image_count)
else:
print("invalid:", result.error)
export PICTOGRAPH_SOURCE_KEY=v7_api_token_example
pictograph connectors validate v7
curl -s -X POST "https://api.pictograph.io/api/v1/developer/connectors/validate" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"provider": "v7", "api_key": "v7_api_token_example"}'
Returns ValidationResult
ValidationResult · 4 fields
class ValidationResult(BaseModel):
"""Outcome of Connectors.validate."""
valid: bool
workspace: str = ''
datasets: list[RemoteDataset] = []
error: str | None = None
Check valid first - .datasets is populated only on success.
check_limits
Preflight whether an import fits under your plan’s image-count and storage caps, before you start it.
Source: Connectors.check_limits
| Arg | Type | Default | Notes |
|---|---|---|---|
total_images |
int |
required | Images about to be imported |
estimated_size_bytes |
int |
required | Estimated total import size, in bytes |
check = client.connectors.check_limits(
total_images=12500,
estimated_size_bytes=4_000_000_000,
)
if not check.allowed:
print("blocked by:", check.exceeded)
pictograph connectors check-limits --images 12500 --bytes 4000000000
curl -s -X POST "https://api.pictograph.io/api/v1/developer/connectors/check-limits" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"total_images": 12500, "estimated_size_bytes": 4000000000}'
Returns LimitCheckResult
LimitCheckResult · 8 fields
class LimitCheckResult(BaseModel):
"""Outcome of Connectors.check_limits."""
allowed: bool
current_images: int
image_limit: int
images_after_import: int
current_storage_bytes: int
storage_limit_bytes: int
storage_after_import_bytes: int
exceeded: Optional[Literal['images', 'storage', 'both']] = None
import_
Kick off the import. The trailing underscore avoids shadowing the Python
import keyword. The REST endpoint returns an import_id immediately; the SDK
then polls until terminal status when wait=True.
Source: Connectors.import_
| Arg | Type | Default | Notes |
|---|---|---|---|
provider |
ConnectorProvider |
required | "v7" or "roboflow" (positional) |
api_key |
str |
required | Sent only to fetch source data |
datasets |
Sequence[RemoteDataset | dict] |
required | RemoteDataset instances or raw dicts |
wait |
bool |
True |
Poll until terminal |
poll_interval |
float |
3.0 |
Seconds between polls |
timeout |
float |
3600.0 |
Max seconds to wait. Large V7 exports take 30+ minutes |
job = client.connectors.import_(
provider="v7",
api_key="v7_api_token_example",
datasets=[
{"id": "ds_abc", "name": "Road signs", "slug": "road-signs"},
],
wait=True,
poll_interval=3.0,
timeout=3600.0,
)
print(job.import_id, job.status)
for ds in job.datasets:
print(ds.name, ds.imported, "/", job.total_images)
export PICTOGRAPH_SOURCE_KEY=v7_api_token_example
# --dataset is repeatable and resolves ids against `connectors validate`.
pictograph connectors import v7 --dataset ds_abc --dataset ds_def
# Return as soon as the job is queued instead of waiting for it.
pictograph connectors import v7 --dataset ds_abc --no-wait
curl -s -X POST "https://api.pictograph.io/api/v1/developer/connectors/import/start" \
-H "X-API-Key: $PICTOGRAPH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "v7",
"api_key": "v7_api_token_example",
"datasets": [
{"id": "ds_abc", "name": "Road signs", "slug": "road-signs"}
]
}'
Returns ImportJob
ImportJob · 8 fields
class ImportJob(BaseModel):
"""Snapshot of an import operation - totals + per-dataset breakdown."""
import_id: str
status: Literal['processing', 'completed', 'error', 'cancelled']
progress: float = 0.0
total_images: int = 0
imported_images: int = 0
failed_images: int = 0
current_dataset: str = ''
datasets: list[DatasetImportProgress] = []
The raw REST response carries import_id plus the created dataset list; poll the status endpoint below for progress.
get_import
Fetch the current state of an import job.
Source: Connectors.get_import
| Arg | Type | Default | Notes |
|---|---|---|---|
import_id |
str |
required | Import job id returned by import_ (positional) |
job = client.connectors.get_import(
import_id="imp_7f31a904",
)
print(job.status, job.progress, job.imported_images)
pictograph connectors status imp_7f31a904
curl -s "https://api.pictograph.io/api/v1/developer/connectors/import/status/imp_7f31a904" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns ImportJob
ImportJob · 8 fields
class ImportJob(BaseModel):
"""Snapshot of an import operation - totals + per-dataset breakdown."""
import_id: str
status: Literal['processing', 'completed', 'error', 'cancelled']
progress: float = 0.0
total_images: int = 0
imported_images: int = 0
failed_images: int = 0
current_dataset: str = ''
datasets: list[DatasetImportProgress] = []
Status transitions processing to completed, or to error / cancelled.
wait_for_import
Poll an import until it reaches a terminal status. This is an SDK convenience
over get_import and uses the same status endpoint.
Source: Connectors.wait_for_import
| Arg | Type | Default | Notes |
|---|---|---|---|
import_id |
str |
required | Import job id (positional) |
poll_interval |
float |
3.0 |
Seconds between polls |
timeout |
float |
3600.0 |
Max seconds to wait |
job = client.connectors.wait_for_import(
import_id="imp_7f31a904",
timeout=600.0,
)
print(job.status)
# No wait command - `connectors import` already blocks unless you pass
# --no-wait. Otherwise poll status until it reports a terminal state.
pictograph connectors status imp_7f31a904
# No wait endpoint - poll this one until status is completed, error or cancelled.
curl -s "https://api.pictograph.io/api/v1/developer/connectors/import/status/imp_7f31a904" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns ImportJob
ImportJob · 8 fields
class ImportJob(BaseModel):
"""Snapshot of an import operation - totals + per-dataset breakdown."""
import_id: str
status: Literal['processing', 'completed', 'error', 'cancelled']
progress: float = 0.0
total_images: int = 0
imported_images: int = 0
failed_images: int = 0
current_dataset: str = ''
datasets: list[DatasetImportProgress] = []
cancel_import
Soft-cancel an in-flight import. Already-imported images are kept; the worker
stops downloading new ones and the status transitions to cancelled.
Source: Connectors.cancel_import
| Arg | Type | Default | Notes |
|---|---|---|---|
import_id |
str |
required | Import job id (positional) |
job = client.connectors.cancel_import(
import_id="imp_7f31a904",
)
print(job.status)
pictograph connectors cancel imp_7f31a904 --yes
curl -s -X POST "https://api.pictograph.io/api/v1/developer/connectors/import/cancel/imp_7f31a904" \
-H "X-API-Key: $PICTOGRAPH_API_KEY"
Returns ImportJob
ImportJob · 8 fields
class ImportJob(BaseModel):
"""Snapshot of an import operation - totals + per-dataset breakdown."""
import_id: str
status: Literal['processing', 'completed', 'error', 'cancelled']
progress: float = 0.0
total_images: int = 0
imported_images: int = 0
failed_images: int = 0
current_dataset: str = ''
datasets: list[DatasetImportProgress] = []
Annotation conversion
| V7 / COCO | Pictograph |
|---|---|
V7 polygon.paths |
polygon.paths (passthrough) |
V7 bounding_box (no polygon) |
bounding_box |
V7 line.path |
polyline.path |
V7 keypoint |
keypoint |
V7 tag |
an image tag plus a tag class, not a geometry annotation |
V7 ellipse / mask / cuboid / raster_layer |
skipped (no Pictograph equivalent) |
COCO segmentation (flat array) |
polygon.paths (paired into points) |
COCO bbox (no segmentation) |
bounding_box |
COCO keypoints triplets |
keypoint (skips v=0) |
V7 nested directories are preserved: each item’s source path becomes the image’s virtual directory. Roboflow has no directory concept, so those imports land at the dataset root. Classes whose source provides no color are assigned a distinct palette color per dataset.
Tier caps
Imports are charged against your storage and image-count plan caps. See Credits and your plan in the web app.
Common errors
| Status | Exception | Cause |
|---|---|---|
| 401 | AuthError |
Source provider API key rejected |
| 402 | PaymentRequiredError |
Plan cap exceeded |
| 403 | ForbiddenError |
import_ and cancel_import require member+ |
| 404 | NotFoundError |
import_id does not exist |
| 408 | PollTimeoutError |
wait=True exceeded timeout; the job keeps running |
| 422 | ValidationError |
Invalid provider, or an empty datasets list |