Skip to main content

Storage Quickstart

Get started with Taruvi Storage in 5 minutes. This guide walks you through creating buckets and uploading your first files.

Prerequisites

  • A Taruvi app created
  • API authentication token
  • Basic understanding of REST APIs

Step 1: Create a Bucket

First, create a storage bucket to hold your files:

curl -X POST https://your-api.com/api/apps/my-app/storage/buckets/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "User Avatars",
"app_category": "user_content",
"visibility": "public",
"file_size_limit": 5242880,
"allowed_mime_types": ["image/*"]
}'

Response:

{
"id": 1,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "User Avatars",
"slug": "user-avatars",
"app_category": "user_content",
"visibility": "public",
"object_count": 0,
"file_size_limit": 5242880,
"allowed_mime_types": ["image/*"],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}

The slug is auto-generated from the name and will be used in subsequent requests.

Step 2: Upload a File

Upload using PUT with raw binary data:

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" \
--data-binary "@/path/to/avatar.jpg"

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": {
"user-id": "john-doe"
},
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T10:05:00Z"
}

Why use PutObject?

  • ✅ Simpler - file bytes directly in body
  • ✅ Metadata via headers
  • ✅ Path in URL (more RESTful)
  • ✅ Clean, modern API design

Method 2: Multipart Form Upload (Legacy)

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

Method 3: POST with Key in URL

curl -X POST 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=@/path/to/avatar.jpg"

Step 3: Download a File

Files download by default when accessing object endpoints.

Method 1: By UUID (Default Download)

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 avatar.jpg

Method 2: By Path (Default Download)

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

Method 3: Get Metadata First

Add ?metadata=true to get JSON metadata instead of downloading the file:

# Get object metadata (JSON)
curl https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/7c9e6679-7425-40de-944b-e07fc1f90ae7/?metadata=true \
-H "Authorization: Bearer YOUR_TOKEN"

# Then download (default behavior)
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 avatar.jpg

Step 4: List Objects

Get all objects in a bucket:

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

Response:

{
"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",
"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",
"created_at": "2025-01-15T10:05:00Z",
"updated_at": "2025-01-15T10:05:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 10
}

Pagination: Results are paginated (10 items per page by default). Use ?page=2&page_size=20 to customize.

Filter by Path Prefix

Use file__startswith or the simpler prefix parameter with bucket-relative paths:

# Using prefix parameter (simpler)
curl "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/?prefix=users/john-doe/" \
-H "Authorization: Bearer YOUR_TOKEN"

# Using file__startswith (explicit)
curl "https://your-api.com/api/apps/my-app/storage/buckets/user-avatars/objects/?file__startswith=users/john-doe/" \
-H "Authorization: Bearer YOUR_TOKEN"

Note: Prefixes are bucket-relative (e.g., users/john-doe/), not full S3 paths.

Search by Filename

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

Step 5: Delete a File

By UUID

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"

By Path

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:

{
"message": "Object \"users/john-doe/avatar.jpg\" deleted successfully"
}

JavaScript/TypeScript Example

Here's a complete example using the Fetch API:

const API_BASE = 'https://your-api.com/api/apps/my-app/storage';
const AUTH_TOKEN = 'your-auth-token';

// Helper function
async function apiCall(url: string, options: RequestInit = {}) {
return fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
...options.headers,
},
});
}

