Sluby

@sluby/sdk reference

Every option, namespace, method, error class and exported type of the Sluby TypeScript SDK.

@sluby/sdk is the TypeScript client for the Sluby REST API. It wraps every request in typed methods, uploads files over tus, verifies webhook signatures and opens the browser session that streams video straight from Sia. For a walkthrough, start with Upload videos from your app.

Install

npm install @sluby/sdk

Package

FactValue
VersionThis page describes @sluby/sdk 0.2.1
Module formatsESM (import) and CommonJS (require), each with its own type declarations
Entry pointsOne: @sluby/sdk. There are no subpath exports.
Node.js^20.16.0 || >=22.3.0 (the engines field)
BrowsersEvery REST method, uploads and verifyRequestAsync run in a browser. client.sia runs only in a browser.
HTTPThe global fetch. There is no option to pass your own.
Dependenciestus-js-client for uploads, @siafoundation/sia-storage for direct-from-Sia playback
Side effectsNone ("sideEffects": false)

The Sia storage SDK is a WebAssembly module. The client imports it the first time client.sia connects (connect(), prepare() or resolveObjectId()), so code that never touches client.sia never loads it.

SlubyConfig

interface SlubyConfig {
  baseUrl: string;
  apiKey?: string;
  credentials?: RequestCredentials;
  deliveryBaseUrl?: string;
  sia?: SiaViewerConfig;
  timeoutMs?: number;
}
OptionTypeDefaultBehavior
baseUrlstringrequiredThe origin of your Sluby server, such as https://sluby.example.com. The SDK appends the /api/v1/... paths and removes trailing slashes.
apiKeystringnoneSent as Authorization: Bearer <apiKey> on every API request and every upload request. The public share map request of client.sia never carries it. Leave it out in a browser app on the same origin as the API: requests then carry the Studio session cookie.
credentialsRequestCredentialsnot setPassed to fetch for API requests. With 'include', uploads also send cookies cross-origin. Set it only when a cookie session crosses origins.
deliveryBaseUrlstringbaseUrlThe origin the SDK resolves playback URLs against: playback.streamUrl, playback.getUrl, playback.getSignedUrl and relative src values in client.sia. The SDK removes trailing slashes.
siaSiaViewerConfignoneThe viewer identity for direct-from-Sia playback. When set, client.sia is a SiaSession. When not set, client.sia is undefined.
timeoutMsnumbernoneRejects any API request that has not finished, headers and body, within this many milliseconds, with a TimeoutError. It does not apply to uploads.

The constructor throws an Error when:

ConditionMessage
apiKey is set to an empty stringSlubyClient apiKey must be non-empty when provided; omit it to use a cookie session.
baseUrl is emptySlubyClient requires a non-empty baseUrl.
timeoutMs is not a finite number above 0SlubyClient timeoutMs must be a positive number of milliseconds.

SiaViewerConfig

The publishable, low-privilege Sia identity a browser uses to download shared objects. Stream from Sia in the browser shows how to create one.

FieldTypeDefaultBehavior
indexerUrlstringrequiredThe indexer the viewer connects to, such as https://sia.storage
appIdstringrequired32 bytes as 64 hex characters, with or without 0x
appKeystringrequired32 bytes as 64 hex characters, with or without 0x
namestringSluby PlayerApp name shown to the indexer
descriptionstringSluby direct-from-Sia video playerApp description shown to the indexer
serviceUrlstringindexerUrlApp service URL shown to the indexer

SlubyClient

import { SlubyClient } from '@sluby/sdk';

const client = new SlubyClient({
  baseUrl: 'https://sluby.example.com',
  apiKey: process.env.SLUBY_API_KEY,
  timeoutMs: 30_000,
});
MemberTypePurpose
uploadsUploadManagerUpload files, read and cancel upload sessions
assetsAssetManagerList, read, edit, retry, delete and wait for videos
playbackPlaybackManagerPublic and signed playback URLs
playbackIdsPlaybackIdManagerpb_... playback ids
keysKeyManagerAPI keys
sessionsSessionManagerSigned-in Studio browsers
reconciliationReconciliationManagerThe storage check
webhookEndpointsWebhookEndpointManagerRegister and manage webhook endpoints
webhooksWebhookManagerVerify and parse incoming webhooks
siaSiaSession | undefinedDirect-from-Sia viewer session, present only with config.sia
resolveDeliveryUrl(pathOrUrl)(pathOrUrl: string) => stringReturns an absolute http(s) URL unchanged and prefixes anything else with deliveryBaseUrl

