Quickstart

This page walks through submitting a real job end-to-end. By the end you will have transcoded a file with ffmpeg, then packaged an encrypted DASH stream with shaka-packager and DRMtoday — both from your terminal, using curl.

If the vocabulary feels unfamiliar (organization, job, task, tool, secret), skim Concepts first.

Prerequisites

You will be juggling three independent sets of credentials — make sure you can tell them apart before you start:

  1. Your VTK API access key (access_key_id, secret_access_key, user_urn) — proves to VTK who you are. Created once at https://account.castlabs.com/accountAPI Keys[New API Key]. The dialog shows all three values once; copy them now. See Setup → Authentication.

  2. AWS credentials for your S3 bucket — let the VTK worker read or write objects in your AWS account. Three ways to provide them, you only need one:

    • Org secret (used in the examples below): create an access key for an IAM user in the AWS console (IAM → Users → Security credentials → Create access key), then save it under Secrets in the VTK web UI.

    • Assume role: create an IAM role in your account that trusts the VTK worker role; pass its ARN as role_arn on the job (Security → IAM).

    • Credential dispenser: any HTTPS endpoint you host that returns AWS credentials per request — short-lived from an STS broker, static, mocked, whatever fits (Security → Credential dispenser).

  3. DRMtoday credentials (Example B only) — merchant and user come from your DRMtoday account portal; the matching password must be stored as a VTK org secret and referenced as {name} — never inlined.

You also need an organization_urn (the VTK org you’re a member of). Find it in the web UI at https://fe.vtk.castlabs.com/Organizations; it looks like urn:janus:organization:acme.

In the examples below, replace these placeholders with your values:

  • urn:janus:organization:acme — your organization_urn.

  • acme-bucket — your S3 bucket.

  • {acme-aws-access-keys} — name of the org secret holding your AWS access keys (create it under Secrets in the UI).

  • {acme-drmtoday-password} — name of the org secret holding the DRMtoday password.

  • acme::ops and merchant: acme — your DRMtoday user and merchant.

Step 1 — Get a bearer token

VTK authenticates every request with a Castlabs OAuth bearer token. To get one, POST your API access key to the credential-exchange endpoint at https://auth.castlabs.com/api/v1/keypair/credentialexchange; the body is signed with an HMAC chain derived from your secret_access_key and user_urn. Setup → Authentication has the full request shape and a ready-to-run Python implementation — copy it, run it, and pick the access_token out of the response.

The rest of this page assumes you’ve stored it in $TOKEN:

TOKEN=eyJraWQiOi…                              # access_token from the exchange
ORG=urn:janus:organization:acme                # your organization_urn

Step 2 — Submit a job

Jobs are submitted with POST /o/{organization_urn}/jobs. The body is a JSON object with at least tasks; common optional fields are region, tags, notify, and role_arn.

curl -X POST "https://api.vtk.castlabs.com/o/$ORG/jobs" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-castlabs-organization: $ORG" \
  -H "Content-Type: application/json" \
  -d @job.json

A 201 Created response returns the full job, including the assigned id and the initial state of every task. Save the id — you’ll use it to poll status and fetch logs.

{ "id": "abcDEFGHIjk", "status": 0, "tasks": [ { "task_id": 12345, "tool": "storage:get", "status": 0 },  ] }

Step 3 — Poll the job

JOB_ID=abcDEFGHIjk

while :; do
  status=$(curl -s "https://api.vtk.castlabs.com/o/$ORG/jobs/$JOB_ID" \
    -H "Authorization: Bearer $TOKEN" \
    -H "x-castlabs-organization: $ORG" | jq -r '.status')
  echo "status=$status"
  case "$status" in 2|3|4|6) break ;; esac
  sleep 10
done

Status codes: 0 submitted, 5 waiting for resources, 1 running, 2 completed, 3 failed, 4 aborted, 6 resource terminated. See Status for the full table.

You can also watch the job live in the web UI at https://fe.vtk.castlabs.com/Jobs, or skip polling entirely by adding "notify": "mailto:you@example.com" (or an HTTPS callback) to the submission body — VTK then pushes a notification when the job reaches a terminal state.

Step 4 — Fetch task logs

If a task fails, the log usually tells you why:

curl "https://api.vtk.castlabs.com/o/$ORG/jobs/$JOB_ID/tasks/12345/log" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-castlabs-organization: $ORG"
{ "count": 2, "results": [
  { "timestamp": 1607374149000, "message": "ffmpeg version 6.0 …" },
  { "timestamp": 1607374152000, "message": "frame=  150 fps= 75 …" }
] }

Example A — Transcode an MP4 (S3 → ffmpeg → S3)

A three-task job: download a source file from S3, transcode it with ffmpeg, upload the result to a per-job folder.

job.json:

