Skip to content

File Endpoints

File upload, download, and management.

Upload File

POST /files/upload

Upload a file.

bash
curl -X POST http://localhost:3000/files/upload \
  -H "Authorization: Bearer <token>" \
  -F "file=@/path/to/file.pdf" \
  -F "entityType=documents" \
  -F "entityId=doc-123"

Response:

json
{
  "success": true,
  "file": {
    "id": "file-abc123",
    "name": "file.pdf",
    "size": 1024000,
    "mimeType": "application/pdf",
    "url": "https://storage.example.com/files/file-abc123",
    "entityType": "documents",
    "entityId": "doc-123"
  }
}

Upload Parameters

ParameterTypeRequiredDescription
filefileYesFile to upload
entityTypestringNoEntity type
entityIdstringNoEntity ID
publicbooleanNoPublic access

Download File

GET /files/download/:bucket/:path

Download file by path.

bash
curl http://localhost:3000/files/download/bucket/path/to/file.pdf \
  -H "Authorization: Bearer <token>" \
  --output file.pdf

Signed URL

GET /files/signed-url/:bucket/:path

Get signed download URL.

bash
curl "http://localhost:3000/files/signed-url/bucket/path/to/file.pdf?expiresIn=3600" \
  -H "Authorization: Bearer <token>"

Response:

json
{
  "url": "https://storage.example.com/files/file.pdf?token=...",
  "expiresAt": "2024-01-01T12:00:00Z"
}

Entity Files

GET /files/entity/:entityType/:entityId

List files for entity.

bash
curl http://localhost:3000/files/entity/documents/doc-123 \
  -H "Authorization: Bearer <token>"

Response:

json
{
  "files": [
    {
      "id": "file-abc123",
      "name": "document.pdf",
      "size": 1024000,
      "mimeType": "application/pdf",
      "createdAt": "2024-01-01T00:00:00Z"
    }
  ]
}

Delete File

DELETE /files/:fileId

Delete a file.

bash
curl -X DELETE http://localhost:3000/files/file-abc123 \
  -H "Authorization: Bearer <token>"

Response:

json
{
  "success": true,
  "deleted": "file-abc123"
}

Storage Configuration

json
{
  "storageProvider": "supabase",
  "supabase": {
    "url": "&#123;&#123;env.SUPABASE_URL&#125;&#125;",
    "anonKey": "&#123;&#123;env.SUPABASE_ANON_KEY&#125;&#125;"
  }
}
json
{
  "storageProvider": "r2",
  "r2": {
    "accountId": "&#123;&#123;env.R2_ACCOUNT_ID&#125;&#125;",
    "accessKeyId": "&#123;&#123;env.R2_ACCESS_KEY_ID&#125;&#125;",
    "secretAccessKey": "&#123;&#123;env.R2_SECRET_ACCESS_KEY&#125;&#125;",
    "bucket": "my-bucket",
    "publicUrl": "https://my-bucket.r2.dev"
  }
}

File Metadata Storage

File metadata is stored in your configured database for querying and access control.

sql
-- tenant_assets table schema
CREATE TABLE tenant_assets (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id TEXT NOT NULL,
  bucket TEXT NOT NULL,
  path TEXT NOT NULL,
  filename TEXT,
  content_type TEXT,
  size INTEGER,
  entity_type TEXT,
  entity_id TEXT,
  uploaded_by TEXT,
  uploaded_at TIMESTAMPTZ DEFAULT NOW()
);

-- Query files for entity
SELECT * FROM tenant_assets
WHERE tenant_id = 'tenant-123'
  AND entity_type = 'documents'
  AND entity_id = 'doc-123';
js
// Firestore collection: tenant_assets
{
  tenant_id: "tenant-123",
  bucket: "my-bucket",
  path: "tenant-123/documents/doc-123/1704067200000-file.pdf",
  filename: "file.pdf",
  content_type: "application/pdf",
  size: 1024000,
  entity_type: "documents",
  entity_id: "doc-123",
  uploaded_by: "user-456",
  uploaded_at: Timestamp
}

// Query files for entity
db.collection('tenant_assets')
  .where('tenant_id', '==', 'tenant-123')
  .where('entity_type', '==', 'documents')
  .where('entity_id', '==', 'doc-123')
  .get()
sql
-- tenant_assets table schema
CREATE TABLE tenant_assets (
  id TEXT PRIMARY KEY,
  tenant_id TEXT NOT NULL,
  bucket TEXT NOT NULL,
  path TEXT NOT NULL,
  filename TEXT,
  content_type TEXT,
  size INTEGER,
  entity_type TEXT,
  entity_id TEXT,
  uploaded_by TEXT,
  uploaded_at TEXT DEFAULT (datetime('now'))
);

-- Query files for entity
SELECT * FROM tenant_assets
WHERE tenant_id = 'tenant-123'
  AND entity_type = 'documents'
  AND entity_id = 'doc-123';

Route Examples

Define file-related routes in your config:

json
{
  "path": "/projects/:projectId/files",
  "method": "GET",
  "supabaseQueries": [{
    "table": "tenant_assets",
    "operation": "select",
    "select": "id, filename, content_type, size, uploaded_at",
    "filters": [
      { "column": "entity_type", "operator": "eq", "value": "projects" },
      { "column": "entity_id", "operator": "eq", "value": "&#123;&#123;params.projectId&#125;&#125;" }
    ],
    "order": { "column": "uploaded_at", "ascending": false }
  }]
}
json
{
  "path": "/projects/:projectId/files",
  "method": "GET",
  "firebaseQueries": [{
    "collection": "tenant_assets",
    "operation": "query",
    "filters": [
      { "field": "entity_type", "operator": "==", "value": "projects" },
      { "field": "entity_id", "operator": "==", "value": "&#123;&#123;params.projectId&#125;&#125;" }
    ],
    "orderBy": { "field": "uploaded_at", "direction": "desc" }
  }]
}
json
{
  "path": "/projects/:projectId/files",
  "method": "GET",
  "supabaseQueries": [{
    "table": "tenant_assets",
    "operation": "select",
    "select": "id, filename, content_type, size, uploaded_at",
    "filters": [
      { "column": "entity_type", "operator": "eq", "value": "projects" },
      { "column": "entity_id", "operator": "eq", "value": "&#123;&#123;params.projectId&#125;&#125;" }
    ],
    "order": { "column": "uploaded_at", "ascending": false }
  }]
}

Path Structure

Files are stored with tenant isolation:

{tenantId}/{entityType}/{entityId}/{timestamp}-{filename}

Example: PVqyIY2BHiH8khPFEAdc/documents/doc-123/1704067200000-report.pdf

Presigned URLs

Presigned URLs use S3 Signature V4 for secure, time-limited access.

EndpointDefault Expiry
/files/download/*5 minutes
/files/signed-url/*30 seconds (max)
/files/upload-json24 hours

Access Control

Configure via fileAccessControl.mode:

ModeBehavior
user_onlyOnly uploader can access
user_or_adminUploader or admin roles
tenant_sharedAll tenant users (default)
rbacRole-based via allowedRoles
json
{
  "fileAccessControl": {
    "mode": "user_or_admin",
    "adminRoles": ["admin", "super_admin"]
  }
}

File Size Limits

Default: 10MB

Configured via body limit middleware.

Supported Types

All file types supported. Common types:

  • Documents: PDF, DOCX, TXT
  • Images: PNG, JPG, GIF, SVG
  • Data: JSON, CSV, XML
  • Archives: ZIP, TAR

Released under the ISC License.