@usevelo/client
v0.1.1
Published
Official TypeScript SDK for Velo live video: server-side REST client, browser-safe room joining, and data usage reporting for metered mobile networks
Readme
@usevelo/client
Add live audio and video to a web application. This is the official TypeScript SDK for Velo: a server-side client for the REST API, a browser-safe helper for joining a call, and data usage reporting for users on metered mobile networks.
It is a thin wrapper rather than a fork. Media handling stays in
livekit-client, which is a peer dependency, so
upgrading the media stack is a version bump on your side.
- Documentation: usevelo.xyz/docs
- Quickstart: usevelo.xyz/docs/quickstart
- Source and issues: github.com/judeotine/Velo
Never put a Velo API key in a browser. A
vk_...key controls your whole project. Browsers receive only a short-lived room token. See Security model.
Requirements
- Node.js 18 or newer for server-side use
- A browser with WebRTC support for calls
livekit-client2.15 or newer as a peer dependency
Install
npm install @usevelo/client livekit-clientGet an API key
- Sign in at usevelo.xyz.
- Create a project.
- Create an API key for that project and store it as a server-side secret.
The API base URL is https://api.usevelo.xyz.
Quickstart
Two pieces of code. Your backend mints a token; the browser joins with it.
On your server
import { VeloClient } from "@usevelo/client";
const velo = new VeloClient({
baseUrl: "https://api.usevelo.xyz",
apiKey: process.env.VELO_API_KEY,
});
await velo.createRoom("consultation-42", { maxParticipants: 2 });
const token = await velo.createToken({
room: "consultation-42",
identity: "patient-1187",
ttlSeconds: 3600,
});Return token.token and token.url to the browser, and nothing else.
In the browser
import { connectToRoom, RoomEvent } from "@usevelo/client";
const { token, url } = await fetch("/api/velo-token").then((r) => r.json());
const room = await connectToRoom({ token, url });
room.on(RoomEvent.TrackSubscribed, (track) => {
document.body.appendChild(track.attach());
});connectToRoom re-exports the livekit-client types you need, so Room, RoomEvent,
Track and the participant classes all come from this package.
Security model
A project API key grants full control: creating rooms, minting tokens for any identity, removing participants, starting recordings, reading usage. Treat it like a database password.
| | Where it runs | What it holds |
| --- | --- | --- |
| VeloClient | Your server only | The project API key |
| VeloAdminClient | Your server only | An admin token |
| connectToRoom | Browser | A room token, scoped to one identity and one room |
| exchangeRoomCode | Your server | Nothing, it is unauthenticated |
The correct shape is always: browser asks your backend for a token, backend calls createToken,
browser calls connectToRoom.
Joining without a token backend
A room code is a short, shareable string bound to one room and one role, so you can put someone into a call without running a token endpoint.
const code = await velo.createRoomCode("consultation-42", {
role: "patient",
ttlSeconds: 3600,
maxUses: 1,
});Redeem it from your server, not the browser. exchangeRoomCode is a standalone function rather
than a VeloClient method precisely because it needs no credentials, and the API's CORS allowlist
admits only the Velo console origin, so a browser call is rejected before it reaches the handler.
import { exchangeRoomCode } from "@usevelo/client";
const token = await exchangeRoomCode({
baseUrl: "https://api.usevelo.xyz",
code: "abcdefghjkmnpqrs",
identity: "patient-1187",
});Codes always expire: 24 hours by default, 30 days at most. Disabled, expired, exhausted and unknown codes all fail identically, so a caller learns nothing about the code space.
Data usage on metered networks
Mobile data is a real cost for end users. DataUsageMonitor reports what a call is actually
consuming so your application can show it or react to it.
import { DataUsageMonitor, suggestAudioOnly, setAudioOnly } from "@usevelo/client";
const monitor = new DataUsageMonitor(room, { intervalMs: 5000, pricePerMbUgx: 120 });
monitor.on("update", (snapshot) => {
showBanner(`${(snapshot.totalBytes / 1_000_000).toFixed(1)} MB`);
if (suggestAudioOnly(snapshot)) setAudioOnly(room, true);
});Each snapshot carries bytesSent, bytesReceived, totalBytes, sendBitrateBps,
recvBitrateBps, estimatedCostUgx and durationMs.
Errors
Failures arrive as typed errors rather than raw responses.
import { VeloApiError, VeloPermissionError, VeloQuotaError } from "@usevelo/client";
try {
await velo.removeParticipant("consultation-42", "patient-1187");
} catch (error) {
if (error instanceof VeloQuotaError) {
showUpgradePrompt();
} else if (error instanceof VeloPermissionError) {
disableButton(error.permission);
} else if (error instanceof VeloApiError) {
report(error.status, error.code, error.message);
}
}VeloQuotaError is separate because hitting a plan limit is an expected, actionable state rather
than a bug. VeloPermissionError carries the specific permission that was missing, such as
remove_others, so a UI can disable the control that produced it instead of parsing a message.
VeloConnectionError covers transport failures.
What the client covers
VeloClient wraps the full control plane: rooms, tokens, participants and data messages,
recordings, streaming, templates and roles, room codes, destinations, runtime role changes,
sessions and usage, webhook endpoints and deliveries. VeloAdminClient adds project and plan
administration.
Every method maps to one documented endpoint. The full reference, with request and response shapes for each, lives at usevelo.xyz/docs.
TypeScript and module formats
Types ship with the package. Both ESM and CommonJS builds are published, so import and
require both work without a bundler shim.
Contributing
The SDK lives in sdks/web of the Velo
repository.
npm install
npm run typecheck
npm test
npm run buildReleases are described in RELEASING.md.
License
MIT. See LICENSE.
