New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@clipwright/sdk

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@clipwright/sdk

TypeScript client for the Clipwright UGC video API

Source
npmnpm
Version
0.28.0
Version published
Weekly downloads
484
62.42%
Maintainers
1
Weekly downloads
 
Created
Source

@clipwright/sdk

TypeScript client for the Clipwright UGC and faceless video API.

import { ClipwrightClient } from "@clipwright/sdk";

const client = new ClipwrightClient({ apiKey: process.env.CLIPWRIGHT_API_KEY! });

const quote = await client.quoteUgc({ script: "Hello from Clipwright." });
const run = await client.makeUgc({ script: "Hello from Clipwright." });

makeUgc renders a clip and costs money; quoteUgc does not. Every call carries an idempotency key derived from the input and from this installation's id, so a retried request returns the original run instead of paying twice. Pass idempotencyKey explicitly when you deliberately want a fresh run.

quoteFaceless(input) prices a faceless video for a duration_seconds of 30 to 90 (the delivered video runs from 25 seconds to a quarter past it, never past 90, and never costs more than the quote), and startFaceless(input, { attempt }) starts it and returns the run at once; poll getRun until it succeeds. The input names its source with input_mode: { input_mode: "script", script, duration_seconds } reads your text as written, { input_mode: "brief", brief, duration_seconds } writes the narration from a short description. Captions are on unless captions: false. Both methods refuse a mismatched script/brief before sending anything. voice picks the narration voice: a name from listVoices({ skill: "make_faceless" }), in English, Russian, Spanish, Portuguese, French or Italian. Without it the narration is read by narrator_en_wise_lady. The API refuses an unknown name and a make_ugc voice before any charge; voice_id is refused before sending.

listVoices({ language, gender, age, use_case, model, skill }) returns the voices for voice, each filter optional. It is free. A voice with a sample carries a preview_url that expires in an hour. Pass a voice's name as voice; language is a filter only, since any voice speaks any supported language. Each voice names the one skill that speaks it in skill: make_ugc or make_faceless. The other skill refuses it before any charge. Without skill the list holds the make_ugc voices only; the narration voices of make_faceless are listed with skill: "make_faceless".

For make_ugc the list holds the presets, the thirty voices of the speech model gemini-3.8-flash-tts (kind: "model_voice") and the catalog. A model_voice voice has no language of its own: language: "en" and language: "ru" find it, and its sample is in English unless you ask for language: "ru". Naming such a voice makes the run speak gemini-3.8-flash-tts without tts_model. That model speaks only its own voices and refuses captions: true before any charge.

Without voice or voice_id, the voice follows the actor's gender: sarah for a woman, george for a man. actor_id brings its catalog gender; with image, pass actor_gender ("female" or "male"). An image without actor_gender gets george and a warning. actor_gender next to actor_id or without image is refused before any charge. With tts_model: "gemini-3.8-flash-tts" the defaults are kore for a woman and charon otherwise.

getRunShots(runId) reads the shots of a finished video: for each shot its span in frames, its kind, the words it covers, its description and a signed link to its source picture or clip. A run with no shots to show answers planned: false with editable.state and editable.reason; that is an answer, not an error. getRunEdit(runId) reads the draft of an edit of that run and saveRunEdit(runId, { base_version, document }) saves it. Version 0 means nothing is stored. A save names the version the draft was read at and is refused with edit_conflict when the stored one differs; the error carries failure.version. A run without a draft is refused with run_not_editable, carrying failure.state and failure.reason. saveRunEdit sends one request and never repeats it: a save whose answer was lost answers edit_conflict when sent again, so read the draft and compare it with your document before you treat that as a conflict. The three calls are free. A draft charges nothing and renders nothing. Responses are parsed tolerantly: a kind of shot, an action or the code of an issue that a later API adds arrives as a plain string.

quoteRevision(runId, request) prices a revision of a faceless video for nothing, and startRevision(runId, request, { attempt }) starts it: a new run, returned at once, that renders the video again with the shots you change. The request is { document }, the edited document of the draft, or { changes: [{ shot_id, operation }] }. A revision that delivers is charged its quoted total; one that fails is charged nothing, even when its paid work was already done. The same request and attempt send the same idempotency key, so a resend returns the first run; attempt: 2 starts a second one. While paid edits are closed on the server, both calls throw a ClipwrightApiError of kind editing_closed, which is not retried.

Requires CLIPWRIGHT_CLIENT_ID, or a writable ~/.clipwright/ for the SDK to create one on first use. It fails loudly rather than generating a throwaway id, because a per-process id would disable duplicate protection without saying so.

Deadlines cancel the request (behaviour change)

fetch has no default timeout in Node, so until this release a connection that was accepted and then went silent — a proxy dying between you and the API — hung forever. maxWaitMs did not save you: it was checked between status polls, so control never came back to it.

Two deadlines now abort the request itself, not just the wait:

  • maxWaitMs (per makeUgc call, default 10 min) bounds the whole call, including the request in flight when it expires.
  • requestTimeoutMs (per client, default 30 s) bounds every single request.
const client = new ClipwrightClient({
  apiKey: process.env.CLIPWRIGHT_API_KEY!,
  requestTimeoutMs: 60_000,
});

What changes for you: a call that used to hang now rejects. A makeUgc whose polling is cut short still throws the same timeout error, naming the run so you can pick it up later with getRun. A start request aborted mid-flight throws ClipwrightResponseError with mayHaveTakenEffect: true and deliveryConfirmed: false — the run may exist and may be charged, so check your recent runs instead of retrying with a fresh idempotency key. Requests on the free paths (quoteUgc, listVoices, getRun) reject with a TimeoutError DOMException; retrying them is safe.

Raise requestTimeoutMs if you sit behind a slow proxy; it must stay positive, and a non-positive or non-finite value is rejected loudly rather than replaced with the default.

Changelog

What changed in each release, and what was removed, is in CHANGELOG.md inside this package. Read its Breaking: entries before upgrading.

FAQs

Package last updated on 15 Oct 2026

Related posts