S4 provides a comprehensive RESTful API for storage operations, configuration management, and file transfers. All API endpoints are prefixed with /api.
Development: http://localhost:5000/api
Production: https://your-domain/api
S4 supports two authentication modes:
Disabled (default for development):
- No authentication required
- All endpoints accessible without credentials
Enabled (production):
- JWT token-based authentication
- Enabled when both
UI_USERNAMEandUI_PASSWORDenvironment variables are set
curl -X POST http://localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "your-password"
}'Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "admin",
"username": "admin",
"roles": ["admin"]
},
"expiresIn": 28800
}Option 1: Authorization Header (recommended for API clients):
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:5000/api/bucketsOption 2: HttpOnly Cookie (automatic for browser):
- Token automatically set as
s4_auth_tokencookie - Sent automatically with requests from browser
- Cleared on logout
EventSource API cannot set custom headers, so S4 uses one-time tickets for SSE endpoints to avoid exposing JWT tokens in URLs.
Generate Ticket:
curl -X POST http://localhost:5000/api/auth/sse-ticket \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"resource": "transfer-job-id",
"resourceType": "transfer"
}'Response:
{
"ticket": "abc123...",
"sseUrl": "/transfer/progress/transfer-job-id?ticket=abc123...",
"expiresAt": 1234567890000,
"expiresIn": 60
}Use Ticket:
const eventSource = new EventSource(`/api/transfer/progress/job-123?ticket=${ticket}`);Security:
- 60-second TTL (configurable)
- Single-use (deleted after validation)
- Resource-scoped (tied to specific transfer/upload)
- Rate-limited (20 tickets per minute)
S4 uses Base64 encoding for object keys and file paths in URLs to handle special characters safely.
Encode (Base64):
- Object keys (S3):
models/llama/config.json - File paths (local):
/data/datasets/training.csv
Don't Encode:
- Bucket names (validated to URL-safe characters)
- Location IDs (
local-0,my-bucket)
URL-safe Base64:
// Encode
const encoded = btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
// Decode
const decoded = atob(encoded.replace(/-/g, '+').replace(/_/g, '/'));Example:
Original: models/llama-2-7b/config.json
Base64: bW9kZWxzL2xsYW1hLTItN2IvY29uZmlnLmpzb24
URL: /api/objects/my-bucket/bW9kZWxzL2xsYW1hLTItN2IvY29uZmlnLmpzb24
List operations support pagination using continuation tokens:
# First page
GET /api/objects/my-bucket?maxKeys=100
# Next page
GET /api/objects/my-bucket?maxKeys=100&continuationToken=PREVIOUS_TOKENResponse:
{
"objects": [...],
"isTruncated": true,
"nextContinuationToken": "TOKEN_FOR_NEXT_PAGE"
}Object listing supports search filtering:
# Starts with search (fast)
GET /api/objects/my-bucket?q=model&mode=startsWith
# Contains search (slower, scans multiple pages)
GET /api/objects/my-bucket?q=config&mode=containsRate Limiting: Contains search is limited to 5 requests per minute.
Upload and download operations use streaming for memory efficiency:
Upload:
curl -X POST http://localhost:5000/api/objects/upload/my-bucket/ZmlsZS50eHQ \
-H "Content-Type: multipart/form-data" \
-F "file=@largefile.bin"Download:
curl http://localhost:5000/api/objects/download/my-bucket/ZmlsZS50eHQ \
-o downloaded.binTransfer and upload progress is streamed via SSE:
Example:
const eventSource = new EventSource(`/api/transfer/progress/job-123?ticket=${ticket}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(`Progress: ${data.completed}/${data.total}`);
};
eventSource.onerror = () => {
eventSource.close();
};Event Format:
{
"type": "progress",
"job": {
"id": "job-123",
"status": "in_progress",
"filesCompleted": 5,
"totalFiles": 10,
"bytesTransferred": 1048576,
"totalBytes": 10485760
},
"currentFile": {
"path": "models/config.json",
"loaded": 512000,
"total": 1048576
}
}{
"error": "ErrorName",
"message": "Human-readable error description"
}| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Authentication required or invalid token |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Resource already exists |
| 413 | Payload Too Large | File exceeds size limit |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error |
| 507 | Insufficient Storage | Disk full |
When rate limited, response includes retryAfter:
{
"error": "RateLimitExceeded",
"message": "Too many requests. Maximum 5 per minute.",
"retryAfter": 1234567890
}Rate Limits (per IP address):
- Login: 5 attempts per minute
- SSE ticket generation: 20 requests per minute
- Object contains search: 5 requests per minute
- Local file uploads: 20 requests per minute
Rate limits are hardcoded and stored in-memory. Exceeded limits return HTTP 429 with retryAfter timestamp. See Configuration Reference for customization.
- Authentication API
GET /auth/info- Check auth statusPOST /auth/login- LoginPOST /auth/logout- LogoutGET /auth/me- Get current userPOST /auth/sse-ticket- Generate SSE ticket
- Buckets API
GET /buckets- List bucketsPOST /buckets- Create bucketDELETE /buckets/:bucketName- Delete bucket
- Objects API
GET /objects/:bucketName- List objectsPOST /objects/upload/:bucketName/:encodedKey- Upload objectGET /objects/download/:bucketName/:encodedKey- Download objectDELETE /objects/:bucketName/:encodedKey- Delete objectGET /objects/view/:bucketName/:encodedKey- View object metadataPOST /objects/huggingface-import- Import HuggingFace modelGET /objects/upload-progress/:encodedKey- Upload progress (SSE)
- Notifications API
GET /notifications/:bucketName- Get bucket notification configurationsPUT /notifications/:bucketName- Set bucket notification configurationsDELETE /notifications/:bucketName/:notificationId- Remove a notificationPOST /notifications/test-endpoint- Test a webhook endpoint
- Transfer API
POST /transfer- Create transfer jobGET /transfer/progress/:jobId- Transfer progress (SSE)POST /transfer/cancel/:jobId- Cancel transferPOST /transfer/cleanup/:jobId- Clean up transferPOST /transfer/check-conflicts- Check for conflicts
- Settings API
GET /settings/s3- Get S3 settingsPUT /settings/s3- Update S3 settingsPOST /settings/test-s3- Test S3 connectionGET /settings/huggingface- Get HuggingFace tokenPUT /settings/huggingface- Update HuggingFace tokenPOST /settings/test-huggingface- Test HuggingFace connectionGET /settings/proxy- Get proxy settingsPUT /settings/proxy- Update proxy settingsPOST /settings/test-proxy- Test proxy connectionGET /settings/max-concurrent-transfers- Get concurrency limitPUT /settings/max-concurrent-transfers- Update concurrency limitGET /settings/max-files-per-page- Get pagination limitPUT /settings/max-files-per-page- Update pagination limitGET /settings/disclaimer- Get disclaimer statusPUT /settings/disclaimer- Update disclaimer status
- Local Storage API
GET /local/locations- List storage locationsGET /local/files/:locationId/*- List filesPOST /local/upload/:locationId/*- Upload fileGET /local/download/:locationId/*- Download fileDELETE /local/:locationId/*- Delete file/directoryGET /local/view/:locationId/*- View file metadataPOST /local/create-directory/:locationId/*- Create directory
See API Examples for complete workflow examples using curl and aws-cli.
Development Origins (default):
http://localhost:5000http://localhost:8888http://localhost:9000http://127.0.0.1:5000http://127.0.0.1:8888http://127.0.0.1:9000
Production: Override via ALLOWED_ORIGINS environment variable (comma-separated).
Credentials: CORS configured with credentials: true for cookie support.
Current Version: v1 (implicit, no version prefix in URL)
Future: API versioning will be added as /api/v2/... when breaking changes are introduced.
- API Examples - Complete workflow examples
- Backend Architecture - Implementation details
- Authentication Security - Security considerations