Core concepts

You hand the SDK files and the transforms you want. It uploads the bytes, runs each output as an async job, and returns signed URLs in input order.

Two entry points

Everything is built on two methods.

  • runQueue(files, media): a fluent chain for linear work. Add steps (filter, maxSize, convert, rename, …) and await; they apply to every file.
  • runPipeline(files, pipeline): run a graph that can fan one input out to many outputs.

Both take the same file inputs and return the same result shape. A queue compiles to a pipeline under the hood, so there is exactly one engine. Same transform, both forms:

const results = await tk
  .runQueue([{ bytes, filename: 'photo.jpg' }], 'image')
  .maxSize(1600)
  .convert({ format: 'webp' });

const [photo] = results;
if (photo.ok) console.log(photo.outputs[0]!.media.url);

Rename outputs

.rename()sets each file's output base name before upload. It runs client-side and does not add a pipeline node. Useful when a batch would otherwise collide on the same stem.

const results = await tk
  .runQueue(files, 'image')
  .maxSize(1600)
  .convert({ format: 'webp' })
  .rename((file, index) => `photo-${index}`);

Bring your own storage

By default the SDK uploads over a short-lived presigned URL so bytes never proxy through the API. With bring-your-own storage you keep the source and the results in your own bucket instead.

  • .upload(): store each source yourself and return a presigned GET URL for the worker.
  • .deliver(): mint a presigned PUT target per output.
byos.ts
const [photo] = await tk
  .runQueue(files, 'image')
  .upload(async (file) => uploadSourceAndReturnGetUrl(file))
  .maxSize(1600)
  .convert({ format: 'webp', quality: 82 })
  .deliver(async (output) => mintOutputPutUrl(output));

The raw input_url and output_targets request shape is in the HTTP & cURL guide.

Async jobs

Transforms are durable jobs. They run in the background, survive deploys, and do not block a request. You never write the polling loop: runQueue and runPipeline submit the work and wait for each output to finish before resolving. Under the hood, a job moves through a canonical status:

type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed';

Stream upload, submit, and output stages with .options({ onProgress }). See Run a pipeline for a batch example.

Isolated failures

One result comes back per input. A bad file is ok: false with an error message; the rest of the batch still completes. Only a malformed pipeline throws, before anything uploads.

Status and state

Alongside the canonical status, the API exposes a derived state that also reflects expiry. A succeeded job whose output has aged out reads as success-expired; a failed one whose input is gone reads as error-expired.

Signed URL results

Outputs are never streamed back inline. Each result carries a signed URL plus metadata (dimensions, content type, size).

{
  "job_id": "…",
  "output": "hero",
  "media": {
    "url": "https://…signed…",
    "expires_in": 86400,
    "content_type": "image/webp",
    "format": "webp",
    "width": 1600,
    "height": 1067,
    "file_size": 184320
  }
}

24-hour retention

Inputs and outputs both live for about 24 hours, then storage deletes them. Keep a result by fetching the signed URL, or skip managed storage with bring-your-own storage above. We do not train on your media.