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.
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 only for the portion that was completed before cancellation.
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
}
]
}
}