---
summary: Resume, verify, stream, and pick variants.
title: Downloading a model
path: guides/downloading-a-model
status: published
---

# Downloading a model

Pull a whole version, a single file, or stream bytes — with resume and checksum
verification.

## Download a whole version

```python
from pathlib import Path
from scaiatlas import ScaiAtlas

client = ScaiAtlas.from_env()

files = client.download.version(
    "poolnoodle", "noodletron", "1.0.0",
    destination=Path("./noodletron"),
    resume=True,             # continue partial files instead of restarting
    verify_checksum=True,    # SHA-256 every file; raises ChecksumError on mismatch
)
for f in files:
    print("saved", f)
```

Use `latest` to always get the newest semantic version:

```python
client.download.version("poolnoodle", "noodletron", "latest", destination="./noodletron")
```

## Download a single file

```python
path = client.download.file(
    "poolnoodle", "noodletron", "1.0.0",
    file_path="config.json",
    destination="./config.json",
    verify_checksum=True,
)
```

## Stream without touching disk

```python
buf = bytearray()
for chunk in client.download.stream("poolnoodle", "noodletron", "1.0.0",
                                    file_path="config.json"):
    buf.extend(chunk)
print(len(buf), "bytes")
```

## Progress reporting

```python
from scaiatlas.resources.download import DownloadProgress

def on_progress(p: DownloadProgress):
    print(f"{p.file_name}: {p.percent_complete:.1f}%")

client.download.version("poolnoodle", "noodletron", "1.0.0",
                        destination="./noodletron",
                        progress_callback=on_progress)
```

## Downloading a specific variant

By default you get the primary variant. To pull a conversion (e.g. the GGUF build),
target it over REST with the `variant` query parameter:

```bash
GET /api/v1/namespaces/poolnoodle/models/noodletron/versions/1.0.0/download?variant=gguf-q4_k_m
```

List variants first to see what's available and `available`:

```python
for v in client.variants.list_all("poolnoodle", "noodletron", "1.0.0"):
    print(v.variant_name, v.status)
```

## How resume + verification behave

- **Resume** (`resume=True`): if a partial file exists, the client issues a `Range`
  request and appends from the last byte. A fully-present file whose checksum already
  matches is **skipped**.
- **Verification** (`verify_checksum=True`, the default): after writing, the client
  recomputes SHA-256 and compares against the registry's value. On mismatch it raises
  `ChecksumError` carrying `expected` and `actual`.

## Errors to handle

| Exception | When |
|---|---|
| `NotFoundError` | Namespace, model, version, variant, or file doesn't exist. |
| `AuthorizationError` | You lack `download` permission on the namespace. |
| `ChecksumError` | Downloaded bytes don't match the recorded checksum. |
| `DownloadError` | Transfer failed (network, storage). |

```python
from scaiatlas.exceptions import NotFoundError, ChecksumError

try:
    client.download.version("poolnoodle", "noodletron", "9.9.9", destination="./x")
except NotFoundError:
    print("no such version")
except ChecksumError as e:
    print("corrupt download:", e.expected, "!=", e.actual)
```

## Under the hood (REST)

- `GET …/versions/{version}/download` — file metadata + URLs (optional `variant`, `file`).
- `GET …/versions/{version}/download/files/{path}` — streamed bytes, `Range`-aware (`206 Partial Content`, `X-Checksum-SHA256`, `Accept-Ranges: bytes`).

Downloads require the namespace **DOWNLOAD** permission and the version to be in an
`available`/`ready` state.
