Search Documentation
Search across all documentation pages
Idempotency

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:

StrategyExampleBest For
UUID v4550e8400-e29b-41d4-a716-446655440000Simple, guaranteed uniqueness
Operation-basedupload_usr12345_2026-01-15T10:30:00ZReadable, debuggable
Content hashsha256:a1b2c3d4e5f6...Deduplication based on input
{action}_{entity_id}_{timestamp}

Examples:

  • transcode_vid_abc123_2026-01-15T10:30:00Z — ties to a specific upload
  • batch_campaign_summer2026_chunk_42 — ties to a specific batch item
  • retry_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

ScenarioBehavior
Same key, same request bodyReturns the original job
Same key, different request bodyReturns the original job (request body is not compared)
Same key, different API key (same app)Returns the original job
Same key, different appCreates 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
            raise

Best Practices

  1. Generate the key before the first attempt and reuse it across retries.
  2. Use descriptive, deterministic keys when possible — they make debugging easier.
  3. Never reuse a key for a different operation — always generate a new key for each distinct request.
  4. Store the key alongside your internal records so you can trace which Transcodely job maps to which internal entity.