Skip to main content

Storage Objects API

Complete API reference for managing storage objects (files). This guide covers uploading, downloading, listing, and deleting files.

Endpoints Overview

RESTful API

MethodEndpointDescription
GET/api/apps/{app_slug}/storage/buckets/{bucket}/objects/List objects
POST/api/apps/{app_slug}/storage/buckets/{bucket}/objects/Upload file with multipart form (legacy)
POST/api/apps/{app_slug}/storage/buckets/{bucket}/objects/batch-upload/Upload multiple files (batch)
GET/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}/Download file (default)
GET/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}/?metadata=trueGet object metadata (JSON)
PATCH/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}/Update metadata
DELETE/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}/Delete object
POST/api/apps/{app_slug}/storage/buckets/{bucket}/objects/batch-delete/Delete multiple objects (batch)
MethodEndpointDescription
PUT/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}Upload/update object
POST/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}Upload/update object (alternative)
GET/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}Download file (default) or get metadata (?metadata=true)
DELETE/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}Delete object

Both PUT and POST support:

  • Multipart form data: Traditional file upload with file field
  • Raw binary: Send file bytes directly with metadata in headers

Object Model

Fields

FieldTypeDescription
idintegerInternal database ID
uuidUUIDUnique identifier for API references
bucketintegerBucket ID
bucket_slugstringBucket slug (read-only)
bucket_namestringBucket name (read-only)
filenamestringOriginal filename
file_pathstringBucket-relative file path (e.g., users/123/avatar.jpg)
file_urlstringAPI endpoint URL for accessing/downloading the file
sizeintegerFile size in bytes
mimetypestringMIME type (e.g., image/jpeg)
metadataobjectCustom JSON metadata
visibilitystringVisibility level: "private", "public", or null (inherits from bucket)
is_office_editablebooleanWhether SharePoint browser edit mode is supported for this object's MIME type
created_atdatetimeUpload timestamp
updated_atdatetimeLast update timestamp
created_byintegerUser who uploaded the file
modified_byintegerUser who last modified

Example Response

{
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bucket": 1,
"bucket_slug": "user-avatars",
"bucket_name": "User Avatars",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"file_url": "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/users/john-doe/avatar.jpg",
"size": 245678,
"mimetype": "image/jpeg",
"metadata": {
"width": 512,
"height": 512,
"uploaded_from": "web"
},
"visibility": null,
"is_office_editable": false,
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T10:05:00Z",
"created_by": 1,
"modified_by": null
}

SharePoint-backed buckets expose current-user access actions for opening files in the browser:

MethodEndpointDescription
GET/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}/view/Grant or refresh current-user view access and return SharePoint URL
GET/api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}/edit/Grant or refresh current-user Restricted Edit access and return SharePoint URL

These endpoints have no request body. The access target is always the current authenticated user.

Response Behavior (Content Negotiation)

The response format depends on the Accept header:

Accept HeaderResponseUse Case
application/json200 OK with JSON body containing the URLSPA/frontend fetch() calls
Other / not set302 redirect to intermediate loader pageDirect browser navigation (window.open)

For API clients (SPA frontend):

GET /api/apps/{app_slug}/storage/buckets/{bucket}/objects/{key}/view/
Authorization: Bearer YOUR_TOKEN
Accept: application/json

Success Response (200 OK):

{
"status": "success",
"message": "View access granted",
"data": {
"url": "https://tenant.sharepoint.com/sites/taruvi-my-site/_layouts/15/Doc.aspx?sourcedoc={GUID}&action=embedview&web=1",
"mode": "view"
}
}

The frontend then opens the URL with window.open(data.url, '_blank').

For browser navigation (window.open directly to the endpoint):

When no Accept: application/json header is present, the endpoint redirects to a server-rendered intermediate page that:

  1. Shows a loading indicator ("Opening document...")
  2. Grants SharePoint permissions via a background SSE connection
  3. Redirects the browser to the SharePoint document URL on completion

This allows apps to simply call window.open('/api/.../objects/file.docx/view/') without implementing loading UI.

Edit Restrictions

The /edit/ endpoint is restricted to Office-editable MIME types. The is_office_editable field on object responses indicates whether a file supports browser editing. Non-Office files (e.g., PDFs, images) will receive a 422 error on /edit/.

Supported Office MIME types include:

  • Word: application/vnd.openxmlformats-officedocument.wordprocessingml.document, .docx, .doc
  • Excel: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, .xlsx, .xls
  • PowerPoint: application/vnd.openxmlformats-officedocument.presentationml.presentation, .pptx, .ppt

