Entities Upload API
Overviewβ
The Entities Upload API allows authenticated users to upload files and attachments related to entities within the Ring Platform. This endpoint uses Vercel Blob storage for secure, scalable file management with proper access controls.
Endpoint Detailsβ
- URL:
/api/entities/upload
- Method:
POST
- Authentication: Required (Bearer token or session)
- Authorization: SUBSCRIBER level and above
- Content Type:
multipart/form-data
- File Size Limit: 10MB per file
- Supported Formats: Images, documents, PDFs, presentations
Ring Platform Entity File Managementβ
File Types and Use Casesβ
Ring Platform supports various file types for entity documentation and branding:
Supported File Categoriesβ
- Entity Logos: PNG, JPG, SVG (company branding)
- Documentation: PDF, DOC, DOCX (company profiles, certifications)
- Presentations: PPT, PPTX (pitch decks, company overviews)
- Images: JPG, PNG, GIF (office photos, team pictures)
- Certificates: PDF (business licenses, certifications, awards)
File Organizationβ
- Public Access: Files are publicly accessible via CDN
- Unique Naming: Automatic file naming to prevent conflicts
- Metadata Tracking: File size, type, upload date, and user information
- Entity Association: Files can be linked to specific entities
Request Formatβ
Headersβ
POST /api/entities/upload
Content-Type: multipart/form-data
Authorization: Bearer <token>
Form Data Parametersβ
file
(required): The file to uploadentityId
(optional): Associate file with specific entityfileType
(optional): Category of file (logo, document, certificate, etc.)description
(optional): File description or purpose
Example Form Dataβ
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('entityId', 'ent_abc123def456');
formData.append('fileType', 'logo');
formData.append('description', 'Company logo for TechCorp Solutions');
Response Formatβ
Success Response (200 OK)β
{
"success": true,
"url": "https://blob.vercel-storage.com/entities/ent_abc123/user_456_1705234567890_company-logo.png",
"downloadUrl": "https://blob.vercel-storage.com/entities/ent_abc123/user_456_1705234567890_company-logo.png?download=1",
"filename": "company-logo.png",
"size": 245760,
"contentType": "image/png",
"entityId": "ent_abc123def456",
"fileType": "logo",
"description": "Company logo for TechCorp Solutions",
"uploadedAt": "2025-01-14T16:45:00Z",
"uploadedBy": "user_456",
"metadata": {
"width": 512,
"height": 512,
"format": "PNG"
}
}
Error Responsesβ
Unauthorized (401)β
{
"error": "Unauthorized"
}
No File Provided (400)β
{
"error": "No file provided"
}
File Too Large (413)β
{
"error": "File size exceeds the maximum allowed limit"
}
Unsupported File Type (415)β
{
"error": "Invalid file type"
}
Upload Failed (500)β
{
"error": "Error uploading file: Storage service unavailable"
}
Code Examplesβ
JavaScript/TypeScriptβ
interface EntityFileUploadResult {
success: boolean;
url: string;
downloadUrl: string;
filename: string;
size: number;
contentType: string;
entityId?: string;
fileType?: string;
description?: string;
uploadedAt: string;
uploadedBy: string;
metadata?: {
width?: number;
height?: number;
format?: string;
};
}
async function uploadEntityFile(
file: File,
authToken: string,
options: {
entityId?: string;
fileType?: string;
description?: string;
} = {}
): Promise<EntityFileUploadResult> {
const formData = new FormData();
formData.append('file', file);
if (options.entityId) {
formData.append('entityId', options.entityId);
}
if (options.fileType) {
formData.append('fileType', options.fileType);
}
if (options.description) {
formData.append('description', options.description);
}
const response = await fetch('/api/entities/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to upload file');
}
return response.json();
}
// Usage examples
try {
// Upload entity logo
const logoFile = document.getElementById('logoInput').files[0];
const logoResult = await uploadEntityFile(logoFile, userToken, {
entityId: 'ent_abc123',
fileType: 'logo',
description: 'Company logo'
});
console.log('Logo uploaded:', logoResult.url);
// Upload company document
const docFile = document.getElementById('docInput').files[0];
const docResult = await uploadEntityFile(docFile, userToken, {
entityId: 'ent_abc123',
fileType: 'document',
description: 'Company profile PDF'
});
console.log('Document uploaded:', docResult.url);
} catch (error) {
console.error('Upload failed:', error.message);
}
React Component for File Uploadβ
import { useState, useRef } from 'react';
interface EntityFileUploaderProps {
entityId?: string;
fileType?: string;
onUploadComplete?: (result: EntityFileUploadResult) => void;
acceptedTypes?: string;
maxSize?: number; // in bytes
}
function EntityFileUploader({
entityId,
fileType,
onUploadComplete,
acceptedTypes = "image/*,.pdf,.doc,.docx,.ppt,.pptx",
maxSize = 10 * 1024 * 1024 // 10MB
}: EntityFileUploaderProps) {
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [uploadProgress, setUploadProgress] = useState(0);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// Validate file size
if (file.size > maxSize) {
setError(`File size exceeds ${maxSize / (1024 * 1024)}MB limit`);
return;
}
setIsUploading(true);
setError(null);
setUploadProgress(0);
try {
const token = localStorage.getItem('authToken');
if (!token) {
throw new Error('Authentication required');
}
// Simulate upload progress (in real implementation, use XMLHttpRequest for progress)
const progressInterval = setInterval(() => {
setUploadProgress(prev => Math.min(prev + 10, 90));
}, 100);
const result = await uploadEntityFile(file, token, {
entityId,
fileType,
description: `${fileType || 'File'} for entity`
});
clearInterval(progressInterval);
setUploadProgress(100);
onUploadComplete?.(result);
// Reset form
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Upload failed');
} finally {
setIsUploading(false);
setTimeout(() => setUploadProgress(0), 1000);
}
};
return (
<div className="entity-file-uploader">
<div className="upload-area">
<input
ref={fileInputRef}
type="file"
accept={acceptedTypes}
onChange={handleFileSelect}
disabled={isUploading}
className="file-input"
/>
<div className="upload-prompt">
<div className="upload-icon">π</div>
<p>
{isUploading
? `Uploading... ${uploadProgress}%`
: `Choose ${fileType || 'file'} to upload`
}
</p>
<p className="upload-hint">
Max size: {maxSize / (1024 * 1024)}MB
</p>
</div>
{isUploading && (
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${uploadProgress}%` }}
/>
</div>
)}
</div>
{error && (
<div className="error-message">
{error}
</div>
)}
</div>
);
}
// Usage in entity profile form
function EntityProfileForm({ entityId }: { entityId: string }) {
const [entityFiles, setEntityFiles] = useState<EntityFileUploadResult[]>([]);
const handleFileUpload = (result: EntityFileUploadResult) => {
setEntityFiles(prev => [...prev, result]);
console.log('File uploaded successfully:', result.filename);
};
return (
<div className="entity-profile-form">
<h3>Entity Files</h3>
<div className="file-upload-sections">
<div className="upload-section">
<h4>Company Logo</h4>
<EntityFileUploader
entityId={entityId}
fileType="logo"
acceptedTypes="image/*"
maxSize={5 * 1024 * 1024} // 5MB for images
onUploadComplete={handleFileUpload}
/>
</div>
<div className="upload-section">
<h4>Company Documents</h4>
<EntityFileUploader
entityId={entityId}
fileType="document"
acceptedTypes=".pdf,.doc,.docx"
onUploadComplete={handleFileUpload}
/>
</div>
<div className="upload-section">
<h4>Certificates</h4>
<EntityFileUploader
entityId={entityId}
fileType="certificate"
acceptedTypes=".pdf,.jpg,.png"
onUploadComplete={handleFileUpload}
/>
</div>
</div>
<div className="uploaded-files">
<h4>Uploaded Files</h4>
{entityFiles.map((file, index) => (
<div key={index} className="file-item">
<span className="file-name">{file.filename}</span>
<span className="file-type">{file.fileType}</span>
<span className="file-size">{(file.size / 1024).toFixed(1)} KB</span>
<a href={file.url} target="_blank" rel="noopener noreferrer">
View
</a>
</div>
))}
</div>
</div>
);
}
cURL Exampleβ
# Upload entity logo
curl -X POST "https://ring.ck.ua/api/entities/upload" \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-F "file=@company-logo.png" \
-F "entityId=ent_abc123def456" \
-F "fileType=logo" \
-F "description=Company logo for TechCorp Solutions"
# Upload company document
curl -X POST "https://ring.ck.ua/api/entities/upload" \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-F "file=@company-profile.pdf" \
-F "entityId=ent_abc123def456" \
-F "fileType=document" \
-F "description=Company profile and overview"
# Expected response
# {
# "success": true,
# "url": "https://blob.vercel-storage.com/entities/ent_abc123/user_456_1705234567890_company-logo.png",
# "filename": "company-logo.png",
# "size": 245760,
# "contentType": "image/png",
# "uploadedAt": "2025-01-14T16:45:00Z"
# }
Python Exampleβ
import requests
from typing import Dict, Any, Optional
class EntityFileUploader:
def __init__(self, base_url: str, auth_token: str):
self.base_url = base_url.rstrip('/')
self.auth_token = auth_token
self.headers = {
'Authorization': f'Bearer {auth_token}'
}
def upload_file(
self,
file_path: str,
entity_id: Optional[str] = None,
file_type: Optional[str] = None,
description: Optional[str] = None
) -> Dict[str, Any]:
"""Upload a file for an entity"""
url = f"{self.base_url}/api/entities/upload"
# Prepare form data
files = {'file': open(file_path, 'rb')}
data = {}
if entity_id:
data['entityId'] = entity_id
if file_type:
data['fileType'] = file_type
if description:
data['description'] = description
try:
response = requests.post(
url,
headers=self.headers,
files=files,
data=data
)
if response.status_code == 200:
return response.json()
elif response.status_code == 401:
raise Exception("Unauthorized - check your authentication token")
elif response.status_code == 400:
error_data = response.json()
raise Exception(f"Bad request: {error_data.get('error', 'Unknown error')}")
elif response.status_code == 413:
raise Exception("File too large - exceeds size limit")
elif response.status_code == 415:
raise Exception("Unsupported file type")
else:
error_data = response.json()
raise Exception(f"Upload failed: {error_data.get('error', 'Unknown error')}")
finally:
files['file'].close()
def upload_logo(self, file_path: str, entity_id: str) -> Dict[str, Any]:
"""Convenience method for uploading entity logos"""
return self.upload_file(
file_path,
entity_id=entity_id,
file_type='logo',
description='Company logo'
)
def upload_document(self, file_path: str, entity_id: str, description: str) -> Dict[str, Any]:
"""Convenience method for uploading entity documents"""
return self.upload_file(
file_path,
entity_id=entity_id,
file_type='document',
description=description
)
def upload_certificate(self, file_path: str, entity_id: str, description: str) -> Dict[str, Any]:
"""Convenience method for uploading certificates"""
return self.upload_file(
file_path,
entity_id=entity_id,
file_type='certificate',
description=description
)
# Usage examples
uploader = EntityFileUploader("https://ring.ck.ua", "your-auth-token")
try:
# Upload company logo
logo_result = uploader.upload_logo("./assets/company-logo.png", "ent_abc123")
print(f"Logo uploaded: {logo_result['url']}")
print(f"File size: {logo_result['size']} bytes")
# Upload company profile document
doc_result = uploader.upload_document(
"./documents/company-profile.pdf",
"ent_abc123",
"Comprehensive company profile and overview"
)
print(f"Document uploaded: {doc_result['url']}")
# Upload business certificate
cert_result = uploader.upload_certificate(
"./certificates/business-license.pdf",
"ent_abc123",
"Official business license certificate"
)
print(f"Certificate uploaded: {cert_result['url']}")
# Batch upload multiple files
files_to_upload = [
{"path": "./images/office-photo1.jpg", "type": "image", "desc": "Office main area"},
{"path": "./images/office-photo2.jpg", "type": "image", "desc": "Office meeting room"},
{"path": "./docs/company-brochure.pdf", "type": "document", "desc": "Company brochure"}
]
upload_results = []
for file_info in files_to_upload:
try:
result = uploader.upload_file(
file_info["path"],
entity_id="ent_abc123",
file_type=file_info["type"],
description=file_info["desc"]
)
upload_results.append({
'filename': result['filename'],
'url': result['url'],
'status': 'success'
})
except Exception as e:
upload_results.append({
'filename': file_info["path"],
'error': str(e),
'status': 'error'
})
# Summary
successful = [r for r in upload_results if r['status'] == 'success']
failed = [r for r in upload_results if r['status'] == 'error']
print(f"Batch upload complete: {len(successful)} successful, {len(failed)} failed")
except Exception as e:
print(f"Error uploading file: {e}")
File Management Best Practicesβ
File Organizationβ
- Naming Convention: Automatic unique naming prevents conflicts
- Directory Structure: Files organized by entity and user
- Metadata Storage: Track file purpose, upload date, and user
- Version Control: Handle file updates and replacements
Security Considerationsβ
- Access Control: Files are publicly accessible but require authentication to upload
- File Validation: Server-side validation of file types and sizes
- Malware Scanning: Implement virus scanning for uploaded files
- Content Filtering: Prevent upload of inappropriate content
Performance Optimizationβ
- CDN Distribution: Files served via global CDN for fast access
- Image Optimization: Automatic image compression and format optimization
- Lazy Loading: Load files on demand to improve page performance
- Caching Strategy: Implement appropriate caching headers
Related Endpointsβ
- Create Entity - Create new entities
- Update Entity - Modify entity information
- Get Entity - Retrieve entity details
- Opportunities Upload - Upload opportunity files
Changelogβ
- v1.0.0 - Initial implementation with basic file upload
- v1.1.0 - Added file type categorization and metadata
- v1.2.0 - Enhanced security and validation features
- v1.3.0 - Improved performance and CDN integration