The SDK’s uploadFile is the easy path, but it doesn’t resume across calls (an
interrupted upload restarts from the first chunk). When you need robust
resume — flaky networks, multi-GB files, uploads that survive a page reload —
drive the transfer with a proven TUS client instead. This is
a short tutorial for both the browser and the server.
The one thing to understand: two auth levels
The Videas upload flow has two steps with different security:
| Step | Endpoint | Needs the API key? | Where |
|---|---|---|---|
| 1. Create the session | POST /upload/ (Write Assets) |
Yes — secret sk_ key |
Server only |
| 2. Transfer the bytes | PATCH/HEAD on the returned Location |
No — the Location is a secret capability URL |
Server or browser |
So the Location you get back is an unguessable, one-shot upload URL: anyone
holding it can send the bytes, no API key required. That’s what makes safe
browser uploads possible — your server mints the session, the browser only
ever sees the capability URL.
Browser uploads (recommended pattern)
Never put your sk_ key in the browser. Instead:
1. Your server mints the session (holds the key) and returns the Location.
See Upload with the REST API → step 1;
with the SDK it’s videas.upload.createSession(...). Expose a tiny endpoint:
// server — POST /api/upload-session { filename, size }
app.post('/api/upload-session', async (req, res) => {
const created = await videas.upload.createSession({
headers: {
'Tus-Resumable': '1.0.0',
'Upload-Length': String(req.body.size),
// key <base64(value)> pairs; filename + workspace_uid are required
'Upload-Metadata': `filename ${btoa(req.body.filename)},workspace_uid ${btoa('ws_123')}`,
},
})
res.json({ location: created.location }) // the capability URL — browser-safe
})
2. The browser transfers to that URL with tus-js-client — chunked, resumable, no key:
<script type="module">
import * as tus from 'https://cdn.jsdelivr.net/npm/tus-js-client@4/+esm'
const file = input.files[0]
const { location } = await (await fetch('/api/upload-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: file.name, size: file.size }),
})).json()
const upload = new tus.Upload(file, {
uploadUrl: location, // resume an already-created session
chunkSize: 32 * 1024 * 1024, // ≤ 50 MiB (server per-chunk limit)
retryDelays: [0, 3000, 10000], // automatic retry with back-off
onProgress: (sent, total) => console.log(`${Math.round((sent / total) * 100)}%`),
onSuccess: () => console.log('done'),
onError: (err) => console.error(err),
})
// Resume if this file was partially uploaded before, else start.
upload.findPreviousUploads().then((prev) => {
if (prev.length) upload.resumeFromPreviousUpload(prev[0])
upload.start()
})
</script>
tus-js-client does a HEAD on the Location to learn the current offset, then
PATCHes only the missing bytes — so a dropped connection (or a page reload)
resumes instead of restarting.
Server uploads (Node)
On the server the key is safe, so you can let tus-js-client do both steps — point it at the create endpoint with your key and the required metadata:
import * as tus from 'tus-js-client'
import { createReadStream, statSync } from 'node:fs'
const path = 'movie.mp4'
const upload = new tus.Upload(createReadStream(path), {
endpoint: 'https://api.videas.com/api/external/v1/upload/',
headers: { Authorization: `Bearer ${process.env.VIDEAS_API_KEY}` },
uploadSize: statSync(path).size,
metadata: { filename: 'movie.mp4', workspace_uid: 'ws_123', content_type: 'video/mp4' },
chunkSize: 32 * 1024 * 1024,
retryDelays: [0, 3000, 10000],
onSuccess: () => console.log('done'),
})
upload.start()
Server uploads (Python)
With tus-py-client (pip install tuspy):
import os
from tusclient import client
tus = client.TusClient(
"https://api.videas.com/api/external/v1/upload/",
headers={"Authorization": f"Bearer {os.environ['VIDEAS_API_KEY']}"},
)
uploader = tus.uploader(
"movie.mp4",
chunk_size=32 * 1024 * 1024, # ≤ 50 MiB
metadata={"filename": "movie.mp4", "workspace_uid": "ws_123"},
)
uploader.upload() # resumable: re-run to continue an interrupted upload
After the upload
Same as any upload: you get an asset_uid, then the video is processed
asynchronously — poll GET /assets/{uid}/ until status: ready. See
How uploads work.
Which should I use?
- Just works, small/medium files, server-side → the SDK’s
uploadFile(Upload with the SDK). - Browser uploads → server mints the session, browser uses tus-js-client
with
uploadUrl(above). - Robust resume / very large files → a TUS client (tus-js-client, tuspy).