// 1. Create a bucket
async function createBucket() {
const response = await apiCall(`${API_BASE}/buckets/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'User Avatars',
app_category: 'user_content',
visibility: 'public',
file_size_limit: 5242880, // 5MB
allowed_mime_types: ['image/*'],
}),
});
return response.json();
}

// 2a. Upload a file (PutObject - Recommended)
async function uploadFilePutObject(bucketSlug: string, file: File, key: string, metadata?: Record<string, string>) {
const headers: Record<string, string> = {
'Content-Type': file.type || 'application/octet-stream',
};

// Add metadata headers
if (metadata) {
for (const [key, value] of Object.entries(metadata)) {
headers[`X-Metadata-${key}`] = value;
}
}

const response = await apiCall(
`${API_BASE}/buckets/${bucketSlug}/objects/${key}`,
{
method: 'PUT',
headers,
body: file,
}
);
return response.json();
}

// 2b. Upload a file (Multipart - Legacy)
async function uploadFile(bucketSlug: string, file: File, path?: string) {
const formData = new FormData();
formData.append('file', file);
if (path) {
formData.append('path', path);
}

const response = await apiCall(
`${API_BASE}/buckets/${bucketSlug}/objects/`,
{
method: 'POST',
body: formData,
}
);
return response.json();
}

// 3. Download a file (downloads by default)
async function downloadFile(bucketSlug: string, objectUuid: string) {
const response = await apiCall(
`${API_BASE}/buckets/${bucketSlug}/objects/${objectUuid}/`
);
return response.blob();
}

// 4. List objects with optional prefix filter
async function listObjects(bucketSlug: string, pathPrefix?: string) {
let url = `${API_BASE}/buckets/${bucketSlug}/objects/`;
if (pathPrefix) {
// Using bucket-relative path prefix
url += `?prefix=${encodeURIComponent(pathPrefix)}`;
}
const response = await apiCall(url);
return response.json();
}

// 5. Delete a file
async function deleteFile(bucketSlug: string, objectUuid: string) {
const response = await apiCall(
`${API_BASE}/buckets/${bucketSlug}/objects/${objectUuid}/`,
{ method: 'DELETE' }
);
return response.json();
}

// Usage example
async function main() {
// Create bucket
const bucket = await createBucket();
console.log('Bucket created:', bucket);

// Upload file from input element
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
if (fileInput.files?.[0]) {
const result = await uploadFile(
'user-avatars',
fileInput.files[0],
'users/john-doe/avatar.jpg'
);
console.log('File uploaded:', result);

// List files
const objects = await listObjects('user-avatars', 'users/john-doe/');
console.log('Objects:', objects);

// Download file
const blob = await downloadFile('user-avatars', result.object.uuid);
console.log('Downloaded blob:', blob);

// Delete file
await deleteFile('user-avatars', result.object.uuid);
console.log('File deleted');
}
}

Python Example

import requests
from pathlib import Path

API_BASE = 'https://your-api.com/api/apps/my-app/storage'
AUTH_TOKEN = 'your-auth-token'
HEADERS = {'Authorization': f'Bearer {AUTH_TOKEN}'}

# 1. Create a bucket
def create_bucket():
response = requests.post(
f'{API_BASE}/buckets/',
headers={**HEADERS, 'Content-Type': 'application/json'},
json={
'name': 'User Avatars',
'app_category': 'user_content',
'visibility': 'public',
'file_size_limit': 5242880, # 5MB
'allowed_mime_types': ['image/*'],
}
)
return response.json()

# 2a. Upload a file (PutObject - Recommended)
def upload_file_putobject(bucket_slug: str, file_path: str, key: str, metadata: dict = None):
with open(file_path, 'rb') as f:
file_content = f.read()

headers = {**HEADERS, 'Content-Type': 'application/octet-stream'}

# Add metadata headers
if metadata:
for k, v in metadata.items():
headers[f'X-Metadata-{k}'] = str(v)

response = requests.put(
f'{API_BASE}/buckets/{bucket_slug}/objects/{key}',
headers=headers,
data=file_content
)
return response.json()

# 2b. Upload a file (Multipart - Legacy)
def upload_file(bucket_slug: str, file_path: str, object_path: str = None):
with open(file_path, 'rb') as f:
files = {'file': f}
data = {}
if object_path:
data['path'] = object_path

response = requests.post(
f'{API_BASE}/buckets/{bucket_slug}/objects/',
headers=HEADERS,
files=files,
data=data
)
return response.json()

# 3. Download a file (downloads by default)
def download_file(bucket_slug: str, object_uuid: str, save_path: str):
response = requests.get(
f'{API_BASE}/buckets/{bucket_slug}/objects/{object_uuid}/',
headers=HEADERS
)
with open(save_path, 'wb') as f:
f.write(response.content)

# 4. List objects with optional prefix filter
def list_objects(bucket_slug: str, path_prefix: str = None):
params = {}
if path_prefix:
# Using bucket-relative path prefix
params['prefix'] = path_prefix

response = requests.get(
f'{API_BASE}/buckets/{bucket_slug}/objects/',
headers=HEADERS,
params=params
)
return response.json()

# 5. Delete a file
def delete_file(bucket_slug: str, object_uuid: str):
response = requests.delete(
f'{API_BASE}/buckets/{bucket_slug}/objects/{object_uuid}/',
headers=HEADERS
)
return response.json()

# Usage
if __name__ == '__main__':
# Create bucket
bucket = create_bucket()
print(f"Bucket created: {bucket}")

# Upload file
result = upload_file('user-avatars', 'avatar.jpg', 'users/john-doe/avatar.jpg')
print(f"File uploaded: {result}")

# List objects
objects = list_objects('user-avatars', 'users/john-doe/')
print(f"Objects: {objects}")

# Download file
download_file('user-avatars', result['object']['uuid'], 'downloaded_avatar.jpg')
print("File downloaded")

# Delete file
delete_file('user-avatars', result['object']['uuid'])
print("File deleted")

React Component Example

import React, { useState } from 'react';

const API_BASE = 'https://your-api.com/api/apps/my-app/storage';
const AUTH_TOKEN = 'your-auth-token';

function FileUpload() {
const [file, setFile] = useState<File | null>(null);
const [uploading, setUploading] = useState(false);
const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);

const handleUpload = async () => {
if (!file) return;

setUploading(true);
try {
const formData = new FormData();
formData.append('file', file);
formData.append('path', `uploads/${Date.now()}-${file.name}`);

const response = await fetch(
`${API_BASE}/buckets/user-avatars/objects/`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTH_TOKEN}`,
},
body: formData,
}
);