Request behavior

  • Request bodies go out with the API's snake_case field names. Responses come back mapped to the camelCase types on this page.
  • Every method that takes options accepts signal?: AbortSignal. When the signal fires, the call rejects with an AbortError whose cause is the signal's reason.
  • The SDK reads the whole response body before it resolves, so signal and timeoutMs cover the body too.
  • A response outside 200 to 299 rejects with the error class for its status.
  • A network failure rejects with the TypeError that fetch threw, not a SlubyError.
  • The SDK does not retry REST requests. Uploads retry on their own (see retryDelays in UploadFileOptions).

The endpoint decides which scope a request needs. Each method below links to its endpoint page.

client.uploads

class UploadManager {
  upload(file: UploadInput, options: UploadOptions): AssetUploadHandle;
  getStatus(uploadId: string, options?: RequestOptions): Promise<UploadSession>;
  cancel(uploadId: string, options?: RequestOptions): Promise<void>;
}

type UploadInput = Blob | Uint8Array;
MethodRequestReturns
uploadtus upload to POST /api/v1/uploads, then PATCH requests for the chunksAn AssetUploadHandle right away, without waiting
getStatusGET /api/v1/uploads/{uploadId}UploadSession
cancelDELETE /api/v1/uploads/{uploadId}Nothing

upload() takes a File or Blob in the browser, and a Buffer, Uint8Array or Blob (for example from fs.openAsBlob) in Node.js. It does not take Node.js streams. Each call creates one video asset, unless it resumes an unfinished upload (see resume below). The upload runs as a single tus stream.

UploadOptions

UploadOptions is UploadFileOptions plus the fields of the new video. The SDK sends them as tus metadata.

FieldTypeDefaulttus metadata key
titlestringrequiredtitle
descriptionstring''description
accessTier'public' | 'private''public'accessTier

The SDK also sends filename (the File's name, or video) and filetype (the Blob's type, or an empty string).

UploadFileOptions

FieldTypeDefaultBehavior
chunkSizenumber10485760 (10 MiB)Bytes per tus PATCH request
retryDelaysnumber[][0, 1000, 3000, 5000]Milliseconds to wait before each tus retry
onProgress(percent: number, bytesUploaded: number, bytesTotal: number) => voidnonepercent is Math.round(bytesUploaded / bytesTotal * 100). Not called while bytesTotal is 0.
onSuccess() => voidnoneCalled once when every byte is uploaded
onError(error: Error) => voidnoneCalled with the same SlubyError the handle rejects with
resumebooleantrueIn a browser, resumes an unfinished upload of the same file, with the same metadata, by the same key, from an earlier page load. A finished upload is never resumed. A Uint8Array input is never resumed.
signalAbortSignalnoneAborts the upload like handle.abort()

Before it resumes a stored upload, the SDK sends a HEAD to it and resumes only when the server reports Upload-Offset below Upload-Length.

AssetUploadHandle

type AssetUploadHandle = Promise<void> & {
  assetId: Promise<string>;
  uploadId: Promise<string>;
  pause: () => Promise<void>;
  resume: () => void;
  abort: () => Promise<void>;
  readonly isPaused: boolean;
};
MemberBehavior
await handleResolves when every byte is uploaded. Processing then continues on the server: use assets.waitForReady to wait for it.
assetIdThe id of the asset the upload created, available as soon as the server creates the upload
uploadIdThe last path segment of the tus upload URL, the id getStatus and cancel take
pause()Stops sending and keeps the partial upload
resume()Continues a paused upload from where it stopped
abort()Stops sending, terminates the upload on the server (which discards the asset it created) and rejects the handle, assetId and uploadId (those not yet settled) with AbortError('Upload aborted.'). Does nothing once the upload has finished.
isPausedWhether the upload is paused

The handle, assetId and uploadId reject with the error class for the HTTP status of a failed tus request (the tus error is on cause), or with a SlubyError whose message starts with Upload failed: for any other failure.

client.assets

