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_KEY
Hitting a local API over portless TLS? Point TK_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.jpg

3. 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.
  • 401missing_api_key or invalid_api_key.
  • 402plan_required or quota_exceeded (out of credits).
  • 409job_not_ready (result requested before the job finished).
  • 410result_expired (past the 24h window).
  • 422job_failed.
  • 429rate_limited (short-burst limit).

Next steps