Files API β
The Files API allows you to upload, manage, and retrieve files associated with boards.
Upload File β
Uploads a file to the specified board.
- URL:
/boards/:uuid/files - Method:
POST - Headers:
Authorization:Bearer <board-token>Content-Type:multipart/form-data
- Body:
file: The file to upload (Binary)
Supported File Types β
- Images:
image/jpeg,image/png,image/gif,image/webp - Audio:
audio/mpeg,audio/wav,audio/ogg - Documents:
application/pdf
Limits β
- Max file size: 50MB
Response (201 Created) β
{
"fileId": "file-uuid-string",
"url": "https://example.com/files/file-uuid-string",
"filename": "image.jpg",
"size": 1024000,
"mimeType": "image/jpeg",
"metadata": {
"width": 800,
"height": 600
}
}List Files β
Retrieves a list of all files uploaded to the specified board.
- URL:
/boards/:uuid/files - Method:
GET - Headers:
Authorization:Bearer <board-token>
Response β
[
{
"id": "uuid-string",
"fileId": "file-uuid-string",
"filename": "image.jpg",
"mimeType": "image/jpeg",
"sizeBytes": 1024000,
"uploadedBy": "user-uuid-string",
"uploadedAt": "2025-11-17T10:00:00.000Z",
"metadata": { ... }
}
]Get File URL β
Retrieves a temporary presigned URL to download the specified file.
- URL:
/boards/:uuid/files/:fileId/url - Method:
GET - Headers:
Authorization:Bearer <board-token>
Response β
{
"url": "https://example.com/file-download-url"
}Delete File β
Deletes a file from the specified board. Only the board host can delete files.
- URL:
/boards/:uuid/files/:fileId - Method:
DELETE - Headers:
Authorization:Bearer <board-token>
Response (204 No Content) β
// No contentComplete Upload Flow β
Step-by-Step File Upload Example β
Here's a complete example showing how to upload a file from frontend to board:
1. Obtain Board Token
First, get a valid board token (host or guest):
const response = await fetch('/api/v1/boards', {
method: 'POST',
headers: {
'X-API-Key': 'your-org-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'My Board',
initialData: {}
})
});
const { hostToken } = await response.json();2. Prepare File for Upload
In browser, use FormData:
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);3. Upload File
const uploadResponse = await fetch(`/api/v1/boards/${boardUuid}/files`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${hostToken}`
},
body: formData
});
const uploadResult = await uploadResponse.json();
console.log(uploadResult);
// {
// "fileId": "file-abc123",
// "url": "https://cdn.boardapi.io/files/file-abc123",
// "filename": "image.jpg",
// "size": 1024000,
// "mimeType": "image/jpeg"
// }4. Use File in Board Object
Once uploaded, reference the file in your board objects:
await fetch(`/api/v1/boards/${boardUuid}/objects`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${hostToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'vocabulary-card',
x: 100,
y: 100,
customData: {
word: 'hello',
translation: 'ΠΏΡΠΈΠ²Π΅Ρ',
pronunciation: uploadResult.url // Use uploaded file URL
}
})
});Supported File Types and Limits β
Supported MIME Types β
BoardAPI accepts the following file types:
Images
image/jpeg- JPEG/JPG imagesimage/png- PNG imagesimage/gif- GIF animationsimage/webp- WebP imagesimage/svg+xml- SVG vector graphics
Audio
audio/mpeg- MP3 audioaudio/wav- WAV audioaudio/ogg- OGG audioaudio/webm- WebM audio
Documents
application/pdf- PDF documentsapplication/vnd.ms-excel- Excel spreadsheets (XLS)application/vnd.openxmlformats-officedocument.spreadsheetml.sheet- Excel (XLSX)text/csv- CSV files
Archives
application/zip- ZIP archivesapplication/x-tar- TAR archives
File Size Limits β
| Plan | Max File Size | Total Storage |
|---|---|---|
| Starter | 10 MB | 1 GB |
| Professional | 50 MB | 10 GB |
| Business | 100 MB | 100 GB |
| Enterprise | 500 MB | Custom |
Quota Enforcement β
Upload will fail with 402 Payment Required if:
- File exceeds plan's max file size
- Organization storage quota is exceeded
- Board storage limit is reached
Example Error:
{
"statusCode": 402,
"message": "Storage quota exceeded",
"current": 10737418240,
"limit": 10737418240,
"upgradeRequired": true
}Presigned URLs Usage β
BoardAPI uses presigned URLs for secure, temporary file access.
What are Presigned URLs? β
Presigned URLs are time-limited, signed URLs that grant temporary access to files without exposing storage credentials.
Getting a Presigned URL β
GET /boards/:uuid/files/:fileId/url
Authorization: Bearer <board-token>Response:
{
"url": "https://minio.boardapi.io/org-abc123/file-xyz789?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Expires=3600"
}URL Expiration β
Presigned URLs expire after:
- Default: 1 hour (3600 seconds)
- Custom: Can request different expiration (up to 7 days for Enterprise)
Security Benefits β
- No storage credentials exposed to clients
- Time-limited access
- Can be revoked by deleting file
- CORS-safe for browser usage
Best Practices β
Do:
- Request presigned URL when needed (don't cache long-term)
- Use HTTPS for all file operations
- Validate file exists before requesting URL
Don't:
- Store presigned URLs in database (they expire)
- Share presigned URLs publicly (anyone with URL can access)
- Use expired URLs (will return 403 Forbidden)
CDN Integration β
BoardAPI integrates with CDN for fast file delivery worldwide.
How CDN Works β
User requests file
β
CDN edge location (cached)
β (cache miss)
Origin storage (MinIO/S3)
β (cache and serve)
User receives file (fast!)CDN Benefits β
- Low Latency - Files served from nearest edge location
- High Throughput - Parallel downloads from multiple edges
- Bandwidth Savings - Origin protected from traffic spikes
- Global Distribution - 50+ edge locations worldwide
Cache Headers β
Files are served with cache-friendly headers:
Cache-Control: public, max-age=31536000, immutable
ETag: "abc123def456"This enables:
- Browser caching
- CDN caching
- Conditional requests (If-None-Match)
Cache Invalidation β
When file is deleted, CDN cache is purged within 5 minutes.
Error Handling β
Common File Upload Errors β
400 Bad Request - No File Provided
{
"statusCode": 400,
"message": "No file uploaded"
}Solution: Ensure file is attached to request as multipart/form-data with field name file.
400 Bad Request - Invalid File Type
{
"statusCode": 400,
"message": "Unsupported file type: application/x-executable"
}Solution: Only upload supported MIME types (see Supported File Types above).
413 Payload Too Large - File Size Exceeded
{
"statusCode": 413,
"message": "File size exceeds limit of 50 MB"
}Solution: Compress file or upgrade plan for higher limits.
402 Payment Required - Storage Quota Exceeded
{
"statusCode": 402,
"message": "Storage quota exceeded",
"current": 10737418240,
"limit": 10737418240,
"upgradeRequired": true
}Solution: Delete unused files or upgrade storage quota.
404 Not Found - File Not Found
{
"statusCode": 404,
"message": "File not found"
}Solution: Check fileId is correct and file hasn't been deleted.
403 Forbidden - Permission Denied
{
"statusCode": 403,
"message": "Only board host can delete files"
}Solution: Only host token can delete files. Guests have read-only access.
Upload Examples β
Example 1: Image Upload (Browser) β
Upload profile picture for vocabulary card:
<input type="file" id="imageInput" accept="image/*">
<button onclick="uploadImage()">Upload</button>
<script>
async function uploadImage() {
const fileInput = document.getElementById('imageInput');
const file = fileInput.files[0];
if (!file) {
alert('Please select a file');
return;
}
// Check file type
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
// Check file size (10MB limit for example)
if (file.size > 10 * 1024 * 1024) {
alert('File too large (max 10MB)');
return;
}
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch(`/api/v1/boards/${boardUuid}/files`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${boardToken}`
},
body: formData
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
const result = await response.json();
console.log('Uploaded:', result);
// Use file URL in vocabulary card
await createVocabularyCard({
word: 'apple',
translation: 'ΡΠ±Π»ΠΎΠΊΠΎ',
image_url: result.url
});
} catch (error) {
console.error('Upload error:', error);
alert('Upload failed: ' + error.message);
}
}
</script>Example 2: Audio Upload (Node.js) β
Upload pronunciation audio files:
import axios from 'axios';
import FormData from 'form-data';
import fs from 'fs';
async function uploadAudio(boardUuid: string, boardToken: string, audioPath: string) {
const formData = new FormData();
formData.append('file', fs.createReadStream(audioPath));
try {
const response = await axios.post(
`https://api.boardapi.io/api/v1/boards/${boardUuid}/files`,
formData,
{
headers: {
'Authorization': `Bearer ${boardToken}`,
...formData.getHeaders()
},
maxContentLength: 50 * 1024 * 1024, // 50MB
maxBodyLength: 50 * 1024 * 1024
}
);
console.log('Audio uploaded:', response.data);
return response.data.url;
} catch (error) {
if (error.response?.status === 402) {
console.error('Storage quota exceeded');
} else if (error.response?.status === 413) {
console.error('File too large');
} else {
console.error('Upload failed:', error.message);
}
throw error;
}
}
// Usage
const audioUrl = await uploadAudio(
'board-uuid-123',
'board-token-xyz',
'./pronunciation/hello.mp3'
);
// Create vocabulary card with audio
await createObject({
type: 'vocabulary-card',
customData: {
word: 'hello',
translation: 'ΠΏΡΠΈΠ²Π΅Ρ',
pronunciation: audioUrl
}
});Example 3: Document Upload (React) β
Upload PDF study materials:
import { useState } from 'react';
import axios from 'axios';
export function DocumentUploader({ boardUuid, boardToken }) {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// Validate file type
if (file.type !== 'application/pdf') {
alert('Only PDF files are supported');
return;
}
const formData = new FormData();
formData.append('file', file);
setUploading(true);
try {
const response = await axios.post(
`/api/v1/boards/${boardUuid}/files`,
formData,
{
headers: {
'Authorization': `Bearer ${boardToken}`
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setProgress(percentCompleted);
}
}
);
console.log('Document uploaded:', response.data);
alert(`Uploaded: ${response.data.filename}`);
} catch (error) {
console.error('Upload failed:', error);
if (axios.isAxiosError(error)) {
if (error.response?.status === 402) {
alert('Storage quota exceeded. Please upgrade your plan.');
} else if (error.response?.status === 413) {
alert('File is too large. Maximum size is 50 MB.');
} else {
alert('Upload failed: ' + error.message);
}
}
} finally {
setUploading(false);
setProgress(0);
}
};
return (
<div>
<input
type="file"
accept="application/pdf"
onChange={handleUpload}
disabled={uploading}
/>
{uploading && (
<div className="progress-bar">
<div style={{ width: `${progress}%` }}>
{progress}%
</div>
</div>
)}
</div>
);
}Metadata Extraction β
BoardAPI automatically extracts metadata from uploaded files:
Image Metadata β
{
"fileId": "file-123",
"filename": "photo.jpg",
"mimeType": "image/jpeg",
"size": 2048576,
"metadata": {
"width": 1920,
"height": 1080,
"format": "jpeg",
"hasAlpha": false,
"orientation": 1
}
}Audio Metadata β
{
"fileId": "file-456",
"filename": "pronunciation.mp3",
"mimeType": "audio/mpeg",
"size": 512000,
"metadata": {
"duration": 3.5,
"bitrate": 128,
"sampleRate": 44100,
"channels": 2
}
}Document Metadata β
{
"fileId": "file-789",
"filename": "lesson.pdf",
"mimeType": "application/pdf",
"size": 5242880,
"metadata": {
"pageCount": 24,
"title": "English Lesson 1",
"author": "Teacher Name",
"createdAt": "2025-01-15T10:00:00Z"
}
}Performance Tips β
1. Compress Before Upload β
Reduce file sizes for faster uploads:
- Images: Use WebP format (30-50% smaller than JPEG)
- Audio: Use OGG Vorbis or MP3 at 128kbps
- Documents: Compress PDFs with tools like Ghostscript
2. Use Appropriate Dimensions β
Don't upload unnecessarily large images:
- Vocabulary cards: 800x600 is sufficient
- Profile pictures: 400x400 max
- Thumbnails: 200x200 or smaller
3. Lazy Load Files β
Don't load all files on board initialization. Load files only when needed:
// Bad: Load all files upfront
const files = await fetchAllFiles(boardUuid);
// Good: Load on demand
const file = await fetchFile(boardUuid, fileId);4. Parallelize Uploads β
Upload multiple files in parallel (max 5 concurrent):
const uploads = files.slice(0, 5).map(file => uploadFile(file));
const results = await Promise.all(uploads);5. Resume Failed Uploads β
For large files, implement retry logic:
async function uploadWithRetry(file, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await uploadFile(file);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(1000 * Math.pow(2, attempt)); // Exponential backoff
}
}
}