Skip to content

CBFiles Developer Guide

Complete integration guide for the CBFiles file management service.

Overview

CBFiles provides a REST API for file storage and management, wrapping MinIO object storage. It offers:

  • File Operations: Upload, download, list, delete files
  • Bucket Management: Create and configure storage buckets
  • Access Control: Private and public bucket access modes
  • Signed URLs: Time-limited URLs for secure file sharing
  • CDN: Public file delivery via cdn.nominate.ai

Endpoints

Service URL Purpose
API https://files.nominate.ai Full API access
CDN https://cdn.nominate.ai Public file delivery
Docs https://files.nominate.ai/docs OpenAPI/Swagger UI

Authentication

All API requests (except public endpoints) require an API key.

Header Formats

# Option 1: X-API-Key header
curl -H "X-API-Key: cbfiles_abc123..." https://files.nominate.ai/api/v1/users/me

# Option 2: Bearer token
curl -H "Authorization: Bearer cbfiles_abc123..." https://files.nominate.ai/api/v1/users/me

Obtaining an API Key

Contact your administrator or use the CLI if you have server access:

python -m cbfiles.cli bootstrap --name "Service Name" --email "service@example.com"

Python Client Library

The recommended way to integrate with CBFiles from Python applications.

Installation

# From the cbfiles package
pip install cbfiles

# Or install httpx separately if using client only
pip install httpx

Basic Usage

from cbfiles.client import CBFilesClient

# Initialize client
client = CBFilesClient(
    base_url="https://files.nominate.ai",
    api_key="cbfiles_your_api_key_here"
)

# Upload a file
with open("document.pdf", "rb") as f:
    result = client.upload_file(
        bucket_name="my-bucket",
        object_name="documents/report.pdf",
        content=f,
        content_type="application/pdf"
    )
print(f"Uploaded: {result['name']}, ETag: {result['etag']}")

# Download a file
content = client.download_file("my-bucket", "documents/report.pdf")
with open("downloaded.pdf", "wb") as f:
    f.write(content)

# List files
files = client.list_files("my-bucket", prefix="documents/")
for file in files:
    print(f"{file['name']}: {file['size']} bytes")

# Get signed URL for sharing
signed = client.get_signed_url("my-bucket", "documents/report.pdf", expires_in=3600)
print(f"Share this URL: {signed['url']}")

# Clean up
client.close()

Context Manager

from cbfiles.client import CBFilesClient

with CBFilesClient("https://files.nominate.ai", "cbfiles_xxx") as client:
    files = client.list_files("my-bucket")
    # Client automatically closes when exiting the block

Error Handling

from cbfiles.client import CBFilesClient, CBFilesError

try:
    client = CBFilesClient("https://files.nominate.ai", "cbfiles_xxx")
    content = client.download_file("my-bucket", "nonexistent.txt")
except CBFilesError as e:
    print(f"Error: {e}")
    print(f"Status code: {e.status_code}")

REST API Reference

Base URL

https://files.nominate.ai/api/v1

Buckets

List Buckets

GET /buckets?include_public=true

# Response
[
  {
    "name": "my-bucket",
    "owner_id": "uuid",
    "access": "private",
    "description": "My files",
    "created_at": "2024-01-01T00:00:00Z"
  }
]

Create Bucket

POST /buckets
Content-Type: application/json

{
  "name": "my-bucket",
  "access": "private",
  "description": "Optional description"
}

# access options: "private" | "public_read"

Update Bucket

PATCH /buckets/{bucket_name}
Content-Type: application/json

{
  "access": "public_read",
  "description": "Updated description"
}

Delete Bucket

DELETE /buckets/{bucket_name}?force=false

# force=true deletes all files first

Files

List Files

GET /buckets/{bucket_name}/files?prefix=documents/&recursive=true

# Response
[
  {
    "bucket": "my-bucket",
    "name": "documents/file.pdf",
    "size": 12345,
    "content_type": "application/pdf",
    "etag": "abc123",
    "last_modified": "2024-01-01T00:00:00Z"
  }
]

Upload File

