Search Documentation
Search across all documentation pages
Errors

Errors

The Transcodely API uses Connect-RPC error codes with structured error details. Every error response includes a machine-readable code, a human-readable message, and — for validation errors — field-level details that pinpoint exactly what went wrong.

Error Response Format

All error responses follow this structure:

{
  "code": "invalid_argument",
  "message": "Request validation failed",
  "details": [
    {
      "type": "transcodely.v1.ErrorDetails",
      "value": {
        "code": "validation_error",
        "message": "Request validation failed",
        "field_violations": [
          {
            "field": "outputs[0].video[0].codec",
            "description": "codec is required"
          },
          {
            "field": "input_url",
            "description": "must match pattern: ^(gs|s3|https?)://.*$"
          }
        ]
      }
    }
  ]
}

Key design decisions:

  • All validation errors are returned at once — the API does not stop at the first error
  • Field paths include array indices — e.g., outputs[0].video[0].h264.crf
  • Errors are machine-readable — the code field is always a stable, lowercase string

Connect-RPC Error Codes

CodeHTTP StatusDescription
invalid_argument400Request validation failed (bad input)
not_found404Resource does not exist
already_exists409Resource already exists (e.g., duplicate slug)
permission_denied403Authenticated but not authorized for this action
unauthenticated401Missing or invalid API key
failed_precondition400Request cannot be fulfilled in current state
resource_exhausted429Rate limit exceeded
internal500Unexpected server error
unavailable503Service temporarily unavailable
deadline_exceeded504Request timed out
unimplemented501Endpoint not yet implemented

ErrorDetails

The details array contains one or more ErrorDetails objects with additional context:

FieldTypeDescription
codestringMachine-readable error code (e.g., validation_error, parameter_out_of_range)
messagestringHuman-readable error description
field_violationsFieldViolation[]List of field-level errors (for validation failures)

FieldViolation

Each field violation points to a specific field in the request:

FieldTypeDescription
fieldstringDot-notation path to the invalid field
descriptionstringWhat is wrong with the field

Field paths use dot notation with array indices:

  • input_url — top-level field
  • outputs[0].type — first output’s type field
  • outputs[0].video[0].h264.crf — nested codec option
  • metadata.my_key — metadata entry

Common Error Scenarios

Invalid API Key

{
  "code": "unauthenticated",
  "message": "Invalid or missing API key"
}

Cause: The Authorization header is missing, the key is malformed, the key has been revoked, or the key has expired.

Solution: Check that you are passing a valid API key as Bearer {{API_KEY}} in the Authorization header.

Resource Not Found

{
  "code": "not_found",
  "message": "Job not found: job_nonexistent123"
}

Cause: The resource ID does not exist, or it belongs to a different app.

Solution: Verify the resource ID and ensure you are using the correct API key (keys are scoped to an app).

Validation Error

{
  "code": "invalid_argument",
  "message": "Request validation failed",
  "details": [
    {
      "type": "transcodely.v1.ErrorDetails",
      "value": {
        "code": "validation_error",
        "message": "Request validation failed",
        "field_violations": [
          {
            "field": "outputs[0].video[0].h264.crf",
            "description": "CRF must be between 15 and 35"
          }
        ]
      }
    }
  ]
}

Cause: One or more request fields failed validation.

Solution: Read each field_violation to identify and fix the invalid fields. All violations are returned at once so you can fix them in a single pass.

Organization Suspended

{
  "code": "permission_denied",
  "message": "Organization is suspended"
}

Cause: The organization associated with the API key is suspended (usually due to a billing issue).

Solution: Contact support or resolve the billing issue to reactivate the organization.

Duplicate Resource

{
  "code": "already_exists",
  "message": "Preset with slug 'web_720p_fast' already exists"
}

Cause: Attempting to create a resource with a slug or identifier that is already in use.

Solution: Use a different slug, or retrieve the existing resource.

State Conflict

{
  "code": "failed_precondition",
  "message": "Job is not in a cancelable state: completed"
}

Cause: The requested operation is not valid for the resource’s current state (e.g., canceling a completed job, confirming a job that is not awaiting confirmation).

Solution: Check the resource’s current status before making state-transition requests.

Error Handling Best Practices

  1. Always check the code field for programmatic error handling — do not parse the message string.
  2. Handle all validation errors at once — the API returns every field violation in a single response.
  3. Retry on unavailable and resource_exhausted — use exponential backoff with jitter. The official SDKs honor Retry-After and retry these automatically.
  4. Do not retry on invalid_argument or not_found — these indicate a problem with your request.
  5. Log the full error response — including details and field_violations — for debugging.

SDK error handling

If you use one of the official SDKs, prefer the typed error classes — they give you instanceof / isinstance / errors.As-style matching without parsing strings.

Every error inherits from a base TranscodelyError and exposes the same fields across all three SDKs:

ClassHTTP statusWhen
APIConnectionErrorNetwork / DNS / TLS failure
APIError5xxServer-side internal error
AuthenticationError401Invalid, missing, or revoked API key
PermissionError403Authenticated but lacking permission
NotFoundError404Resource doesn’t exist
ConflictError409Idempotency conflict, slug taken
RateLimitError429Carries retryAfterMs / retry_after_ms / RetryAfter
InvalidRequestError400 / 422Carries errors: FieldViolation[]
PreconditionError412Wrong state (e.g. job not cancelable)

Every error carries code, httpStatus, requestId, and raw (the original response body) for debugging.

Without an SDK there are no typed exceptions to catch — instead, inspect the HTTP status and read the structured code and field_violations straight out of the JSON body (e.g. with jq):