class AssetManager {
  list(options?: ListAssetsOptions): Promise<PaginatedResponse<VideoAsset>>;
  get(videoAssetId: string, options?: RequestOptions): Promise<VideoAsset>;
  update(
    videoAssetId: string,
    data: Partial<Pick<VideoAsset, 'title' | 'description' | 'accessTier'>>,
    options?: RequestOptions & { ifMatch?: VideoAsset | string },
  ): Promise<VideoAsset>;
  delete(videoAssetId: string, options?: RequestOptions): Promise<void>;
  processing(videoAssetId: string, options?: RequestOptions): Promise<ProcessingJob>;
  renditions(videoAssetId: string, options?: RequestOptions): Promise<Rendition[]>;
  retry(videoAssetId: string, options?: RequestOptions): Promise<{ stage: RetryStage }>;
  stats(options?: RequestOptions): Promise<AssetStats>;
  usage(options?: RequestOptions & { days?: number }): Promise<UsageReport>;
  waitForReady(
    videoAssetId: string,
    options?: { pollInterval?: number; timeout?: number; signal?: AbortSignal },
  ): Promise<VideoAsset>;
}

function assetEtag(asset: Pick<VideoAsset, 'updatedAt'>): string;
MethodRequestReturns
listGET /api/v1/assetsOne page of assets with total, page, limit, nextCursor and hasMore
getGET /api/v1/assets/{id}VideoAsset
updatePATCH /api/v1/assets/{id}The updated VideoAsset
deleteDELETE /api/v1/assets/{id}Nothing
processingGET /api/v1/assets/{id}/processingProcessingJob: stage, percent, error and log lines
renditionsGET /api/v1/assets/{id}/renditionsRendition[], tallest first, empty until the asset is ready
retryPOST /api/v1/assets/{id}/retryThe stage processing restarted from: 'transcode' or 'upload-segments'
statsGET /api/v1/statsAssetStats. byStatus has every status, 0 when none.
usageGET /api/v1/stats/usage, with ?days= when days is setUsageReport: plays per UTC day
waitForReadyRepeated getThe asset once its status is ready

update and If-Match

Pass ifMatch to apply the edit only if nobody changed the asset since you read it. Pass the asset as you read it, or an ETag string. assetEtag(asset) builds the ETag the server uses: asset.updatedAt in milliseconds since the epoch, in double quotes. It throws a TypeError when updatedAt is not a date.

When the asset changed, update rejects with a ConflictError whose statusCode is 412 and whose current is the asset as the server has it now.

import { ConflictError } from '@sluby/sdk';

try {
  await client.assets.update(asset.id, { title: 'New title' }, { ifMatch: asset });
} catch (err) {
  if (err instanceof ConflictError && err.current) {
    // the asset changed on the server; err.current is the version there now
  }
}

waitForReady

OptionDefaultBehavior
pollInterval5000Milliseconds between reads
timeout600000 (10 minutes)Longest wait in milliseconds. The asset is read at least once, even with timeout: 0.
signalnoneStops polling and rejects with an AbortError: Stopped waiting: the signal was aborted. between reads, The request was aborted. during a read
OutcomeResult
Status readyResolves with the asset
Status failedRejects with ProcessingFailedError, whose asset is the asset as last read
TimeoutRejects with TimeoutError('Timed out after <timeout>ms waiting for asset <id> to become ready.')

ListAssetsOptions

FieldTypeQuery parameterBehavior
pagenumberpagePage number
limitnumberlimitAssets per page
cursorstringcursorThe nextCursor of the previous page. Works only with the default order (created_at, desc).
statusVideoAssetStatusstatusOnly assets in this status
accessTier'public' | 'private'access_tierOnly assets with this tier
searchstringsearchCase-insensitive match on title or description, or an exact asset id
sort'created_at' | 'title' | 'duration' | 'size'sortDefault created_at
order'asc' | 'desc'orderDefault desc

Defaults and limits for page and limit are on the endpoint page.

client.playback

