@wral/sdk-live-vod-handoff
v0.0.5
Published
A Software Development Kit for working with the Live VOD Handoff API
Keywords
Readme
sdk-live-vod-handoff
The sdk-live-vod-handoff is a JavaScript SDK (Software Development Kit) for
the Live VOD Handoff API (service-live-vod-handoff), which owns one stable
reference URL per live event and reroutes what that URL resolves to across the
event's life — live source, then a slate, then the finished VOD.
Installation
npm install @wral/sdk-live-vod-handoffUsage
import { createClient } from '@wral/sdk-live-vod-handoff/v1';
// Or import everything:
// import * as v1 from '@wral/sdk-live-vod-handoff/v1';Alternatively, import the whole SDK and use multiple versions:
import handoff from '@wral/sdk-live-vod-handoff';
// Then use it with handoff.v1.createClient(), etc.Create a client with your configuration:
const config = {
baseUrl: 'https://api.wral.com/dev/live-vod-handoff/v1',
apiKey: 'YOUR_JWT',
};
const client = createClient(config);baseUrl may be given with or without the /v1 suffix.
| | |
| --- | --- |
| dev | https://api.wral.com/dev/live-vod-handoff/v1 |
| prod | https://api.wral.com/live-vod-handoff/v1 — not deployed yet |
Then use the client's methods:
// Start broadcasting now — this claims a shared encoder port immediately
const handoff = await client.createHandoff({
title: 'Wake County commissioners',
slug: 'wake-county-commissioners',
source: 'magnum://172.16.90.12:4013/SDVN/src/2',
});
// Or schedule it; scheduledEnd is required whenever scheduledStart is set
await client.createHandoff({
title: 'Wake County commissioners',
slug: 'wake-county-commissioners',
source: 'magnum://172.16.90.12:4013/SDVN/src/2',
scheduledStart: '2026-08-07T14:00:00Z',
scheduledEnd: '2026-08-07T15:00:00Z',
});
// Read one record
const record = await client.getHandoff({ id: handoff.id });
// Page through a state
let page = await client.listHandoffs({ state: 'Live' });
while (page.next) {
page = await page.fetchNext();
}
// Stop the stream and tear the infrastructure down
await client.endHandoff({ id: handoff.id });
// Send a rendered clip off to be transcoded
await client.submitSource({ id: handoff.id, mp4Url: 'https://…/clip.mp4' });
// Or point straight at a VOD asset that already exists
await client.setTarget({ id: handoff.id, uri: 'urn:uuid:8a85c11f-…' });API
createClient(config)
Creates a new client instance with the provided configuration.
config: An object containing API configuration parameters:baseUrl: The base URL of the API, with or without/v1.apiKey: The bearer JWT.
Returns a client instance with methods for interacting with the API.
Methods
api(path, options): fetch wrapper for the API. Takes either a path (/handoffs/{id}) or a whole URL the API handed back, such as a page'snextUrl. A URL on any other origin is refused rather than called.listHandoffs({ state, limit, order, next }): a page of handoffs in one state.stateis required — the index partitions by it — and the SDK rejects without it.limitis 1–100 (default 25),orderisasc|desc(defaultdesc, byupdatedAt). Resolves to{ items, count, next, nextUrl, fetchNext }.getHandoff({ id }): the full handoff record.createHandoff({ title, slug, source, vodAssetId, scheduledStart, scheduledEnd }): creates a handoff.title,slugandsourceare required.endHandoff({ id }): ends the stream and tears down the encoder port, video-live registration and recording.setTarget({ id, uri }): points the handoff at an existing service-vod asset. Idempotent.submitSource({ id, mp4Url }): submits a rendered mp4 for transcoding.
Scopes
Scopes are per endpoint, and they are the downstream scopes the call will
exercise — the service deliberately has no handoff:* namespace of its own, so
it can never exceed your token's authority. Hide or disable whatever the
operator's token cannot perform.
| Method | Scopes required (all of) |
| --- | --- |
| listHandoffs, getHandoff | video:read |
| createHandoff (immediate) | publisher:write, sr:write, video:write |
| createHandoff (scheduled) | publisher:write, vid-sch:write |
| endHandoff | publisher:write, sr:write, video:write |
| setTarget | publisher:write, vod:read |
| submitSource | publisher:write, vod:write |
Which createHandoff row applies is decided by scheduledStart. A 403 means
the token is missing one or more of the scopes listed above.
Pagination
count is the size of the page, not a total — the API has no total. Follow
next until it is null, or call fetchNext() on the resolved page. Calling
fetchNext() past the last page resolves to an object with only a
fetchNext, so a while (page.items?.length) loop terminates cleanly.
nextUrl is the same cursor as a ready-made URL. Pass it straight to api()
if you would rather drive the paging yourself.
Errors
A non-2xx response throws an Error enriched with:
status,statusText,urlbody: the parsed response payloadcode: the API's stable error code, e.g.ILLEGAL_TRANSITION
The error's message is the API's human-readable message. Branch on code,
display message.
body and code are populated only when the response was JSON. A gateway
failure — a 502 in HTML, say — leaves both undefined. code is also absent
on 401 and 403, which carry only a generic message. Check status before
keying off code.
The SDK also throws before it makes a request, with a code but no status:
MISSING_STATE—listHandoffsgot neitherstatenornext.CROSS_ORIGIN_URL—api()got a URL on an origin other than the configuredbaseUrl, which would have sent your JWT off-site.
try {
await client.endHandoff({ id });
} catch (error) {
if (error.code === 'TEARDOWN_INCOMPLETE') {
// Infrastructure may still be allocated — retrying /end is the repair path
}
}Notes
- Creating is not free.
createHandoffwithoutscheduledStartclaims one of eight shared encoder ports and starts broadcasting. An abandoned handoff holds that hardware until someone callsendHandoff. - Only
id,state,title,createdAtandupdatedAtare guaranteed. Every other field appears once the lifecycle has produced it, so treat any field as possibly absent rather than keying off state. lastErrorpresent does not mean the handoff is dead. It records the most recent failed attempt and is cleared when a later one succeeds.- State changes arrive by webhook, not from your calls, and every response
is
no-store. Poll or offer a refresh.
Related
service-live-vod-handoff— the API, includingapi.mdand the generatedopenapi.json.
