@rendobar/sdk
v5.15.0
Published
Rendobar SDK: TypeScript client for the media processing and AI generation API.
Readme
@rendobar/sdk is the official TypeScript client for Rendobar, a serverless media processing API. You submit a job, the SDK polls it to completion, and you get back a hosted URL. It ships ESM and CJS builds with full types, retries 429 and 5xx automatically, auto-paginates, and accepts an AbortSignal on every method.
Let your AI assistant do it
Paste this one line into Claude, ChatGPT, or Cursor and the assistant reads the full integration playbook, detects your stack, and wires Rendobar into your codebase. Your API key stays in your env file, never in the chat.
Add Rendobar to my app: read and follow https://rendobar.com/prompts/integrate.mdRequirements
Node 18 or later. Works in any runtime with fetch, including Cloudflare Workers, Deno, and Bun.
Install
npm install @rendobar/sdkQuickstart
Run an FFmpeg command against a remote file and wait for the result. Put input
URLs directly in -i positions; the SDK stages them before execution.
import { createClient, outputUrl } from "@rendobar/sdk";
const client = createClient({ apiKey: process.env.RENDOBAR_API_KEY });
const job = await client.jobs.create({
type: "ffmpeg",
params: {
command:
"ffmpeg -i https://cdn.example.com/source.mp4 " +
"-vf scale=1280:720 -c:v libx264 -crf 23 output.mp4",
},
});
const done = await client.jobs.wait(job.id);
console.log(outputUrl(done));Prefer named inputs? Pass them in inputs and reference the keys in the command:
await client.jobs.create({
type: "ffmpeg",
inputs: { source: "https://cdn.example.com/source.mp4" },
params: { command: "ffmpeg -i source -vf scale=1280:720 output.mp4" },
});ffmpeg is one of several job types. Composition, compression to a size budget,
subtitle burn-in, animated captions, and media inspection each have their own.
Browse them at rendobar.com/docs/jobs, or call
client.jobs.types() to read the live registry.
Reading a result
Every job type returns the same output shape, so you write this handling once.
type Output = {
data: unknown; // structured result (probe, detections, transcript), or null
file: OutputFile | null; // the headline file or stream manifest, or null
files: OutputFile[]; // every produced file (empty for data-only jobs)
expiresAt: number | null; // Unix ms URL expiry, set when files is non-empty
};
type OutputFile = {
url: string; // ready-to-fetch, time-limited URL
path: string; // path within the job output
type: "video" | "image" | "audio" | "captions" | "playlist" | "data" | "other";
size: number; // bytes
sha256: string | null; // SHA-256 of the file as lowercase hex, null when not computed
meta?: { format?: string; width?: number; height?: number; durationMs?: number };
};output exists only when status === "complete", so narrow on status before
reading it.
import { outputUrl, jobData } from "@rendobar/sdk";
const job = await client.jobs.wait("job_abc");
// Headline URL: the single file, or a stream manifest (.m3u8/.mpd). Undefined
// for data-only jobs and for pure file sets with no single headline.
const url = outputUrl(job);
if (job.status === "complete") {
for (const f of job.output.files) {
console.log(f.type, f.path, f.size, f.url);
}
}
if (job.status === "failed") {
console.log(job.error.code, job.error.message, job.error.detail);
}Inspection jobs put their answer in output.data, which is unknown at the
contract level because its shape depends on the job type. jobData<T>(job)
reads it as your expected type:
type Probe = { format: string; durationMs: number; width: number; height: number };
const probe = jobData<Probe>(await client.jobs.wait("job_abc")); // Probe | nullT is your claim about the shape, not a guarantee. If the input is untrusted,
validate output.data with Zod rather than asserting.
Errors
import { isApiError } from "@rendobar/sdk";
try {
await client.jobs.create({ type: "ffmpeg", params: { command } });
} catch (err) {
if (isApiError(err)) {
console.log(err.code); // "INSUFFICIENT_CREDITS"
console.log(err.statusCode); // 402
console.log(err.retryAfter); // seconds, on 429s
}
}Codes: UNAUTHORIZED, FORBIDDEN, VALIDATION_ERROR, INSUFFICIENT_CREDITS,
RATE_LIMITED, NOT_FOUND, CONFLICT, INTERNAL_ERROR. Full reference at
rendobar.com/docs/support/errors.
429 and 5xx retry automatically with exponential backoff (500ms, then 1s), twice
by default, respecting Retry-After. 400, 401, 403 and 404 throw immediately.
The rest of the API
await client.jobs.create({ type, inputs, params, idempotencyKey });
await client.jobs.get("job_abc");
await client.jobs.cancel("job_abc");
await client.jobs.logs("job_abc");
await client.jobs.getTimings("job_abc");
await client.jobs.types(); // live job registry
const page = await client.jobs.list({ status: "complete", limit: 20 });
// page.data: Job[], page.meta: { total, page, limit }
for await (const job of client.jobs.listAll({ status: "complete" })) { /* ... */ }
await client.jobs.wait("job_abc", {
timeout: 300_000,
interval: 2_000,
onProgress: (job) => console.log(job.status),
signal: controller.signal,
});
const response = await client.jobs.download("job_abc"); // raw ResponseComposeParams types the whole timeline, so an unknown field or a misspelt
effect is a compile error rather than a 400.
import { createClient, type ComposeParams } from "@rendobar/sdk";
const params: ComposeParams = {
schemaVersion: 1,
output: { format: "mp4", size: "1080p" },
timeline: {
tracks: [
{
clips: [
{
asset: { type: "video", src: "https://example.com/clip.mp4" },
effects: [{ type: "color", saturation: 1.2 }],
transform: {
scale: [
{ time: "0%", value: 1, easing: "easeInOutSine" },
{ time: "100%", value: 1.12 },
],
},
},
],
},
],
},
};
const job = await client.jobs.create({ type: "compose", params });validate parses a timeline exactly as a render would, for free, and returns
the params as the engine reads them, any warnings, and the price: what a minute
of output costs and the fewest seconds a render bills. A video above your plan
comes back invalid, with upgradeTo naming the plan that renders it. effects returns the
reference for every effect, which is what an agent reads before it writes one.
const check = await client.compose.validate(params);
if (check.valid) console.log(check.price); // { perMinuteUsd: 0.04, minSeconds: 15 }
else console.error(check.problems);
const reference = await client.compose.effects();const { downloadUrl } = await client.uploads.upload(file, { filename: "input.mp4" });
await client.batches.create({
jobs: [
{ type: "ffmpeg", inputs: { source: url1 }, params: { command } },
{ type: "ffmpeg", inputs: { source: url2 }, params: { command } },
],
});
await client.billing.state();
await client.billing.usage({ start: "2026-01-01", end: "2026-03-29" });
await client.billing.transactions({ page: 1, limit: 50 });
// Start or change a paid plan: "pro" or "studio".
const plan = await client.billing.checkoutPlan("studio");
if (plan.kind === "checkout") console.log(plan.checkoutUrl);await client.webhooks.create({
name: "My Webhook",
url: "https://example.com/webhook",
subscribedEvents: ["job.completed", "job.failed"],
});Verify a delivery with the raw body, not parsed JSON. verifyWebhook reads
the signature and timestamp headers, rebuilds the signed string, checks the
HMAC, rejects stale deliveries, and handles secret rotation.
import { verifyWebhook } from "@rendobar/sdk/webhooks";
if (!(await verifyWebhook(rawBody, request.headers, signingSecret))) {
throw new Error("invalid signature");
}See the webhooks guide.
Realtime needs session cookie auth. On an API key, use jobs.wait() instead.
const connection = client.realtime.connect({
onEvent: (e) => console.log(e.type, e),
onLive: () => console.log("replay complete, now live"),
onResync: () => console.log("buffer overflow, refresh data"),
});
const sub = client.realtime.subscribeJob("job_abc", {
onProgress: (e) => console.log(`${e.progress}%`),
onComplete: (e) => console.log("done", e.status),
});
connection.disconnect();
sub.unsubscribe();const client = createClient({
apiKey: "rb_...",
baseUrl: "https://api.rendobar.com", // default
timeout: 30_000, // default 30s
maxRetries: 2, // default 2
orgId: "org_abc", // X-Org-Id header
debug: false, // log request metadata
fetch: customFetch, // inject a fetch, for tests
});
// Browser session cookie instead of a key
createClient({ baseUrl: "https://api.rendobar.com", credentials: "include" });Response types are all exported: Job, JobStep, Output, OutputFile,
FileType, JobError, Cost, JobType, BillingState, Transaction,
UsageSummary, WebhookEndpoint, WebhookDelivery, Organization, ApiKey,
and PaginationMeta.
