byteplus-js
v0.1.2
Published
Fal-shaped TypeScript SDK for BytePlus ModelArk: Seedance video, Seedream images, and embeddings.
Maintainers
Readme
byteplus-js
A fal-shaped TypeScript SDK for BytePlus ModelArk. It talks to the ModelArk REST API directly (no vendor SDK) and gives you both:
- typed namespaces —
byteplus.seedance.*(async video),byteplus.images.*(Seedream, sync),byteplus.embeddings.*(sync); and - a generic fal-style façade —
byteplus.subscribe(modelId, { input }),byteplus.run(...), andbyteplus.queue.submit/status/result/cancelover the async task API.
This is the JS counterpart to the Python byteplus-py SDK.
Install
npm install byteplus-jsNode ≥ 18 (uses the built-in fetch). ESM and CommonJS are both supported.
Configure
import { byteplus } from "byteplus-js";
byteplus.config({ apiKey: process.env.BYTEPLUS_API_KEY });
// or read BYTEPLUS_API_KEY / ARK_API_KEY from the environment automatically.Or create an isolated client:
import { createBytePlus } from "byteplus-js";
const client = createBytePlus({
apiKey: process.env.BYTEPLUS_API_KEY,
region: "ap-southeast", // or baseUrl: "https://.../api/v3"
timeout: 60_000,
maxRetries: 2,
});Seedance video (typed namespace)
import { byteplus } from "byteplus-js";
// Submit and poll to completion (fal-style subscribe).
const task = await byteplus.seedance.subscribe({
model: "dreamina-seedance-2-0-fast-260128",
prompt: "A tiny robot waters a basil plant in morning light.",
ratio: "16:9",
duration: 4,
generateAudio: true,
onUpdate: (t) => console.log(t.status),
});
console.log(task.videoUrl);Flat sugar and raw content[] are both accepted ("both inputs"):
import { byteplus, text, imageUrl } from "byteplus-js";
await byteplus.seedance.submit({
model: "dreamina-seedance-2-0-fast-260128",
content: [
text("A cinematic shot of fruit tea being prepared."),
imageUrl("https://.../tea.jpg", { role: "reference_image" }),
],
duration: 4,
});Fine-grained control:
const submitted = await byteplus.seedance.submit({ model, prompt: "..." });
const done = await byteplus.seedance.wait(submitted.id);
const list = await byteplus.seedance.list({ status: "succeeded", pageSize: 10 });
await byteplus.seedance.delete(submitted.id);Download a completed video (Node)
const path = await byteplus.seedance.downloadVideo("cgt-...", { outputDir: "downloads" });Note: the output
video_urlis deleted ~24 hours after the task is created (the taskiditself lives 7 days). Download promptly — usetask.createdAtto track the window.
Handle a callback webhook
If you pass callbackUrl to submit, ModelArk POSTs a payload identical to the
retrieve-task response when the task finishes. Wrap it with SeedanceTask.from
to get the same typed accessors:
import { SeedanceTask } from "byteplus-js/seedance";
const task = SeedanceTask.from(req.body);
if (task.status === "succeeded") console.log(task.videoUrl);
else console.error(task.error?.message);Estimate cost
ModelArk returns usage.completion_tokens for completed tasks. Pricing tables
live in src/seedance/pricing.ts (the single place to
update when BytePlus changes rates).
const task = await byteplus.seedance.retrieve("cgt-...");
const cost = byteplus.seedance.estimateCost(task, { inputIncludesVideo: true });
console.log(cost.amountUsd);Generic fal-style façade
Use the generic surface for any async content-generation model id. input maps
directly to the task request body (content + top-level params).
const result = await byteplus.subscribe("seedance-1-5-pro-251215", {
input: { content: [{ type: "text", text: "a kitten yawns" }], ratio: "16:9", duration: 5 },
onQueueUpdate: (u) => console.log(u.status), // IN_QUEUE | IN_PROGRESS | COMPLETED
});
console.log(result.data); // task content (e.g. { video_url })
// Or manage the queue yourself:
const { requestId } = await byteplus.queue.submit("seedance-1-5-pro-251215", { input });
const status = await byteplus.queue.status("seedance-1-5-pro-251215", { requestId });
const final = await byteplus.queue.result("seedance-1-5-pro-251215", { requestId });
await byteplus.queue.cancel("seedance-1-5-pro-251215", { requestId });The queue façade maps ModelArk's native task states (
queued/running/succeeded/failed/expired/cancelled) onto fal'sIN_QUEUE/IN_PROGRESS/COMPLETED. ModelArk does not expose queue logs or queue position, so those fal fields are unavailable.
Seedream image (sync)
const out = await byteplus.images.generate({
model: "seedream-5-0-lite-260128",
prompt: "A glass teapot with jasmine tea on a white table.",
size: "2048x2048",
responseFormat: "url",
watermark: false,
});
console.log(out.data[0]?.url);Embeddings (sync)
const res = await byteplus.embeddings.create({
model: "ep-your-embedding-endpoint",
input: "The quick brown fox.",
});
console.log(res.data[0]?.embedding);Errors
import { BytePlusAPIError, BytePlusTimeoutError } from "byteplus-js";
try {
await byteplus.seedance.subscribe({ model, prompt: "..." });
} catch (err) {
if (err instanceof BytePlusAPIError) console.error(err.statusCode, err.message, err.response);
if (err instanceof BytePlusTimeoutError) console.error("timed out", err.taskId);
}Smoke tests
The example scripts require a local .env (see .env.example):
npm run example:seedance -- --no-wait
npm run example:seedance -- --wait --download
npm run example:seedreamDevelop
npm install
npm run typecheck
npm test # vitest, no live API calls
npm run build # dual ESM/CJS + .d.ts via tsupLicense
MIT
