---
title: Python SDK reference
path: reference/python-sdk
status: published
---

# Python SDK reference

Complete reference for the `scaiatlas` Python SDK (v0.1.2). Install with
`pip install scaiatlas` (Python 3.9+).

## Clients

### `ScaiAtlas` (sync)

```python
ScaiAtlas(
    *,
    api_key: str | None = None,
    auth: Authenticator | None = None,
    base_url: str = "https://api.scaiatlas.io",
    timeout: float = 30.0,
    retry_config: RetryConfig | None = None,
    config: ClientConfig | None = None,
)
```

If `config` is given, its `base_url`/`timeout`/`retry_config` win. Auth precedence:
`api_key` → `auth` → anonymous. Use as a context manager to close cleanly.

**Constructors:**

| Method | Purpose |
|---|---|
| `ScaiAtlas.from_env()` | Read `SCAIATLAS_API_KEY`, `SCAIATLAS_BASE_URL`, `SCAIATLAS_TIMEOUT`, `SCAIATLAS_VERIFY_SSL`. |
| `ScaiAtlas.with_oauth(client_id, *, base_url=…, auth_url=…, scopes=None, **kw)` | Browser OAuth (PKCE). |
| `ScaiAtlas.with_device_flow(client_id="scaiatlas-cli", *, base_url=…, auth_url=…, scopes=None, **kw)` | Headless device flow. |

**Instance methods:** `login()`, `logout()`, `close()`, `__enter__`/`__exit__`.
(`login`/`logout` apply to OAuth/device-flow clients.)

**Resource properties:** `namespaces`, `models`, `versions`, `variants`, `upload`,
`download`, `search`, `admin` (→ `.users`, `.groups`, `.api_keys`).

```python
from scaiatlas import ScaiAtlas

with ScaiAtlas.from_env() as client:
    for ns in client.namespaces.list_all():
        print(ns.name)
```

### `AsyncScaiAtlas` (async)

Same constructors and configuration. Use `async with`; `await` every operation.

```python
import asyncio
from scaiatlas import AsyncScaiAtlas

async def main():
    async with AsyncScaiAtlas(api_key="sk_live_...") as client:
        async for ns in client.namespaces.list_all():
            print(ns.name)

asyncio.run(main())
```

**Resource properties:** `namespaces`, `models`, `versions`, `variants`, `search`,
`admin`. **`upload` and `download` are sync-only** and are not exposed on the async
client — run transfers with `ScaiAtlas` (e.g. in a thread executor).

### `ClientConfig`

```python
ClientConfig(base_url="https://api.scaiatlas.io", timeout=30.0,
             retry_config=RetryConfig(), verify_ssl=True)
ClientConfig.from_env()
```

### `RetryConfig`

Import from `scaiatlas._http.retry`.

```python
RetryConfig(max_retries=3, initial_delay=0.5, max_delay=30.0,
            exponential_base=2.0, jitter=0.1,
            retryable_status_codes={408, 429, 500, 502, 503, 504})
```

Exponential backoff with jitter; retries the status codes above.

---

## Pagination

List methods return `PaginatedResponse[T]`:

```python
resp = client.models.list("poolnoodle", per_page=50)
resp.items            # list[Model] for this page
resp.pagination       # PaginationMeta: page, per_page, total_pages, total_count, has_next, has_prev
for m in resp:        # iterate this page
    ...
```

Every list method has a companion that transparently walks all pages:

```python
for m in client.models.list_all("poolnoodle"):   # sync: Iterator[Model]
    ...
async for m in aclient.models.list_all("poolnoodle"):   # async: async generator
    ...
```

---

## Namespaces — `client.namespaces`

| Method | Signature |
|---|---|
| `list` | `(*, page=1, per_page=20, visibility=None) -> PaginatedResponse[Namespace]` |
| `list_all` | `(*, per_page=100, visibility=None) -> Iterator[Namespace]` |
| `get` | `(name) -> Namespace` |
| `create` | `(name, *, display_name=None, description=None, visibility="tenant") -> Namespace` |
| `update` | `(name, *, display_name=None, description=None) -> Namespace` |
| `delete` | `(name) -> None` |
| `list_members` | `(name, *, page=1, per_page=20) -> PaginatedResponse[NamespaceMember]` |
| `add_member` | `(name, user_id, role="member") -> NamespaceMember` |
| `remove_member` | `(name, user_id) -> None` |