Public vs Private Visibility

The access flow differs based on the object's effective visibility:

Visibility/view/ behavior/edit/ behavior
PublicReturns an anonymous sharing link (no SharePoint sign-in needed)Returns an anonymous edit link (no sign-in needed)
PrivateGrants per-user Read permission, returns Doc.aspx URL (SharePoint sign-in required)Grants per-user Restricted Edit permission, returns Doc.aspx URL

For private objects, if the user is not yet in the Microsoft tenant, a silent guest invitation is created and the endpoint waits for propagation (up to ~8 seconds) before granting permission.

Authorization

  • /view/ requires the caller to have read permission on the object (Cerbos storage:read)
  • /edit/ requires the caller to have update permission on the object (Cerbos storage:update)

Error Responses

CodeError CodeWhen
200Success (JSON mode)
302Success (browser mode, redirects to loader page)
404NOT_FOUNDObject or bucket does not exist
409CONFLICTSharePoint site not ready (provider_error_code: "sharepoint_site_not_ready")
422VALIDATION_ERRORNon-Office file type for /edit/
502BAD_REQUESTProvider communication failure
503CONFLICTGuest invitation propagation timeout (provider_error_code: "guest_invitation_propagation_timeout") — retry the request

Example error (non-Office edit attempt):

{
"status": "error",
"message": "This file type cannot be edited in SharePoint browser mode.",
"code": "VALIDATION_ERROR"
}

Example error (propagation timeout):

{
"status": "error",
"message": "Guest user invitation did not propagate within the timeout. Please retry.",
"code": "CONFLICT",
"detail": "guest_invitation_propagation_timeout"
}

S3-Backed Objects

The /view/ and /edit/ endpoints are only available for SharePoint-backed buckets. Calling them on S3-backed objects returns 422 Unprocessable Entity.

Upload or update an object using PUT requests with raw binary data.

Request

PUT /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/{key}
Authorization: Bearer YOUR_TOKEN
Content-Type: image/jpeg
X-Metadata-User-Id: 123
X-Metadata-Description: Profile photo

[raw binary data]

Features

  • Raw Binary: Send file bytes directly in request body
  • Metadata Headers: Use X-Metadata-* headers for custom metadata
  • Path in URL: Object key/path is part of the URL
  • Upsert Behavior: Creates new or updates existing objects
  • Also supports POST: Use POST instead of PUT if preferred

Examples

Upload with Raw Binary (curl)

curl -X PUT https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/users/john-doe/avatar.jpg \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: image/jpeg" \
-H "X-Metadata-User-Id: john-doe" \
-H "X-Metadata-Uploaded-From: mobile" \
--data-binary "@avatar.jpg"

Upload with Multipart (also works with PUT)

curl -X PUT https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/users/john-doe/avatar.jpg \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@avatar.jpg"

JavaScript Example (Raw Binary)

const file = document.querySelector('input[type="file"]').files[0];
const key = `users/${userId}/avatar.jpg`;

const response = await fetch(
`https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/${key}`,
{
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': file.type,
'X-Metadata-User-Id': userId,
'X-Metadata-Uploaded-At': new Date().toISOString(),
},
body: file,
}
);

const result = await response.json();
console.log(result);

Python Example (Raw Binary)

import requests

with open('avatar.jpg', 'rb') as f:
file_content = f.read()

response = requests.put(
'https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/users/john-doe/avatar.jpg',
headers={
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'image/jpeg',
'X-Metadata-User-Id': 'john-doe',
'X-Metadata-Source': 'python-script',
},
data=file_content
)

print(response.json())

Response (201 Created)

When creating a new object:

{
"success": true,
"message": "Object created successfully",
"status_code": 201,
"data": {
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bucket": 1,
"bucket_slug": "user-avatars",
"bucket_name": "User Avatars",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"size": 245678,
"mimetype": "image/jpeg",
"metadata": {
"user-id": "john-doe",
"uploaded-from": "mobile"
},
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T10:05:00Z",
"created_by": 1,
"modified_by": null
}
}

Response (200 OK)

When updating an existing object:

{
"success": true,
"message": "Object updated successfully",
"status_code": 200,
"data": {
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bucket": 1,
"bucket_slug": "user-avatars",
"bucket_name": "User Avatars",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"size": 312456,
"mimetype": "image/jpeg",
"metadata": {
"user-id": "john-doe",
"uploaded-from": "mobile"
},
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T11:30:00Z",
"created_by": 1,
"modified_by": 1
}
}

Metadata Headers

