FFmpegLab doesn't just render video — it gives you a S3-compatible object store attached to your account. Every project you render, every source file you upload, and every output you export can live in a bucket you control, addressed with the same API shape you already know from AWS S3, Cloudflare R2 or MinIO.
That means you never have to build a bespoke upload path. You generate one API key, ask the storage endpoint for your configuration, and hand that configuration to whatever tool you already trust. This guide walks through the whole flow end to end.
Key takeaways
- One key unlocks everything. Generate an API key in
Settings → API Keys— it authenticates both the render API and the storage endpoint. - Your namespace is based on your userId. The storage config endpoint returns a prefix like
users/12345/that is uniquely yours. - Credentials are scoped to your namespace. You can only read and write inside your own prefix — not the rest of the shared bucket.
- Never hard-code bucket names. Call the storage config endpoint instead; it returns the active bucket, endpoint and credentials for your account.
- Credentials are temporary. Fetch them at runtime and refresh on expiry rather than pasting them into source control.
- It's just S3. rclone, boto3, the AWS CLI, s5cmd and Cyberduck all work with a custom endpoint URL.
- Zero egress. Reading, rendering and sharing outputs doesn't incur per-gigabyte transfer charges on supported tiers.
What you're actually getting
The storage layer is a managed, S3-compatible bucket bound to your FFmpegLab account. It's a shared bucket with per-user namespaces — meaning the bucket itself is common infrastructure, but your access is isolated to a folder-like prefix derived from your userId.
You get:
- A shared bucket — managed by FFmpegLab, so you don't have to worry about global name collisions.
- A regional endpoint — a custom HTTPS hostname that behaves like
s3.amazonaws.combut points at FFmpegLab's infrastructure. - Your personal namespace — a prefix like
users/12345/, where12345is youruserId. This is your only writable area. - Scoped credentials — an access key, secret key and session token, issued for a limited window and limited to your namespace.
Because it speaks the S3 protocol, you can point any existing pipeline at it without rewriting a single line of upload logic. If you already have scripts that talk to S3, you change one variable: the endpoint. Just remember that all paths must begin with your namespace prefix.
Storage tiers at a glance
| Tier | Typical use | Egress fees | Scaling model |
|---|---|---|---|
| Free / Starter | Individual projects, experiments, small renders | €0 | Fixed starter allocation |
| Team | Shared media libraries, collaborative pipelines | €0 | Expanded allocation, shared access |
| Enterprise | Archival, AI training sets, petabyte-scale media | €0 | Petabyte-scale nodes, provisioned on request |
Prerequisites
- An FFmpegLab account (free tier is enough to follow along).
- Access to the Settings → API Keys screen for that account.
curlplusjq(optional, but makes the output readable).- An S3 client of your choice: rclone, Python + boto3, or the AWS CLI.
Step 1 — Sign in to FFmpegLab
Everything starts with an authenticated session. Open the app and sign in with the account that owns the storage allocation you want to use.
Credentials are scoped per account, so if you belong to more than one workspace, make sure you're in the right one before generating a key. A key created in the wrong workspace will happily authenticate — and then return a namespace you didn't expect.
Step 2 — Generate an API key
Once you're signed in:
- Open Settings from the sidebar or the account menu.
- Switch to the API Keys tab.
- Click Generate new key (or Create API key).
- Give it a descriptive name —
rclone-laptop,ci-render-runner,training-loader. You will thank yourself later when you need to revoke one. - Copy the secret immediately. Most implementations show the full key exactly once.
The key is a bearer token. Everything you do next — fetching storage config, starting renders, listing jobs — is authenticated with it.
Store it safely
Put the key in an environment variable or a secrets manager. Never commit it to a repository, and never paste it into a client-side bundle.
# .env (never commit this file) FFMPEGLAB_API_KEY=ffl_live_xxxxxxxxxxxxxxxxxxxxxxxx
Step 3 — Call the storage config endpoint
With the key in hand, request your storage configuration. This single call returns everything an S3 client needs: the bucket, the endpoint, the region, your namespace prefix, and a set of credentials scoped to that prefix.
curl -sS https://api.ffmpeglab.com/api/files/s3config \ -H "Authorization: Bearer $FFMPEGLAB_API_KEY" \ -H "Accept: application/json" | jq
A successful response looks roughly like this:
{
"bucketId": "ffl-9f2c1a7d-media",
"endpoint": "https://s3.eu-central-1.ffmpeglab.com",
"region": "eu-central-1",
"accessKeyId": "FFLXXXXXXXXXXXXXXXX",
"secretAccessKey": "********************************",
"sessionToken": "********************************",
"prefix": "users/12345/",
"forcePathStyle": true,
"expiresAt": "2026-09-12T18:30:00Z"
}
Understanding each field
| Field | What it is | Where it goes |
|---|---|---|
bucketId | The shared bucket bound to your account | AWS_BUCKET / rclone remote root |
endpoint | Regional HTTPS hostname for the S3 API | --endpoint-url / endpoint = |
region | Signing region used for SigV4 | AWS_REGION |
accessKeyId | Public half of the credential pair | AWS_ACCESS_KEY_ID |
secretAccessKey | Secret half — treat as a password | AWS_SECRET_ACCESS_KEY |
sessionToken | Temporary-session token, if issued | AWS_SESSION_TOKEN |
prefix | Your personal namespace, derived from your userId (e.g. users/12345/). All uploads must go here. | Upload path root |
forcePathStyle | Whether to use path-style addressing | force_path_style = true |
expiresAt | When the credentials stop working | Your refresh logic |
Your namespace is your boundary
The credentials you receive are only valid inside your prefix. You cannot list, read, or write to any other part of the bucket. If you try, the S3 API will return an AccessDenied error. Treat the prefix as your personal root directory — every object you create should live under it.
Don't cache forever
Because credentials can expire, treat this endpoint as something you call at the start of a job, not something you call once and paste into a config file you keep for a year. A short-lived token fetched at runtime is both safer and more reliable.
Step 4 — Wire it into your S3 client
rclone
rclone is the fastest way to move large media libraries. Add a remote to your config:
# ~/.config/rclone/rclone.conf [ffmpeglab] type = s3 provider = Other access_key_id = FFLXXXXXXXXXXXXXXXX secret_access_key = ******************************** session_token = ******************************** endpoint = https://s3.eu-central-1.ffmpeglab.com region = eu-central-1 acl = private force_path_style = true
Then copy, sync or mount. Always include your namespace prefix in the path.
# List your namespace only rclone lsf ffmpeglab:ffl-9f2c1a7d-media/users/12345/ # Copy files into your namespace rclone copy ./renders ffmpeglab:ffl-9f2c1a7d-media/users/12345/renders --progress # Mount your namespace rclone mount ffmpeglab:ffl-9f2c1a7d-media/users/12345/ /mnt/media --vfs-cache-mode writes
Python + boto3
The cleanest pattern is a helper that fetches the config, builds a client, and automatically prefixes your keys with your namespace.
import os
import requests
import boto3
from botocore.config import Config
API = "https://api.ffmpeglab.com/api/files/s3config"
def storage_config():
r = requests.get(
API,
headers={"Authorization": f"Bearer {os.environ['FFMPEGLAB_API_KEY']}"},
timeout=15,
)
r.raise_for_status()
return r.json()
def s3_client():
cfg = storage_config()
return boto3.client(
"s3",
endpoint_url=cfg["endpoint"],
region_name=cfg["region"],
aws_access_key_id=cfg["accessKeyId"],
aws_secret_access_key=cfg["secretAccessKey"],
aws_session_token=cfg.get("sessionToken"),
config=Config(
s3={"addressing_style": "path" if cfg.get("forcePathStyle") else "auto"}
),
), cfg
client, cfg = s3_client()
# Always prepend your namespace prefix
key = f"{cfg['prefix']}input.mov"
client.upload_file("input.mov", cfg["bucketId"], key)
# List only your namespace
for obj in client.list_objects_v2(
Bucket=cfg["bucketId"], Prefix=cfg["prefix"]
).get("Contents", []):
print(obj["Key"], obj["Size"])
AWS CLI
Prefer the command line? Export the values and pass the endpoint on every call. Remember to include your namespace in the S3 URI.
export AWS_ACCESS_KEY_ID=FFLXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=********************************
export AWS_SESSION_TOKEN=********************************
export AWS_DEFAULT_REGION=eu-central-1
export ENDPOINT=https://s3.eu-central-1.ffmpeglab.com
export BUCKET=ffl-9f2c1a7d-media
export PREFIX=users/12345/
# List only your namespace
aws s3 ls "s3://$BUCKET/$PREFIX" --endpoint-url "$ENDPOINT"
# Upload into your namespace
aws s3 cp ./output.mp4 "s3://$BUCKET/${PREFIX}output.mp4" --endpoint-url "$ENDPOINT"
A shell helper that regenerates config on demand
If you'd rather not keep credentials on disk at all, pull them into your shell session each time:
#!/usr/bin/env bash set -euo pipefail JSON=$(curl -sS https://api.ffmpeglab.com/api/files/s3config \ -H "Authorization: Bearer $FFMPEGLAB_API_KEY") export AWS_ACCESS_KEY_ID=$(echo "$JSON" | jq -r .accessKeyId) export AWS_SECRET_ACCESS_KEY=$(echo "$JSON" | jq -r .secretAccessKey) export AWS_SESSION_TOKEN=$(echo "$JSON" | jq -r '.sessionToken // empty') export AWS_DEFAULT_REGION=$(echo "$JSON" | jq -r .region) export ENDPOINT=$(echo "$JSON" | jq -r .endpoint) export BUCKET=$(echo "$JSON" | jq -r .bucketId) export PREFIX=$(echo "$JSON" | jq -r .prefix) echo "Ready: s3://$BUCKET/$PREFIX via $ENDPOINT"
Path-style vs virtual-hosted addressing
S3 has two ways to put a bucket in a URL:
- Virtual-hosted —
https://bucket.endpoint/key - Path-style —
https://endpoint/bucket/key
If the config response sets forcePathStyle: true, use path-style addressing. Most clients need to be told explicitly:
| Client | Setting |
|---|---|
| rclone | force_path_style = true |
| boto3 | Config(s3={"addressing_style": "path"}) |
| AWS CLI | aws configure set default.s3.addressing_style path |
| s5cmd | --use-path-style (or config flag) |
If you get a 301, 307, or a TLS certificate mismatch, this setting is almost always the culprit.
Security & namespace best practices
- Stay inside your prefix. Every object you create must begin with your namespace prefix. Writing to the bucket root or another user's prefix will be rejected.
- One key per client. Give every machine, CI runner and teammate their own key so revocation is surgical.
- Rotate on a schedule. Regenerate keys periodically and immediately after anyone leaves the project.
- Never inline secrets. Use environment variables, a secrets manager, or your CI's secret store — not
docker-compose.ymlchecked into git. - Refresh temporary credentials. Re-call the storage endpoint when
expiresAtapproaches instead of retrying a failing request. - Revoke aggressively. If a key leaks, delete it in
Settings → API Keysfirst and ask questions later.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
401 Unauthorized on the config call | Missing, malformed or revoked API key | Re-check the Authorization: Bearer header and regenerate the key in Settings → API Keys |
403 Forbidden on upload | Credentials expired, or writing outside your namespace | Re-fetch the storage config and ensure your key starts with prefix (e.g. users/12345/) |
AccessDenied when listing | Listing the bucket root instead of your prefix | Add your prefix to the list call — you cannot list the whole bucket |
| Certificate / hostname mismatch | Virtual-hosted addressing against a custom endpoint | Enable path-style addressing |
301 Moved Permanently | Wrong region in the signing config | Use the region value from the config response verbatim |
| Uploads stall on large files | Multipart threshold too high for your link | Lower the multipart chunk size (e.g. rclone's --s3-chunk-size) |
Frequently asked questions
Where exactly do I generate the API key?
Sign in, open Settings, then the API Keys tab. Create a key, name it, and copy the secret — it's normally displayed only once.
Do I need a separate key for storage?
No. The same API key authenticates the storage config endpoint and the rest of the platform API. One key, one revocation point.
What is my user namespace?
Your namespace is a folder-like prefix inside the shared bucket, derived from your userId. For example, if your userId is 12345, your prefix will be users/12345/. The S3 credentials you receive are scoped to that prefix only.
Can I access other users' data in the same bucket?
No. The temporary credentials only grant access to your own namespace. Attempting to read or write outside your prefix will result in an AccessDenied error. This isolation is enforced at the credential level.
Are the S3 credentials permanent?
Treat them as temporary. Fetch them at the start of a job and refresh when they expire, rather than persisting them indefinitely.
Can I use rclone, Cyberduck or s5cmd?
Yes. Anything that supports a custom endpoint URL and path-style addressing will work. rclone and boto3 are the two most commonly used.
Is there an egress charge when I download my renders?
The platform is built around zero egress fees, so moving data out — including large renders and shared outputs — doesn't add per-gigabyte transfer costs on supported tiers.
Can I store petabytes here?
Yes, at the upper tiers. Rather than splitting data across thousands of small accounts, you provision capacity at the enterprise level and manage it through the same single endpoint and API key. Check the dashboard or pricing page for the current scaling options.
Next steps
Once the connection works, the interesting part starts: pointing your rendering pipeline at your namespace so sources and outputs never leave the platform.
- Upload a source file into your
users/<id>/namespace with rclone or boto3. - Trigger a render via the API using the same key.
- Write the output back to your prefix automatically.
- Serve or archive the result — no transfer bill, no intermediary storage service.
Working with containers and codecs alongside this? See the complete FFmpeg formats guide and the complete codecs guide. If you'd rather keep processing local, read about the offline, private editor or self-hosting with Docker.
Want to try it end to end? Open FFmpegLab, generate a key in Settings → API Keys, and access your user namespace in under a minute.