class PlaybackManager {
  streamUrl(idOrPlaybackId: string): string;
  getUrl(videoAssetId: string, options?: RequestOptions): Promise<PlaybackInfo>;
  getSignedUrl(
    videoAssetId: string,
    options?: { expiresIn?: number; signal?: AbortSignal },
  ): Promise<SignedPlaybackInfo>;
}
MethodRequestReturns
streamUrlNone. It builds the URL of GET /v1/stream/{id}/master.m3u8 without a request.<deliveryBaseUrl>/v1/stream/<id>/master.m3u8 for an asset id or a pb_... playback id
getUrlGET /api/v1/playback/{id}PlaybackInfo
getSignedUrlGET /api/v1/playback/{id}/signed, with ?expires_in= when expiresIn is setSignedPlaybackInfo

getSignedUrl rejects with a RangeError before sending anything when expiresIn is not a whole number above 0. Without expiresIn the server's default lifetime applies; Playback and delivery has the default and the maximum.

PlaybackInfo fieldTypeValue
playbackUrlstringAbsolute master manifest URL, resolved against deliveryBaseUrl
playbackPathstringThe server-relative path the API returned
posterUrlstring | nullAbsolute poster URL, null when the asset has no thumbnail
durationMsnumber | nullDuration
resolutionstring | nullSource resolution such as 1920x1080
accessTier'public' | 'private'The asset's tier
SignedPlaybackInfo fieldTypeValue
signedUrlstringAbsolute signed master manifest URL, resolved against deliveryBaseUrl
signedPathstringThe server-relative signed path the API returned
posterUrlstring | nullThe first thumbnail, signed until the same time, null when there is none
expiresAtstringISO 8601 time the signed URL stops working

client.playbackIds

class PlaybackIdManager {
  list(videoAssetId: string, options?: RequestOptions): Promise<PlaybackId[]>;
  create(
    videoAssetId: string,
    input?: CreatePlaybackIdInput,
    options?: RequestOptions,
  ): Promise<PlaybackId>;
  delete(playbackId: string, options?: RequestOptions): Promise<void>;
}

interface CreatePlaybackIdInput {
  policy?: 'public' | 'signed'; // default 'public'
  name?: string;
}
MethodRequestReturns
listGET /api/v1/assets/{id}/playback-idsPlaybackId[]
createPOST /api/v1/assets/{id}/playback-idsThe new PlaybackId
deleteDELETE /api/v1/playback-ids/{playbackId}, by the pb_... handleNothing

PlaybackId has id (the record id), playbackId (the pb_... handle to use in playback URLs), policy, name (empty string when none was given) and createdAt.

client.keys

class KeyManager {
  list(options?: ListApiKeysOptions): Promise<ApiKeyPage>;
  create(input: CreateApiKeyInput, options?: RequestOptions): Promise<CreatedApiKey>;
  update(keyId: string, input: { name: string }, options?: RequestOptions): Promise<ApiKey>;
  rotate(
    keyId: string,
    options?: { gracePeriodSeconds?: number; signal?: AbortSignal },
  ): Promise<RotatedApiKey>;
  revoke(keyId: string, options?: RequestOptions): Promise<void>;
}

interface ListApiKeysOptions extends RequestOptions {
  limit?: number;
  cursor?: string;
}

interface CreateApiKeyInput {
  name: string;
  scopes?: ApiKeyScope[];
  rateLimit?: number; // requests per minute
}
MethodRequestReturns
listGET /api/v1/keysApiKeyPage: data, hasMore, nextCursor and limits (maxKeys, maxRateLimit)
createPOST /api/v1/keysCreatedApiKey: the key plus its raw key, returned only here
updatePATCH /api/v1/keys/{id}The renamed ApiKey. Secret, scopes and rate limit stay.
rotatePOST /api/v1/keys/{id}/rotate, with grace_period_seconds when gracePeriodSeconds is setRotatedApiKey: the replacement with its raw key, plus rotatedFrom and previousKeyExpiresAt
revokeDELETE /api/v1/keys/{id}Nothing

rotate rejects with a ConflictError (409) for a key that was already rotated. Rotate its replacement instead.

ApiKey fields: id, name, scopes ('upload' | 'read' | 'manage', see Scopes), rateLimit (requests per minute), isActive, createdAt, expiresAt (set while a rotated-out key is in its grace period, otherwise null), prefix (the first 12 characters of the key, or null), lastUsedAt and createdBy ({ id, name } of the member who made it, or null).

client.sessions

class SessionManager {
  list(options?: RequestOptions): Promise<StudioSession[]>;
  revoke(sessionId: string, options?: RequestOptions): Promise<void>;
  revokeAll(options?: RequestOptions): Promise<{ revoked: number }>;
}

