@rendobar/sdk
v3.4.0
Published
TypeScript client for the Rendobar media processing API
Readme
@rendobar/sdk
TypeScript client for the Rendobar media processing API.
Install
npm install @rendobar/sdkQuick Start
import { createClient } from "@rendobar/sdk";
const client = createClient({ apiKey: "rb_..." });
// Submit a job
const job = await client.jobs.create({
type: "watermark.apply",
inputs: { source: "https://example.com/video.mp4" },
params: { text: "PREVIEW", position: "center", opacity: 0.3 },
});
// Wait for completion
import { outputUrl } from "@rendobar/sdk";
const result = await client.jobs.wait(job.id);
console.log(outputUrl(result)); // primary download/playable URLAuthentication
// API key (external users, CLI, scripts)
const client = createClient({ apiKey: "rb_..." });
// Session cookie (internal dashboard)
const client = createClient({
baseUrl: "https://api.rendobar.com",
credentials: "include",
});Configuration
const client = createClient({
apiKey: "rb_...",
baseUrl: "https://api.rendobar.com", // default
timeout: 30_000, // default: 30s
maxRetries: 2, // default: 2 (retries 429, 5xx)
orgId: "org_abc", // X-Org-Id header
debug: false, // log request metadata
fetch: customFetch, // custom fetch for testing
});Jobs
// Create
const job = await client.jobs.create({
type: "watermark.apply",
inputs: { source: "https://example.com/video.mp4" },
params: { text: "PREVIEW", position: "center" },
idempotencyKey: "unique-key-123",
});
// Get
const job = await client.jobs.get("job_abc");
// List (paginated — works with React Query)
const page = await client.jobs.list({ status: "complete", limit: 20 });
// page.data: Job[], page.meta: { total, page, limit }
// Auto-paginate all pages
for await (const job of client.jobs.listAll({ status: "complete" })) {
console.log(job.id);
}
// Wait for completion (polls until terminal status)
const result = await client.jobs.wait("job_abc", {
timeout: 300_000,
interval: 2_000,
onProgress: (job) => console.log(job.status),
signal: abortController.signal,
});
// Cancel
await client.jobs.cancel("job_abc");
// Download output (returns raw Response)
const response = await client.jobs.download("job_abc");
const blob = await response.blob();
// Execution logs
const logs = await client.jobs.logs("job_abc");
// Available job types
const types = await client.jobs.types();Job output
Every job type returns the same output shape. A completed job carries an
output with four fields:
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
meta?: { format?: string; width?: number; height?: number; durationMs?: number };
};output exists only on a completed job (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). Returns
// undefined for data-only jobs and pure file sets (no single headline).
const url = outputUrl(job);
// Or read it yourself after a status check
if (job.status === "complete") {
console.log(job.output.file?.url);
// Iterate every produced file (frame extraction, HLS segments, sprite sets)
for (const f of job.output.files) {
console.log(f.type, f.path, f.size, f.url);
}
}
// A failed job carries a structured error instead
if (job.status === "failed") {
console.log(job.error.code, job.error.message, job.error.detail);
}For data jobs (extract.metadata, caption.extract, detections), the answer is
in output.data. It is unknown at the contract level because its shape is
job-type-specific. Use jobData<T>(job) to read it with your expected type, or
read job.output.data and narrow it yourself:
import { jobData } from "@rendobar/sdk";
type Metadata = { format: string; durationMs: number; width: number; height: number };
const job = await client.jobs.wait("job_abc");
const meta = jobData<Metadata>(job); // Metadata | null
if (meta) {
console.log(meta.format, meta.durationMs);
}T is your claim about the shape for the job type you submitted. If the input is
untrusted, validate output.data yourself (e.g. with Zod) instead of asserting a
type.
Raw FFmpeg
Run arbitrary FFmpeg commands in a sandboxed container. Commands are parsed, sanitized (protocol whitelist/blacklist flags blocked), and executed with a clean process environment.
Put input URLs directly in -i positions — they're extracted automatically and staged via presigned R2 URLs before execution:
import { createClient } 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 the top-level inputs map 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" },
});Need a typed params object? Import FfmpegParams — optional fields (outputFormat, timeout) have server-side defaults.
See the FFmpeg guide for the full security model and supported flags.
Billing
const state = await client.billing.state();
const usage = await client.billing.usage({ start: "2026-01-01", end: "2026-03-29" });
const txns = await client.billing.transactions({ page: 1, limit: 50 });Uploads
// Upload a file, get a URL to use as job input
const { downloadUrl } = await client.uploads.upload(file, { filename: "input.mp4" });
const job = await client.jobs.create({
type: "watermark.apply",
inputs: { source: downloadUrl },
params: { text: "PREVIEW" },
});Batches
const batch = await client.batches.create({
jobs: [
{ type: "watermark.apply", inputs: { source: url1 }, params: { text: "1" } },
{ type: "watermark.apply", inputs: { source: url2 }, params: { text: "2" } },
],
});Webhooks
// Create endpoint
const endpoint = await client.webhooks.create({
name: "My Webhook",
url: "https://example.com/webhook",
subscribedEvents: ["job.completed", "job.failed"],
});
// Verify signature in your webhook handler
import { verifyWebhookSignature } from "@rendobar/sdk/webhooks";
const isValid = await verifyWebhookSignature(
requestBody,
request.headers.get("X-Rendobar-Signature"),
signingSecret,
);Realtime Events (WebSocket)
// Org-wide event stream
const connection = client.realtime.connect({
onEvent: (event) => console.log(event.type, event),
onLive: () => console.log("Replay complete, now live"),
onResync: () => console.log("Buffer overflow — refresh data"),
});
// Per-job subscription
const sub = client.realtime.subscribeJob("job_abc", {
onProgress: (e) => console.log(`${e.progress}%`),
onStep: (e) => console.log(`Step: ${e.stepName}`),
onComplete: (e) => console.log("Done!", e.status),
});
// Cleanup
connection.disconnect();
sub.unsubscribe();Note: Realtime requires session cookie auth. API key users should use
jobs.wait()for polling.
Error Handling
import { createClient, ApiError, isApiError } from "@rendobar/sdk";
try {
await client.jobs.create({ type: "watermark.apply", ... });
} catch (err) {
if (isApiError(err)) {
console.log(err.code); // "INSUFFICIENT_CREDITS"
console.log(err.statusCode); // 402
console.log(err.message); // "Not enough credits"
console.log(err.retryAfter); // seconds (for 429s)
}
}Error codes: UNAUTHORIZED, FORBIDDEN, VALIDATION_ERROR, INSUFFICIENT_CREDITS, RATE_LIMITED, NOT_FOUND, CONFLICT, INTERNAL_ERROR.
Retries
The SDK automatically retries:
- 429 (rate limited) — respects
Retry-Afterheader - 500, 502, 503, 504 — server errors
Retries use exponential backoff (500ms, 1s). Max 2 retries by default. Non-retryable errors (400, 401, 403, 404) throw immediately.
AbortSignal
Every method accepts an optional signal for cancellation:
const controller = new AbortController();
const job = await client.jobs.get("job_abc", { signal: controller.signal });
// Cancel the request
controller.abort();Types
All response types are exported:
import type {
Job, JobStep, Output, OutputFile, FileType, JobError, Cost, JobType, JobCreatedResponse,
BillingState, Transaction, UsageSummary,
WebhookEndpoint, WebhookDelivery,
Organization, ApiKey, PaginationMeta,
BillingInvoice, PaymentMethod, LogEntryData,
} from "@rendobar/sdk";