Skip to content

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) ​

json
{
  "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 ​

json
[
  {
    "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 ​

json
{
  "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) ​

json
// No content

Complete 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):

typescript
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:

typescript
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];

const formData = new FormData();
formData.append('file', file);

3. Upload File

typescript
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:

typescript
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 images
  • image/png - PNG images
  • image/gif - GIF animations
  • image/webp - WebP images
  • image/svg+xml - SVG vector graphics

Audio

  • audio/mpeg - MP3 audio
  • audio/wav - WAV audio
  • audio/ogg - OGG audio
  • audio/webm - WebM audio

Documents

  • application/pdf - PDF documents
  • application/vnd.ms-excel - Excel spreadsheets (XLS)
  • application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - Excel (XLSX)
  • text/csv - CSV files

Archives

  • application/zip - ZIP archives
  • application/x-tar - TAR archives

File Size Limits ​

PlanMax File SizeTotal Storage
Starter10 MB1 GB
Professional50 MB10 GB
Business100 MB100 GB
Enterprise500 MBCustom

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:

json
{
  "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 ​

bash
GET /boards/:uuid/files/:fileId/url
Authorization: Bearer <board-token>

Response:

json
{
  "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

json
{
  "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

json
{
  "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

json
{
  "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

json
{
  "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

json
{
  "statusCode": 404,
  "message": "File not found"
}

Solution: Check fileId is correct and file hasn't been deleted.


403 Forbidden - Permission Denied

json
{
  "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:

html
<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:

typescript
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:

tsx
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 ​

json
{
  "fileId": "file-123",
  "filename": "photo.jpg",
  "mimeType": "image/jpeg",
  "size": 2048576,
  "metadata": {
    "width": 1920,
    "height": 1080,
    "format": "jpeg",
    "hasAlpha": false,
    "orientation": 1
  }
}

Audio Metadata ​

json
{
  "fileId": "file-456",
  "filename": "pronunciation.mp3",
  "mimeType": "audio/mpeg",
  "size": 512000,
  "metadata": {
    "duration": 3.5,
    "bitrate": 128,
    "sampleRate": 44100,
    "channels": 2
  }
}

Document Metadata ​

json
{
  "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:

typescript
// 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):

typescript
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:

typescript
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
    }
  }
}