Idempotency
Idempotency ensures that retrying a request produces the same result as the original request, without creating duplicate resources. This is critical for handling network failures, timeouts, and other transient errors in production systems.
How It Works
When creating a job, include an idempotency_key in the request. If Transcodely receives a second request with the same key, it returns the result of the original request instead of creating a new job.
curl -X POST https://api.transcodely.com/transcodely.v1.JobService/Create
-H "Authorization: Bearer {{API_KEY}}"
-H "X-Organization-ID: org_a1b2c3d4e5"
-H "Content-Type: application/json"
-d '{
"input_url": "gs://my-bucket/video.mp4",
"output_origin_id": "ori_x9y8z7w6v5",
"outputs": [
{
"type": "mp4",
"video": [
{ "codec": "h264", "resolution": "1080p", "quality": "standard" }
]
}
],
"idempotency_key": "upload_usr12345_2026-01-15T10:30:00Z"
}'First Request
The job is created normally and the idempotency key is associated with the job.
Subsequent Requests (Same Key)
The API returns the existing job without creating a new one. The response is identical to what the first request returned.
Key Format
Idempotency keys are free-form strings with a maximum length of 128 characters. We recommend using a format that ties the key to the specific operation:
| Strategy | Example | Best For |
|---|---|---|
| UUID v4 | 550e8400-e29b-41d4-a716-446655440000 | Simple, guaranteed uniqueness |
| Operation-based | upload_usr12345_2026-01-15T10:30:00Z | Readable, debuggable |
| Content hash | sha256:a1b2c3d4e5f6... | Deduplication based on input |
Recommended: Operation-Based Keys
{action}_{entity_id}_{timestamp}Examples:
transcode_vid_abc123_2026-01-15T10:30:00Z— ties to a specific uploadbatch_campaign_summer2026_chunk_42— ties to a specific batch itemretry_job_a1b2c3_attempt_3— explicit retry tracking
Scope
Idempotency keys are scoped to the app associated with the API key. The same key can be used independently across different apps without conflict.
Behavior on Replay
| Scenario | Behavior |
|---|---|
| Same key, same request body | Returns the original job |
| Same key, different request body | Returns the original job (request body is not compared) |
| Same key, different API key (same app) | Returns the original job |
| Same key, different app | Creates a new job (keys are app-scoped) |
Important: The API does not compare request bodies when an idempotency key is replayed. If you reuse a key with a different request body, you will get back the original job — not a new job with the new parameters. Always use unique keys for distinct operations.
Expiration
Idempotency keys are stored for 24 hours. After 24 hours, a previously used key can be reused to create a new job.
When to Use Idempotency Keys
- Network retries — your HTTP client automatically retries on timeout or connection reset
- Queue-based processing — a message queue may deliver the same message more than once
- User-triggered actions — a user clicks “Submit” multiple times before the UI disables the button
- Batch processing — processing a list of items where some may need to be retried
Example: Safe Retry Logic
async function createJobWithRetry(
client: JobServiceClient,
request: CreateJobRequest,
maxRetries = 3
): Promise<Job> {
// Generate a unique idempotency key for this operation
const idempotencyKey = `job_${crypto.randomUUID()}`;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await client.create({
...request,
idempotency_key: idempotencyKey,
});
return response.job;
} catch (err) {
if (err instanceof ConnectError) {
// Don't retry on client errors
if (err.code === Code.InvalidArgument || err.code === Code.NotFound) {
throw err;
}
// Retry on transient errors
if (attempt < maxRetries) {
await sleep(Math.pow(2, attempt) * 1000); // Exponential backoff
continue;
}
}
throw err;
}
}
throw new Error('Max retries exceeded');
}import time
import uuid
from connectrpc.exceptions import ConnectError
def create_job_with_retry(client, request, max_retries=3):
idempotency_key = f"job_{uuid.uuid4()}"
for attempt in range(max_retries + 1):
try:
request.idempotency_key = idempotency_key
response = client.create(request)
return response.job
except ConnectError as e:
if e.code in ("invalid_argument", "not_found"):
raise # Don't retry client errors
if attempt < max_retries:
time.sleep(2 ** attempt) # Exponential backoff
continue
raiseBest Practices
- Generate the key before the first attempt and reuse it across retries.
- Use descriptive, deterministic keys when possible — they make debugging easier.
- Never reuse a key for a different operation — always generate a new key for each distinct request.
- Store the key alongside your internal records so you can trace which Transcodely job maps to which internal entity.