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:readgrants read access only to theinvoicesbucket.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" }
Navigate the hierarchy
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_idfield (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 |