Metadata can be passed via headers when uploading with raw binary data.

Recommended Format:

X-Metadata-User-Id: 123
X-Metadata-Custom-Field: value
X-Metadata-Author: John Doe
X-Metadata-Version: 2

S3-Compatible Format (also supported): For compatibility with S3 clients and tools, X-Amz-Meta-* headers are also supported:

X-Amz-Meta-User-Id: 123
X-Amz-Meta-Custom-Field: value

Headers are converted to lowercase keys with hyphens:

  • X-Metadata-User-Iduser-id
  • X-Amz-Meta-Custom-Fieldcustom-field (when using S3 format)

Validation

Files are validated against bucket rules:

  • Size: Must not exceed file_size_limit
  • MIME Type: Must match allowed_mime_types (if set)

Permissions

OperationPublic BucketPrivate Bucket
Read (GET)AnyoneOwner or staff
Write (PUT/POST/PATCH/DELETE)Authenticated usersOwner or staff only

Unauthenticated users can only read objects in public buckets.

Error Responses

File Too Large (400)

{
"success": false,
"message": "File size (10485760 bytes) exceeds bucket limit (5MB)",
"status_code": 400
}

Invalid MIME Type (400)

{
"success": false,
"message": "MIME type 'application/pdf' not allowed. Allowed types: ['image/*']",
"status_code": 400
}

Upload File (Legacy)

Upload a file to a bucket with multipart/form-data.

Request

POST /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/
Authorization: Bearer YOUR_TOKEN
Content-Type: multipart/form-data

file: [binary data]
path: users/john-doe/avatar.jpg (optional)
metadata: {"key": "value"} (optional)

Form Fields

FieldTypeRequiredDescription
filefileYesThe file to upload
pathstringNoCustom path (defaults to filename)
metadataJSONNoCustom metadata object

Validation

Before upload, the file is validated against bucket rules:

  • Size: Must not exceed file_size_limit
  • MIME Type: Must match allowed_mime_types (if set)

Upsert Behavior

If an object with the same path already exists:

  • The existing object is updated (not duplicated)
  • File content is replaced
  • Metadata is updated
  • Returns 200 OK instead of 201 Created

Examples

Basic Upload (curl)

curl -X POST https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@avatar.jpg"

Upload with Custom Path

curl -X POST https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@avatar.jpg" \
-F "path=users/john-doe/profile.jpg"

Upload with Metadata

curl -X POST https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@report.pdf" \
-F "path=reports/2025/q1-report.pdf" \
-F 'metadata={"quarter":"Q1","year":2025,"author":"John Doe"}'

JavaScript Example

const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('path', 'users/john-doe/avatar.jpg');
formData.append('metadata', JSON.stringify({ uploaded_from: 'web' }));

const response = await fetch(
'https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/',
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
body: formData,
}
);

const result = await response.json();
console.log(result);

Response (201 Created)

{
"success": true,
"message": "Object created successfully",
"status_code": 201,
"data": {
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bucket": 1,
"bucket_slug": "user-avatars",
"bucket_name": "User Avatars",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"size": 245678,
"mimetype": "image/jpeg",
"metadata": {},
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T10:05:00Z",
"created_by": 1,
"modified_by": null
}
}

Response (200 OK - Update)

When replacing an existing file:

{
"success": true,
"message": "Object updated successfully",
"status_code": 200,
"data": {
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bucket": 1,
"bucket_slug": "user-avatars",
"bucket_name": "User Avatars",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"size": 312456,
"mimetype": "image/jpeg",
"metadata": {},
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T11:30:00Z",
"created_by": 1,
"modified_by": 1
}
}

Error Responses

File Too Large (400)

{
"success": false,
"message": "File size (10485760 bytes) exceeds bucket limit (5MB)",
"status_code": 400
}

Invalid MIME Type (400)

{
"success": false,
"message": "MIME type 'application/pdf' not allowed. Allowed types: ['image/*']",
"status_code": 400
}

Empty File (400)

{
"error": "Cannot upload empty file"
}

Batch Upload Multiple Files

For uploading multiple files efficiently (up to 10 at once), use the Batch Upload API:

POST /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/batch-upload/

Benefits:

  • ~10x faster than sequential uploads
  • Single request for up to 10 files
  • 100MB total size limit across all files
  • Detailed success/failure tracking per file
  • Partial success handling

Example:

curl -X POST https://your-api.com/api/apps/my-app/storage/buckets/my-bucket/objects/batch-upload/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "files=@photo1.jpg" \
-F "files=@photo2.jpg" \
-F "files=@document.pdf" \
-F 'paths=["users/123/photo1.jpg","users/123/photo2.jpg","users/123/doc.pdf"]'