`visibility` ∈ `public`\|`tenant`\|`private`. `role` ∈ `owner`\|`admin`\|`member`\|`viewer`.

---

## Models — `client.models`

| Method | Signature |
|---|---|
| `list` | `(namespace, *, page=1, per_page=20, type=None, tags=None) -> PaginatedResponse[Model]` |
| `list_all` | `(namespace, *, per_page=100, type=None, tags=None) -> Iterator[Model]` |
| `get` | `(namespace, name) -> Model` |
| `create` | `(namespace, name, *, type, display_name=None, description=None, license=None, capabilities=None, modalities=None, metadata=None, tags=None) -> Model` |
| `update` | `(namespace, name, *, display_name=None, description=None, license=None, capabilities=None, modalities=None, metadata=None, tags=None) -> Model` |
| `delete` | `(namespace, name) -> None` |

`type` is a **required** keyword on `create` (a [model type](/docs/scaiatlas/reference/formats#model-types)).

---

## Versions — `client.versions`

| Method | Signature |
|---|---|
| `list` | `(namespace, model, *, page=1, per_page=20) -> PaginatedResponse[Version]` |
| `list_all` | `(namespace, model, *, per_page=100) -> Iterator[Version]` |
| `get` | `(namespace, model, version) -> Version` |
| `create` | `(namespace, model, version, *, format, release_notes=None, requirements=None, quantization=None, files=None) -> Version` |
| `update` | `(namespace, model, version, *, release_notes=None, status=None) -> Version` |
| `delete` | `(namespace, model, version) -> None` |

`version` may be `latest` on `get`. For publishing files, prefer the upload manager
(below) over calling `create` directly.

---

## Variants — `client.variants`

| Method | Signature |
|---|---|
| `list` | `(namespace, model, version, *, page=1, per_page=20) -> PaginatedResponse[Variant]` |
| `list_all` | `(namespace, model, version, *, per_page=100) -> Iterator[Variant]` |
| `get` | `(namespace, model, version, name) -> Variant` |
| `create` | `(namespace, model, version, variant_name, *, format, quantization=None) -> Variant` |
| `delete` | `(namespace, model, version, name) -> None` |

`format` ∈ `safetensors`\|`pytorch`\|`gguf`\|`onnx`\|`other`.

---

## Search — `client.search`

| Method | Signature |
|---|---|
| `models` | `(query, *, page=1, per_page=20, type=None, tag=None, namespace=None, sort="updated", order="desc") -> PaginatedResponse[Model]` |
| `models_all` | `(query, *, per_page=100, type=None, tag=None, namespace=None, sort="updated", order="desc") -> Iterator[Model]` |
| `namespaces` | `(query, *, page=1, per_page=20) -> PaginatedResponse[Namespace]` |
| `namespaces_all` | `(query, *, per_page=100) -> Iterator[Namespace]` |

`sort` ∈ `updated`\|`created`\|`name`\|`downloads`; `order` ∈ `asc`\|`desc`. Filter by a
single `tag`.

---

## Upload — `client.upload` (sync only)

```python
session = client.upload.version(
    namespace, model, version, *,
    format: str,
    release_notes: str | None = None,
    requirements: dict | None = None,
    chunk_size: int = 10 * 1024 * 1024,       # 10 MB
    progress_callback: Callable[[UploadProgress], None] | None = None,
) -> UploadSession
```

**`UploadSession`** (context manager):

| Method | Purpose |
|---|---|
| `add_file(path, *, target_path=None)` | Add one file (computes SHA-256, streams chunks). |
| `add_directory(dir, *, pattern="*", recursive=True)` | Add every matching file. |
| `complete() -> Version` | Finalize the version. |
| `abort()` | Cancel and clean up. |

If an exception leaves the `with` block before `complete()`, the session auto-aborts.

**`UploadProgress`** (dataclass): `file_path`, `file_name`, `bytes_uploaded`,
`total_bytes`, `chunk_index`, `total_chunks`, and `percent_complete` (property).

---

## Download — `client.download` (sync only)

| Method | Signature |
|---|---|
| `version` | `(namespace, model, version, destination, *, resume=False, verify_checksum=True, progress_callback=None, chunk_size=8*1024*1024) -> list[Path]` |
| `file` | `(namespace, model, version, file_path, destination, *, resume=False, verify_checksum=True, progress_callback=None, chunk_size=…) -> Path` |
| `stream` | `(namespace, model, version, file_path, *, chunk_size=…) -> Iterator[bytes]` |

**`DownloadProgress`** (dataclass): `file_path`, `file_name`, `bytes_downloaded`,
`total_bytes`, and `percent_complete` (property).

---

## Admin — `client.admin`

`client.admin.users`, `client.admin.groups`, `client.admin.api_keys`.

**Users** — `list(*, page=1, per_page=20, search=None)`, `list_all(...)`,
`get(user_id)`, `list_permissions(user_id, *, page=1, per_page=20, resource_type=None)`,
`grant_permission(user_id, resource_type, resource_id, permission)`,
`revoke_permission(user_id, permission_id)`.
`resource_type` ∈ `namespace`\|`model`\|`version`; `permission` ∈ `read`\|`write`\|`admin`.

**Groups** — `list(*, page=1, per_page=20, search=None)`, `list_all(...)`,
`get(group_id)`, `list_members(group_id, *, page=1, per_page=20)`, `list_members_all(...)`.

**API keys** — `list(*, page=1, per_page=20, user_id=None)`, `list_all(...)`,
`get(key_id)`, `create(name, *, scopes=None, expires_in_days=None) -> APIKeyCreated`,
`update(key_id, *, name=None, scopes=None)`, `revoke(key_id)`.
`create` returns the full `key` **once**.

---

## Models (data types)

Response models ignore unknown fields; request/create models forbid extras.

### `Namespace`
`id`, `name`, `display_name?`, `description?`, `visibility` (`public`\|`tenant`\|`private`,
default `tenant`), `model_count` (0), `created_at?`, `updated_at?`, `owner_tenant_id?`.

### `NamespaceMember`
`id`, `user: UserSummary?`, `group: GroupSummary?`, `role`, `granted_by`, `granted_at`.
`UserSummary = {id, email, display_name?, tenant_id}`; `GroupSummary = {id, name, tenant_id}`.

### `Model`
`id`, `namespace`, `name`, `display_name?`, `description?`, `type` (ModelType),
`license?`, `capabilities?`, `modalities?` (`Modalities`), `metadata?`, `tags?`,
`version_count` (0), `latest_version?`, `download_count` (0), `created_at`,
`updated_at`, `created_by?`. Computed: `full_name` = `namespace/name`.
`Modalities = {input: [...], output: [...]}`.

### `Version`
`id`, `model_id`, `namespace`, `model`, `version`, `full_name`, `version_major`,
`version_minor`, `version_patch`, `version_prerelease?`, `release_notes?`,
`requirements?`, `format`, `quantization?`, `files: [VersionFile]`, `total_size` (0),
`checksum_sha256`, `status` (default `ready`), `variant_count` (0), `created_at`,
`updated_at`, `published_at?`, `created_by?`.
`VersionFile = {path, size, checksum_sha256}` with computed aliases `name` (=path)
and `sha256` (=checksum_sha256).

### `Variant`
`id`, `version_id`, `variant_name`, `format`, `quantization?`, `total_size` (0),
`checksum_sha256`, `is_primary` (False), `status` (default `available`),
`files: [VariantFile]`, `created_at`. `VariantFile = {path, size, checksum_sha256}`.

### `PaginatedResponse[T]` / `PaginationMeta`
`PaginatedResponse` = `{ items: [T], pagination: PaginationMeta }`, iterable and
indexable. `PaginationMeta` = `{ page, per_page, total_pages, total_count, has_next, has_prev }`.

### Auth models
`TokenInfo = {access_token, token_type="Bearer", expires_in?, refresh_token?, scope?}`.
`UserInfo = {id, username, email?, display_name?, avatar_url?, created_at?}`.

---

## Exceptions

Import from `scaiatlas.exceptions` (top-level for the common ones). See
[Errors](/docs/scaiatlas/reference/errors) for the full hierarchy and handling
patterns. Summary:

```
ScaiAtlasError
├── APIError (status_code, response_body)
│   ├── AuthenticationError (401)   ├── ConflictError (409)
│   ├── AuthorizationError (403)    ├── ValidationError (422, .errors)
│   ├── NotFoundError (404)         └── RateLimitError (429, .retry_after)
├── UploadError (file_path, chunk_index)
├── DownloadError (file_path, bytes_downloaded)
│   └── ChecksumError (expected, actual)
└── TimeoutError (timeout_seconds)
```
