Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Uploads and Downloads

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
1
2
3
4
5
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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
1
2
3
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     -o spec.pdf \
     $SCAIDRIVE_URL/api/v1/files/fil_01J3K/content
python
1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
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
1
2
3
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
1
2
3
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
1
2
3
4
5
6
{
  "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
1
2
3
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
1
2
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 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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#

Updated 2026-05-18 15:04:14 View source (.md) rev 2