These methods work only with a Studio session cookie: create the client without apiKey. A client with an API key gets an AuthenticationError (401). On a server with sign-in turned off (AUTH_DISABLED), they reject with a ConflictError (409).

MethodRequestReturns
listGET /api/v1/auth/sessionsThe account's live sessions, newest first. current marks the one making the call.
revokeDELETE /api/v1/auth/sessions/{id}Nothing. Revoking the current session also clears its cookie.
revokeAllDELETE /api/v1/auth/sessionsHow many sessions were signed out, the current one included

StudioSession fields: id, userAgent, createdAt, expiresAt, lastSeenAt, current.

client.reconciliation

class ReconciliationManager {
  latest(options?: RequestOptions): Promise<ReconciliationReport>;
  run(options?: RequestOptions): Promise<ReconciliationTrigger>;
}
MethodRequestReturns
latestGET /api/v1/reconciliationThe latest finished storage check. Rejects with NotFoundError before the first check finishes.
runPOST /api/v1/reconciliation/run{ enqueued, alreadyPending }. Only an admin can run it; anyone else gets a PermissionError (403).

ReconciliationReport is a union on scope:

  • scope: 'deployment' (for an admin): the whole deployment, with dbObjectCount, indexerObjectCount, inSyncCount, orphanCount, orphanedIds and errorMessage.
  • scope: 'account' (for a caller that is not an admin): the missing objects of the workspace's videos, without the deployment-wide counts.

Both carry id, startedAt, finishedAt, createdAt, status ('ok' | 'drift' | 'failed'), missingCount, missingIds and missingObjects (each { objectId, videoAssetId, title }). Deletion and reconciliation explains the check.

client.webhookEndpoints

class WebhookEndpointManager {
  list(options?: RequestOptions): Promise<WebhookEndpoint[]>;
  get(endpointId: string, options?: RequestOptions): Promise<WebhookEndpoint>;
  create(
    input: CreateWebhookEndpointInput,
    options?: RequestOptions,
  ): Promise<CreatedWebhookEndpoint>;
  update(
    endpointId: string,
    input: UpdateWebhookEndpointInput,
    options?: RequestOptions,
  ): Promise<WebhookEndpoint>;
  delete(endpointId: string, options?: RequestOptions): Promise<void>;
  rotateSecret(
    endpointId: string,
    options?: RequestOptions,
  ): Promise<{ id: string; secret: string }>;
  test(endpointId: string, options?: RequestOptions): Promise<WebhookTestResult>;
  deliveries(
    endpointId: string,
    options?: { limit?: number; signal?: AbortSignal },
  ): Promise<WebhookDelivery[]>;
  redeliver(
    endpointId: string,
    deliveryId: string,
    options?: RequestOptions,
  ): Promise<{ deliveryId: string }>;
}

interface CreateWebhookEndpointInput {
  url: string;
  events: WebhookEventName[];
}

interface UpdateWebhookEndpointInput {
  url?: string;
  events?: WebhookEventName[]; // replaces the subscribed events
  isActive?: boolean; // false pauses the endpoint, true resumes it
}
MethodRequestReturns
listGET /api/v1/webhooksEvery endpoint, paused ones included, newest first
getGET /api/v1/webhooks/{id}One endpoint, with lastDeliveryAt and lastDeliveryOk
createPOST /api/v1/webhooksCreatedWebhookEndpoint with the signing secret, returned only here
updatePATCH /api/v1/webhooks/{id}The updated endpoint. The secret stays.
deleteDELETE /api/v1/webhooks/{id}Nothing
rotateSecretPOST /api/v1/webhooks/{id}/rotate-secretThe new secret, returned only here
testPOST /api/v1/webhooks/{id}/testWebhookTestResult: delivered, statusCode, error
deliveriesGET /api/v1/webhooks/{id}/deliveries, with ?limit= when limit is setWebhookDelivery[], newest first
redeliverPOST /api/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverThe id of the new delivery. Rejects with ConflictError (409) while the endpoint is paused.

WebhookDelivery fields: id, eventId, event, statusCode, delivered, error, attempt (1 for the first try), createdAt, payload (the JSON body that was sent) and manual (true for a resend made with redeliver). Webhook events describes the events, payloads and retries.