const result = await response.json();

// Construct download URL (downloads by default)
const downloadUrl = `${API_BASE}/buckets/user-avatars/objects/${result.object.uuid}/`;
setUploadedUrl(downloadUrl);

alert('File uploaded successfully!');
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed');
} finally {
setUploading(false);
}
};

return (
<div>
<input
type="file"
onChange={(e) => setFile(e.target.files?.[0] || null)}
/>
<button onClick={handleUpload} disabled={!file || uploading}>
{uploading ? 'Uploading...' : 'Upload'}
</button>

{uploadedUrl && (
<div>
<p>File uploaded!</p>
<img src={uploadedUrl} alt="Uploaded file" />
</div>
)}
</div>
);
}

export default FileUpload;

Common Patterns

Folder-Like Organization

Use path prefixes to create folder structures:

users/john-doe/avatar.jpg
users/john-doe/documents/resume.pdf
users/jane-doe/avatar.jpg
projects/project-1/images/banner.png

List files in a "folder":

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

Replace Existing Files

Uploading to the same path automatically replaces the file:

# First upload
curl -X POST .../objects/ -F "file=@avatar-v1.jpg" -F "path=avatar.jpg"

# Replace (same path)
curl -X POST .../objects/ -F "file=@avatar-v2.jpg" -F "path=avatar.jpg"

Public URLs for Static Assets

For public buckets, you can construct direct URLs:

GET /api/apps/{app_slug}/storage/buckets/{bucket}/object/{path}

Use in <img> tags, CDN, etc.

Next Steps

Troubleshooting

File Too Large

{
"error": "File size (10485760 bytes) exceeds bucket limit (5MB)"
}

Solution: Increase the bucket's file_size_limit setting when creating the bucket to allow larger files (up to several hundred MB depending on server configuration).

Invalid MIME Type

{
"error": "MIME type 'application/pdf' not allowed. Allowed types: ['image/*']"
}

Solution: Update bucket's allowed_mime_types or upload correct file type.

File Not Found

{
"error": "Object file not found in storage",
"detail": "Object users/avatar.jpg exists in database but file is missing"
}

Solution: File was removed from storage. Delete the object record and re-upload the file.