Upload large files
A multipart upload sends a file in parts straight to storage, so no single request has to carry the whole file. A single PUT works up to 512 MB, but use multipart for files over about 100 MB, on slow or unreliable connections, or whenever a proxy in your network rejects large requests. The maximum file size is 512 MB (536,870,912 bytes) either way.
The SDK uses multipart automatically for uploads between 1 MiB and its 50 MiB limit. For anything larger, use the script below or the raw steps after it.
Prerequisites
- An API key in
STROVIO_API_KEY. - Node.js 18 or later for the script, on macOS, Windows or Linux.
Upload with a script (any platform)
Save this as upload-large.mjs:
import { open, stat } from "node:fs/promises";
const [file, key, contentType = "application/octet-stream", access = "private"] = process.argv.slice(2);
if (!process.env.STROVIO_API_KEY) throw new Error("Set STROVIO_API_KEY first: https://strovio.xyz/docs/get-an-api-key");
if (!file || !key) throw new Error("Usage: node upload-large.mjs <file> <key> [contentType] [public|private]");
const api = "https://strovio.xyz/api/v1";
const auth = { Authorization: `Bearer ${process.env.STROVIO_API_KEY}` };
async function post(path, body) {
const res = await fetch(`${api}${path}`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const text = await res.text();
if (!res.ok) throw new Error(`${path} failed with status ${res.status}: ${text}`);
return JSON.parse(text);
}
const { size } = await stat(file);
const start = await post("/files/multipart/start", { key, size, contentType, access });
const handle = await open(file);
const parts = [];
let offset = 0;
for (const part of start.parts) {
const chunk = Buffer.alloc(part.contentLength);
await handle.read(chunk, 0, part.contentLength, offset);
offset += part.contentLength;
const res = await fetch(part.url, { method: "PUT", body: chunk });
if (!res.ok) throw new Error(`Part ${part.partNumber} failed with status ${res.status}`);
parts.push({ PartNumber: part.partNumber, ETag: res.headers.get("etag") });
console.error(`Uploaded part ${part.partNumber} of ${start.parts.length}`);
}
await handle.close();
console.log(await post("/files/multipart/complete", { s3Key: start.s3Key, uploadId: start.uploadId, key, contentType, access, parts }));Run it:
node upload-large.mjs video.mp4 videos/demo.mp4 video/mp4 privateExpected result: a progress line per part, then the file object with "access": "private" and the full size.
The raw steps
1. Start
curl -X POST "https://strovio.xyz/api/v1/files/multipart/start" \
-H "Authorization: Bearer $STROVIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key":"videos/demo.mp4","contentType":"video/mp4","size":134217728,"access":"private"}'Expected result: uploadId, s3Key, bucket and a parts list. Each part has a partNumber, a signed url and a contentLength. Use each part's contentLength rather than assuming a size: today every part is 5 MiB (5242880 bytes) except the last, which holds the remainder.
2. Upload each part
PUT bytes [offset, offset + contentLength) of the file to each part's url, and keep the ETag response header of each, quotes included. On macOS or Linux, for part n (counting from 1) with 5 MiB parts:
dd if=video.mp4 of=part.bin bs=5242880 skip=$((n - 1)) count=1 2>/dev/null
curl -sf -D - -o /dev/null -X PUT -H "Content-Type:" --data-binary @part.bin "<parts[n-1].url>" \
| grep -i '^etag:' | cut -d' ' -f2 | tr -d '\r'The last command prints the ETag, for example "9b2cf535f27731c974343645a3985328". -H "Content-Type:" stops curl adding a form content type. On Windows, use the script above. Parts can be sent one at a time or in parallel. Do not send an Authorization header to the part URLs; they are already signed. Part URLs stay valid for two hours.
3. Complete
curl -X POST "https://strovio.xyz/api/v1/files/multipart/complete" \
-H "Authorization: Bearer $STROVIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"s3Key":"<s3Key>","uploadId":"<uploadId>","key":"videos/demo.mp4","contentType":"video/mp4","access":"private","parts":[{"PartNumber":1,"ETag":"\"<etag 1>\""},{"PartNumber":2,"ETag":"\"<etag 2>\""}]}'Note the field names: start returns partNumber, while complete takes PartNumber and ETag. Send the same key and access as in step 1.
Expected result: 201 with the same fields as a normal upload.
There is no abort call. An upload that is never completed does not create a file.
Confirm success
curl -I https://strovio.xyz/api/v1/files/videos/demo.mp4 -H "Authorization: Bearer $STROVIO_API_KEY" returns 200 and the full Content-Length.
Common errors
400 invalid_requestwith "s3Key does not match key and access": send the samekeyandaccessyou sent to start.403 forbidden: thes3Keycame from a different account's upload. Use thes3Keyfrom your own start call.413 file_too_largeorstorage_quota_exceeded: the file is over 512 MB or your storage is full.- A part
PUTreturns403: the part URL expired. Start again. 500 internal_erroron complete, often an ETag that does not match: start again and copy the ETags exactly.
Small files without multipart
For a single signed upload, call POST /api/v1/files/presign-put with key, contentType, size and access, PUT the bytes to the returned putUrl with the same Content-Type within one hour, then call POST /api/v1/files/complete with key, contentType and access. See the API reference.