Core concepts
TransformKit has a small, consistent shape. You hand the SDK files and the transforms you want; it uploads the bytes, runs the work as async jobs, 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 (maxSize,convert,rename) andawait; 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.
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);Uploads go direct to storage
The SDK uploads each file straight to object storage over a short-lived presigned URL — the bytes never proxy through the API — so large files upload quickly and cheaply. You pass the bytes (and a filename or contentType so the service knows the media type); the SDK handles the rest.
Every output is an async job
Transforms are durable jobs — they run in the background, survive deploys, and don't 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 → output stages with .options({ onProgress }) — see Run a pipeline for a batch example.
Failures are isolated
One result comes back per input. A bad file (a corrupt image, or quota running out mid-batch) is ok: false with an error message — the rest of the batch still completes. Only a malformed pipeline throws, before anything uploads.
Status vs. state
Alongside the canonical status, the API exposes a derived state that also reflects expiry — useful when you list jobs in a dashboard. A succeeded job whose output has aged out reads as success-expired; a failed one whose input is gone reads as error-expired.
Results are signed URLs
Outputs are never streamed back inline. Each result output carries a signed URL plus metadata (dimensions, content type, size). Hand the URL to a browser or CDN as-is.
{
"job_id": "…",
"output": "hero",
"media": {
"url": "https://…signed…",
"expires_in": 86400,
"content_type": "image/webp",
"format": "webp",
"width": 1600,
"height": 1067,
"file_size": 184320
}
}Everything expires in 24h
Inputs and outputs both live for about 24 hours, then storage deletes them — and that's intentional: the less we retain, the less there is to expose. We don't train on your media. Keep a result by fetching the signed URL, or skip our storage entirely with bring your own storage. One output costs one credit; Auth & billing has the retention policy, quotas, and the error contract.