See Batch Upload Objects for complete documentation with JavaScript, Python, and React examples.

List Objects

Get all objects in a bucket with filtering and pagination.

Request

GET /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/
Authorization: Bearer YOUR_TOKEN

Query Parameters

Basic Parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
page_sizeintegerResults per page (default: 10, max: 100)
orderingstringOrder by field (prefix with - for descending). Options: created_at, updated_at, size, filename, path

Search & Filter Parameters

ParameterTypeDescription
searchstringSearch in both filename AND file path (case-insensitive)
filestringExact file path match
file__startswithstringFilter by file path prefix (e.g., invoices/)
file__icontainsstringSearch anywhere in file path (case-insensitive)
file__istartswithstringFile path starts with (case-insensitive)
filenamestringExact filename match
filename__icontainsstringSearch in filename only (case-insensitive)
filename__istartswithstringFilename starts with (case-insensitive)
filename__iendswithstringFilename ends with (case-insensitive)

Range Filters

ParameterTypeDescription
size__gteintegerNew: Minimum file size in bytes (greater than or equal)
size__lteintegerNew: Maximum file size in bytes (less than or equal)
size__gtintegerNew: File size greater than
size__ltintegerNew: File size less than
min_sizeintegerNew: Friendly alternative to size__gte
max_sizeintegerNew: Friendly alternative to size__lte
created_at__gtedatetimeCreated on or after date (ISO 8601 format)
created_at__ltedatetimeCreated on or before date
created_at__datedateCreated on specific date (YYYY-MM-DD)
created_at__yearintegerFilter by year (e.g., 2024)
created_at__monthintegerFilter by month (1-12)
created_at__dayintegerFilter by day (1-31)
created_afterdatetimeFriendly alternative to created_at__gte
created_beforedatetimeFriendly alternative to created_at__lte
updated_at__gtedatetimeModified on or after date
updated_at__ltedatetimeModified on or before date
updated_at__yearintegerFilter by year
updated_at__monthintegerFilter by month (1-12)
updated_at__dayintegerFilter by day (1-31)
modified_afterdatetimeFriendly alternative to updated_at__gte
modified_beforedatetimeFriendly alternative to updated_at__lte

MIME Type Filters

ParameterTypeDescription
mimetypestringExact MIME type match (e.g., image/jpeg)
mimetype__icontainsstringNew: MIME type contains (e.g., jpeg)
mimetype__istartswithstringNew: MIME type starts with (e.g., image/)
mimetype__instringNew: Multiple MIME types (comma-separated)
mimetype_categorystringNew: Filter by category: image, video, audio, application, text

Visibility & User Filters

ParameterTypeDescription
visibilitystringNew: Filter by visibility: public or private
created_by_mebooleanNew: Filter files created by authenticated user (true/false)
modified_by_mebooleanNew: Filter files modified by authenticated user
created_byintegerNew: Filter by creator user ID
created_by__usernamestringNew: Filter by creator username (exact match)
created_by__username__icontainsstringNew: Creator username contains
modified_byintegerNew: Filter by modifier user ID
modified_by__username__icontainsstringNew: Modifier username contains
ParameterTypeDescription
metadata_searchstringNew: Search anywhere in metadata JSON (case-insensitive)

Examples

Basic Listing

List All Objects

curl https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/ \
-H "Authorization: Bearer YOUR_TOKEN"

With Pagination

curl "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/?page=2&page_size=50" \
-H "Authorization: Bearer YOUR_TOKEN"

Search Examples

Search by Filename or Path

# Finds "avatar.jpg" OR files in path containing "avatar"
curl "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/?search=avatar" \
-H "Authorization: Bearer YOUR_TOKEN"

Search in File Path

# Find all files in "financials" folder
curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?file__icontains=financials" \
-H "Authorization: Bearer YOUR_TOKEN"

Search by Filename Only

# Only search filename, not full path
curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?filename__icontains=report" \
-H "Authorization: Bearer YOUR_TOKEN"

Range Filtering

Files Between 1MB and 10MB

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?size__gte=1048576&size__lte=10485760" \
-H "Authorization: Bearer YOUR_TOKEN"

# Or use friendly names
curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?min_size=1048576&max_size=10485760" \
-H "Authorization: Bearer YOUR_TOKEN"

Files Uploaded in January 2024

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?created_at__gte=2024-01-01&created_at__lte=2024-01-31" \
-H "Authorization: Bearer YOUR_TOKEN"