POST /buckets/{bucket_name}/files/{object_name}
Content-Type: multipart/form-data

# Form field: file (binary)

# Response
{
  "bucket": "my-bucket",
  "name": "documents/file.pdf",
  "size": 12345,
  "etag": "abc123",
  "url": "https://..."
}

Download File

GET /buckets/{bucket_name}/files/{object_name}

# Returns binary content with appropriate Content-Type header

Delete File

DELETE /buckets/{bucket_name}/files/{object_name}

# Returns 204 No Content

Get File Info (HEAD)

HEAD /buckets/{bucket_name}/files/{object_name}

# Returns headers: Content-Type, Content-Length, ETag, Last-Modified

Signed URLs

Generate Download URL

POST /buckets/{bucket_name}/files/{object_name}/signed-url?expires_in=3600

# Response
{
  "url": "https://...",
  "expires_at": "2024-01-01T01:00:00Z",
  "bucket": "my-bucket",
  "object_name": "file.pdf"
}

Generate Upload URL

POST /buckets/{bucket_name}/files/{object_name}/upload-url?expires_in=3600

# Response - use this URL with PUT to upload directly
{
  "url": "https://...",
  "expires_at": "2024-01-01T01:00:00Z"
}

Users & API Keys

Get Current User

GET /users/me

# Response
{
  "id": "uuid",
  "name": "Service Name",
  "email": "service@example.com",
  "is_active": true,
  "created_at": "2024-01-01T00:00:00Z"
}

List API Keys

GET /users/me/keys

# Response (note: actual key values are never returned)
[
  {
    "id": "uuid",
    "name": "production-key",
    "prefix": "cbfiles_abc",
    "created_at": "2024-01-01T00:00:00Z",
    "last_used_at": "2024-01-15T12:00:00Z",
    "is_active": true
  }
]

Create API Key

POST /users/me/keys?key_name=new-key

# Response (key shown only once!)
{
  "key_info": {
    "id": "uuid",
    "name": "new-key",
    "prefix": "cbfiles_xyz"
  },
  "api_key": "cbfiles_xyzABC123..."
}

Revoke API Key

DELETE /users/me/keys/{key_id}

# Returns 204 No Content

CDN Access

Public buckets can be accessed directly via the CDN without authentication.

Direct CDN URL

https://cdn.nominate.ai/{bucket}/{object_path}

Example:

https://cdn.nominate.ai/public-assets/images/logo.png

Making a Bucket Public

# Via API
PATCH /buckets/my-bucket
{"access": "public_read"}

# Via Python client
client.update_bucket("my-bucket", access="public_read")

CDN Headers

CDN responses include caching headers: - Cache-Control: public, max-age=31536000 (1 year) - ETag for cache validation


Integration Examples

FastAPI Service Integration

from fastapi import FastAPI, UploadFile, HTTPException
from cbfiles.client import CBFilesClient, CBFilesError

app = FastAPI()
cbfiles = CBFilesClient("https://files.nominate.ai", "cbfiles_xxx")

@app.post("/upload")
async def upload_document(file: UploadFile):
    try:
        content = await file.read()
        result = cbfiles.upload_file(
            "documents",
            f"uploads/{file.filename}",
            content,
            file.content_type or "application/octet-stream"
        )
        return {"url": result["url"], "name": result["name"]}
    except CBFilesError as e:
        raise HTTPException(status_code=e.status_code or 500, detail=str(e))

@app.get("/download/{filename}")
async def get_download_url(filename: str):
    try:
        signed = cbfiles.get_signed_url("documents", f"uploads/{filename}", expires_in=300)
        return {"download_url": signed["url"]}
    except CBFilesError as e:
        raise HTTPException(status_code=404, detail="File not found")

Django Integration

# settings.py
CBFILES_URL = "https://files.nominate.ai"
CBFILES_API_KEY = os.environ.get("CBFILES_API_KEY")

# utils.py
from django.conf import settings
from cbfiles.client import CBFilesClient

def get_cbfiles_client():
    return CBFilesClient(settings.CBFILES_URL, settings.CBFILES_API_KEY)

