Overview
By default, transcoding jobs start encoding immediately after the input file is probed. The delayed start workflow adds a confirmation step: the job pauses after probing, gives you the estimated cost, and waits for explicit confirmation before proceeding.
This is useful when you need to:
- Show end users the transcoding cost before charging them
- Implement approval workflows for expensive jobs
- Validate estimated costs against a budget before committing
- Let users review input metadata (duration, resolution, codecs) before encoding
How It Works
The delayed job workflow has three phases:
Create (delayed_start: true)
|
v
Pending --> Probing --> Awaiting Confirmation
|
[Review estimated cost]
|
+------------+------------+
| |
Confirm Cancel
| |
v v
Processing Canceled
|
v
Completed- Create — Submit the job with
delayed_start: true. Status ispending. - Probe — Transcodely analyzes the input file to determine duration, resolution, and codec information. Status moves to
probing, thenawaiting_confirmation. - Confirm or Cancel — Review the estimated cost and either confirm the job to start encoding, or cancel it to avoid charges.
Step 1: Create a Delayed Job
Set delayed_start to true in your create request:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Create
-H "Content-Type: application/json"
-H "Authorization: Bearer {{API_KEY}}"
-H "X-Organization-ID: {{ORG_ID}}"
-d '{
"input_origin_id": "ori_input12345",
"input_path": "uploads/my-video.mp4",
"output_origin_id": "ori_output6789",
"delayed_start": true,
"outputs": [
{
"type": "mp4",
"video": [{ "codec": "h264", "resolution": "1080p", "quality": "standard" }]
},
{
"type": "hls",
"video": [
{ "codec": "h264", "resolution": "1080p", "quality": "standard" },
{ "codec": "h264", "resolution": "720p", "quality": "standard" },
{ "codec": "h264", "resolution": "480p", "quality": "economy" }
]
}
],
"webhook_url": "https://yourapp.com/webhooks/transcodely"
}'The response confirms the job was created with delayed_start enabled:
{
"job": {
"id": "job_a1b2c3d4e5f6",
"status": "pending",
"delayed_start": true,
"outputs": [
{ "id": "out_mp4_001", "status": "pending", "progress": 0 },
{ "id": "out_hls_002", "status": "pending", "progress": 0 }
],
"metadata": {},
"created_at": "2026-02-28T10:30:00Z"
}
}Step 2: Wait for Probing to Complete
The job automatically transitions through probing to awaiting_confirmation. You can poll the job status or use webhooks to be notified.
Polling
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Get
-H "Content-Type: application/json"
-H "Authorization: Bearer {{API_KEY}}"
-H "X-Organization-ID: {{ORG_ID}}"
-d '{ "id": "job_a1b2c3d4e5f6" }'When the status is awaiting_confirmation, the response includes estimated costs and input metadata:
{
"job": {
"id": "job_a1b2c3d4e5f6",
"status": "awaiting_confirmation",
"delayed_start": true,
"total_estimated_cost": 0.3455,
"currency": "EUR",
"input_metadata": {
"duration_seconds": 120,
"width": 1920,
"height": 1080,
"framerate": 30.0,
"video_codec": "h264",
"audio_codec": "aac",
"file_size_bytes": 52428800
},
"outputs": [
{
"id": "out_mp4_001",
"status": "pending",
"estimated_cost": 0.12
},
{
"id": "out_hls_002",
"status": "pending",
"estimated_cost": 0.2255,
"variant_pricing": [
{ "index": 0, "resolution": "1080p", "estimated_cost": 0.106 },
{ "index": 1, "resolution": "720p", "estimated_cost": 0.0795 },
{ "index": 2, "resolution": "480p", "estimated_cost": 0.04 }
]
}
],
"probed_at": "2026-02-28T10:30:15Z"
}
}Using Watch for Real-Time Updates
Instead of polling, you can use the Watch RPC to stream status updates:
const stream = jobClient.watch({ id: "job_a1b2c3d4e5f6" });
for await (const response of stream) {
if (response.job?.status === "awaiting_confirmation") {
console.warn("Estimated cost:", response.job.totalEstimatedCost);
// Show cost to user, prompt for confirmation
break;
}
}Step 3: Review the Estimate
Before confirming, review the cost breakdown:
| Field | Description |
|---|---|
total_estimated_cost | Sum of all output estimated costs |
currency | ISO 4217 currency code (e.g., EUR) |
outputs[].estimated_cost | Per-output cost |
outputs[].variant_pricing[] | Per-variant cost breakdown (for ABR outputs) |
input_metadata | Source file properties (duration, resolution, codecs) |
Estimated costs are calculated using locked pricing snapshots captured at job creation time. This means the price will not change between creation and confirmation, even if Transcodely updates its pricing in the interim.
Step 4: Confirm or Cancel
Confirm the Job
To proceed with encoding:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Confirm
-H "Content-Type: application/json"
-H "Authorization: Bearer {{API_KEY}}"
-H "X-Organization-ID: {{ORG_ID}}"
-d '{ "id": "job_a1b2c3d4e5f6" }'The job status transitions to processing and encoding begins:
{
"job": {
"id": "job_a1b2c3d4e5f6",
"status": "processing",
"delayed_start": true,
"confirmed_at": "2026-02-28T10:31:00Z"
}
}Cancel the Job
If the estimated cost is too high or the input is not what was expected, cancel the job:
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Cancel
-H "Content-Type: application/json"
-H "Authorization: Bearer {{API_KEY}}"
-H "X-Organization-ID: {{ORG_ID}}"
-d '{ "id": "job_a1b2c3d4e5f6" }'Canceled jobs incur no encoding charges. The only cost is the brief probing phase.
Implementation Example
Here is a typical integration pattern for delayed jobs in a web application:
import { createOrgApiClient } from "$lib/api/client";
import { JobService } from "$lib/gen/transcodely/v1/job_connect";
const jobClient = createOrgApiClient(JobService);
// 1. Create the delayed job
const { job } = await jobClient.create({
inputOriginId: "ori_input12345",
inputPath: "uploads/my-video.mp4",
outputOriginId: "ori_output6789",
delayedStart: true,
outputs: [
{
type: "mp4",
video: [{ codec: "h264", resolution: "1080p", quality: "standard" }],
},
],
});
// 2. Poll until awaiting confirmation
let current = job;
while (current.status !== "awaiting_confirmation") {
await new Promise((resolve) => setTimeout(resolve, 2000));
const response = await jobClient.get({ id: current.id });
current = response.job;
}
// 3. Show cost to user and get approval
const approved = await showCostApprovalDialog(current.totalEstimatedCost, current.currency);
// 4. Confirm or cancel
if (approved) {
await jobClient.confirm({ id: current.id });
} else {
await jobClient.cancel({ id: current.id });
}When to Use Delayed Jobs
| Scenario | Recommended |
|---|---|
| User-uploaded content with pay-per-use billing | Yes |
| Internal batch processing with fixed budgets | Yes |
| Automated pipelines with predictable inputs | No, use immediate start |
| Preview generation for quick feedback | No, use immediate start |
| High-value content requiring cost approval | Yes |