# Capture the response body and the HTTP status separately (no -f, so the
# error body is still written to /tmp/resp.json on a 4xx/5xx).
http_status=$(curl -sS -o /tmp/resp.json -w '%{http_code}' 
  -X POST "https://api.transcodely.com/transcodely.v1.JobService/Create" 
  -H "Authorization: Bearer {{API_KEY}}" 
  -H "Content-Type: application/json" 
  -d '{"input_url": "not-a-url", "outputs": []}')

if [ "$http_status" -ge 400 ]; then
  # Top-level Connect code (e.g. "invalid_argument", "not_found").
  jq -r '.code, .message' /tmp/resp.json
  # Field-level violations, when present (HTTP 400/422).
  jq -r '.details[]?.value.field_violations[]? | "(.field): (.description)"' /tmp/resp.json
fi
import {
  TranscodelyError,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
  InvalidRequestError,
} from "transcodely";

try {
  await client.jobs.create(request);
} catch (err) {
  if (err instanceof InvalidRequestError) {
    for (const v of err.errors) {
      console.error(`${v.field}: ${v.description}`);
    }
  } else if (err instanceof RateLimitError) {
    await new Promise((r) => setTimeout(r, err.retryAfterMs ?? 1000));
  } else if (err instanceof NotFoundError) {
    console.error("not found:", err.message);
  } else if (err instanceof AuthenticationError) {
    console.error("bad API key");
  } else if (err instanceof TranscodelyError) {
    console.error(`[${err.requestId}] ${err.code}: ${err.message}`);
  } else {
    throw err;
  }
}
import time
from transcodely import (
    TranscodelyError,
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    InvalidRequestError,
)

try:
    client.jobs.create(input_url=..., outputs=[...])
except InvalidRequestError as err:
    for v in err.errors:
        print(f"{v.field}: {v.description}")
except RateLimitError as err:
    time.sleep((err.retry_after_ms or 1000) / 1000)
except NotFoundError as err:
    print(f"not found: {err}")
except AuthenticationError:
    print("bad API key")
except TranscodelyError as err:
    print(f"[{err.request_id}] {err.code}: {err}")
import (
    "errors"
    "time"

    "github.com/transcodely/transcodely-go"
)

job, err := client.Jobs.Create(ctx, params)
if err != nil {
    var notFound *transcodely.NotFoundError
    var invalid  *transcodely.InvalidRequestError
    var rate     *transcodely.RateLimitError
    var auth     *transcodely.AuthenticationError

    switch {
    case errors.As(err, &notFound):
        log.Printf("not found, request_id=%s", notFound.RequestID())
    case errors.As(err, &invalid):
        for _, v := range invalid.Errors() {
            log.Printf("%s: %s", v.Field, v.Description)
        }
    case errors.As(err, &rate):
        time.Sleep(rate.RetryAfter)
    case errors.As(err, &auth):
        log.Fatal("bad API key")
    default:
        log.Fatal(err)
    }
}
_ = job

Direct Connect-RPC error handling

If you call the API without an SDK (e.g. from a generated Connect-ES or connect-go stub), match on the Connect Code directly. The SDK examples above are equivalent — they wrap this code-based matching in typed classes.

There is no public Connect-RPC client library for Python — the official Python SDK speaks Connect over plain HTTP/JSON internally, and there is no low-level ConnectError to catch. If you are not using the SDK from Python, inspect the HTTP status and JSON body directly (see the curl + jq snippet under SDK error handling); otherwise use the SDK’s typed errors shown below.

import { ConnectError, Code } from '@connectrpc/connect';

try {
  const job = await client.create(request);
} catch (err) {
  if (err instanceof ConnectError) {
    switch (err.code) {
      case Code.InvalidArgument:
        // Parse field violations from err.details
        console.error('Validation failed:', err.message);
        break;
      case Code.NotFound:
        console.error('Resource not found');
        break;
      case Code.Unauthenticated:
        // Redirect to login or refresh credentials
        break;
      case Code.Unavailable:
        // Retry with exponential backoff
        break;
      default:
        console.error('Unexpected error:', err.code, err.message);
    }
  }
}
# Python has no low-level Connect client — use the SDK's typed errors instead.
from transcodely import (
    InvalidRequestError,
    NotFoundError,
    AuthenticationError,
    APIError,
    TranscodelyError,
)

try:
    job = client.jobs.create(input_url=..., outputs=[...])
except InvalidRequestError as err:
    # Field violations are already parsed for you.
    for v in err.errors:
        print(f"validation failed: {v.field}: {v.description}")
except NotFoundError:
    print("resource not found")
except AuthenticationError:
    # Refresh credentials.
    ...
except APIError:
    # 5xx / unavailable — retry with exponential backoff.
    ...
except TranscodelyError as err:
    print(f"unexpected error: {err.code}: {err}")
import (
	"errors"

	"connectrpc.com/connect"
)

// client is a generated connect-go stub, e.g. from
// transcodelyv1connect.NewJobServiceClient(httpClient, "https://api.transcodely.com").
_, err := client.Create(ctx, connect.NewRequest(req))
if err != nil {
	var connectErr *connect.Error
	if errors.As(err, &connectErr) {
		switch connectErr.Code() {
		case connect.CodeInvalidArgument:
			// Field violations live in connectErr.Details().
			log.Printf("validation failed: %v", connectErr.Message())
		case connect.CodeNotFound:
			log.Print("resource not found")
		case connect.CodeUnauthenticated:
			// Refresh credentials.
		case connect.CodeUnavailable:
			// Retry with exponential backoff.
		default:
			log.Printf("unexpected error: %v: %v", connectErr.Code(), connectErr.Message())
		}
	}
}