HTTP & cURL

Authentication, errors, and the raw REST flow behind the SDK. Use the HTTP endpoints from any language or from the terminal.

Base URL and authentication

The API base URL is https://api.transform-kit.com. Authenticate every request with a bearer token: an API key created under Credentials, a CLI login key, or an OAuth-minted token.

export API_KEY=tk_your_key_here
export TK_API=https://api.transform-kit.com

# Every call carries the bearer header:
#   Authorization: Bearer $API_KEY

Keys are secrets

Anyone with a key can access the account and run transforms. Keep keys in server-side environment variables. Revoke a leaked key under Credentials. For a local API over portless TLS, point TK_API at that origin and pass --cacert ~/.portless/ca.pem to curl if needed.

Pipeline request flow

Five steps: mint an upload URL → PUT the bytes → submit the pipeline → poll → fetch the result.

1. Create 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 file

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. Build and validate the graph under 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 each.

Node types, one media family per graph: pipeline.input,pipeline.output,image.resize,image.filter,image.convert,video.resize,video.filter,video.convert,audio.filter,audio.convert,document.filter,document.convert. Fields and defaults are in the pipeline node reference.

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 returns the current job state. kind names the media the graph operates on: image.pipeline, video.pipeline, audio.pipeline or document.pipeline.

5. Retrieve 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.

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 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?
curl -s "$TK_API/v1/me" -H "Authorization: Bearer $API_KEY"

# Is the API up? (no auth)
curl -s "$TK_API/health"

Result retention

Inputs and outputs are deleted about 24 hours after job creation, and signed result URLs expire with them. Copy anything you need to keep to your own storage.

Status codes

  • 202, job accepted (submit).
  • 400, bad request, e.g. missing_input, invalid_input_key, invalid_pipeline.
  • 401, missing_api_key or invalid_api_key.
  • 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).