---
title: Files
description: Upload, organize, and recover organization files.
sidebar:
  order: 2
---

Every file in Siglata belongs to an organization. Files remain with the organization when its members change. The storage architecture incorporates pre-allocated upload reservations, fixed 8 MiB multipart chunking, atomic metadata operations, and an automated 30-day trash recovery lifecycle.

## Storage Quota Architecture [#storage-quota]

Each organization starts with a baseline storage quota of **10 GiB** (**10,737,418,240 bytes**).

Siglata tracks storage through an atomic, transactional ledger that distinguishes between retained data and reserved upload capacity:

```
┌────────────────────────────────────────────────────────────────────────┐
│                   Total Organization Quota (10 GiB)                    │
├──────────────────────────────────────┬──────────────────┬──────────────┤
│              Used Bytes              │  Reserved Bytes  │  Available   │
│ (Active Files + 30-Day Trash Files)  │ (Active Uploads) │  Free Space  │
└──────────────────────────────────────┴──────────────────┴──────────────┘
```

- **`limitBytes`**: Total storage capacity allocated to the organization (`10,737,418,240` bytes).
- **`usedBytes`**: Retained storage across both **active files** and **files in trash**. Moving an item to trash does not free quota immediately; bytes remain accounted for until the 30-day recovery period lapses and cleanup purges the data.
- **`reservedBytes`**: Storage locked by in-flight multipart uploads. When an upload begins, Siglata reserves the declared file size upfront to prevent concurrent uploads from colliding or exceeding organization limits.
- **Available Space**: Calculated in real time as `limitBytes - usedBytes - reservedBytes`.

## Multipart Chunking & Upload Reservations [#chunking-reservations]

To support massive files with high transfer reliability across unstable connections, Siglata uses a fixed 8 MiB chunking pipeline:

| Parameter | Specification | Value |
| :-- | :-- | :-- |
| **Chunk Size (`CHUNK_SIZE`)** | Fixed part size | `8,388,608` bytes (8 MiB) |
| **Maximum Part Count** | Maximum parts per upload | `10,000` parts |
| **Maximum File Size (`MAX_FILE_BYTES`)** | Upper limit per single file | `83,886,080,000` bytes (~80 GB) |

### The Upload Lifecycle

```
1. upload_begin ──> Check Quota ──> Pre-allocate reservedBytes ──> Return Upload ID & URL
        │
        ▼
2. PUT /parts/{n} ──> Upload 8 MiB Chunks (Parallel or Sequential) ──> Record receivedParts
        │
        ▼
3. upload_complete ──> Verify Part Count ──> Convert reservedBytes to usedBytes ──> File Active
        │
        └── (If Aborted) ──> upload_cancel ──> Delete Chunks ──> Release reservedBytes
```

1. **Upload Reservation (`upload_begin`)**:
   - The client provides a unique `requestId` (UUID), `name`, `mediaType`, and `size`.
   - Siglata verifies that `size <= (limitBytes - usedBytes - reservedBytes)`.
   - The declared bytes are atomically added to `reservedBytes`, and an upload session is created with an expiration timestamp.
   - The server returns an upload identifier and transfer instructions containing the HTTP `PUT` URL template.

2. **Chunk Transmission (`PUT /v1/mcp/uploads/{uploadId}/parts/{partNumber}`)**:
   - The client streams parts numbered sequentially from `1` to `N` (up to 10,000).
   - Each part must be exactly `8,388,608` bytes, except for the final part which sends the remaining declared bytes.
   - Parts can be uploaded concurrently. The server validates each chunk's length and records its arrival in the upload manifest.

3. **Sealing & Activation (`upload_complete`)**:
   - Once all parts have been received, the client calls `upload_complete`.
   - The server verifies that every part from `1` to `N` is accounted for and that the total uploaded size matches the reservation.
   - The upload record transitions to `completed`, the file is marked `active`, and the reserved capacity moves from `reservedBytes` into `usedBytes`.
   - This operation is completely idempotent: retrying `upload_complete` with the same upload ID returns the active file record safely.

