@bevyl-ai/bevyl-sdk
v0.9.1
Published
Official TypeScript SDK for the Bevyl API: signed requests, uploads, project generation and history, exports, and webhook verification.
Readme
Bevyl TypeScript SDK
@bevyl-ai/bevyl-sdk is the typed client for the Bevyl partner API. It
supports workspace setup, brand updates, video uploads, project generation and
history, exports, and webhook verification.
Requires Node.js 18 or newer and ES modules.
npm install @bevyl-ai/bevyl-sdkCreate a client
import { BevylClient } from '@bevyl-ai/bevyl-sdk';
const bevyl = new BevylClient({
apiKey: process.env.BEVYL_API_KEY!,
workspaceId: 'workspace-id',
});
await bevyl.createWorkspace({ name: 'Tasty Burgers' });
await bevyl.updateBrand({
name: 'Tasty Burgers',
summary: 'A neighborhood burger restaurant.',
});
const brand = await bevyl.getBrand();
console.log(brand.summary);createWorkspace is idempotent. The client sends its workspaceId and API key
with every request.
Upload and generate
uploadVideo prepares the upload, sends the file to the signed URL, and
completes the upload:
import { createReadStream } from 'node:fs';
import { stat } from 'node:fs/promises';
import { Readable } from 'node:stream';
const path = 'kitchen-broll.mp4';
const file = createReadStream(path);
try {
const upload = await bevyl.uploadVideo({
filename: path,
fileSize: (await stat(path)).size,
durationSeconds: 30,
contentType: 'video/mp4',
body: Readable.toWeb(file),
});
console.log(upload.video.id);
} finally {
file.destroy();
}Wait for the video to reach processed before creating a project. Wait for the
project to reach completed before starting an export.
const videoStatus = await bevyl.getVideoStatus({ videoId });
if (videoStatus.video.status !== 'processed') {
throw new Error(videoStatus.video.statusMessage);
}
const voices = await bevyl.listVoices();
const project = await bevyl.createProject({
format: 'voiceover',
sourceVideoIds: [videoStatus.video.id],
voiceoverProfileId: voices.defaultVoiceoverProfileId,
videoIdea: 'A 15-second vertical ad for the truffle burger.',
aspectRatio: '9:16',
});
const projectStatus = await bevyl.getProjectStatus({
projectId: project.projectId,
runId: project.generation.runId,
});
if (projectStatus.generation.status !== 'completed') {
throw new Error(
projectStatus.generation.statusMessage ??
`Generation ended with ${projectStatus.generation.status}`,
);
}
const started = await bevyl.startExport({ projectId: project.projectId });
await bevyl.getExportStatus({ exportId: started.exportId });For background music with no generated narration, use the voiceover format with an explicit null voice and a catalog track ID:
const { tracks } = await bevyl.listBackgroundMusic();
const musicOnlyProject = await bevyl.createProject({
format: 'voiceover',
sourceVideoIds: [videoStatus.video.id],
voiceoverProfileId: null,
backgroundMusicTrackId: tracks[0]!.id,
videoIdea: 'Caption the lunch rush with upbeat background music.',
aspectRatio: '9:16',
});For a trending-sounds project, select a published trend first:
const { trends } = await bevyl.listTrends();
const trend = trends[0];
if (!trend) {
throw new Error('No published trends are available');
}
await bevyl.createProject({
format: 'trending-sounds',
sourceVideoIds: [videoStatus.video.id],
trendId: trend.id,
videoIdea: 'Cut the lunch rush to the beat.',
});aspectRatio ('9:16', '4:5', '1:1', or '16:9') is optional; when
omitted, Bevyl infers it from the source videos and defaults to vertical.
Status responses in a working state include a suggested pollIntervalMs.
Terminal failures do not emit completion webhooks, so keep polling as a
fallback.
Regenerate a project
regenerateProject queues a new generation for an existing project. Omitted
brief fields carry over from the most recent generation; provided fields
override it, and an explicit null clears a prior userScript,
userMoments, minDuration, or maxDuration. The response has the same
shape as createProject.
const regen = await bevyl.regenerateProject({
projectId: project.projectId,
videoIdea: 'Same cut, but focus on dessert and slow the pacing.',
maxDuration: 30,
});
await bevyl.getProjectStatus({
projectId: project.projectId,
runId: regen.generation.runId,
});Regeneration returns 409 generation_in_progress while a generation is
running for the project, and 404 project_not_found when the project is not
in the mapped workspace. The regenerated timeline replaces the applied edit;
earlier versions remain available as snapshots in the Bevyl editor.
Review project history
listProjects returns projects newest first. Each project includes its
generation attempts newest first and the curated creative brief used for each
attempt. Pass nextCursor back to retrieve the next page.
const page = await bevyl.listProjects({ limit: 20 });
for (const project of page.projects) {
console.log(project.title, project.generations);
}
const nextPage = page.nextCursor
? await bevyl.listProjects({ limit: 20, cursor: page.nextCursor })
: null;Reuse uploaded source videos
listVideos returns upload sessions from the scoped partner workspace, newest
first, including uploads and processing that are still in progress. Every item
has an uploadId; videoId remains null until upload completion creates the
source video. Use a processed item's videoId in a later createProject call
without uploading the same source again.
const page = await bevyl.listVideos({ limit: 20 });
for (const video of page.videos) {
console.log(video.title, video.status, video.durationSeconds);
}
const nextPage = page.nextCursor
? await bevyl.listVideos({ limit: 20, cursor: page.nextCursor })
: null;Webhooks
Verify the signature against the raw request body, not parsed and re-serialized JSON:
import { SIGNATURE_HEADER, parseWebhookEvent } from '@bevyl-ai/bevyl-sdk';
const event = parseWebhookEvent({
rawBody,
signature: request.headers.get(SIGNATURE_HEADER),
secret: process.env.BEVYL_WEBHOOK_SECRET!,
});Supported events are broll.processed, project.completed, and export.ready.
Use event.eventId as an idempotency key.
Webhook signatures use x-webhook-signature:
sha256=<hex HMAC-SHA256 of the raw body>. The lower-level signBody and
verifySignature functions are also exported.
Schemas and errors
Request and response schemas are available from the separate schema export:
import { CreateProjectRequestSchema } from '@bevyl-ai/bevyl-sdk/schemas';
const request = CreateProjectRequestSchema.parse(input);Non-2xx API responses throw BevylApiError. It exposes the HTTP status, the
raw response body, and a machine-readable code when the API provides one.
