---
title: Connectors
description: Import datasets from V7 (Darwin) and Roboflow into Pictograph. Annotations are converted to canonical Pictograph JSON automatically.
section: API Reference
order: 12
---
Import remote datasets in two steps: validate the source API key →
kick off the import. The import runs as an async job; the SDK polls
until terminal status by default.

```python
from pictograph import Client
client = Client()
```

## Supported providers

| `provider` | Source | Notes |
|---|---|---|
| `v7` | V7 Darwin | Polygon paths, bboxes, polylines, keypoints, tags |
| `roboflow` | Roboflow | COCO export → Pictograph JSON |

## validate

Verify the source API key and list available remote datasets. No quota
consumed; the API key is sent only on this call.

```python
result = client.connectors.validate(
    provider="v7",
    api_key="v7_api_token_…",
)
if result.valid:
    for ds in result.datasets:
        print(ds.id, ds.name, ds.image_count)
else:
    print("invalid:", result.error)
```

Returns `ValidationResult` — inspect `.valid` first; `.datasets` is
populated only on success.

## check_limits

Pre-flight tier-cap check before kicking off an import.

```python
check = client.connectors.check_limits(
    total_images=12500,
    estimated_size_bytes=4_000_000_000,   # 4 GB
)
if not check.allowed:
    print("blocked by:", check.exceeded)   # "images" / "storage" / "both"
```

## import_

Kick off the import. Trailing underscore avoids shadowing the Python
`import` keyword.

```python
job = client.connectors.import_(
    provider="v7",
    api_key="v7_api_token_…",
    datasets=[
        {"id": "ds_abc", "name": "Road signs", "slug": "road-signs"},
        # OR pass RemoteDataset instances from validate():
        # *result.datasets[:2],
    ],
    wait=True,
    poll_interval=3.0,
    timeout=3600.0,                    # 1h default
)
print(job.import_id, job.status)
for ds in job.datasets:
    print(ds.dataset_name, ds.images_imported, "/", ds.total_images)
```

| Arg | Type | Default | Notes |
|---|---|---|---|
| `provider` | `ConnectorProvider` | required | `"v7"` / `"roboflow"` |
| `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 |
| `timeout` | `float` | `3600.0` | Max poll seconds (V7 large exports take 30+ min) |

Returns `ImportJob`.

## get_import / wait_for_import / cancel_import

```python
job = client.connectors.get_import(import_id)
job = client.connectors.wait_for_import(import_id, timeout=600.0)
job = client.connectors.cancel_import(import_id)
```

## 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` / `ellipse` / `mask` | 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`) |

## Tier caps

Imports are charged against your storage + image-count tier caps. See
[Credits](/docs/api-reference/credits.md) and your plan in the web app.

## Common errors

| Status | Exception | Cause |
|---|---|---|
| 401 | `AuthError` | Source provider API key rejected |
| 402 | `PaymentRequiredError` | Tier cap exceeded |
| 404 | `NotFoundError` | `import_id` missing |
| 408 | `PollTimeoutError` | `wait=True` exceeded `timeout` (job keeps running) |
| 422 | `ValidationError` | Invalid provider, empty datasets list |