HTTP & curl
The SDK is a thin wrapper over a small REST API. These are the raw endpoints — use them from any language, or from the terminal.
Presigned uploads, async jobs, and signed results → Core concepts. Credits and error codes → Auth & billing.
Base URL & auth
The API base URL is . Authenticate every request with a bearer token — your API key.
export API_KEY=tk_your_key_here
export TK_API=
# Every call carries the bearer header:
# Authorization: Bearer $API_KEYTK_API at that origin and pass --cacert ~/.portless/ca.pem to curl if needed.The pipeline flow
Five steps: mint an upload URL → PUT the bytes → submit the pipeline → poll → fetch the result.
1. Mint an upload URL
curl -s -X POST "$TK_API/v1/uploads" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"content_type":"image/jpeg"}'{
"upload_url": "https://…presigned-put…",
"input_key": "inputs/<user>/<uuid>",
"expires_in": 3600,
"content_type": "image/jpeg"
}2. Upload the bytes
PUT the file straight to the presigned URL. No API auth here — the URL is the credential.
curl -s -X PUT "<upload_url>" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg3. Submit a pipeline
Post a { nodes, edges } graph. Returns 202 with one job per pipeline.output. Graph shape and metering → Pipelines.
curl -s -X POST "$TK_API/v1/pipelines" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_key": "inputs/<user>/<uuid>",
"content_type": "image/jpeg",
"filename": "photo.jpg",
"pipeline": {
"nodes": [
{ "id": "in", "type": "pipeline.input" },
{ "id": "resize", "type": "image.resize",
"config": { "mode": { "value": "pixels" },
"width": { "value": 1600 }, "height": { "value": 1600 },
"fit": { "value": "inside" } } },
{ "id": "convert", "type": "image.convert",
"config": { "format": { "value": "webp" }, "quality": { "value": 82 } } },
{ "id": "out", "type": "pipeline.output" }
],
"edges": [
{ "source": "in", "target": "resize" },
{ "source": "resize", "target": "convert" },
{ "source": "convert", "target": "out" }
]
}
}'{ "jobs": [ { "job_id": "<uuid>", "output": "out" } ] }Body fields: input_key (from step 1) or a BYOS input_url; pipeline (required { nodes, edges }); optional content_type, filename, and output_targets (BYOS delivery — see below). Add more pipeline.output nodes to fan out — one job (one credit) each.
Node types, one media family per graph: pipeline.input, pipeline.output, image.resize, image.convert, video.resize, video.convert, audio.convert, document.convert. What each one accepts is on its reference page.
4. Poll the job
curl -s "$TK_API/v1/jobs/<job_id>" \
-H "Authorization: Bearer $API_KEY"{
"job_id": "<uuid>",
"kind": "image.pipeline",
"status": "succeeded",
"state": "success",
"created_at": "2026-07-26T00:00:00.000Z",
"completed_at": "2026-07-26T00:00:02.000Z",
"expires_at": "2026-07-27T00:00:00.000Z",
"retryable": false
}Poll until status is succeeded or failed. This endpoint is unmetered. kind names the media the graph operates on — image.pipeline, video.pipeline, audio.pipeline or document.pipeline.
5. Fetch the result
curl -s "$TK_API/v1/jobs/<job_id>/result" \
-H "Authorization: Bearer $API_KEY"{
"media": {
"url": "https://…signed-get…",
"expires_in": 86400,
"content_type": "image/webp",
"format": "webp",
"width": 1600,
"height": 1067,
"file_size": 184320
}
}The envelope is always media — same shape for every medium. Dimensions are present when the transport reports them (images and video); audio and documents omit them.
Bring your own storage
Skip TransformKit's bucket entirely. Pass an HTTPS input_url the worker can GET, and/or an output_targets array with a presigned PUT per output. The SDK's .upload() / .deliver() hooks build this body for you — see Node.js → Bring your own storage.
curl -s -X POST "$TK_API/v1/pipelines" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_url": "https://your-bucket.example/inputs/photo.jpg?<signed-get>",
"content_type": "image/jpeg",
"filename": "photo.jpg",
"pipeline": { "nodes": [/* … */], "edges": [/* … */] },
"output_targets": [
{
"output_id": "out",
"key": "outputs/<user>/<uuid>.webp",
"put_url": "https://your-bucket.example/outputs/photo.webp?<signed-put>",
"content_type": "image/webp",
"public_url": "https://cdn.example/photo.webp"
}
]
}'input_url and each put_url / public_url must be HTTPS. The worker fetches and delivers under SSRF constraints. Presigned URLs are short-lived — a dashboard retry reuses the same URLs, so sign them long enough for a retry window or treat BYOS as fire-once.Identity & health
# Who am I? (unmetered)
curl -s "$TK_API/v1/me" -H "Authorization: Bearer $API_KEY"
# Is the API up? (no auth)
curl -s "$TK_API/health"Status codes
202— job accepted (submit).400— bad request, e.g.missing_input,invalid_input_key,invalid_pipeline.401—missing_api_keyorinvalid_api_key.402—plan_requiredorquota_exceeded(out of credits).409—job_not_ready(result requested before the job finished).410—result_expired(past the 24h window).422—job_failed.429—rate_limited(short-burst limit).
Next steps
- Pipelines — the graph shape and fan-out.
- Node.js — the same flow behind the SDK.
- Auth & billing — credits, quotas, and errors.