# views.py
from django.http import JsonResponse
from .utils import get_cbfiles_client

def upload_file(request):
    if request.method == "POST":
        file = request.FILES["file"]
        client = get_cbfiles_client()
        result = client.upload_file(
            "uploads",
            file.name,
            file.read(),
            file.content_type
        )
        return JsonResponse(result)

JavaScript/Fetch Integration

const CBFILES_URL = 'https://files.nominate.ai/api/v1';
const API_KEY = 'cbfiles_xxx';

// Upload a file
async function uploadFile(bucket, objectName, file) {
  const formData = new FormData();
  formData.append('file', file);

  const response = await fetch(
    `${CBFILES_URL}/buckets/${bucket}/files/${objectName}`,
    {
      method: 'POST',
      headers: {
        'X-API-Key': API_KEY
      },
      body: formData
    }
  );

  if (!response.ok) {
    throw new Error(`Upload failed: ${response.statusText}`);
  }

  return response.json();
}

// Get signed URL for download
async function getSignedUrl(bucket, objectName, expiresIn = 3600) {
  const response = await fetch(
    `${CBFILES_URL}/buckets/${bucket}/files/${objectName}/signed-url?expires_in=${expiresIn}`,
    {
      method: 'POST',
      headers: {
        'X-API-Key': API_KEY
      }
    }
  );

  return response.json();
}

// List files
async function listFiles(bucket, prefix = '') {
  const params = new URLSearchParams({ prefix, recursive: true });
  const response = await fetch(
    `${CBFILES_URL}/buckets/${bucket}/files?${params}`,
    {
      headers: {
        'X-API-Key': API_KEY
      }
    }
  );

  return response.json();
}

cURL Examples

# Set your API key
export CBFILES_KEY="cbfiles_your_key_here"

# Create a bucket
curl -X POST "https://files.nominate.ai/api/v1/buckets" \
  -H "X-API-Key: $CBFILES_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-files", "access": "private"}'

# Upload a file
curl -X POST "https://files.nominate.ai/api/v1/buckets/my-files/files/docs/readme.txt" \
  -H "X-API-Key: $CBFILES_KEY" \
  -F "file=@./README.md"

# List files
curl "https://files.nominate.ai/api/v1/buckets/my-files/files" \
  -H "X-API-Key: $CBFILES_KEY"

# Download a file
curl "https://files.nominate.ai/api/v1/buckets/my-files/files/docs/readme.txt" \
  -H "X-API-Key: $CBFILES_KEY" \
  -o downloaded.txt

# Get signed URL
curl -X POST "https://files.nominate.ai/api/v1/buckets/my-files/files/docs/readme.txt/signed-url?expires_in=3600" \
  -H "X-API-Key: $CBFILES_KEY"

# Delete a file
curl -X DELETE "https://files.nominate.ai/api/v1/buckets/my-files/files/docs/readme.txt" \
  -H "X-API-Key: $CBFILES_KEY"

Best Practices

Bucket Naming

  • Use lowercase letters, numbers, and hyphens
  • 3-63 characters
  • Must start and end with alphanumeric

Object Naming

  • Use forward slashes for logical folder structure: reports/2024/q1/summary.pdf
  • Avoid special characters except: - _ . /
  • Maximum path length: 1024 characters

Security

  • Store API keys in environment variables, never in code
  • Use short expiration times for signed URLs (5-60 minutes for sensitive files)
  • Use private buckets by default, only make public what needs to be

Performance

  • Use presigned upload URLs for large files to upload directly to storage
  • Leverage CDN caching for public assets
  • Use prefix-based listing for large buckets

Error Codes

HTTP Code Meaning
400 Bad request (invalid parameters)
401 Unauthorized (missing or invalid API key)
403 Forbidden (no access to resource)
404 Not found (bucket or file doesn't exist)
409 Conflict (duplicate bucket name)
500 Server error

Error responses include a detail field:

{
  "detail": "Bucket not found: my-bucket"
}

Support

  • API Documentation: https://files.nominate.ai/docs
  • Issues: Contact your administrator