---
title: Files
path: api-guides/files
status: published
---

File CRUD plus versioning, copy, move, and restore. Every file lives inside a share and (optionally) inside a folder.

**Base path:** `/api/v1/files/`

## File metadata shape

```json
{
  "id": "fil_01J3K",
  "name": "spec.pdf",
  "share_id": "shr_01J3H",
  "folder_id": "fld_01J3I",
  "path": "/Engineering/spec.pdf",
  "mime_type": "application/pdf",
  "size": 48293,
  "checksum_sha256": "a9f2...",
  "version": 4,
  "owner_id": "usr_01J3N",
  "created_by": "usr_01J3N",
  "created_at": "2026-04-20T10:00:00Z",
  "modified_at": "2026-04-23T10:15:00Z",
  "modified_by": "usr_01J3N"
}
```

## Get file metadata

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     $SCAIDRIVE_URL/api/v1/files/fil_01J3K
```

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

```typescript
const resp = await fetch(`${url}/api/v1/files/fil_01J3K`, {
  headers: { Authorization: `Bearer ${token}` },
});
const meta = await resp.json();
```

Requires `READ` on the file.

## Download file content

```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:
    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 buf = Buffer.from(await resp.arrayBuffer());
writeFileSync("spec.pdf", buf);
```

Query parameter `?version=N` downloads a specific version. Without it, you get the current version.

Response headers:
- `Content-Length` — full file size
- `Content-Disposition: attachment; filename="..."` — original filename
- `Accept-Ranges: bytes` — range requests supported

For large files, use `Range: bytes=0-999` to fetch a byte range. The server responds with `206 Partial Content`.

## Upload a 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")},
    )
```

```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,
});
```

Multipart fields:

| Field | Required | Notes |
|-------|----------|-------|
| `share_id` | Yes | Destination share |
| `folder_id` | No | Destination folder; omit for share root |
| `name` | No | Override the filename |
| `file` | Yes | File content |

Requires `CREATE` on the destination folder (or on the share, if uploading to the root).

If a file with the same name exists at the destination, the server returns `409 NAME_CONFLICT`. To update instead, use the update flow (upload to an existing file via the resumable upload protocol with `replace_file_id` — see [Resumable Uploads](/docs/scaidrive/advanced/resumable-uploads)) or rename first.

For files over 100 MB, use the resumable upload protocol. The multipart endpoint has a hard limit (default 5 GB, tenant-configurable).

## Update file metadata

Rename, move, or update attributes:

```bash
curl -X PATCH $SCAIDRIVE_URL/api/v1/files/fil_01J3K \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "spec-final.pdf"}'
```

Requires `WRITE` on the file. Renaming a file records a `renamed` change in the change log.

## Delete a file

Soft delete (default):

```bash
curl -X DELETE $SCAIDRIVE_URL/api/v1/files/fil_01J3K \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN"
```

Permanent delete:

```bash
curl -X DELETE "$SCAIDRIVE_URL/api/v1/files/fil_01J3K?permanent=true" \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN"
```

Requires `DELETE` on the file. Soft-deleted files can be restored (via the same PATCH endpoint with a restore flag) for as long as the tenant's trash retention allows.

Legal Hold and Retention policies may block deletion — see [Enterprise Compliance](/docs/scaidrive/advanced/enterprise-compliance).

## Copy a file

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/files/fil_01J3K/copy \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "target_folder_id": "fld_01J4A",
    "new_name": "spec-copy.pdf"
  }'
```

Either `target_folder_id` (same share) or `target_share_id` (across shares) must be set.

Requires `READ` on the source and `CREATE` on the target. Cross-share copies respect deduplication — the copied file's chunks are shared with the original, not duplicated.

## Move a file

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/files/fil_01J3K/move \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target_folder_id": "fld_01J4B"}'
```

Requires `DELETE` on the source and `CREATE` on the target. The file keeps its ID; version history is preserved.

## List file versions

```bash
curl -H "Authorization: Bearer $SCAIDRIVE_TOKEN" \
     $SCAIDRIVE_URL/api/v1/files/fil_01J3K/versions
```

See [Versioning and Deduplication](/docs/scaidrive/core-concepts/versioning-and-deduplication) for the versions response shape.

## Restore a previous version

```bash
curl -X POST $SCAIDRIVE_URL/api/v1/files/fil_01J3K/versions/2/restore \
  -H "Authorization: Bearer $SCAIDRIVE_TOKEN"
```

Creates a new version (e.g., version 5) with the content of version 2. Requires `WRITE`.

## Error cases

| Code | When |
|------|------|
| `NOT_FOUND` | File doesn't exist or caller can't see it |
| `AUTHZ_PERMISSION_DENIED` | Missing required permission |
| `NAME_CONFLICT` | Destination already has a file with this name |
| `PAYLOAD_TOO_LARGE` | Upload exceeds tenant's max file size |
| `QUOTA_EXCEEDED` | Share, user, or tenant quota would be exceeded |
| `RESOURCE_DELETED` | File was soft-deleted; restore before reuse |

See [Errors](/docs/scaidrive/core-concepts/errors) for the full error vocabulary.

## What's next

- [Folders](/docs/scaidrive/api-guides/folders) — folder CRUD and hierarchical queries.
- [Uploads and Downloads](/docs/scaidrive/api-guides/uploads-and-downloads) — resumable uploads and range downloads.
- [Sync](/docs/scaidrive/api-guides/sync) — pulling file changes across devices.