SDK Documentation
The official JavaScript / TypeScript SDK (ultron-live-sdk). Viewing the v2 surface.
Overview & Install
The official JavaScript / TypeScript SDK for Ultron Live — real-time AI vision, screen commentary, WebRTC streaming, realtime voice, and assessment. It works in the browser and in Node, and ships with full TypeScript types.
Install
npm install ultron-live-sdkImport
// ESM (recommended)
import { UltronLive } from "ultron-live-sdk";
// CommonJS
const { UltronLive } = require("ultron-live-sdk");Or via UMD in a plain browser page:
<script src="https://cdn.skypack.dev/ultron-live-sdk"></script>ulk_…) or a JWT to use protected features. Get a key from your dashboard, or via the auth flow below.Quick Start
Initialize with your key and start a screen-share commentary loop in three lines:
import { UltronLive } from "ultron-live-sdk";
const ultron = new UltronLive({ apiKey: "ulk_your_api_key" });
await ultron.startScreenShare({
model: "gemini-2.5-flash",
onTranscript: (text) => console.log("AI:", text),
onCreditsUpdate: (remaining) => console.log("credits:", remaining),
onCreditsExhausted: () => alert("Top up to continue"),
});
// later…
ultron.stop();Configuration
Create one instance with new UltronLive(config). Everything (REST, WebRTC, realtime voice) inherits these settings — including the API version.
const ultron = new UltronLive({
apiKey: "ulk_your_api_key", // or token: "<jwt>"
apiVersion: "v1", // "v1" (default) or "v2"
model: "gemini-3.1-flash-lite-preview",
frameInterval: 1500, // ms between captured frames
frameQuality: 0.4, // JPEG quality 0–1
maxFrameWidth: 1280,
maxFrameHeight: 720,
});| Field | Type | Required | Notes |
|---|---|---|---|
| apiKey | string | required | Your key (ulk_…). Either apiKey or token is required. |
| token | string | optional | JWT from auth.verifyOtp(). |
| baseUrl | string | optional | Backend URL. Defaults to the production API. |
| apiVersion | 'v1' | 'v2' | optional | Backend workflow. Default 'v1'. See Versioning. |
| model | ModelValue | optional | Default vision model. Default gemini-3.1-flash-lite-preview. |
| historyWindow | number | optional | Recent commentary strings kept for context. Default 5. |
| frameInterval | number | optional | ms between frame captures. Default 1500. |
| frameQuality | number | optional | JPEG quality 0–1. Default 0.4. |
| maxFrameWidth | number | optional | Max capture width px. Default 1280. |
| maxFrameHeight | number | optional | Max capture height px. Default 720. |
Read it back any time via ultron.config, and check the active version with ultron.apiVersion. Update credentials at runtime with ultron.setApiKey(key) or ultron.setToken(jwt).
API Versions (v1 & v2)
The backend runs two parallel workflows. Choose one per instance with apiVersion — it flows through REST, createWebRTC(), and createRealtimeVoice() automatically.
| v1 — Stable (default) | v2 — Current | |
|---|---|---|
| Vision pipeline | One model call per frame | Golden Frame pipeline (filter + two-tier) |
| Extra features | — | Assessment engine, MCP servers |
| Realtime voice | Standard relay | Relay with realtime tools |
// Stable (default) — unchanged behavior
const ultron = new UltronLive({ apiKey: "ulk_..." });
// Current — server-side frame filtering, assessment, MCP, realtime tools
const ultronV2 = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });v1 is the default, so upgrading the package never changes behavior. Calling a v2-only API (assessment.*, mcp.*) on a v1 instance throws a clear error.Running the classic per-frame pipeline
On v2, frame filtering is managed by Ultron and tuned server-side — there is no client-side toggle, and any such keys passed to startSession() are stripped before the message is sent. To run the classic one-call-per-frame mode, use the v1 pipeline:
const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v1" });Authentication
The flow is always: request an OTP → verify it → get a JWT + API key. Use signup for a new account, login for an existing one.
// 1. Request an OTP (emails a 6-digit code)
await ultron.auth.signup({ email: "user@example.com", firstName: "Ada" }); // new user
// or: await ultron.auth.login({ email: "user@example.com" }); // existing user
// 2. Verify the code → JWT + user (incl. permanent apiKey)
const { data } = await ultron.auth.verifyOtp({ email: "user@example.com", otp: "123456" });
ultron.setToken(data.token);
console.log(data.user.apiKey, data.user.credits);
// 3. Live profile + credits any time
const me = await ultron.auth.getMe();| Method | Description |
|---|---|
| auth.signup(opts) | New account → emails an OTP. |
| auth.login(opts) | Existing account → emails an OTP. |
| auth.sendOtp(opts) | Legacy get-or-create + OTP (kept for compatibility). |
| auth.verifyOtp(opts) | Verify OTP → { token, user }. |
| auth.getMe() | Current profile + live credit balance. |
| setToken(jwt) / setApiKey(key) | Update credentials at runtime. |
Memory & History
Ultron remembers across sessions. Each night it distils a user's sessions into short facts that stay true — what they are building, which tools they use, how they like to be helped — and states them back in every later conversation.
const { data } = await ultron.memory.get();
data.enabled; // false = Ultron neither learns nor recalls
data.facts.forEach((f) => {
console.log(f.text, f.established ? "" : "(seen once — tentative)");
});
// The user correcting Ultron. A user-stated fact outranks anything inferred.
await ultron.memory.addFact({ text: "Prefers short answers", category: "preference" });
await ultron.memory.updateFact(factId, { text: "Uses MongoDB Atlas" });
await ultron.memory.pinFact(factId); // never evicted
await ultron.memory.deleteFact(factId);
await ultron.memory.clear(); // forget everything
await ultron.memory.setEnabled(false); // stop remembering entirely
// Distillation runs nightly; run it now to see it work immediately.
await ultron.memory.refresh();Session history
Every session gets a close-out digest when it ends — one paragraph describing the whole session — so history stays readable without replaying hundreds of frames.
const { data } = await ultron.sessions.list({ limit: 20 });
data.sessions.forEach((s) => console.log(s.startedAt, s.summary));
const detail = await ultron.sessions.get(sessionId, { frames: 100 });
detail.data.summary; // the close-out digest
detail.data.frames; // timeline: commentary, tags, image URLs
await ultron.sessions.remove(sessionId); // Ultron can no longer recall itHow the AI uses it
You do not wire any of this into chat(). The backend assembles context itself and picks how far back to look from the question — the live screen only for "what am I looking at?", the last couple of hours for "what did I just do?", and deep history ranked purely by relevance for "what did we decide back in July?". Profile memory is included in every request regardless.
| Method | Description |
|---|---|
| memory.get() | Everything Ultron remembers about the user. |
| memory.setEnabled(bool) | Master switch for learning and recall. |
| memory.addFact(input) | State a fact directly { text, category?, pinned? }. |
| memory.updateFact(id, patch) | Correct a fact; promotes it to a user statement. |
| memory.pinFact(id, pinned?) | Protect a fact from eviction. |
| memory.deleteFact(id) | Forget one fact. |
| memory.clear() | Forget everything. |
| memory.refresh(force?) | Distil recent sessions now. |
| sessions.list(opts?) | Past sessions, newest first { page?, limit?, from?, to? }. |
| sessions.get(id, opts?) | One session: digest, summaries, frame timeline. |
| sessions.remove(id) | Delete a session. |
Account
Profile fields, and issuing browser-safe tokens for your own end users.
await ultron.auth.updateProfile({ firstName: "Ada", dateOfBirth: "1990-12-10" });
await ultron.auth.updateTimezone({ timezone: "Asia/Kolkata" });Session tokens (server-side)
Never ship your API key to a browser. Mint a short-lived token on your server, hand it to the page, and give the page a tokenProvider that fetches a fresh one when it expires.
// On your server — requires the raw API key.
const { data } = await ultron.auth.sessionTokens.create({
endUserId: "user_42",
ttlSeconds: 900,
});
res.json({ token: data.token });
// If a token leaks, kill it — or block that end user entirely.
await ultron.auth.sessionTokens.revoke({ jti: data.jti });
await ultron.auth.sessionTokens.revoke({ endUserId: "user_42" });
await ultron.auth.sessionTokens.unblock("user_42");Screen & Video Streaming
The high-level streaming API requests screen/camera access, runs a capture loop, and emits callbacks — no manual frame handling.
// Screen share
await ultron.startScreenShare({
onTranscript: (text) => render(text),
onCreditsUpdate: (n) => setCredits(n),
onError: (e) => console.error(e),
});
// Any MediaStream (camera, <video>.captureStream(), etc.)
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
await ultron.startVideoStream({ stream, onTranscript: console.log });
ultron.stop();Live preview
ultron.attachPreview(document.getElementById("preview")); // attach captured stream to a <video>
const stream = ultron.getPreviewStream(); // or wire it up yourselfVoice narration
// ElevenLabs with your own key — the backend no longer synthesizes speech.
ultron.configureAudio({ voiceProvider: "elevenlabs", elevenLabsApiKey: "sk_..." });
ultron.setAudioEnabled(true);| Method | Description |
|---|---|
| startScreenShare(opts?) | Request screen share + run the commentary loop. |
| startVideoStream(opts) | Run commentary on any MediaStream. |
| stop() | Stop the active loop and clean up. |
| attachPreview(el) / detachPreview(el) | Attach/detach the live stream to a <video>. |
| getPreviewStream() | The active MediaStream, or null. |
| setAudioEnabled(bool) / configureAudio(opts) | Toggle and configure ElevenLabs narration. |
| isStreaming / sessionId | Stream state + active session id. |
Frame Analysis & Chat
Low-level REST calls when you want to drive frames/chat yourself.
const { sessionId } = await ultron.startSession();
const result = await ultron.analyseFrame({
image: "data:image/jpeg;base64,...",
history: previousCommentaries,
sessionId,
});
console.log(result.response, result.creditsRemaining);
const reply = await ultron.chat({
sessionId,
message: "What was I working on?",
});WebRTC (Low-Latency)
A persistent peer connection with data channels for frames, chat, and control — much lower latency than REST. Create one pre-wired with your credentials and version:
const rtc = ultron.createWebRTC();
rtc.onFrameResult = (r) => render(r.response, r.creditsRemaining);
rtc.onConnected = () => rtc.startSession();
await rtc.connect();
// Send frames (binary preferred — no base64 bloat)
canvas.toBlob((blob) => rtc.sendFrame(blob), "image/jpeg", 0.4);
// Chat over the data channel
rtc.sendChat("What changed on screen?");
rtc.stopSession();
rtc.disconnect();v2: Golden pipeline & live assessment
On apiVersion: "v2" the session runs the Golden Frame pipeline and can act as a live assessment session. Pass seq + captureTs so frames can be ordered, and read info.ingestFps to pace your capture.
const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });
const rtc = ultron.createWebRTC();
rtc.onFrameResult = (f) => console.log("frame:", f.response);
rtc.onFlowResult = (s) => console.log("summary:", s.response); // tier-2 narrative
// Assessment session — structured verdict instead of commentary
rtc.onAssessmentResult = (r) => console.log("verdict:", r.assessment);
rtc.onConnected = () => rtc.startSession({
outputType: "json",
prompt: "Verify the procedure.",
responseSchema: { type: "object", properties: { verdict: { type: "string" } }, required: ["verdict"] },
});
await rtc.connect();
let seq = 0;
setInterval(() => rtc.sendFrameBase64(base64, { seq: seq++, captureTs: Date.now() }), 150);
// rtc.stopSession() → emits the final assessment:result
// rtc.finalize() → verdict mid-stream, keep streamingMethods & callbacks
| Method | Description |
|---|---|
| connect() | Connect; resolves when all 3 channels are open. |
| sendFrame(blob | buffer) | Send a binary JPEG frame. |
| sendFrameBase64(b64, { seq?, captureTs? }) | Send a base64 frame with ordering metadata (v2). |
| sendChat(message, opts?) | Send a chat message. |
| startSession(config?) | Start a session; pass assessment config (outputType, prompt, template) on v2. |
| stopSession() / finalize() | End the session / emit a verdict mid-stream (v2). |
| setModel(m) / ping() / disconnect() | Change model, measure latency, tear down. |
Callbacks: onFrameResult, onFrameError, onChatResult, onSessionStarted(id, info?), onConnected, onDisconnected, and (v2) onFlowResult, onAssessmentProgress, onAssessmentResult, onAssessmentError, onSessionStartError.
Realtime Voice
Two-way live voice with OpenAI Realtime or Gemini Live, normalized to one protocol. Audio plays back automatically by default.
const voice = ultron.createRealtimeVoice({ provider: "gemini" });
voice.onReady = () => console.log("ready — start streaming mic");
voice.onTranscriptDelta = (t) => appendCaption(t);
voice.onUserTranscript = (t) => showUserSaid(t);
await voice.connect(); // waits for relay:ready
voice.sendAudio(base64Pcm16Chunk); // PCM16 mono, 16kHz (gemini) / 24kHz (openai)
voice.disconnect();onReady before sending audio. The relay does not resample — send 16 kHz for Gemini, 24 kHz for OpenAI; playback is 24 kHz. Server VAD handles turn-taking and barge-in.Assessment
apiVersion: "v2". Turns media (a video, frames, or documents) into a calibrated PASS / FAIL / UNCERTAIN verdict with a confidence score.assessAndWait uploads the media and polls until the verdict is ready:
const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });
const result = await ultron.assessment.assessAndWait({
media: [fileInput.files[0]], // video, frames, or documents (≤30, ≤18 MB each)
outputType: "json",
prompt: "Verify the sample-collection procedure.",
});
// Calibrated verdict (json output):
const v = JSON.parse(result.assessment);
v.verdict; // 'PASS' | 'FAIL' | 'UNCERTAIN'
v.confidence; // 0–100 (lowest per-check confidence)
v.needs_review; // true → route to a human
v.checks; // [{ name, result, confidence, observed }]Calibrated verdict & human-in-the-loop
For outputType: "json" the fields verdict, confidence, needs_review and per-question checks[] are guaranteed. An element that can't be directly verified is capped low and marked UNCERTAIN (absence of evidence is never a confident PASS/FAIL). When confidence is below the template's confidenceThreshold (default 70), the verdict is forced to UNCERTAIN + needs_review: true — so a human is engaged automatically.
const v = JSON.parse(result.assessment);
if (v.needs_review) routeToHumanReviewer(v); // low confidence or genuinely uncertain
else applyAutomatically(v.verdict);Add or skip questions
Adjust a template's questions for one run without editing it — append extraChecks or drop existing ones with skipChecks (by 1-based index or title). The same fields work on a live rtc.startSession() call.
await ultron.assessment.assessAndWait({
media: [file],
templateId: tpl.id,
extraChecks: [{ title: "Gloves worn", instruction: "Were gloves worn the whole time?" }],
skipChecks: [2, "Identity Match"], // drop question #2 and "Identity Match"
});
// Permanent change to the test's questions:
await ultron.assessment.templates.update(tpl.id, { checks: [...tpl.checks, newCheck] });Off-camera collections
For privately-collected types (urine, vaginal, rectal) where only the post-collection sealing/packaging is on camera, set collectionMode: "off_camera". It auto-skips every check marked onCameraOnly: true and tells the model not to penalize the unseen collection — evaluating only what's on screen.
await ultron.assessment.assessAndWait({ media: [file], templateId, collectionMode: "off_camera" });
rtc.startSession({ outputType: "json", templateId, collectionMode: "off_camera" });
// Mark collection-step questions so they drop automatically in off-camera mode:
// checks: [{ title: "Proper Collection", instruction: "…", onCameraOnly: true }, …]Single frame, and saved presets (templates):
await ultron.assessment.assessFrame(base64DataUrl, { outputType: "json", prompt: "Gauge in green?" });
const { data: templates } = await ultron.assessment.templates.list();
await ultron.assessment.assessAndWait({ media: [file], templateId: templates[0].id, fields: { type: "swab" } });File/Blob directly; in Node pass { data: buffer, filename, contentType }. Live assessment over a screen share uses the WebRTC client (see WebRTC → v2).MCP Servers
apiVersion: "v2". Manage the user's own MCP (web-MCP) tool endpoints. HTTP transport only; secret header values are encrypted at rest and never returned (only a masked hint).const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });
const { data: server } = await ultron.mcp.servers.create({
name: "docs",
url: "https://example.com/mcp",
headers: { "X-Workspace": "acme" },
secrets: [{ key: "Authorization", value: "Bearer abc123" }], // encrypted at rest
});
await ultron.mcp.servers.test(server.id); // connect + list tools to validate
const { data: servers } = await ultron.mcp.servers.list();
const { data: tools } = await ultron.mcp.tools(); // effective tools for the user| Method | Description |
|---|---|
| mcp.servers.list() / get(id) | List / fetch the user's servers (secrets masked). |
| mcp.servers.create(input) | Add a server { name, url, headers?, secrets? }. |
| mcp.servers.update(id, input) / remove(id) | Update (re-encrypts secrets) / delete. |
| mcp.servers.test(id) | Connect + list tools to validate config. |
| mcp.tools() | Effective tools available to the user. |
Models
Browse the model catalogue and switch models at runtime:
ultron.models; // ModelMeta[] — full catalogue
ultron.getModelsByProvider("gemini"); // filter by provider
ultron.getModelMeta("gpt-4o"); // metadata for one model
ultron.setModel("gemini-2.5-flash"); // change the default (no restart)Error Handling
All API failures throw typed errors you can branch on:
import { UltronAuthError, UltronCreditsError, UltronAPIError } from "ultron-live-sdk";
try {
await ultron.analyseFrame({ image });
} catch (err) {
if (err instanceof UltronCreditsError) promptTopUp(); // 402
else if (err instanceof UltronAuthError) redirectToLogin(); // 401
else if (err instanceof UltronAPIError) console.error(err.statusCode, err.message);
}| Method | Description |
|---|---|
| UltronAPIError | Base error — has .statusCode and .message. |
| UltronAuthError | 401 — missing/invalid/expired credentials. |
| UltronCreditsError | 402 — insufficient credits. |
Full Method Reference
The complete surface of the main UltronLive instance.
| Method | Description |
|---|---|
| auth.{ signup, login, sendOtp, verifyOtp, getMe, updateProfile, updateTimezone, getTransactions, getSubscription, cancelSubscription } | Account & session. |
| billing.createCheckoutSession(opts) | Start a Stripe checkout. |
| startSession() / analyseFrame(opts) / chat(opts) | Low-level REST session, frame, chat. |
| startScreenShare(opts?) / startVideoStream(opts) / stop() | High-level streaming. |
| attachPreview / detachPreview / getPreviewStream | Live preview. |
| setAudioEnabled / configureAudio | ElevenLabs voice narration. |
| models / getModelMeta / getModelsByProvider / setModel | Model catalogue & selection. |
| createWebRTC(overrides?) | Low-latency WebRTC client. |
| createRealtimeVoice(overrides?) | Realtime voice client. |
| assessment.* (v2) | assess, assessAndWait, get, list, assessFrame, templates.* |
| mcp.* (v2) | servers.{ list,get,create,update,remove,test }, tools() |
| setApiKey / setToken / config / apiVersion | Credentials, config, active version. |