API v1

Immersive AI Video Generator SDK

Add BuildVR's Immersive AI Video Generator to your own website. Your users upload a normal video, pick the scenes they want, and each picked scene is converted into an immersive cylindrical video (IMRS 2.2). The scenes are then stitched into one final video with the original audio.

The flow
Upload video → choose scenes → convert → check results → create final video

There are two ways to use it:

  • Ready-made React components — drop in the complete tool (upload page, scene picker, player, final video) with a few lines. No third-party branding, and you can match its colours to your site.
  • The JavaScript API — build your own interface on the same calls the components use.

Both run in the browser with no backend of your own and no secret key. Detected cuts are used as they are; changing a scene's fill, AI prompt or cut points is optional.

Prerequisites

Enterprise plan required

The SDK works on BuildVR enterprise plans only. On other plans, every call returns upgrade_required. If you haven't subscribed yet, view enterprise pricing.

  1. A BuildVR enterprise plan.
  2. A publishable client key (PUB key). Its client_id is the only credential the SDK uses.
  3. Your website's domain on that key's allowed domains, for example www.example.com, plus your local development host such as localhost:5173.

Step 1 — Create API Credentials

After your plan is active, generate a publishable key from your profile:

https://buildvr.ai/profile/api-credentials

Manage, rotate, and revoke your API credentials from this page.

Add Allowed Domains

BuildVR only accepts requests from the browser's real Origin, which page scripts can't fake, and only if that origin is on the key's allowed list. Add every host that will load the SDK, separated by commas:

Allowed Domains input — example
localhost:3000, localhost:5173, www.example.com
Note: Include the port for local development. Do not include http:// or https://.

Save Your Client ID

Copy the key's client ID. You'll pass it as clientId. It can safely appear in your page source, but keep it in an environment variable so you can rotate it easily. Everything users do through the SDK (uploads, conversions, credits) is billed to the account that owns the key.

.env (Next.js)
NEXT_PUBLIC_VIDEO_CLIENT_ID=YOUR_CLIENT_ID

Step 2 — Install the Package

npm
npm install @buildvr/immersive-ai-video-generator
yarn
yarn add @buildvr/immersive-ai-video-generator
pnpm
pnpm add @buildvr/immersive-ai-video-generator

The React components need React 18 or later (react and react-dom). Without a bundler, load the API from a CDN:

Script tag
<script src="https://unpkg.com/@buildvr/immersive-ai-video-generator@0.3"></script>
<script>
  // window.SceneReview
  SceneReview.init({ clientId: "YOUR_CLIENT_ID" });
</script>

Changelog and release notes: npmjs.com/package/@buildvr/immersive-ai-video-generator

Step 3 — Add the Drop-in Components

Everything in one component

ImmersiveVideoGenerator gives your users the whole flow: the upload page, the job page with scene picking, conversion and the final video download, and a list of earlier jobs. It connects with clientId itself, so you don't need to call init(). Import the stylesheet once.

VideoToolPage.jsx
import { ImmersiveVideoGenerator } from "@buildvr/immersive-ai-video-generator/panel";
import "@buildvr/immersive-ai-video-generator/panel/style.css";

export default function VideoToolPage() {
  return (
    <ImmersiveVideoGenerator
      clientId={process.env.NEXT_PUBLIC_VIDEO_CLIENT_ID}
      onComplete={(job) => console.log("Final video:", job.output_url)}
    />
  );
}

To keep the open job in the URL so reloads and shared links work, control it yourself:

Controlled job ID
const [jobId, setJobId] = useState(new URLSearchParams(location.search).get("job"));

<ImmersiveVideoGenerator
  clientId={process.env.NEXT_PUBLIC_VIDEO_CLIENT_ID}
  jobId={jobId}                 // null = upload page
  onJobChange={(id) => {        // job created or opened, or "New video" clicked
    setJobId(id);
    history.pushState(null, "", id ? `?job=${id}` : location.pathname);
  }}
/>

One component per page (Next.js and other routers)

If your site has routing, use an upload page and a job page. A Next.js App Router example:

app/video/page.jsx
"use client";
import { useRouter } from "next/navigation";
import { CreateVideo } from "@buildvr/immersive-ai-video-generator/panel";
import "@buildvr/immersive-ai-video-generator/panel/style.css";

export default function NewVideoPage() {
  const router = useRouter();
  return (
    <CreateVideo
      clientId={process.env.NEXT_PUBLIC_VIDEO_CLIENT_ID}
      onCreated={(jobId) => router.push(`/video/${jobId}`)}
    />
  );
}
app/video/[jobId]/page.jsx
"use client";
import { use } from "react";
import { useRouter } from "next/navigation";
import { VideoJob } from "@buildvr/immersive-ai-video-generator/panel";
import "@buildvr/immersive-ai-video-generator/panel/style.css";

