DocsPlatformStorage

Storage

Global file storage for the workspace. Upload, organize, and share files across apps and users. PostgreSQL-backed (BYTEA), organized in buckets with a folder hierarchy, and protected by RBAC.


How it works

Files are stored in buckets. Every workspace starts with a default bucket (auto-created, cannot be deleted). You can create additional buckets to organize files by purpose (invoices, contracts, assets) and control access per bucket via RBAC.

Objects within a bucket can be organized in folders. Folders are first-class objects with a parent-child hierarchy. Files can be nested inside folders.

Any authenticated app, agent, or user can read and write to storage if they have the appropriate permission.


Buckets

A bucket is a logical container with optional constraints.

Property Type Description
name string Unique identifier. 1-63 characters. Alphanumeric, dashes, underscores.
public boolean If true, objects can be downloaded without authentication. Default: false.
max_file_size number Maximum upload size in bytes. Null for no bucket-level limit (global max: 64 MB).
allowed_types string[] Allowed MIME types. Supports wildcards like image/*. Null allows any type.

Permissions

Storage uses the platform RBAC system. Four permission keys:

Permission Grants
storage:read List buckets, list objects, download files (all buckets).
storage:write Upload files, create folders (all buckets).
storage:delete Delete files and folders (all buckets).
storage:admin Create and delete buckets.

Per-bucket permissions use the standard wildcard convention:

  • storage:invoices:read grants read access only to the invoices bucket.
  • storage:* grants full access to all buckets.
  • * (global admin) grants everything.

The Core checks both specific (storage:{bucket}:{action}) and global (storage:{action}) permissions. Either grants access.


Folders

Storage supports a folder hierarchy. Folders are objects with is_folder: true and no content. Files and folders have an optional parent_id that creates the tree structure.

Create a folder

POST /api/v1/storage/objects/{bucket}
{ "name": "invoices-2026", "parent_id": "optional-parent-uuid" }

List objects in a folder by passing parent_id:

GET /api/v1/storage/objects/{bucket}?parent_id={folderId}

Without parent_id, lists root-level objects.

Get ancestors (breadcrumb)

GET /api/v1/storage/objects/{bucket}/{id}/ancestors

Returns the chain of parent folders from root to the current object. Useful for building breadcrumb navigation.


Upload a file

POST /api/v1/storage/objects/{bucket}/upload
Content-Type: multipart/form-data

Multipart fields:

  • File field (required): the file data. Filename and content type are extracted from the field.
  • parent_id field (optional): UUID of the parent folder.

Max file size: 64 MB (global). Bucket-level max_file_size may impose a lower limit.

If the bucket has allowed_types configured, the file's content type must match at least one entry (wildcards like image/* supported).

Response (201):

{
  "id": "a1b2c3d4-...",
  "name": "invoice-2026.pdf",
  "content_type": "application/pdf",
  "size": 84521
}

List objects

GET /api/v1/storage/objects/{bucket}
GET /api/v1/storage/objects/{bucket}?parent_id={folderId}

Returns objects sorted by: folders first, then by name ascending.

[
  {
    "id": "...",
    "bucket": "contracts",
    "parent_id": null,
    "name": "invoices-2026",
    "is_folder": true,
    "content_type": null,
    "size": null,
    "metadata": null,
    "uploaded_by": "user-uuid",
    "created_at": "2024-12-01T10:00:00Z"
  },
  {
    "id": "...",
    "bucket": "contracts",
    "parent_id": null,
    "name": "contract-2026.pdf",
    "is_folder": false,
    "content_type": "application/pdf",
    "size": 84521,
    "metadata": null,
    "uploaded_by": "user-uuid",
    "created_at": "2024-12-01T10:30:00Z"
  }
]

Download a file

GET /api/v1/storage/objects/{bucket}/{id}

Returns the raw file bytes with Content-Type, Content-Disposition (attachment), and Content-Length headers.

Public buckets allow unauthenticated downloads. Private buckets require storage:{bucket}:read permission.

Cannot download folders (returns 400).


Rename/move an object

PATCH /api/v1/storage/objects/{bucket}/{id}
{ "name": "new-name.pdf", "parent_id": "target-folder-uuid" }

Both fields are optional. Use parent_id to move an object into a different folder (or set to null to move to root). Requires storage:{bucket}:write permission.


Delete an object

DELETE /api/v1/storage/objects/{bucket}/{id}

Deletes the object. If the object is a folder and contains children, they are deleted recursively. Requires storage:{bucket}:delete permission.


Bucket management

Create a bucket

POST /api/v1/storage/buckets

Requires storage:admin permission (or *).

{
  "name": "contracts",
  "public": false,
  "max_file_size": 10485760,
  "allowed_types": ["application/pdf", "image/*"]
}

List buckets

GET /api/v1/storage/buckets

Requires storage:read permission.

Delete a bucket

DELETE /api/v1/storage/buckets/{name}

Requires storage:admin permission. Cannot delete the default bucket. Fails with 409 Conflict if the bucket contains objects. Delete all objects first.


Storage vs app files

RootCX has two file storage mechanisms:

Platform Storage App Files
Scope Global workspace Per-app
Lifecycle Survives app uninstall Deleted with app
Access Any app/user via RBAC Only the owning app
Hierarchy Buckets + folders Flat
Max size 64 MB 50 MB
Use case Shared documents, org files, cross-app assets Attachments on records, agent working files
API /api/v1/storage/objects/{bucket}/upload /api/v1/apps/{appId}/upload

API endpoints summary

Method Path Permission Description
GET /api/v1/storage/buckets storage:read List buckets
POST /api/v1/storage/buckets storage:admin Create a bucket
DELETE /api/v1/storage/buckets/{name} storage:admin Delete a bucket (must be empty)
GET /api/v1/storage/objects/{bucket} storage:{bucket}:read List objects (optional ?parent_id=)
POST /api/v1/storage/objects/{bucket} storage:{bucket}:write Create a folder
POST /api/v1/storage/objects/{bucket}/upload storage:{bucket}:write Upload a file (multipart, 64 MB max)
GET /api/v1/storage/objects/{bucket}/{id} storage:{bucket}:read Download a file
PATCH /api/v1/storage/objects/{bucket}/{id} storage:{bucket}:write Rename or move an object
DELETE /api/v1/storage/objects/{bucket}/{id} storage:{bucket}:delete Delete an object
GET /api/v1/storage/objects/{bucket}/{id}/ancestors storage:{bucket}:read Get folder ancestor chain