Decentralized Video Upload & Encoding Service
Test the upload system with a live demo
Manage API keys (password protected)
Integration guide and API reference
Robust uploads with pause/resume support for large video files
Decentralized storage with automatic pinning to IPFS
Load balancing across multiple encoding nodes
Get embed URLs immediately, watch videos as they encode
Monitor encoding progress and status in real-time
Short-lived signed tokens for secure client-side uploads without exposing API keys
Real-time encoding progress via simple polling — build custom progress UI
The service supports two authentication methods. Both are fully supported — choose the one that fits your architecture:
X-API-Key header directly. Best for server-to-server calls and mobile apps where the key isn't exposed in browser DevTools.The simplest integration — pass your API key directly in the upload request:
import * as tus from 'tus-js-client';
const file = document.getElementById('video-input').files[0];
const upload = new tus.Upload(file, {
endpoint: 'https://embed.3speak.tv/uploads',
headers: {
'X-API-Key': 'your-api-key-here'
},
metadata: {
filename: file.name,
owner: 'username',
frontend_app: 'your-app-name',
short: 'false' // 'true' for 60s clips
},
onError: (error) => console.error('Upload failed:', error),
onProgress: (bytesUploaded, bytesTotal) => {
console.log(`${(bytesUploaded / bytesTotal * 100).toFixed(2)}%`);
},
onSuccess: () => console.log('Upload complete!'),
onAfterResponse: (req, res) => {
const embedUrl = res.getHeader('X-Embed-URL');
console.log('Embed URL:', embedUrl);
}
});
upload.start();
For web apps, use upload tokens so your API key never reaches the browser.
Step 1 — Your backend requests a token:
// Server-side (Node.js, Python, etc.)
const response = await fetch('https://embed.3speak.tv/uploads/token', {
method: 'POST',
headers: {
'X-API-Key': process.env.EMBED_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
owner: 'username',
frontend_app: 'your-app-name',
short: false,
allowed_origins: ['https://your-app.com'],
max_file_size: 1073741824, // 1GB
ttl: 600 // 10 minutes
})
});
const { token, upload_url, expires_at } = await response.json();
// Send token and upload_url to your frontend
Step 2 — Your frontend uploads with the token:
// Client-side (browser) — API key never appears here
const upload = new tus.Upload(file, {
endpoint: uploadUrl, // from your backend
headers: {
'Authorization': `Bearer ${token}` // from your backend
},
metadata: {
filename: file.name,
filetype: file.type,
// owner, frontend_app, short are already in the token
},
onError: (error) => console.error('Upload failed:', error),
onProgress: (bytesUploaded, bytesTotal) => {
console.log(`${(bytesUploaded / bytesTotal * 100).toFixed(2)}%`);
},
onSuccess: () => console.log('Upload complete!'),
onAfterResponse: (req, res) => {
const embedUrl = res.getHeader('X-Embed-URL');
console.log('Embed URL:', embedUrl);
}
});
upload.start();
Token properties:
After starting an upload, poll GET /video/:permlink to track encoding progress. This endpoint is public (no auth required). Extract the permlink from the X-Embed-URL response header.
const permlink = embedUrl.split('/').pop(); // e.g. "yn77aj9g"
async function pollProgress() {
const res = await fetch(`https://embed.3speak.tv/video/${permlink}`);
const video = await res.json();
console.log(`Status: ${video.status}, Progress: ${video.encodingProgress}%`);
if (video.status === 'published') {
console.log('Video ready! CID:', video.manifest_cid);
return;
}
if (video.status === 'failed') {
console.error('Encoding failed');
return;
}
setTimeout(pollProgress, 5000); // poll every 5s
}
pollProgress();
Status lifecycle: uploading → processing → published (or failed)
The embed player handles all states automatically (showing upload/processing animations), so the embed URL is usable immediately — but polling lets your app show custom progress UI.
Videos are instantly accessible via:
https://play.3speak.tv/embed?v={username}/{videoId}
Simple HTML5 video tag:
<video src="play.3speak.tv/watch?v={username}/{videoId}" controls></video>
Full featured player with controls:
<iframe
src="https://play.3speak.tv/embed?v={username}/{videoId}"
width="100%"
height="500"
frameborder="0"
allowfullscreen>
</iframe>
short: 'false' - Full length, multiple qualities (1080p, 720p, 480p)short: 'true' - Max 60 seconds, 480p only, faster encodingPOST /uploads - TUS upload endpoint (requires API key or upload token)POST /uploads/token - Request an upload token (requires API key)GET /video/:permlink - Get video metadata and encoding progress (public)POST /video/:permlink/thumbnail - Update video thumbnail (requires API key)GET /health - Service health checkSet a custom thumbnail for your video after upload:
fetch('https://embed.3speak.tv/video/ABC123XY/thumbnail', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'your-api-key'
},
body: JSON.stringify({
thumbnail_url: 'https://your-cdn.com/thumbnail.jpg'
})
});
owner (string) - Username of video owner (not needed with upload tokens)frontend_app (string) - Your application identifier (not needed with upload tokens)short (string) - 'true' or 'false' (not needed with upload tokens)filename (string, optional) - Original file nameTo integrate this service into your application: