> ## Documentation Index
> Fetch the complete documentation index at: https://rendobar.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Keep the API key in an env var (RENDOBAR_API_KEY), never in chat or committed code.
> Job types change, so fetch the live catalog from GET https://api.rendobar.com/jobs/types rather than relying on memory.
> The full integration prompt for coding agents is at https://rendobar.com/prompts/integrate.md.

# Use the TypeScript SDK

> Submit a job from Node, Deno, Bun, or Workers with @rendobar/sdk, wait for it to finish, and read the signed output URL.

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
__html: JSON.stringify({
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "@id": "https://rendobar.com/docs/sdk#article",
  "headline": "Use the TypeScript SDK",
  "description": "Submit a job from Node, Deno, Bun, or Workers with @rendobar/sdk, wait for it to finish, and read the signed output URL.",
  "datePublished": "2026-06-22",
  "author": { "@type": "Organization", "@id": "https://rendobar.com/#organization" },
  "publisher": { "@type": "Organization", "@id": "https://rendobar.com/#organization" }
})
}}
/>

`@rendobar/sdk` is the TypeScript client for the Rendobar API: typed methods, automatic retries, and responses already unwrapped from the `{ data }` envelope. ESM and CommonJS builds run on Node 18+, Deno, Bun, Workers, and the browser.

```bash theme={null}
npm install @rendobar/sdk
```

```ts title="render.ts" theme={null}
import { createClient, outputUrl } from "@rendobar/sdk";

const client = createClient({ apiKey: "rb_YOUR_KEY" });

const created = await client.jobs.create({
  type: "ffmpeg",
  params: {
    command: "ffmpeg -i https://example.com/input.mp4 -c:v libx265 -crf 28 output.mp4",
  },
});

const job = await client.jobs.wait(created.id);

if (job.status === "complete") {
  console.log(outputUrl(job)); // signed download URL
}
```

`jobs.create` returns as soon as the job is queued. A job settles on one of three outcomes, and you learn which by polling or by being pushed to.

```mermaid theme={null}
flowchart LR
    create["jobs.create()"] --> waiting["status: waiting"]
    waiting --> poll["jobs.wait()"]
    waiting --> push["webhook or realtime"]
    poll --> ends{"outcome"}
    push --> ends
    ends -->|complete| ok["outputUrl(job)"]
    ends -->|failed| bad["job.error"]
    ends -->|cancelled| stop["nothing to read"]
```

## Jobs

Every method takes an optional `{ signal }` to abort the request or the `wait` loop.

| Method | Returns |
| - | - |
| `jobs.create({ type, inputs, params })` | `{ id, status: "waiting" }` |
| `jobs.wait(id, options)` | the job once it is `complete`, `failed`, or `cancelled` |
| `jobs.get(id)` | one job |
| `jobs.list({ status, type, limit, offset })` | `{ data, meta }` |
| `jobs.listAll(params)` | async iterator across every page |
| `jobs.cancel(id)` | the cancelled job |
| `jobs.download(id)` | the output as a raw `Response` |
| `jobs.logs(id)` | execution log lines |
| `jobs.types()` | job types available to your org |
| `jobs.stats({ window })` | counts, success rate, and spend over `24h`, `7d`, `30d`, `90d`, `365d`, or `all` (since your first job) |

`type`, `inputs`, and `params` are per [job type](/docs/jobs/ffmpeg). Pass `idempotencyKey` so a retried request returns the original job instead of a second one.

### Wait

```ts theme={null}
const job = await client.jobs.wait(created.id, {
  timeout: 600_000,                          // default: the job's own budget
  onProgress: (j) => console.log(j.status),
});
```

`wait` polls until the job is `complete`, `failed`, or `cancelled`, then hands it back. Polling starts at 2s and backs off to 10s. Past the timeout it throws `WaitTimeoutError` carrying the last status seen.

With no `timeout` given, the wait follows the job's own budget (`job.timeoutMs`, your plan's per-job limit) plus grace for upload and callback, so a long job is never reported as timed out while it is still running. Until the server reports that budget the bound is 5 minutes, which is also the floor. Pass a `timeout` to bound the wait yourself. Pass `throwOnFailure: true` to throw `JobFailedError` on a failed or cancelled job instead of returning it.

### Read the output

A `Job` is a union discriminated on `status`. `output` exists only on `complete` and `error` only on `failed`, so narrow before reading either.

```ts theme={null}
import { outputUrl, jobData } from "@rendobar/sdk";

if (job.status === "complete") {
  outputUrl(job);       // headline URL: the single file, or the HLS/DASH manifest
  job.output.files;     // every file, for frame sequences, segments, or a ladder
  jobData<Probe>(job);  // output.data as Probe | null
} else if (job.status === "failed") {
  console.error(job.error.code, job.error.message, job.error.retryable);
}
```

