Write output to Cloudflare R2
Ittybit writes processed files to its CDN by default. If you want output to land in your own Cloudflare R2 bucket instead, set an s3:// output URL on the task. R2 is S3-compatible, so the same s3:// scheme works — you just need a connection with write access.
Create an R2 connection
Generate an R2 API token in the Cloudflare dashboard under R2 > Manage R2 API Tokens. The token needs Object Read & Write permission on the target bucket.
Then register the connection with Ittybit:
ittybit connections add s3 \
--name my-r2-bucket \
--endpoint https://<account-id>.r2.cloudflarestorage.com \
--region auto \
--access-key-id $R2_ACCESS_KEY_ID \
--secret-access-key $R2_SECRET_ACCESS_KEYconst res = await fetch('https://api.ittybit.com/connections', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
kind: 's3',
name: 'my-r2-bucket',
endpoint: `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
region: 'auto',
access_key_id: process.env.R2_ACCESS_KEY_ID,
secret_access_key: process.env.R2_SECRET_ACCESS_KEY,
}),
});
const connection = await res.json();
// connection.id -> "conn_abc123"import requests
import os
res = requests.post(
"https://api.ittybit.com/connections",
headers={"Authorization": f"Bearer {os.environ['ITTYBIT_API_KEY']}"},
json={
"kind": "s3",
"name": "my-r2-bucket",
"endpoint": f"https://{os.environ['CLOUDFLARE_ACCOUNT_ID']}.r2.cloudflarestorage.com",
"region": "auto",
"access_key_id": os.environ["R2_ACCESS_KEY_ID"],
"secret_access_key": os.environ["R2_SECRET_ACCESS_KEY"],
},
)
connection = res.json()
# connection["id"] -> "conn_abc123"curl -X POST https://api.ittybit.com/connections \
-H "Authorization: Bearer $ITTYBIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "s3",
"name": "my-r2-bucket",
"endpoint": "https://<account-id>.r2.cloudflarestorage.com",
"region": "auto",
"access_key_id": "'$R2_ACCESS_KEY_ID'",
"secret_access_key": "'$R2_SECRET_ACCESS_KEY'"
}' Save the connection_id from the response. You’ll pass it in every task that reads from or writes to this bucket.
Write output to R2
Set the output field to an s3:// URL pointing to the destination path in your R2 bucket. Include the connection_id in options so Ittybit knows which credentials to use.
ittybit video \
-i https://example.com/uploads/video.mov \
-o s3://my-r2-bucket/processed/video.mp4 \
--connection-id conn_abc123 \
--width 1280 \
--format mp4const res = await fetch('https://api.ittybit.com/jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: 'https://example.com/uploads/video.mov',
kind: 'video',
options: {
connection_id: 'conn_abc123',
width: 1280,
format: 'mp4',
},
output: 's3://my-r2-bucket/processed/video.mp4',
}),
});
const task = await res.json();import requests
import os
res = requests.post(
"https://api.ittybit.com/jobs",
headers={"Authorization": f"Bearer {os.environ['ITTYBIT_API_KEY']}"},
json={
"input": "https://example.com/uploads/video.mov",
"kind": "video",
"options": {
"connection_id": "conn_abc123",
"width": 1280,
"format": "mp4",
},
"output": "s3://my-r2-bucket/processed/video.mp4",
},
)
task = res.json()curl -X POST https://api.ittybit.com/jobs \
-H "Authorization: Bearer $ITTYBIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/uploads/video.mov",
"kind": "video",
"options": {
"connection_id": "conn_abc123",
"width": 1280,
"format": "mp4"
},
"output": "s3://my-r2-bucket/processed/video.mp4"
}' When the task completes, the processed file is at s3://my-r2-bucket/processed/video.mp4 in your R2 bucket.
R2 to R2
Read from one path in R2, process, and write to another. Both the input and output use s3:// URLs:
ittybit video \
-i s3://my-r2-bucket/uploads/raw.mov \
-o s3://my-r2-bucket/processed/web.mp4 \
--connection-id conn_abc123 \
--width 1920 \
--format mp4const res = await fetch('https://api.ittybit.com/jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: 's3://my-r2-bucket/uploads/raw.mov',
kind: 'video',
options: {
connection_id: 'conn_abc123',
width: 1920,
format: 'mp4',
},
output: 's3://my-r2-bucket/processed/web.mp4',
}),
});
const task = await res.json();import requests
import os
res = requests.post(
"https://api.ittybit.com/jobs",
headers={"Authorization": f"Bearer {os.environ['ITTYBIT_API_KEY']}"},
json={
"input": "s3://my-r2-bucket/uploads/raw.mov",
"kind": "video",
"options": {
"connection_id": "conn_abc123",
"width": 1920,
"format": "mp4",
},
"output": "s3://my-r2-bucket/processed/web.mp4",
},
)
task = res.json()curl -X POST https://api.ittybit.com/jobs \
-H "Authorization: Bearer $ITTYBIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "s3://my-r2-bucket/uploads/raw.mov",
"kind": "video",
"options": {
"connection_id": "conn_abc123",
"width": 1920,
"format": "mp4"
},
"output": "s3://my-r2-bucket/processed/web.mp4"
}' Folder structure patterns
The output path determines where files land in your bucket. Use this to organize output by type, date, or user:
s3://my-r2-bucket/videos/{user_id}/{filename}.mp4
s3://my-r2-bucket/thumbnails/{user_id}/{filename}.webp
s3://my-r2-bucket/audio/{user_id}/{filename}.mp3
s3://my-r2-bucket/{year}/{month}/{filename}.mp4
For HLS output, use a trailing slash. Ittybit writes the manifest and segments into that directory:
s3://my-r2-bucket/streams/my-video/
-> index.m3u8
-> segment-000.ts
-> segment-001.ts
-> ...
Verify the file in R2
After the task status is succeeded, confirm the file exists in your bucket using Wrangler or the S3 API:
# Using Wrangler
npx wrangler r2 object get my-r2-bucket/processed/video.mp4 --pipe > /dev/null && echo "OK"
# Using the S3 API
aws s3api head-object \
--bucket my-r2-bucket \
--key processed/video.mp4 \
--endpoint-url https://<account-id>.r2.cloudflarestorage.com
See also
- Process files from S3 — set up S3-compatible connections
- Write output to S3 — same pattern for AWS S3 buckets
- Media processing with Cloudflare Workers, R2, and Ittybit — full pipeline with Workers and D1
- Replace Cloudflare Stream with Ittybit and R2 — HLS streaming from R2