---
title: Uploads and Downloads
path: api-guides/uploads-and-downloads
status: published
---

Strategies for moving content in and out of ScaiDrive. The right approach depends on file size, your network, and whether you need resumability.

## When to use what

| File size | Upload | Download |
|-----------|--------|----------|
| < 5 MB | Multipart POST | Single GET |
| 5–100 MB | Multipart POST | Single GET with retry |
| > 100 MB | Resumable upload session | Range requests |
| Large + flaky network | Resumable upload session | Range requests with retry |
| Already-synced clients | Block-level sync | `GET /sync/download/{id}` with range |

## Simple upload

The multipart endpoint is the default for most integrations. One POST, one file.

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/files \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -F "share_id=shr_01J3H" \
  -F "folder_id=fld_01J3I" \
  -F "file=@./spec.pdf"
```

```python
with open("spec.pdf", "rb") as f:
    resp = httpx.post(
        f"{url}/api/v1/files",
        headers={"Authorization": f"Bearer {token}"},
        data={"share_id": "shr_01J3H", "folder_id": "fld_01J3I"},
        files={"file": ("spec.pdf", f, "application/pdf")},
        timeout=300,
    )
    resp.raise_for_status()
    print(resp.json()["id"])
```

```typescript
const form = new FormData();
form.append("share_id", "shr_01J3H");
form.append("folder_id", "fld_01J3I");
form.append("file", new Blob([readFileSync("spec.pdf")]), "spec.pdf");

const resp = await fetch(`${url}/api/v1/files`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
  body: form,
});
console.log((await resp.json()).id);
```

Hard limit: 5 GB per request (tenant-configurable). For larger files use resumable uploads.

## Simple download

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     -o spec.pdf \
     $SCAIDRIVE_URL/api/v1/files/fil_01J3K/content
```

```python
with httpx.stream(
    "GET",
    f"{url}/api/v1/files/fil_01J3K/content",
    headers={"Authorization": f"Bearer {token}"},
) as r:
    r.raise_for_status()
    with open("spec.pdf", "wb") as out:
        for chunk in r.iter_bytes():
            out.write(chunk)
```

```typescript
const resp = await fetch(`${url}/api/v1/files/fil_01J3K/content`, {
  headers: { Authorization: `Bearer ${token}` },
});
const stream = createWriteStream("spec.pdf");
await finished(Readable.fromWeb(resp.body as any).pipe(stream));
```

Stream the response — don't buffer. Large files will OOM your process.

## Range downloads

For resumable downloads or partial reads:

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     -H "Range: bytes=1048576-2097151" \
     $SCAIDRIVE_URL/api/v1/files/fil_01J3K/content
```

Server responds `206 Partial Content` with `Content-Range: bytes 1048576-2097151/48293123`. Combine multiple range requests to reconstruct large files with retries.

For sync-path downloads that already know the target file ID:

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     -H "Range: bytes=0-999" \
     "$SCAIDRIVE_URL/api/v1/sync/download/fil_01J3K?version=4"
```

Same shape but dedicated to sync clients. Supports `range_start`/`range_end` as query parameters as an alternative to the `Range` header.

## Resumable uploads

For large files — especially over flaky networks — use the upload session API.

**Step 1: create a session.**

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/uploads \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "share_id": "shr_01J3H",
    "folder_id": "fld_01J3I",
    "file_name": "big-video.mp4",
    "file_size": 2147483648,
    "chunk_size": 4194304
  }'
```

Response:

```json
{
  "id": "ups_01J5T",
  "total_chunks": 512,
  "chunk_size": 4194304,
  "expires_at": "2026-04-24T10:15:00Z"
}
```

**Step 2: upload chunks in any order (parallel is fine).**

```bash
curl -X PUT "$SCAIDRIVE_URL/api/v1/uploads/ups_01J5T/chunks/0" \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  --data-binary @./chunk_000
```

**Step 3: finalize.** When all chunks are uploaded:

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/uploads/ups_01J5T/complete \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN"
```

Response contains the resulting `file_id`.

Sessions expire after 24 hours of inactivity. See [Resumable Uploads](/docs/scaidrive/advanced/resumable-uploads) for the full protocol including chunk-level checksums and deduplication hints.

## Verifying downloads

Every file has a `checksum_sha256`. Clients should verify after download:

```python
import hashlib

meta = httpx.get(f"{url}/api/v1/files/fil_01J3K",
                 headers={"Authorization": f"Bearer {token}"}).json()

with httpx.stream("GET", f"{url}/api/v1/files/fil_01J3K/content",
                  headers={"Authorization": f"Bearer {token}"}) as r:
    h = hashlib.sha256()
    for chunk in r.iter_bytes():
        h.update(chunk)

assert h.hexdigest() == meta["checksum_sha256"], "content corruption"
```

## Content types and previews

Uploaded files have their MIME type inferred from the multipart `Content-Type`, or from the file extension if the type is generic. You can override by passing `Content-Type` explicitly in the multipart part.

ScaiDrive doesn't transcode or preview content in the core API. For image thumbnails or document previews, integrate an upstream preview service — ScaiDrive gives you the bytes.

## Performance tips

- **Compress before upload.** ScaiDrive doesn't compress on the server. If your client gzips first, you save bandwidth and storage. Set `Content-Encoding: gzip` on the multipart part for the server to record the pre-decompressed size.
- **Parallelize resumable chunks.** Uploads from a single connection are bottlenecked by upstream bandwidth; 4–8 parallel chunk uploads saturate most links.
- **Reuse connections.** Use an HTTP client that keeps a connection pool (`httpx.Client`, Node's default `fetch` with keep-alive).
- **Skip duplicates.** Before uploading, check if the file is already in the share (`checksum_sha256` match). For block-level clients, the resumable protocol handles this with chunk-hash negotiation.

## What's next

- [Resumable Uploads](/docs/scaidrive/advanced/resumable-uploads) — the chunked upload protocol in detail.
- [Files](/docs/scaidrive/api-guides/files) — file CRUD.
- [Versioning and Deduplication](/docs/scaidrive/core-concepts/versioning-and-deduplication) — how uploads interact with dedup.