`outputUrl` is `undefined` for data-only jobs and for file sets with no headline file. `jobData<T>` trusts the `T` you give it, so validate the shape yourself when the input is untrusted. The [output shape](/docs/concepts/job#the-output) is identical for every job type.

## Uploads

`uploads.create` runs the whole asset flow (init, presigned PUT or parallel multipart, complete) and returns the ready `Asset`. Assets expire after 24 hours unless you pass `persist: true`.

```ts theme={null}
const asset = await client.uploads.create(bytes, { filename: "clip.mp4", persist: true });

await client.jobs.create({
  type: "ffmpeg",
  inputs: { "clip.mp4": asset.url },
  params: { command: "ffmpeg -i clip.mp4 -c:v libx265 output.mp4" },
});
```

Each `inputs` key is the filename staged in the working directory, and the command refers to it by that bare name. `asset.url` resolves to a presigned read at dispatch.

## Storage

Use `storage://` URIs to read from and write to a [connected bucket](/docs/storage):

```ts theme={null}
await client.jobs.create({
  type: "ffmpeg",
  inputs: { source: "storage://demo-bucket/raw/clip.mp4" },
  params: { command: "ffmpeg -i source out.mp4" },
  destinations: ["storage://demo-bucket/exports"],
});
```

`jobs.get(id)` returns `deliveries`, one entry per destination, and `jobs.retryDeliveries(id)` retries the failed ones. `client.storage` manages the connections. See [SDK methods](/docs/storage#sdk-methods).

## Webhooks

`webhooks.create({ name, url, subscribedEvents })` registers an endpoint. `rotateSecret`, `test`, `listDeliveries`, and `retryDelivery` operate it.

Verify every delivery with `verifyWebhook`, a zero-dependency entry point that runs anywhere Web Crypto does.

```ts theme={null}
import { verifyWebhook } from "@rendobar/sdk/webhooks";

const raw = await req.text();
const valid = await verifyWebhook(raw, req.headers, process.env.WEBHOOK_SECRET);
if (!valid) return new Response("invalid signature", { status: 401 });
```

Pass the raw body, never parsed JSON. It checks the HMAC over `${timestamp}.${body}`, rejects deliveries more than 5 minutes old, and accepts the previous secret during the 24-hour rotation window.

## Realtime

`realtime.subscribeJob` streams one job over a WebSocket. `realtime.connect` streams every event in the org.

```ts theme={null}
const sub = client.realtime.subscribeJob(created.id, {
  onProgress: (e) => console.log(e.progress),
  onComplete: (e) => console.log(e.status),
});

sub.unsubscribe();
```

<Note>
  A WebSocket can't carry an `Authorization` header, so the SDK appends your API key to the URL as a query param. Subscribe from your server, not from a browser. First-party browser apps authenticate with the session cookie instead (`credentials: "include"`).
</Note>

## Billing

`billing.state()` returns balance and plan. `billing.usage({ start, end })` totals spend for a window, `billing.usageByClient({ days })` groups that spend by what submitted each job (`dashboard`, `sdk`, `cli`, `mcp`, `n8n`, `api`), and `billing.transactions({ page, limit })` reads the ledger.

`apiKeys`, `assets`, and `orgs` follow the same shape. Organization membership lives on `orgs.members` (`invite`, `changeRole`, `remove`) and `orgs.invitations` (`revoke`). The older `team` resource still works and is marked deprecated.

## Client options

```ts theme={null}
const server  = createClient({ apiKey: "rb_YOUR_KEY" });   // server side
const browser = createClient({ credentials: "include" });  // first-party browser, cookie auth
```

| Option | Default | Purpose |
| - | - | - |
| `apiKey` | — | `rb_` key from [Settings → API keys](https://app.rendobar.com). Required server-side. |
| `baseUrl` | `https://api.rendobar.com` | API origin. Override for staging. |
| `timeout` | `30000` | Per-request timeout in ms. |
| `maxRetries` | `2` | Retries for 429 and 5xx. |
| `orgId` | — | Default org, sent as `X-Org-Id`. |
| `client` | `"sdk"` | Attribution label, sent as `X-Rendobar-Client`. |
| `debug` | `false` | Log request metadata. |

## Errors

A failed request throws an `ApiError` with `code`, `statusCode`, `message`, `details`, and `retryAfter` on `RATE_LIMITED`. Narrow it with `isApiError`.

```ts theme={null}
import { isApiError } from "@rendobar/sdk";

try {
  await client.jobs.create({ type: "ffmpeg", params: { command: "ffmpeg -i in.mp4 out.webm" } });
} catch (err) {
  if (isApiError(err)) console.log(err.code, err.statusCode, err.message);
}
```

429 (respecting `Retry-After`) and 5xx retry with backoff before they throw. Every other 4xx throws on the first attempt. The codes are in the [error catalogue](/docs/support/errors).

Every response and param type is exported from `@rendobar/sdk` as a named `type`.

## See also

* [Job types](/docs/jobs/ffmpeg): the type, inputs, and params for every job
* [How a job works](/docs/concepts/job): statuses and the output shape
* [Webhooks](/docs/guides/webhooks): event payloads and delivery retries
* [Error codes](/docs/support/errors): every code a request or job returns


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.