client.webhooks

class WebhookManager {
  verifyRequest(
    payload: string,
    headers: WebhookHeaders,
    secret: string,
    options?: VerifyRequestOptions,
  ): boolean;
  verifyRequestAsync(
    payload: string,
    headers: WebhookHeaders,
    secret: string,
    options?: VerifyRequestOptions,
  ): Promise<boolean>;
  verifySignature(payload: string, signature: string, secret: string): boolean;
  verifySignatureAsync(payload: string, signature: string, secret: string): Promise<boolean>;
  parseEvent(payload: string): WebhookEvent;
}

type WebhookHeaders =
  { get(name: string): string | null } | Record<string, string | string[] | undefined>;

interface VerifyRequestOptions {
  toleranceSeconds?: number; // default 300
  now?: number; // current time in Unix seconds, default the clock
}

const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;

WebhookManager needs no credentials. Use it through any client, or construct it on its own with new WebhookManager().

MethodRuns onBehavior
verifyRequestNode.jstrue when X-Sluby-Signature-V1 is the hex HMAC-SHA256 of <X-Sluby-Timestamp>.<payload> under secret and the timestamp is within toleranceSeconds of now
verifyRequestAsyncWeb Crypto (browsers, edge runtimes, Deno, Node.js)Same check as verifyRequest
verifySignatureNode.jstrue when signature (hex, either case) is the HMAC-SHA256 of payload under secret. Compares in constant time.
verifySignatureAsyncWeb CryptoSame check as verifySignature
parseEventAnywhereParses the body into a WebhookEvent

Details:

  • Pass the raw request body as payload, exactly as received, before any JSON parsing.
  • headers takes a Fetch Headers object or a Node.js req.headers record. Header names match case-insensitively, and for an array the SDK uses the first value.
  • The timestamp must be 1 to 12 digits. A missing header, a malformed timestamp or one outside the tolerance returns false.
  • verifySignature throws where process.getBuiltinModule is missing (outside Node.js): Synchronous signature verification needs Node.js 20.16+ or 22.3+ (process.getBuiltinModule). Use verifySignatureAsync() in browser or edge environments. verifyRequest throws the same error once the headers pass the timestamp checks.
  • parseEvent throws Invalid webhook payload: missing required "event" or "timestamp" field. when either is not a string, and throws the SyntaxError of JSON.parse for a body that is not JSON. It keeps id only when it is a string and sets data to {} when it is missing.
import { WebhookManager } from '@sluby/sdk';

const webhooks = new WebhookManager();

// rawBody: the request body as a string; headers: req.headers
if (webhooks.verifyRequest(rawBody, headers, process.env.SLUBY_WEBHOOK_SECRET!)) {
  const event = webhooks.parseEvent(rawBody);
  console.log(event.id, event.event, event.data.video_asset_id);
}

WebhookEvent is { id?: string; event: WebhookEventName | (string & {}); timestamp: string; data: Record<string, unknown> }. data keeps the snake_case keys of the payload. The headers, signature scheme and every payload are on Webhook events.

client.sia

client.sia is a SiaSession when the client was created with sia, and undefined otherwise. It runs only in a browser. @sluby/player drives it; call it yourself only when you build your own player. Playback and delivery explains direct-from-Sia playback.

class SiaSession {
  static readonly MAX_CACHED_OBJECTS = 64;
  constructor(fetchFn: FetchFn, config: SiaViewerConfig, transport?: SiaSessionTransport);
  connect(): Promise<void>;
  prepare(assetId: string, source?: SiaShareSource): Promise<void>;
  getShareMap(
    assetId: string,
    source?: SiaShareSource,
    options?: { renew?: boolean },
  ): Promise<SiaShareMap>;
  shareMapUrl(assetId: string, src: string): string;
  masterObjectId(assetId: string, source?: SiaShareSource): Promise<string>;
  resolveObjectId(assetId: string, objectId: string, source?: SiaShareSource): Promise<SiaObject>;
  download(object: SiaObject, options?: SiaDownloadOptions): ReadableStream;
  dispose(): void;
}

interface SiaShareSource {
  src?: string;
}

interface SiaDownloadOptions {
  offset?: number;
  length?: number;
}