# Or use friendly names
curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?created_after=2024-01-01&created_before=2024-01-31" \
-H "Authorization: Bearer YOUR_TOKEN"

Large Files Uploaded This Month

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?size__gte=10485760&created_at__gte=2024-01-01" \
-H "Authorization: Bearer YOUR_TOKEN"

MIME Type Filtering

Single MIME Type

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?mimetype=application/pdf" \
-H "Authorization: Bearer YOUR_TOKEN"

Multiple MIME Types (All Images)

curl "https://your-api.com/api/apps/my-app/storage/buckets/assets/objects/?mimetype__in=image/png,image/jpeg,image/gif,image/webp" \
-H "Authorization: Bearer YOUR_TOKEN"

By MIME Category (All Images)

curl "https://your-api.com/api/apps/my-app/storage/buckets/assets/objects/?mimetype_category=image" \
-H "Authorization: Bearer YOUR_TOKEN"

All Videos

curl "https://your-api.com/api/apps/my-app/storage/buckets/media/objects/?mimetype_category=video" \
-H "Authorization: Bearer YOUR_TOKEN"

Visibility Filtering

Only Public Files

curl "https://your-api.com/api/apps/my-app/storage/buckets/media/objects/?visibility=public" \
-H "Authorization: Bearer YOUR_TOKEN"

Only Private Files

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?visibility=private" \
-H "Authorization: Bearer YOUR_TOKEN"

Public Images Only

curl "https://your-api.com/api/apps/my-app/storage/buckets/assets/objects/?visibility=public&mimetype_category=image" \
-H "Authorization: Bearer YOUR_TOKEN"

User/Ownership Filtering

Files I Uploaded

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?created_by_me=true" \
-H "Authorization: Bearer YOUR_TOKEN"

Files Uploaded by Specific User

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?created_by__username=john-doe" \
-H "Authorization: Bearer YOUR_TOKEN"

Files I Modified

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?modified_by_me=true" \
-H "Authorization: Bearer YOUR_TOKEN"

Search for Files by Username

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?created_by__username__icontains=john" \
-H "Authorization: Bearer YOUR_TOKEN"

Metadata Search

Search in Metadata

# Find files with "invoice" anywhere in metadata
curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?metadata_search=invoice" \
-H "Authorization: Bearer YOUR_TOKEN"

Search for Tagged Files

# Find files tagged with "important"
curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?metadata_search=important" \
-H "Authorization: Bearer YOUR_TOKEN"

Sorting

Order by Size (Largest First)

curl "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/?ordering=-size" \
-H "Authorization: Bearer YOUR_TOKEN"

Order by Upload Date (Newest First)

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?ordering=-created_at" \
-H "Authorization: Bearer YOUR_TOKEN"

Order by Filename (A-Z)

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/?ordering=filename" \
-H "Authorization: Bearer YOUR_TOKEN"

Complex Combined Filters

Large PDFs Uploaded by John in January

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/ \
?mimetype=application/pdf \
&size__gte=5242880 \
&created_by__username=john-doe \
&created_at__gte=2024-01-01 \
&created_at__lte=2024-01-31 \
&ordering=-size" \
-H "Authorization: Bearer YOUR_TOKEN"

My Public Images from Last Week

curl "https://your-api.com/api/apps/my-app/storage/buckets/photos/objects/ \
?created_by_me=true \
&visibility=public \
&mimetype_category=image \
&created_at__gte=2024-01-08 \
&ordering=-created_at" \
-H "Authorization: Bearer YOUR_TOKEN"

Find Large Files in Project Folder

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/ \
?file__startswith=projects/project-123/ \
&size__gte=10485760 \
&ordering=-size" \
-H "Authorization: Bearer YOUR_TOKEN"

Search for Reports with Metadata

curl "https://your-api.com/api/apps/my-app/storage/buckets/documents/objects/ \
?search=report \
&metadata_search=Q1 \
&created_at__gte=2024-01-01 \
&ordering=-created_at" \
-H "Authorization: Bearer YOUR_TOKEN"

Response (200 OK)

{
"status": "success",
"message": "Data retrieved successfully",
"status_code": 200,
"data": [
{
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"size": 245678,
"mimetype": "image/jpeg",
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T10:05:00Z"
},
{
"id": 2,
"uuid": "a3c5e2b7-9d4f-4b2a-8e6c-1f5d3a8b9c0d",
"filename": "profile.png",
"file_path": "users/jane-doe/profile.png",
"size": 189234,
"mimetype": "image/png",
"created_at": "2025-01-15T10:10:00Z",
"updated_at": "2025-01-15T10:10:00Z"
}
],
"total": 42,
"page": 1,
"page_size": 10
}

