Guide: Upload and play your first video
Install @videohati/node, upload a file in chunks, wait until it is ready, and create a playback session — end to end.
This guide takes you from an empty project to a playable video using the official Node.js SDK. Every step runs against test mode, so you can follow along without touching live data.
Before you start
You need:
- Node.js 18 or newer (the SDK uses the built-in
fetchandAbortSignal). - A project test API key. Create one in the dashboard under
Project → API keys. A test key starts with
vh_test_. - A short video file to upload, for example
lecture-01.mp4.
Keep your API key on the server. It is project-scoped and grants write
access. Never ship a vh_live_ or vh_test_ key to a browser.
1. Install the SDK
npm install @videohati/node2. Initialize the client
The mode follows the key prefix: a vh_test_ key puts the client in test
mode automatically.
import { Videohati } from "@videohati/node";
const videohati = new Videohati({
apiKey: process.env.VIDEOHATI_API_KEY, // vh_test_...
});
console.log(videohati.mode); // "test"3. Upload a file in chunks
videos.upload() runs the full multipart flow in one call: it creates the
video, opens an upload session, PUTs every part to the upload endpoint, and
completes the upload. Pass a file path, a Uint8Array/Buffer, or a
Blob/File.
const uploaded = await videohati.videos.upload("./lecture-01.mp4", {
onProgress: ({ partsCompleted, totalParts }) => {
console.log(`Uploaded part ${partsCompleted}/${totalParts}`);
},
});
console.log(uploaded.videoId); // 01JZ9WV3N8GQ5T2M7K4C6XBARH
console.log(uploaded.state); // "uploaded"Want manual control instead of the one-call helper? The same flow is
available as videos.create(), videos.startUpload(), the per-part
PUT requests, and videos.completeUpload().
4. Wait until the video is ready
After the upload completes, encoding runs in the background. Poll the video
with videos.waitForState() until it reaches ready (or failed).
const video = await videohati.videos.waitForState(uploaded.videoId, {
targetStates: ["ready", "failed"],
});
if (video.state === "failed") {
throw new Error(`Encoding failed: ${video.errorMessage ?? video.errorCode}`);
}
console.log("Video is ready to stream.");5. Create a playback session
A playback session mints a short-lived, signed HLS manifest URL plus a
sessionToken for the player. Create it server-side and hand only the
session — never the API key — to the page.
const session = await videohati.playback.createSession({
videoId: video.id,
viewerDisplayText: "[email protected]", // shown in the watermark
});
console.log(session.manifestUrl); // signed master.m3u8 URL
console.log(session.sessionToken); // pass this to the playerThe response includes everything the player needs:
| Field | Meaning |
|---|---|
manifestUrl | Signed HLS master playlist URL. |
sessionToken | Ownership token for the embedded player. |
heartbeatIntervalSeconds | How often the player re-asserts the session. |
expiresAt | When the session and its signed URL expire. |
6. Embed the player
Hand the videoId, projectId, and sessionToken to the web player. The
framework-free bundle reads them from data- attributes:
<script src="https://cdn.videohati.com/player.js" async></script>
<div
data-videohati-video-id="01JZ9WV3N8GQ5T2M7K4C6XBARH"
data-videohati-project-id="01JZ9WV3N8GQ5T2M7K4C6XBARP"
data-videohati-session-token="01JZ9WV3N8GQ5T2M7K4C6XBARK.1781485200.bXktbWFj"
></div>For a React app, see Embed a Videohati player in a Next.js 15 app.
Full script
import { Videohati } from "@videohati/node";
async function main() {
const videohati = new Videohati({ apiKey: process.env.VIDEOHATI_API_KEY });
const uploaded = await videohati.videos.upload("./lecture-01.mp4", {
onProgress: ({ partsCompleted, totalParts }) =>
console.log(`Uploaded part ${partsCompleted}/${totalParts}`),
});
const video = await videohati.videos.waitForState(uploaded.videoId);
if (video.state === "failed") {
throw new Error(`Encoding failed: ${video.errorMessage ?? video.errorCode}`);
}
const session = await videohati.playback.createSession({
videoId: video.id,
viewerDisplayText: "[email protected]",
});
console.log("Manifest:", session.manifestUrl);
console.log("Session token:", session.sessionToken);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});