Replace AWS MediaConvert with Ittybit
MediaConvert works, but the setup tax is real: IAM roles, job templates, output presets, queue configuration, CloudWatch alarms for failures, S3 event notifications to trigger jobs. Each piece has its own console, its own API, its own failure modes. Ittybit replaces all of that with a single POST. Your files stay in S3 — Ittybit reads from your bucket, processes the media, and writes the output back.
What you’re replacing
A typical MediaConvert pipeline looks like this:
IAM role (MediaConvert execution role)
-> IAM policy (S3 read/write, CloudWatch logs)
-> Job template (codec, container, resolution, bitrate)
-> Output preset (per rendition)
-> Queue (on-demand or reserved)
-> S3 event notification -> Lambda -> CreateJob API
-> CloudWatch alarm (job failure)
-> SNS topic -> email / Slack
That’s at least 8 resources to create, configure, and maintain before a single frame gets transcoded.
With Ittybit, the entire pipeline is:
S3 bucket -> Ittybit connection (once) -> POST /jobs
Set up your S3 connection
Register your bucket with Ittybit once. This lets Ittybit read inputs from and write outputs to your S3 bucket.
ittybit connections add s3 \
--name my-media-bucket \
--endpoint https://s3.us-east-1.amazonaws.com \
--region us-east-1 \
--access-key-id $AWS_ACCESS_KEY_ID \
--secret-access-key $AWS_SECRET_ACCESS_KEY
Note the connection_id returned — you’ll pass it in your task options.
Video transcode
MediaConvert requires a job template with codec settings, container format, resolution, and a queue. Here’s the equivalent Ittybit call.
const task = {
input: "s3://my-media-bucket/uploads/raw.mov",
kind: "video",
options: {
connection_id: "conn_abc123",
width: 1920,
format: "mp4",
quality: "high",
},
output: "s3://my-media-bucket/processed/raw.mp4",
};
const 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(task),
});
const data = await res.json();
import requests
task = {
"input": "s3://my-media-bucket/uploads/raw.mov",
"kind": "video",
"options": {
"connection_id": "conn_abc123",
"width": 1920,
"format": "mp4",
"quality": "high",
},
"output": "s3://my-media-bucket/processed/raw.mp4",
}
res = requests.post(
"https://api.ittybit.com/jobs",
headers={"Authorization": f"Bearer {api_key}"},
json=task,
)
data = res.json()TASK='{
"input": "s3://my-media-bucket/uploads/raw.mov",
"kind": "video",
"options": {
"connection_id": "conn_abc123",
"width": 1920,
"format": "mp4",
"quality": "high"
},
"output": "s3://my-media-bucket/processed/raw.mp4"
}'
curl -X POST https://api.ittybit.com/jobs \
-H "Authorization: Bearer $ITTYBIT_API_KEY" \
-H "Content-Type: application/json" \
-d "$TASK" No job template. No output preset. No queue. The output field writes the result straight back to S3.
Audio extraction
MediaConvert audio-only jobs need a separate job template with an audio-only output group. With Ittybit, change kind to audio.
const task = {
input: "s3://my-media-bucket/uploads/interview.mov",
kind: "audio",
options: {
connection_id: "conn_abc123",
format: "mp3",
quality: "medium",
},
output: "s3://my-media-bucket/audio/interview.mp3",
};
const 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(task),
});
const data = await res.json();
import requests
task = {
"input": "s3://my-media-bucket/uploads/interview.mov",
"kind": "audio",
"options": {
"connection_id": "conn_abc123",
"format": "mp3",
"quality": "medium",
},
"output": "s3://my-media-bucket/audio/interview.mp3",
}
res = requests.post(
"https://api.ittybit.com/jobs",
headers={"Authorization": f"Bearer {api_key}"},
json=task,
)
data = res.json()TASK='{
"input": "s3://my-media-bucket/uploads/interview.mov",
"kind": "audio",
"options": {
"connection_id": "conn_abc123",
"format": "mp3",
"quality": "medium"
},
"output": "s3://my-media-bucket/audio/interview.mp3"
}'
curl -X POST https://api.ittybit.com/jobs \
-H "Authorization: Bearer $ITTYBIT_API_KEY" \
-H "Content-Type: application/json" \
-d "$TASK" Adaptive bitrate (HLS)
MediaConvert’s ABR workflows require an Apple HLS or DASH output group, multiple output presets (one per rendition), and careful bitrate ladder configuration. Ittybit handles the rendition ladder automatically.
const task = {
input: "s3://my-media-bucket/uploads/keynote.mov",
kind: "adaptive_video",
options: {
connection_id: "conn_abc123",
format: "hls",
},
output: "s3://my-media-bucket/streaming/keynote/",
};
const 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(task),
});
const data = await res.json();
import requests
task = {
"input": "s3://my-media-bucket/uploads/keynote.mov",
"kind": "adaptive_video",
"options": {
"connection_id": "conn_abc123",
"format": "hls",
},
"output": "s3://my-media-bucket/streaming/keynote/",
}
res = requests.post(
"https://api.ittybit.com/jobs",
headers={"Authorization": f"Bearer {api_key}"},
json=task,
)
data = res.json()TASK='{
"input": "s3://my-media-bucket/uploads/keynote.mov",
"kind": "adaptive_video",
"options": {
"connection_id": "conn_abc123",
"format": "hls"
},
"output": "s3://my-media-bucket/streaming/keynote/"
}'
curl -X POST https://api.ittybit.com/jobs \
-H "Authorization: Bearer $ITTYBIT_API_KEY" \
-H "Content-Type: application/json" \
-d "$TASK" The manifest and segments land in your S3 prefix. Point your player at the .m3u8 and it handles quality switching automatically.
Replacing the Lambda trigger
If you have a Lambda that calls CreateJob on MediaConvert when files hit S3, swap the MediaConvert call for an Ittybit call. The Lambda, EventBridge rule, and S3 notification stay the same — only the job creation changes.
Before (MediaConvert):
import { MediaConvertClient, CreateJobCommand } from '@aws-sdk/client-mediaconvert';
const mc = new MediaConvertClient({ endpoint: MEDIACONVERT_ENDPOINT });
await mc.send(
new CreateJobCommand({
Role: MEDIACONVERT_ROLE_ARN,
Settings: {
Inputs: [{ FileInput: `s3://${bucket}/${key}` }],
OutputGroups: [
{
OutputGroupSettings: {
Type: 'FILE_GROUP_SETTINGS',
FileGroupSettings: { Destination: `s3://${bucket}/processed/` },
},
Outputs: [
{
ContainerSettings: { Container: 'MP4' },
VideoDescription: {
Width: 1920,
CodecSettings: {
Codec: 'H_264',
H264Settings: { RateControlMode: 'QVBR', QualityTuningLevel: 'SINGLE_PASS_HQ' },
},
},
AudioDescriptions: [
{
CodecSettings: {
Codec: 'AAC',
AacSettings: { Bitrate: 128000, SampleRate: 48000 },
},
},
],
},
],
},
],
},
}),
);
After (Ittybit):
await fetch('https://api.ittybit.com/jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${ITTYBIT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: `s3://${bucket}/${key}`,
kind: 'video',
options: {
connection_id: CONNECTION_ID,
width: 1920,
format: 'mp4',
quality: 'high',
},
output: `s3://${bucket}/processed/${key.replace(/\.[^.]+$/, '.mp4')}`,
}),
});
You can also remove the MediaConvert execution IAM role, the CloudWatch alarm, and the SNS topic. Ittybit handles retries internally and can notify you via webhooks on completion or failure.
What you can delete
Once you’ve confirmed the Ittybit pipeline is working:
| AWS Resource | Why it existed |
|---|---|
| MediaConvert job template(s) | Codec/resolution/container config |
| MediaConvert output preset(s) | Per-rendition settings |
| MediaConvert queue | Job scheduling |
| IAM role + policy | MediaConvert execution permissions |
| CloudWatch alarm | Job failure detection |
| SNS topic + subscription | Failure notifications |
Keep your S3 bucket, EventBridge rule, and Lambda — those still work, just with a simpler job creation call.
See also
- Process files from S3 — setting up S3 connections
- Write output to S3 — writing processed files back to your bucket
- Create HLS streams — adaptive bitrate streaming
- AWS event-driven media processing — full EventBridge + Lambda + webhook pipeline