SDKs and CLI

Start with the workflow you need: inspect a workspace, send a file, replace a live revision, or move a directory. The clients use the same API as the dashboard.

Pick the right tool

Use TypeScript or Python in trusted server code. Use the CLI for a local folder or CI job. For a browser upload, your server creates the session and the browser only receives the temporary upload URL.

Upload API

Before you start

1. Create a keyCreate a scoped workspace key in Developers. Use assets:read to inspect and assets:write to upload or replace.
2. Keep it server-sidePut STEADYLINK_API_KEY in your server environment. Do not send it to a browser, mobile app, repository, or build output.
3. Find the bucket IDOpen a bucket in the dashboard or call listBuckets(). The ID selects the destination for new files.
Published packages: the TypeScript client is available as @steadylink/sdk on npm, and the Python client is available as steadylink on PyPI.

TypeScript

Use the TypeScript client in Node 20+ or another server runtime with fetch. It requires exactly one credential: an API key or a short-lived user access token.

Install from npm
npm install @steadylink/sdk
Create a client and list buckets
import { SteadyLink } from "@steadylink/sdk";

const steadylink = new SteadyLink({
  apiKey: process.env.STEADYLINK_API_KEY!,
  // workspaceId is optional for an API key. Keys already belong to one workspace.
  timeoutMs: 30_000,
  maxRetries: 2,
});

const { items: buckets } = await steadylink.listBuckets();
const bucket = buckets.find((item) => item.slug === "marketing");
if (!bucket) throw new Error("Marketing bucket not found");
Upload one file
import { readFile } from "node:fs/promises";

const data = await readFile("./public/campaign-hero.webp");

const upload = await steadylink.uploadFile(bucket.id, {
  filename: "campaign-hero.webp",
  path: "campaign/",
  size: data.byteLength,
  contentType: "image/webp",
  data,
});

console.log(upload); // finalization has started; follow its status until it is ready
Replace a live file without changing its URL
const data = await readFile("./public/campaign-hero-v2.webp");

const result = await steadylink.replaceFile(
  bucket.id,
  "campaign/campaign-hero.webp",
  {
    filename: "campaign-hero-v2.webp",
    size: data.byteLength,
    contentType: "image/webp",
    data,
  },
);

console.log(result.version); // the new revision number
Handle an error with its request ID
import { SteadyLinkError, SteadyLinkNetworkError } from "@steadylink/sdk";

try {
  await steadylink.listBuckets();
} catch (error) {
  if (error instanceof SteadyLinkError) {
    console.error(error.status, error.code, error.requestId, error.detail);
  } else if (error instanceof SteadyLinkNetworkError) {
    console.error(error.message);
  }
}

Python

The Python client is synchronous and has no runtime dependencies. Its upload and replacement helpers create the session, send bytes to the presigned URL, then finalize the session for you.

Install from PyPI
pip install steadylink
Create a client and upload a file
import os
from pathlib import Path
from steadylink import SteadyLink

client = SteadyLink(
    api_key=os.environ["STEADYLINK_API_KEY"],
    max_retries=2,
)

buckets = client.list_buckets()["items"]
bucket = next(item for item in buckets if item.get("slug") == "marketing")

file_path = Path("campaign-hero.webp")
upload = client.upload_file(
    bucket["id"],
    filename=file_path.name,
    body=file_path.read_bytes(),
    content_type="image/webp",
    path="campaign/",
)

print(upload)
Replace a file
replacement = Path("campaign-hero-v2.webp")

result = client.replace_file(
    bucket["id"],
    "campaign/campaign-hero.webp",
    filename=replacement.name,
    body=replacement.read_bytes(),
    content_type="image/webp",
)

print(result["version"])
Handle API and network failures
from steadylink import SteadyLinkError, SteadyLinkNetworkError

try:
    client.list_buckets()
except SteadyLinkError as error:
    print(error.status, error.code, error.request_id, error.detail)
except SteadyLinkNetworkError as error:
    print(str(error))

CLI

The CLI ships in the SteadyLink source workspace today. It is useful for a one-off migration or a deployment job. It accepts STEADYLINK_API_KEY, or --api-key for a temporary command.

See the available commands
steadylink help
# inspect | upload | replace | migrate | focal
Upload a file or an entire directory
export STEADYLINK_API_KEY="slk_..."

steadylink upload ./public/campaign-hero.webp --bucket "$BUCKET_ID"
steadylink migrate ./public/media --bucket "$BUCKET_ID"
Inspect, replace, and set a focal point
steadylink inspect "$ASSET_ID"

steadylink replace ./campaign-hero-v2.webp \
  --bucket "$BUCKET_ID" \
  --key "campaign/campaign-hero.webp"

steadylink focal "$ASSET_ID" --x 0.42 --y 0.31
The CLI skips symbolic links, keeps relative folder paths, uploads a directory in groups of 100, and uses an idempotency key when it finalizes each file.

Retries and browser uploads

Both clients retry 429, 502, 503, and 504 for safe reads. A write retries only when it includes an idempotency key. The upload helpers do this for completion automatically.

For a browser upload, call Create upload batch from your server, return only the session's uploadUrl to the browser, then complete the session from your server. The presigned URL is specifically designed for that direct byte transfer.