4. **Cancellation & Expiration (`upload_cancel`)**:
   - If an upload is cancelled by the user or client, `upload_cancel` triggers cleanup of any stored chunks and immediately releases the `reservedBytes`.
   - If a client disconnects unexpectedly, background reconciliation automatically detects expired upload sessions, releases their reservations, and cleans up orphaned chunks.

## 30-Day Trash & Recovery Lifecycle [#trash-recovery]

Siglata implements a safety-first data retention model to protect teams against accidental file deletion:

### Moving to Trash (`file_trash`)

When a member deletes an active file:

- The file's status transitions from `active` to `trash`.
- The timestamp of deletion is recorded (`trashed_at = clock_timestamp()`).
- The recovery deadline is set to exactly 30 days in the future:
  ```sql
  recover_until = clock_timestamp() + interval '30 days'
  ```
- The file disappears from the default files list but appears in the trash view (`files_list` with `state: "trash"`).
- **Quota Accounting**: The file's bytes remain charged to the organization's `usedBytes`. This guarantees that the stored data is fully preserved and that restoring the file will never fail due to quota exhaustion.

### Restoring Files (`file_restore`)

If a file was deleted accidentally:

- Any member with `files:write` permissions can invoke `file_restore` at any point before `recover_until`.
- The file atomically returns to `active` status, clearing `trashed_at` and `recover_until`.
- Because the file's bytes were already accounted for in `usedBytes` while in trash, restoration consumes zero additional storage quota.

### Automated Permanent Purge

- Once the 30-day recovery deadline passes (`recover_until <= clock_timestamp()`), restoration is permanently disabled.
- The Siglata background reconciliation worker queries for expired trash items in batches:
  ```sql
  SELECT id FROM files_object
  WHERE state = 'trash' AND recover_until <= clock_timestamp()
  ```
- The worker marks the object for purging, deletes the stored binary blobs from the object storage tier, and cleans up the database record.
- Only after binary deletion is confirmed are the file's bytes permanently subtracted from the organization's `usedBytes`, freeing up quota for new uploads.

## Metadata Operations [#metadata-operations]

### File Renaming (`file_rename`)

Renaming a file in Siglata updates only the database record's `name` attribute. Because stored object blobs are addressed by immutable UUIDs, renaming is an instantaneous, zero-cost operation that requires no byte copying or quota adjustment.

### Streaming Downloads (`file_download`)

Authorized members can download files directly via the authenticated download endpoint (`GET /v1/mcp/files/{fileId}/download`). The endpoint validates the caller's active organization membership in real time before streaming bytes, ensuring that revoked members or former collaborators cannot access organization assets.

## Folders and Access Control [#folders-access]

Files can live in nested folders, and every file and folder carries a `visibility` flag:

- **`org`** (default): Every organization member can read and write the object, subject to their OAuth scopes. This preserves the behavior of all files created before folders existed.
- **`restricted`**: Only the object's creator, organization `owner`/`admin` members, and members holding an explicit grant can access the object.

A restricted folder restricts its entire subtree: to read or write an object, the caller must satisfy every restricted folder in its ancestor chain. A grant on a folder extends to its unrestricted descendants, while a restricted object inside a shared folder still requires its own grant.

- Denied operations fail with the `forbidden` error code; restricted objects a member cannot read are also omitted from `files_list` and `folders_list` results.
- `grant_create` and `grant_revoke` are limited to the object's creator and organization administrators. To change a grant level or grantee, revoke then create (there is no separate replace operation).
- `file_set_visibility` and `folder_set_visibility` require write access on the object; they do not change existing grants.
- `file_move` and `upload_begin` into a folder require write access on the destination folder.
- Trashing a folder does not trash its contents; when a trashed folder passes its recovery deadline, it is deleted and its children move to the organization root.