function publicShareMapUrl(assetId: string, src: string): string;

FetchFn is not exported. Use the session SlubyClient creates rather than constructing one.

Share map source

Every method that needs an asset's share map takes an optional source:

sourceShare map requestCredentials
{ src }, where src is the gateway playback URL being played (/v1/stream/{id}/master.m3u8 or /v1/objects/{id}, signed or not, absolute or relative to deliveryBaseUrl)GET /v1/share/{id} on the delivery base, with the expires and sig of srcNone, so a page without an API key can play direct from Sia
Not setGET /api/v1/playback/{id}/shareThe client's API key or session

Methods

MethodBehavior
connect()Loads the WebAssembly SDK and connects to the indexer with the viewer app key. Resolves once connected; later calls reuse the connection. A failed attempt is not cached, so the next call tries again. Rejects when the app id or key is not 64 hex characters, or when the app key is not registered with the indexer.
prepare(assetId, source?)connect(), then loads the asset's share map
getShareMap(assetId, source?, options?)Fetches the share map without caching it. renew: true adds renew=1 to a public request, so the server does not count another play. Rejects with a SlubyError when the answer has no valid expires_at.
shareMapUrl(assetId, src)The public share map URL for src, after resolving a relative src against deliveryBaseUrl
masterObjectId(assetId, source?)The object id of the asset's HLS master playlist, from the cached share map
resolveObjectId(assetId, objectId, source?)A downloadable handle for one object of the asset. Cached per asset and object, at most MAX_CACHED_OBJECTS (64), least recently used dropped first. Fetches a fresh share map once when the object is missing from it, or when its share URL fails less than 60 seconds before the map's expiry or later. Rejects with NotFoundError when the map has no share URL for the object.
download(object, options?)A ReadableStream of the object's bytes from Sia, from offset for length bytes. Throws when called before the session is connected.
dispose()Frees every resolved object and the WebAssembly SDK and forgets the share maps. The session stays usable: the next call connects again.

The session caches each share map per asset and source, and fetches a new one when the cached map is missing, expired, or within 60 seconds of its expiresAt. A map that arrived already inside those 60 seconds stays in use until it expires. A refetch of a map the session already held adds renew=1 to a public request.

publicShareMapUrl(assetId, src) builds the public share map URL for an absolute src. The share id is the {id} in a /v1/stream/{id}/master.m3u8 URL, or assetId for a /v1/objects/{id} URL. It throws when src is neither.

SiaShareMap is { masterObjectId: string; shares: Record<string, string>; expiresAt: string }, where shares maps each object id (hex) to its sia:// share URL.

Errors

Every error the SDK creates extends SlubyError, which extends Error. Check classes with instanceof.

ClassstatusCodeThrown whenExtra fields
SlubyErrorHTTP status, or 0Base class. Thrown directly for a status without its own class (such as 500 or 502), for a 2xx response whose body is not JSON, and for an upload failure without an HTTP response.responseBody?: string, cause
ValidationError400 or 422The request is malformed or a value is invalid
AuthenticationError401The key or session is missing, wrong, revoked or expired
PermissionError403The caller is known but not allowed: a key without the needed scope, a member without the needed role, or a signed URL that is invalid or expired
NotFoundError404 or 410The resource does not exist, or no longer does (410)
ConflictError409 or 412The resource is not in a state that allows the request (409), or an update with ifMatch found the asset changed (412)current?: VideoAsset on a 412 from assets.update
RateLimitError429The key or caller went over a rate limitretryAfter?: number, seconds from the Retry-After header (seconds or an HTTP date)
TimeoutError0A request ran past timeoutMs, or waitForReady ran past its timeout
AbortError0A signal fired, or abort() was called on an upload. name is 'AbortError', like the DOM's.cause: the signal's reason
ProcessingFailedError0waitForReady read the asset as failedasset: VideoAsset

The message of an HTTP error is the error string of a JSON body, else its message string. For a body that is not JSON, it is the trimmed body text when that is one line under 200 characters and does not start with <. Otherwise it is the HTTP status text, else Unknown API error. The raw body is on responseBody. Errors in API conventions describes the error body the API sends.

A network failure is not a SlubyError: the SDK rethrows the TypeError from fetch. A statusCode of 0 marks an error raised in the client, not by the server.