export default function VideoJobPage({ params }) {
  const { jobId } = use(params);
  const router = useRouter();
  return (
    <VideoJob
      clientId={process.env.NEXT_PUBLIC_VIDEO_CLIENT_ID}
      jobId={jobId}
      onNewVideo={() => router.push("/video")}
    />
  );
}

The components are already marked as client components. With React Router, Vue Router or any other router the pattern is the same: navigate in onCreated, and pass the job ID from the URL to VideoJob.

The components are designed as full-page tools and fill at least the window height. Give them their own page or a large area of a page.

Component Props

All components accept clientId, plus optional apiBase, className and style. If your page already called SceneReview.init(), you can leave out clientId.

<ImmersiveVideoGenerator />

PropDescription
jobId, onJobChange(jobId | null)Optional. Control which job is open.
onComplete(job)Called once when the final video is ready. job.output_url is the video.

<CreateVideo /> — the upload page

PropDescription
onCreated(jobId)Required. Called when the job is created, and when the user opens an earlier job from the sidebar. Navigate to the job page here.
onOpenJobs()Optional. Makes the “Jobs” breadcrumb a link to your jobs page.
showJobsDefault true. Shows “Resume a job” and recent jobs in the sidebar.

<VideoJob /> — one job, from scenes to final video

PropDescription
jobIdRequired.
onNewVideo()Optional. Shows a “New video” button.
onOpenJobs()Optional. Makes the “Jobs” breadcrumb a link.
onComplete(job)Called once when the final video is ready.

<JobsList /> — recent jobs for your own jobs page

PropDescription
onOpenJob(jobId)Required.
limitJobs per page. Default 8.

Matching Your Brand

The components use a dark theme. Override these CSS variables with style or className:

Custom colours
<ImmersiveVideoGenerator
  clientId={process.env.NEXT_PUBLIC_VIDEO_CLIENT_ID}
  style={{ "--cyan": "#a78bfa", "--cyanStrong": "#8b5cf6", "--bg": "#0b0b12" }}
/>
VariableUsed for
--cyan, --cyanStrongAccent colour, primary buttons, focus rings
--bg, --panel, --card, --inputBackgrounds
--border, --text, --muted, --faintLines and text
--ok, --warn, --badStatus colours

How a Job Works

StepWhat happensHow you know it's done
1. UploaduploadVideo() sends the file to storage and creates a job.You get job_id.
2. DetectBuildVR finds the scene cuts and makes a preview clip and thumbnail for each scene. Usually a few minutes, depending on video length.review.stage === "review" and review.scenes has items.
3. Choose & adjust (optional edits)Choose the scenes to keep. Optionally change a scene's fill or AI prompt, move its cuts, split it, or merge it with the next scene.Your UI decides.
4. ConvertconvertScenes() queues the chosen scenes. Each one converts on its own.Each scene goes queued → processing → completed, with an output_url.
5. Check (optional)Play scene.output_url next to scene.preview_url. Call reprocessScene() to redo one.—
6. Final videocreateFinalVideo() stitches the chosen scenes in order and adds back the original audio. Scenes you didn't choose are left out and not billed.job.status === "completed" and job.output_url is set.

After step 6 starts, the job is locked and no more edits are possible.

Fill modes

Cylindrical video is much wider than the source, so the extra space has to be filled:

fillModeResult
"ai" (default)AI generative expand: new scenery that continues the shot, optionally guided by a prompt.
"blur"A blurred, stretched copy of the frame.
"black" / "white"Solid bars.
Converted scene clips are silent. Only the final video has audio.

Building Your Own UI with the API

All methods are on the default export, SceneReview. Every method except init() resolves to a result object and never throws on API errors.

Quick start
import SceneReview from "@buildvr/immersive-ai-video-generator";

// 1. Connect once per page load. Throws if the key, domain or plan is wrong.
await SceneReview.init({ clientId: "YOUR_CLIENT_ID" });

// 2. Upload. Creates the job and starts scene detection.
const upload = await SceneReview.uploadVideo(file, {
  name: "Beach trip",
  fillMode: "ai",
  targetResolution: "4k",
  onProgress: ({ stage, progress }) => console.log(stage, Math.round(progress * 100) + "%"),
});
if (!upload.success || !upload.job_id) throw new Error(upload.error);
const jobId = upload.job_id;

// 3. Wait for scene detection.
let review = await waitFor(jobId, (r) => r.stage === "review" && r.scenes.length);

// 4. Choose scenes (here: all of them) and convert them.
const picked = review.scenes.map((s) => s.id);
await SceneReview.convertScenes(jobId, picked);

// 5. Wait until every picked scene has a converted output.
review = await waitFor(jobId, (r) =>
  r.scenes.filter((s) => picked.includes(s.id)).every((s) => s.output_url && s.status === "completed"));

// 6. Stitch the final video and wait for it.
await SceneReview.createFinalVideo(jobId, picked);
let job;
do {
  await sleep(5000);
  ({ job } = await SceneReview.getJob(jobId));
} while (job && !["completed", "failed"].includes(job.status));