Response Fields:

  • data: Array of objects in the current page
  • total: Total number of objects matching the filters
  • page: Current page number
  • page_size: Number of items per page

Note: List responses use a simplified format for better performance when fetching many objects.

Get Object Metadata

Retrieve metadata for a specific object by UUID.

Request

GET /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/{uuid}/
Authorization: Bearer YOUR_TOKEN

Example

curl https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/7c9e6679-7425-40de-944b-e07fc1f90ae7/ \
-H "Authorization: Bearer YOUR_TOKEN"

Response (200 OK)

{
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bucket": 1,
"bucket_slug": "user-avatars",
"bucket_name": "User Avatars",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"file_url": "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/users/john-doe/avatar.jpg",
"size": 245678,
"mimetype": "image/jpeg",
"metadata": {
"width": 512,
"height": 512
},
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T10:05:00Z",
"created_by": 1,
"modified_by": null
}

Download File

Download the actual file content (binary data). Files are downloaded by default when accessing object endpoints.

Method 1: By UUID (Default Download)

GET /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/{uuid}/
Authorization: Bearer YOUR_TOKEN

Example:

curl https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/7c9e6679-7425-40de-944b-e07fc1f90ae7/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-o downloaded_avatar.jpg

Method 2: By Path (Default Download)

GET /api/apps/{app_slug}/storage/buckets/{bucket}/objects/{path}
Authorization: Bearer YOUR_TOKEN

Example:

curl https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/users/john-doe/avatar.jpg \
-H "Authorization: Bearer YOUR_TOKEN" \
-o avatar.jpg

Note: To get object metadata as JSON instead of downloading, add ?metadata=true to the URL.

Response Headers

Content-Type: image/jpeg
Content-Disposition: inline; filename="avatar.jpg"
Content-Length: 245678

Response Body

Binary file content (streamed directly from cloud storage).

Error Response (404)

If file is not found:

{
"error": "Object file not found in storage",
"detail": "The requested file could not be found"
}

Using in HTML

<!-- Direct image display -->
<img src="https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/object/users/john-doe/avatar.jpg"
alt="Avatar" />

<!-- Download link -->
<a href="https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/object/users/john-doe/avatar.jpg"
download="avatar.jpg">
Download Avatar
</a>

Note: Add Authorization header via JavaScript for private buckets.

Update Object Metadata

Update an object's metadata without re-uploading the file.

Request

PATCH /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/{uuid}/
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
"metadata": {
"updated": true,
"processed_at": "2025-01-15T12:00:00Z"
}
}

Updatable Fields

FieldTypeDescription
filenamestringChange filename
pathstringChange path (rename/move)
metadataobjectUpdate custom metadata

Note: size, mimetype, bucket, and uuid cannot be changed.

Examples

Update Metadata

curl -X PATCH https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/7c9e6679-7425-40de-944b-e07fc1f90ae7/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"width": 1024,
"height": 1024,
"processed": true
}
}'

Rename File (Change Path)

curl -X PATCH https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/7c9e6679-7425-40de-944b-e07fc1f90ae7/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"file": "users/john-doe/profile-picture.jpg",
"filename": "profile-picture.jpg"
}'

Response (200 OK)

{
"id": 1,
"uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bucket": 1,
"bucket_slug": "user-avatars",
"bucket_name": "User Avatars",
"filename": "avatar.jpg",
"file_path": "users/john-doe/avatar.jpg",
"file_url": "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/users/john-doe/avatar.jpg",
"size": 245678,
"mimetype": "image/jpeg",
"metadata": {
"width": 1024,
"height": 1024,
"processed": true
},
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T12:30:00Z",
"created_by": 1,
"modified_by": 1
}

Delete Object

Delete an object and its file from storage.

Method 1: By UUID

DELETE /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/{uuid}/
Authorization: Bearer YOUR_TOKEN

Example:

curl -X DELETE https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/7c9e6679-7425-40de-944b-e07fc1f90ae7/ \
-H "Authorization: Bearer YOUR_TOKEN"

Method 2: By Path

DELETE /api/apps/{app_slug}/storage/buckets/{bucket}/object/{path}
Authorization: Bearer YOUR_TOKEN

Example:

curl -X DELETE https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/object/users/john-doe/avatar.jpg \
-H "Authorization: Bearer YOUR_TOKEN"

Response (204 No Content)

