---
summary: Create a model, push a version, add variants.
title: Uploading a model
path: guides/uploading-a-model
status: published
---

# Uploading a model

The full path from "nothing" to a downloadable version, with the Python SDK.

## Prerequisites

- Write access to the namespace (role **member** or higher — see [Authentication](/docs/scaiatlas/concepts/authentication)).
- The files you want to publish, locally.

## 1. Make sure the model exists

Models are created once; versions are added over time. `type` is **required**.

```python
from scaiatlas import ScaiAtlas
client = ScaiAtlas.from_env()

client.models.create(
    "poolnoodle", "noodletron",
    type="llm",                       # required; see the model-types reference
    display_name="Noodletron",
    description="A demo language model.",
    license="apache-2.0",
    tags=["demo", "8b"],
)
```

If it already exists you'll get a `ConflictError` — safe to ignore or catch.

## 2. Upload a version

The upload **session** creates the version in `uploading` state, streams each file in
checksum-verified chunks, and finalizes it on `complete()`:

```python
with client.upload.version(
    "poolnoodle", "noodletron", "1.0.0",
    format="safetensors",
    release_notes="Initial release.",
    requirements={"transformers": ">=4.40"},
) as up:
    up.add_file("model.safetensors")
    up.add_file("config.json")
    up.add_file("tokenizer.json")
    version = up.complete()

print(version.full_name, version.status)   # poolnoodle/noodletron:1.0.0 ready
```

Add a whole directory instead of listing files:

```python
with client.upload.version("poolnoodle", "noodletron", "1.0.0",
                           format="safetensors") as up:
    up.add_directory("./noodletron/", pattern="*", recursive=True)
    up.complete()
```

### Progress reporting

```python
from scaiatlas.resources.upload import UploadProgress

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

with client.upload.version("poolnoodle", "noodletron", "1.0.0",
                           format="safetensors",
                           progress_callback=on_progress) as up:
    up.add_file("model.safetensors")
    up.complete()
```

### If something goes wrong

The session is a context manager: if an exception propagates out of the `with`
block before `complete()`, the upload is **aborted** and server-side state is cleaned
up automatically. You can also call `up.abort()` explicitly.

- `version` string must be semver (`1.0.0`, `2.1.0-beta.1`) or you'll get a `ValidationError`.
- `format` must be a supported format (`GET /api/v1/options/formats`) or the create step returns `400`.
- Re-using an existing version string returns a `ConflictError`.

> **Async note:** upload and download are **synchronous-only** helpers. `AsyncScaiAtlas`
> exposes `namespaces`, `models`, `versions`, `variants`, and `search`, but not
> `upload`/`download`. Run transfers with the sync client (e.g. in a thread executor).

## 3. Add format variants (optional)

Request an alternative representation of the version — e.g. a GGUF quantization:

```python
client.variants.create(
    "poolnoodle", "noodletron", "1.0.0",
    variant_name="gguf-q4_k_m",
    format="gguf",
    quantization="Q4_K_M",
)
```

This returns immediately (the server accepts the request and converts asynchronously:
`pending → converting → available`). List variants to check status:

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

## 4. Manage the version later

```python
# Update release notes or lifecycle status
client.versions.update("poolnoodle", "noodletron", "1.0.0",
                       release_notes="Patched tokenizer.", status="ready")

# Deprecate or delete
client.versions.update("poolnoodle", "noodletron", "1.0.0", status="deprecated")
client.versions.delete("poolnoodle", "noodletron", "1.0.0")
```

## Under the hood (REST)

The session maps onto these calls, which you can drive directly in any language:

1. `POST /api/v1/namespaces/{ns}/models/{model}/versions` — create (status `uploading`).
2. `POST …/versions/{version}/upload/init` — per file: returns `upload_id`, `part_size`, `part_count`.
3. `PUT …/upload/{upload_id}/parts/{n}` — push each chunk, collect `etag`s.
4. `POST …/upload/{upload_id}/complete` — assemble and verify.

See the [REST API reference](/docs/scaiatlas/reference/rest-api#upload) for exact schemas.