console.log("Final video:", job.output_url);

// Helpers
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function waitFor(jobId, done) {
  for (;;) {
    const res = await SceneReview.getReview(jobId);
    if (res.success && done(res.review)) return res.review;
    await sleep(3000);
  }
}

Methods

MethodDescription
init({ clientId, apiBase? })Checks the key, domain and plan. Call once before anything else. Throws on failure.
getSession() / destroy()Read or forget the current session, for example on sign-out.
uploadVideo(file, options)Uploads a File or Blob and creates the job. Options: name, fillMode, targetResolution ("4k", "5.7k", "8k" or "WIDTHxHEIGHT"), onProgress, signal.
listJobs({ page, limit, status? })This account's jobs, newest first.
getJob(jobId)The job's overall state, including output_url for the final video.
readVideoDuration(file)A local video's length in seconds, or null.
getReview(jobId)The job and all its scenes. This is the call to poll.
convertScenes(jobId, sceneIds, settings?)Converts the chosen scenes and retries failed ones. settings: { [sceneId]: { fillMode?, prompt? } }.
reprocessScene(jobId, sceneId, opts)Converts a scene again, optionally with a new fill or prompt. Free up to free_reprocesses_left, then uses wallet credits.
createFinalVideo(jobId, sceneIds)Stitches the listed, converted scenes into the final video. Locks the job.
updateSceneSettings(jobId, sceneId, { fillMode })Saves a scene's fill before it is converted.
updateBoundaries / splitScene / mergeScenesOptional cut edits, in frame numbers (review.fps frames per second).
retryPreview(jobId, sceneId)Retries a scene preview that failed to generate.

The full reference, including the review and Scene data shapes and cut-editing rules, is in the package README.

Handling Errors

Every call except init() resolves to an object like this on failure:

Error result
{ success: false, status_code: 409, error: "Human-readable message", code: "machine_readable_code", ...details }
codeWhenWhat to do
quota_exceededFrom uploadVideo: the plan's quota is used up and the wallet can't cover the overage.Recharge at buildvr.ai/profile/credits.
insufficient_creditsA reprocess needs more wallet credits (402).Recharge at buildvr.ai/profile/credits.
not_ready_to_stitchA chosen scene isn't converted yet. scene_ids lists which.Convert them, or remove them from the list.
no_scenesEmpty sceneIds.Choose at least one scene.
scene_convertingThe scene is busy converting.Wait, then retry.
preview_not_readyThe preview is still updating after a cut edit.Wait for preview_status: "ready".
output_requires_reprocessupdateSceneSettings on a converted scene.Use reprocessScene instead.
discard_outputs_requiredA cut edit touches converted scenes.Confirm with the user, then retry with { confirmDiscardOutputs: true }.
neighbour_too_shortA cut edit would leave a neighbouring scene too short.Retry updateBoundaries with { mergeShort: true }.
not_in_reviewScenes are still being detected, or the final video has started.Check review.stage.
job_not_foundWrong ID, or the job belongs to another account (404).—

Account-level failures

status_codeMeaning
401 / 403Unknown or inactive clientId, or this page's domain isn't on the key's allowed list.
400 with upgrade_required: trueThe account isn't on an enterprise plan.

Polling

There are no webhooks yet, so poll for progress. The drop-in components do this for you.

Waiting forCallSuggested interval
Scene detectiongetReview3 s
Scene conversions / preview updatesgetReview3 s
Idle (user is choosing scenes)getReview15 s, or stop polling
Final videogetJob3–5 s
Stop polling once job.status is completed or failed. While an action is running, pause polling so an older response doesn't overwrite the newer review the action returns.

Billing

  • Uploads and conversions count against the plan of the account that owns the client key.
  • When the plan's quota is used up, extra storage and processing minutes are paid automatically from the account's wallet credits, with no confirmation step. Calls fail with quota_exceeded or insufficient_credits only when the wallet runs short.
  • You're billed only for scenes in the final video. Scenes left out are refunded when the final video is made.
  • Each scene includes free reprocesses (init().scene_review.free_reprocesses_per_scene). After those, reprocesses use wallet credits at the scene's length.

Troubleshooting

init() throws "Origin not authorized for this key"?

Add the exact host shown in the browser's address bar, including the port for local development, to the key's allowed domains.

Can I call this from my server (Node)?

No. The SDK depends on the browser's Origin header and uses XMLHttpRequest and <video> for uploads. For server-to-server work, use the Video Processing API with a secret key.

Which browsers are supported?

Current versions of Chrome, Edge, Safari and Firefox.

Next Steps

Try the tool

See exactly what your users will get. Open the Immersive AI Video Generator →

Manage API credentials

Rotate keys, update domain allowlists, and view usage. Open credentials dashboard →

npm package page

Full API reference, release notes and version history. View on npm →

Need help?

Reach our engineering team for integration support. Open a support ticket →