{
"success": true,
"message": "Object \"users/john-doe/avatar.jpg\" deleted successfully",
"status_code": 204
}

Error Response (500)

{
"success": false,
"message": "Failed to delete object \"users/john-doe/avatar.jpg\"",
"status_code": 500
}

Batch Delete Multiple Objects

For deleting multiple objects efficiently (up to 100 at once), use the Batch Delete API:

POST /api/apps/{app_slug}/storage/buckets/{bucket_slug}/objects/batch-delete/

Benefits:

  • ~100x faster than individual deletes
  • Single request for up to 100 objects
  • Detailed success/failure tracking
  • Automatic permission handling

See Batch Delete Objects for complete documentation.

Path Organization

Use forward slashes to create folder-like structures:

User Files

users/{user_id}/avatar.jpg
users/{user_id}/documents/resume.pdf
users/{user_id}/photos/vacation-2025.jpg

Project Files

projects/{project_id}/images/banner.png
projects/{project_id}/assets/logo.svg
projects/{project_id}/reports/monthly-report.pdf

Date-Based Organization

uploads/2025/01/file1.jpg
uploads/2025/02/file2.jpg

Type-Based Organization

images/photos/photo1.jpg
images/avatars/avatar1.jpg
documents/contracts/contract1.pdf
documents/invoices/invoice1.pdf

MIME Type Detection

MIME types are automatically detected from:

  1. File extension (e.g., .jpgimage/jpeg)
  2. Upload Content-Type header (if provided)
  3. Defaults to application/octet-stream if unknown

Common MIME Types

ExtensionMIME Type
.jpg, .jpegimage/jpeg
.pngimage/png
.gifimage/gif
.svgimage/svg+xml
.pdfapplication/pdf
.docapplication/msword
.docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
.xlsapplication/vnd.ms-excel
.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.txttext/plain
.csvtext/csv
.mp4video/mp4
.mp3audio/mpeg

Best Practices

Path Design

Do:

  • Use descriptive paths: users/john-doe/avatar.jpg
  • Group related files: projects/project-123/...
  • Use consistent naming: lowercase, hyphens

Don't:

  • Use absolute paths: /users/avatar.jpg (leading slash)
  • Use special characters: user@123/file!.jpg
  • Create very deep hierarchies: a/b/c/d/e/f/g/file.jpg

Metadata Usage

Store custom information in the metadata field:

{
"metadata": {
"original_filename": "my photo.jpg",
"uploaded_by_device": "iPhone 12",
"image_dimensions": {"width": 1920, "height": 1080},
"tags": ["vacation", "2025", "beach"],
"processed": true,
"thumbnail_generated": true
}
}

Performance

  1. Use File Path Prefixes for Filtering

    ?file__startswith=invoices/

    Efficient filtering by file path prefix.

  2. Limit Result Sets

    ?page_size=20
  3. Use Lightweight List Endpoint The list endpoint returns minimal fields for better performance.

Security

  1. Validate on Client and Server

    • Check file size before upload
    • Validate MIME types
    • Sanitize filenames
  2. Use UUIDs for References

    // Store UUID in your database
    user.avatar_uuid = result.object.uuid;

    // Construct download URL (downloads by default)
    const url = `/api/apps/${appSlug}/storage/buckets/avatars/objects/${user.avatar_uuid}/`;
  3. Sanitize Paths

    # Remove leading/trailing slashes
    path = path.strip('/')

    # Replace multiple slashes
    path = re.sub(r'/+', '/', path)

File Access

Path-Based Organization

Objects are accessed using intuitive paths:

users/john-doe/avatar.jpg

Benefits:

  • Secure: Files are stored securely in the cloud
  • Flexible: Rename files without re-uploading
  • Organized: Use folder-like structures for organization

Efficient Downloads

Files are delivered efficiently for the best performance:

  • No file size limits for downloads
  • Optimized for large files
  • Fast streaming delivery

Visibility Overrides

Overview

Objects can override their bucket's default visibility for fine-grained access control. This allows you to have a public bucket with some private files, or vice versa.

How It Works

Every object has a visibility field:

  • null (default): Inherits visibility from the bucket
  • "public": Object is publicly accessible (no auth required for downloads)
  • "private": Object requires authentication and ownership

Setting Visibility on Upload

Multipart Form Data Upload

POST /api/apps/{app_slug}/storage/buckets/public-images/objects/
Content-Type: multipart/form-data

file=<binary>
path="users/123/private-doc.pdf"
visibility="private" # Override bucket's public visibility
metadata={"sensitive": true}