import { SlubyError, RateLimitError } from '@sluby/sdk';

try {
  await client.assets.list();
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log(`retry in ${err.retryAfter ?? 60} s`);
  } else if (err instanceof SlubyError) {
    console.log(err.name, err.statusCode, err.message);
  } else {
    throw err; // a network failure from fetch
  }
}

Types

Every type below is exported from @sluby/sdk as a type.

VideoAsset

FieldTypeNotes
idstring
titlestring
descriptionstring
manifestObjectIdstring | nullnull until processing finishes
thumbnailObjectIdsstring[]
durationMsnumber | nullnull or 0 until processing has probed the source
resolutionstring | nullSource resolution such as 1920x1080
statusVideoAssetStatus'created' | 'uploading' | 'processing' | 'ready' | 'failed'
accessTierAccessTier'public' | 'private'
creatorAddressstring
segmentCountnumber
totalStorageBytesnumber
playCountnumberAll-time plays
lastPlayedAtstring | nullnull before the first play
createdAtstring
updatedAtstringAlso the asset's version for ifMatch

ProcessingJob

FieldTypeNotes
idstring
status'queued' | 'processing' | 'completed' | 'failed' | 'retrying'retrying means a failed attempt is queued again
stageProcessingStage'queued' | 'transcode' | 'upload' | 'finalize' | 'completed' | 'failed'
progressPercentnumberOverall progress, 0 to 100
retryCountnumber
errorMessagestring | null
startedAt, completedAtstring | null
createdAtstring
logsArray<{ timestamp: string; stage: string; message: string }>The worker's log lines
retryablebooleanWhether assets.retry can start a new run

Other types

TypeShape
UploadSession{ id; videoAssetId: string | null; status: UploadSessionStatus; progressPercent; fileSize; uploadedBytes }
UploadSessionStatus'uploading' | 'completed' | 'cancelled' | 'failed'
UploadHandleAssetUploadHandle without assetId and uploadId
RequestOptions{ signal?: AbortSignal }
PaginatedResponse<T>{ data: T[]; total; page: number | null; limit; nextCursor: string | null; hasMore: boolean }
CursorPage<T>{ data: T[]; hasMore: boolean; nextCursor: string | null }
AssetSortField'created_at' | 'title' | 'duration' | 'size'
RetryStage'transcode' | 'upload-segments'
Rendition{ name; width; height; videoBitrateKbps; segmentCount; byteSize; dataObjectId; playlistObjectId }
AssetStats{ total; byStatus: Record<VideoAssetStatus, number>; storedBytes; readyDurationMs; objectCount }
UsageReport{ days: Array<{ date: string; plays: number }>; total: number }, oldest day first
PlaybackPolicy'public' | 'signed'
ApiKeyScope'upload' | 'read' | 'manage'
ApiKeyPageCursorPage<ApiKey> plus limits: { maxKeys; maxRateLimit }
CreatedApiKeyApiKey plus key: string
RotatedApiKeyCreatedApiKey plus rotatedFrom: string; previousKeyExpiresAt: string
WebhookEventName'upload.started' | 'upload.completed' | 'upload.failed' | 'processing.started' | 'asset.ready' | 'asset.errored'
WebhookEndpoint{ id; url; events: WebhookEventName[]; isActive; createdAt; lastDeliveryAt; lastDeliveryOk }
CreatedWebhookEndpoint{ id; url; events; secret }
WebhookTestResult{ delivered: boolean; statusCode: number | null; error: string | null }
MissingObject{ objectId: string; videoAssetId: string | null; title: string | null }
ReconciliationReportDeployment, ReconciliationReportAccountThe two members of ReconciliationReport
ReconciliationTrigger{ enqueued: boolean; alreadyPending: boolean }
SiaObjectAn opaque handle from resolveObjectId. Pass it only to download of the same session.
SiaSessionTransport{ fetchPublic?: (url: string) => Promise<Response>; resolveDeliveryUrl?: (pathOrUrl: string) => string }

The manager classes (UploadManager, AssetManager, PlaybackManager, PlaybackIdManager, KeyManager, SessionManager, ReconciliationManager, WebhookEndpointManager, WebhookManager, SiaSession) are exported for typing. Except for WebhookManager, reach them through a SlubyClient.

On this page