Search Documentation
Search across all documentation pages
Your First Job

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:

StatusDescription
pendingJob is queued and waiting to be assigned to a worker
probingWorker is analyzing the input file with ffprobe
awaiting_confirmationDelayed-start job waiting for cost review (optional)
processingActively encoding the video
completedAll outputs finished successfully
partialSome outputs completed, others failed
failedJob failed with an error
canceledJob 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":
        break
stream := 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:

EventDescription
snapshotInitial state when the stream opens
progressProgress percentage changed
status_changeJob or output transitioned to a new status
completedJob reached a terminal state (stream closes after this)
heartbeatKeepalive 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
      }
    ]
  }
}

Next Steps

  • SDKs — Use an official SDK instead of raw HTTP
  • Encoding — Learn about codecs, quality tiers, and advanced options
  • Webhooks — Get notified when jobs complete instead of polling