{
  "tags": ["quickstart", "transcode"],
  "region": "aws:eu-west-1",
  "tasks": [
    {
      "tool": "storage:get",
      "parameters": {
        "location": "s3://{acme-aws-access-keys}@acme-bucket/in/",
        "files": ["source.mov"]
      }
    },
    {
      "tool": "ffmpeg:cmd",
      "parameters": {
        "arguments": [
          "-y", "-i", "source.mov",
          "-c:v", "libx264", "-preset", "veryfast",
          "-b:v", "1500k", "-maxrate", "1500k", "-bufsize", "3000k",
          "-c:a", "aac", "-b:a", "128k",
          "source_720p.mp4"
        ]
      }
    },
    {
      "tool": "storage:put",
      "parameters": {
        "location": "s3://{acme-aws-access-keys}@acme-bucket/out/{job_id}/",
        "files": ["source_720p.mp4"]
      }
    }
  ]
}

Notes:

  • The S3 URL form s3://{secret-name}@bucket/path references AWS credentials stored as an org secret. Two alternatives: set role_arn at the job level and let VTK assume an IAM role in your AWS account (Security → IAM), or have each storage:* task fetch credentials from an HTTPS endpoint you host (Security → Credential dispenser).

  • {job_id} in the output path is substituted by the runner; the resulting S3 prefix will be s3://acme-bucket/out/abcDEFGHIjk/.

  • source.mov written by storage:get is read by ffmpeg:cmd from the same working directory (/job/ inside the worker); source_720p.mp4 written by ffmpeg:cmd is read by storage:put the same way.

Submit it with the curl from Step 2 (-d @job.json), then poll as in Step 3. On success, the encoded MP4 lands under s3://acme-bucket/out/abcDEFGHIjk/.

Example B — Encrypted DASH with DRMtoday + Shaka

The shaka:package_dash tool can contact DRMtoday directly: give it asset_id, environment, merchant, user, password, and a list of drmkeys, and it will fetch CENC keys and produce an encrypted DASH manifest in one step.

job.json:

{
  "tags": ["quickstart", "dash", "drm"],
  "region": "aws:eu-west-1",
  "tasks": [
    {
      "tool": "storage:get",
      "parameters": {
        "location": "s3://{acme-aws-access-keys}@acme-bucket/in/",
        "files": ["video_1080p.mp4", "audio_eng.mp4"]
      }
    },
    {
      "tool": "shaka:package_dash",
      "parameters": {
        "inputs": [
          {
            "input_file": "video_1080p.mp4",
            "stream_selector": "video",
            "output": "video.mp4",
            "output_format": "mp4",
            "bandwidth": "2000000",
            "key_select": "video_key"
          },
          {
            "input_file": "audio_eng.mp4",
            "stream_selector": "audio",
            "language": "eng",
            "output": "audio.mp4",
            "output_format": "mp4",
            "bandwidth": "128000",
            "key_select": "audio_key"
          }
        ],
        "output_dir": "dash",
        "mpd_output": "dash.mpd",
        "fragment_duration": "2",
        "segment_duration": "2",
        "protection_scheme": "cenc",
        "protection_systems": ["Widevine", "PlayReady"],
        "enable_raw_key_encryption": "True",
        "asset_id": "quickstart_{job_id}",
        "environment": "STAGING",
        "merchant": "acme",
        "user": "acme::ops",
        "password": "{acme-drmtoday-password}",
        "drmkeys": [
          { "key_label": "video_key", "key_streamtype": "VIDEO" },
          { "key_label": "audio_key", "key_streamtype": "AUDIO" }
        ]
      }
    },
    {
      "tool": "storage:put",
      "parameters": {
        "location": "s3://{acme-aws-access-keys}@acme-bucket/dash/{job_id}/",
        "files": ["dash/*"]
      }
    }
  ]
}

Notes:

  • Each input’s key_select matches a key_label in drmkeys. Video and audio use separate keys here (video_key with key_streamtype: VIDEO, audio_key with key_streamtype: AUDIO) so each track can be unlocked independently downstream.

  • environment is STAGING or PRODUCTION and selects the DRMtoday environment.

  • The DRMtoday password must be an org secret. Storing it in plaintext is rejected by the VTK admin UI; see Security → Passwords.

  • If you only need to register an explicit content key with DRMtoday before packaging, prepend a drmtoday:ingest-cenc-key task. It emits the key material into the job environment under a configurable env_prefix for downstream tools to pick up. See Tools → DRMtoday for the parameter set.

What’s next

  • API → Jobs — full endpoint reference, including job bundles, abort/restart/resubmit, and metrics.

  • API → Status — completion notifications via email or HTTP POST callback.

  • Security → IAM — cross-account S3 access with role_arn.

  • Tools — the complete catalog of tools and their parameters.

Next topic: Concepts
Previous topic: Video Toolkit