Raw Binary Upload (S3-style)

PUT /api/apps/{app_slug}/storage/buckets/public-images/objects/users/123/secret.jpg
Content-Type: image/jpeg
X-Metadata-Visibility: private # Set visibility via header

<binary data>

Or in the request body (multipart):

PUT /api/apps/{app_slug}/storage/buckets/public-images/objects/users/123/secret.jpg
Content-Type: multipart/form-data

file=<binary>
visibility="private"

Updating Visibility

You can change an object's visibility after upload:

PATCH /api/apps/{app_slug}/storage/buckets/public-images/objects/users/123/photo.jpg/
Content-Type: application/json

{
"visibility": "private" # Change from public to private
}

Or make it inherit from the bucket again:

PATCH /api/apps/{app_slug}/storage/buckets/public-images/objects/users/123/photo.jpg/
Content-Type: application/json

{
"visibility": null # Remove override, inherit from bucket
}

Access Control Examples

Example 1: Private File in Public Bucket

Bucket: "marketing-assets" (visibility: "public")
Object: "draft/new-logo.png" (visibility: "private")

Result: ✅ Most files are public
❌ Draft files require authentication
# This works (public image)
curl https://api.example.com/api/apps/my-app/storage/buckets/marketing-assets/objects/released/logo.png
# → 200 OK (no auth required)

# This requires auth (private image)
curl https://api.example.com/api/apps/my-app/storage/buckets/marketing-assets/objects/draft/new-logo.png
# → 403 Forbidden (needs authentication + ownership)

Example 2: Public File in Private Bucket

Bucket: "user-documents" (visibility: "private")
Object: "shared/public-report.pdf" (visibility: "public")

Result: ✅ Most files are private
✅ Specific files can be shared publicly
# This requires auth (private document)
curl https://api.example.com/api/apps/my-app/storage/buckets/user-documents/objects/personal/tax-return.pdf
# → 403 Forbidden

# This works (public document)
curl https://api.example.com/api/apps/my-app/storage/buckets/user-documents/objects/shared/public-report.pdf
# → 200 OK (no auth required)

Use Cases

# Create public bucket for images
POST /api/apps/{app_slug}/storage/buckets/
{
"name": "Product Images",
"visibility": "public"
}

# Upload public image (inherits)
PUT /api/apps/{app_slug}/storage/buckets/product-images/objects/products/shoe-1.jpg
file=<binary>
# visibility: null → effective: public

# Upload draft image (override)
PUT /api/apps/{app_slug}/storage/buckets/product-images/objects/drafts/shoe-2.jpg
file=<binary>
visibility="private"
# visibility: private → effective: private
# Create private bucket for documents
POST /api/apps/{app_slug}/storage/buckets/
{
"name": "Company Documents",
"visibility": "private"
}

# Upload private document (inherits)
PUT /api/apps/{app_slug}/storage/buckets/company-documents/objects/internal/strategy.pdf
file=<binary>
# visibility: null → effective: private

# Upload public shareable document (override)
PUT /api/apps/{app_slug}/storage/buckets/company-documents/objects/public/annual-report.pdf
file=<binary>
visibility="public"
# visibility: public → effective: public

Checking Effective Visibility

The API response always includes the object's visibility field:

{
"file_path": "users/123/avatar.jpg",
"visibility": null, // null = inherits from bucket
"bucket_slug": "user-avatars",
// ... other fields
}

To determine effective visibility:

  • If visibility is null → check the bucket's visibility
  • If visibility is "public" or "private" → use that value

Best Practices

  1. Default to Inheritance: Leave visibility: null unless you need to override
  2. Use Sparingly: Too many overrides can be confusing. Consider using separate buckets instead
  3. Document Overrides: Add metadata explaining why an object has a different visibility:
    {
    "visibility": "private",
    "metadata": {
    "reason": "Contains unreleased product information",
    "expires": "2025-12-31"
    }
    }
  4. Audit Regularly: List objects with specific visibility to find exceptions:
    # Find all private objects in a public bucket
    GET /api/apps/{app_slug}/storage/buckets/public-bucket/objects/?visibility=private

Security Considerations

⚠️ Important Notes:

  1. Write Operations Always Require Auth: Even for public objects, upload/delete/update operations require authentication and ownership
  2. Visibility != Permissions: Visibility controls read access. Write access is always restricted to the owner
  3. Monitor Public Objects: Regularly audit public objects to ensure no sensitive data is exposed:
    GET /api/apps/{app_slug}/storage/buckets/my-bucket/objects/ | grep '"visibility": "public"'