Your First Job
This guide walks through the complete lifecycle of a transcoding job — from submission to completion. You will learn how to configure outputs, monitor progress in real time, and handle the results.
Job Lifecycle
Every job progresses through a series of statuses:
| Status | Description |
|---|---|
pending | Job is queued and waiting to be assigned to a worker |
probing | Worker is analyzing the input file with ffprobe |
awaiting_confirmation | Delayed-start job waiting for cost review (optional) |
processing | Actively encoding the video |
completed | All outputs finished successfully |
partial | Some outputs completed, others failed |
failed | Job failed with an error |
canceled | Job was canceled by the user |
Creating a Job
A job requires an input source and at least one output specification. You can configure outputs inline or reference a saved preset. The example below submits one input and two inline outputs — a 1080p H.264 MP4 and a 720p VP9 WebM — each with its own codec, resolution, and quality tier:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Create
-H "Authorization: Bearer {{API_KEY}}"
-H "Content-Type: application/json"
-d '{
"input_url": "https://storage.example.com/uploads/interview.mp4",
"output_origin_id": "ori_x9y8z7w6v5",
"outputs": [
{
"type": "mp4",
"video": [
{
"codec": "h264",
"resolution": "1080p",
"quality": "standard"
}
]
},
{
"type": "webm",
"video": [
{
"codec": "vp9",
"resolution": "720p",
"quality": "economy"
}
]
}
],
"priority": "standard",
"metadata": {
"user_id": "usr_12345",
"project": "marketing-videos"
}
}'const job = await client.jobs.create({
inputUrl: "https://storage.example.com/uploads/interview.mp4",
outputOriginId: "ori_x9y8z7w6v5",
outputs: [
{
type: OutputFormat.MP4,
video: [
{
codec: VideoCodec.H264,
resolution: Resolution.RESOLUTION_1080P,
quality: QualityTier.STANDARD,
},
],
},
{
type: OutputFormat.WEBM,
video: [
{
codec: VideoCodec.VP9,
resolution: Resolution.RESOLUTION_720P,
quality: QualityTier.ECONOMY,
},
],
},
],
priority: JobPriority.STANDARD,
metadata: {
user_id: "usr_12345",
project: "marketing-videos",
},
});job = client.jobs.create(
input_url="https://storage.example.com/uploads/interview.mp4",
output_origin_id="ori_x9y8z7w6v5",
outputs=[
{
"type": "mp4",
"video": [{"codec": "h264", "resolution": "1080p", "quality": "standard"}],
},
{
"type": "webm",
"video": [{"codec": "vp9", "resolution": "720p", "quality": "economy"}],
},
],
priority="standard",
metadata={"user_id": "usr_12345", "project": "marketing-videos"},
)job, err := client.Jobs.Create(ctx, &transcodely.JobCreateParams{
InputUrl: "https://storage.example.com/uploads/interview.mp4",
OutputOriginId: proto.String("ori_x9y8z7w6v5"),
Outputs: []*transcodely.OutputSpec{
{
Type: transcodely.OutputFormatMP4,
Video: []*transcodely.VideoVariant{{
Codec: transcodely.VideoCodecH264,
Resolution: transcodely.Resolution1080P,
Quality: transcodely.QualityTierStandard,
}},
},
{
Type: transcodely.OutputFormatWebM,
Video: []*transcodely.VideoVariant{{
Codec: transcodely.VideoCodecVP9,
Resolution: transcodely.Resolution720P,
Quality: transcodely.QualityTierEconomy,
}},
},
},
Priority: transcodely.JobPriorityStandard,
Metadata: map[string]string{"user_id": "usr_12345", "project": "marketing-videos"},
})This creates a job with two outputs: a 1080p H.264 MP4 and a 720p VP9 WebM. The metadata fields are stored with the job and returned in all responses and webhook payloads.
To encode only a portion of the input, add a clip range ({ "start_seconds": 2, "end_seconds": 7 }) — see Clipping and trimming.
Using Presets
Instead of configuring every parameter inline, reference a saved preset by its ID (pst_…) or its slug. Both point to the same preset — the pst_ ID is an exact, unchanging handle, while a slug like web_720p_standard is a memorable alias you can read at a glance. This example references one preset by ID and one by slug:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Create
-H "Authorization: Bearer {{API_KEY}}"
-H "Content-Type: application/json"
-d '{
"input_url": "https://storage.example.com/uploads/interview.mp4",
"output_origin_id": "ori_x9y8z7w6v5",
"outputs": [
{"preset": "pst_x9y8z7w6v5"},
{"preset": "web_720p_standard"}
],
"priority": "standard"
}'const job = await client.jobs.create({
inputUrl: "https://storage.example.com/uploads/interview.mp4",
outputOriginId: "ori_x9y8z7w6v5",
outputs: [
{ preset: "pst_x9y8z7w6v5" },
{ preset: "web_720p_standard" },
],
priority: JobPriority.STANDARD,
});job = client.jobs.create(
input_url="https://storage.example.com/uploads/interview.mp4",
output_origin_id="ori_x9y8z7w6v5",
outputs=[
{"preset": "pst_x9y8z7w6v5"},
{"preset": "web_720p_standard"},
],
priority="standard",
)job, err := client.Jobs.Create(ctx, &transcodely.JobCreateParams{
InputUrl: "https://storage.example.com/uploads/interview.mp4",
OutputOriginId: proto.String("ori_x9y8z7w6v5"),
Outputs: []*transcodely.OutputSpec{
{Preset: proto.String("pst_x9y8z7w6v5")},
{Preset: proto.String("web_720p_standard")},
},
Priority: transcodely.JobPriorityStandard,
})Monitoring Progress
Polling
Retrieve the current job state at any time:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Get
-H "Authorization: Bearer {{API_KEY}}"
-H "Content-Type: application/json"
-d '{"id": "job_a1b2c3d4e5f6"}'const job = await client.jobs.get("job_a1b2c3d4e5f6");job = client.jobs.get(id="job_a1b2c3d4e5f6")job, err := client.Jobs.Get(ctx, "job_a1b2c3d4e5f6")The response includes per-output progress:
{
"job": {
"id": "job_a1b2c3d4e5f6",
"status": "processing",
"progress": 45,
"outputs": [
{
"id": "out_g7h8i9j0k1",
"status": "processing",
"progress": 72
},
{
"id": "out_l2m3n4o5p6",
"status": "pending",
"progress": 0
}
]
}
}Real-Time Watch Stream
For live progress updates, use the Watch RPC. It returns the current state immediately, then streams updates as the job progresses:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Watch
-H "Authorization: Bearer {{API_KEY}}"
-H "Content-Type: application/json"
-d '{"id": "job_a1b2c3d4e5f6"}'for await (const event of client.jobs.watch("job_a1b2c3d4e5f6")) {
console.log(`${event.event}: ${event.job?.progress}%`);
if (event.event === WatchEventType.COMPLETED) break;
}for event in client.jobs.watch(id="job_a1b2c3d4e5f6"):
print(f"{event.event}: {event.job.progress}%")
if event.event == "completed":
breakstream := client.Jobs.Watch(ctx, "job_a1b2c3d4e5f6")
defer stream.Close()
for stream.Next() {
event := stream.Current()
fmt.Printf("%s: %d%%\n", event.GetEvent(), event.GetJob().GetProgress())
if event.GetEvent() == transcodely.WatchEventCompleted {
break
}
}Each message includes an event type:
| Event | Description |
|---|---|
snapshot | Initial state when the stream opens |
progress | Progress percentage changed |
status_change | Job or output transitioned to a new status |
completed | Job reached a terminal state (stream closes after this) |
heartbeat | Keepalive sent every 3 seconds (default) |
The names above are the wire-level event values. Logging event.event directly, as the samples do, prints the SDK’s enum representation instead: the JS SDK yields the numeric value (2 for progress), and the Go SDK yields the canonical constant name (WATCH_EVENT_TYPE_PROGRESS). Compare against the WatchEventType enum members, and map to a label if you need the short names shown above.
Delayed Start Jobs
If you want to review the estimated cost before encoding begins, set delayed_start to true:
{
"input_url": "https://storage.example.com/uploads/long-video.mp4",
"output_origin_id": "ori_x9y8z7w6v5",
"outputs": [{"type": "mp4", "video": [{"codec": "h264", "resolution": "2160p", "quality": "premium"}]}],
"delayed_start": true
}After probing, the job enters awaiting_confirmation with a cost estimate. Call Confirm to proceed:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Confirm
-H "Authorization: Bearer {{API_KEY}}"
-H "Content-Type: application/json"
-d '{"id": "job_a1b2c3d4e5f6"}'const job = await client.jobs.confirm("job_a1b2c3d4e5f6");job = client.jobs.confirm(id="job_a1b2c3d4e5f6")job, err := client.Jobs.Confirm(ctx, "job_a1b2c3d4e5f6")Canceling a Job
Cancel a pending or in-progress job at any time:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Cancel
-H "Authorization: Bearer {{API_KEY}}"
-H "Content-Type: application/json"
-d '{"id": "job_a1b2c3d4e5f6"}'const job = await client.jobs.cancel("job_a1b2c3d4e5f6");job = client.jobs.cancel(id="job_a1b2c3d4e5f6")job, err := client.Jobs.Cancel(ctx, "job_a1b2c3d4e5f6")For jobs that are already encoding, you are billed the flat processing fee plus any outputs that completed before cancellation. Canceling a job that is still pending — before an encoder picks it up — costs nothing at all.
Completed Job Response
When all outputs finish, the job reaches completed and every output carries its final results. Three things worth checking: each output_url (where the transcoded file was written in your output origin), the per-output output_size_bytes and duration_seconds, and the cost fields — total_actual_cost is what you are billed and is often a little below the total_estimated_cost quoted up front (here 0.089 against 0.092 EUR):
{
"job": {
"id": "job_a1b2c3d4e5f6",
"status": "completed",
"progress": 100,
"total_estimated_cost": 0.092,
"total_actual_cost": 0.089,
"currency": "EUR",
"outputs": [
{
"id": "out_g7h8i9j0k1",
"status": "completed",
"output_url": "gs://my-bucket/job_a1b2c3d4e5f6/out_g7h8i9j0k1.mp4",
"output_size_bytes": 15728640,
"duration_seconds": 120,
"estimated_cost": 0.052,
"actual_cost": 0.050
},
{
"id": "out_l2m3n4o5p6",
"status": "completed",
"output_url": "gs://my-bucket/job_a1b2c3d4e5f6/out_l2m3n4o5p6.webm",
"output_size_bytes": 8912340,
"duration_seconds": 120,
"estimated_cost": 0.040,
"actual_cost": 0.039
}
]
}
}