Build your first Morphly stream.
A complete path from creating an API key to receiving transformed camera video in your own application.
Your permanent morph_test_... or morph_live_... key stays on your server. The browser requests a short-lived session credential from your server, so your workspace key is never exposed to application users.
Copy a prompt. Integrate with confidence.
Open your project in your AI coding assistant, copy the prompt that fits your task, and paste it into the chat. Each prompt explains the Morphly SDK, secure server setup, session controls, credits, and how to check the result.
Keep your API key in your server or hosting settings. The assistant can guide you through setup without seeing your secret key. Review its changes and test a short stream before releasing your integration.
Build a new integration
Give your AI assistant the setup, SDK methods, and checks needed to add Morphly to your app.
Read or select the prompt
Update an existing integration
Ask your AI assistant to inspect your current setup and apply compatible SDK updates.
Read or select the prompt
One server route. One browser SDK.
You need a Morphly account with credits, a server or serverless function that can hold a secret, and a modern browser with a camera. The browser SDK is a hosted JavaScript module; there is no SDK package to install. A static website still needs the server route shown below.
- Create a key with
realtime:createscope. - Allow your exact website origin on the key, such as
http://localhost:3000orhttps://your-site.com. An origin has no path or trailing slash. - Keep your permanent key on the server and protect your session route with your app’s authentication and rate limits.
- Use HTTPS in production. Open the example through your server, not by double-clicking the HTML file.
Test keys use real streaming credits. Key validation is free; a live stream costs 2 Morphly credits per started second. Session duration is capped by your available balance.
The secure request path
Create and verify your API key
Open API keys, create a Test key, copy it, and click Test Morphly connection before closing the dialog. A passed result confirms the key authenticated successfully and reports current session availability.
For an independent HTTP test, replace the example key and run:
curl "https://api.morphly.fun/v1/realtime/validate-key" \
--header "Authorization: Bearer morph_test_REPLACE_ME"Expected: HTTP 200 containing "valid": true. This check never starts a stream or consumes credits. Never commit or share the permanent API key.
Add the secure session route
Set these variables on your server, then restart it. Use your real website origin when deploying.
# Server environment only (.env.local for Next.js)
MORPHLY_API_KEY=morph_test_REPLACE_ME
APP_ORIGIN=http://localhost:3000
# On your host, change APP_ORIGIN to your exact HTTPS website origin.
# Never prefix the secret key with NEXT_PUBLIC_ or VITE_.The example below uses Next.js App Router and an existing Auth.js login. Replace the auth() import and check with your application’s sign-in system. Do not remove the access check on a public website: this route spends your workspace credits.
// app/api/morphly-token/route.js (Next.js App Router)
// This example uses Auth.js. Replace auth() with your app's login check.
import { auth } from "@/auth";
export async function POST(request) {
const userSession = await auth();
if (!userSession?.user) {
return Response.json({ error: "Sign in first" }, { status: 401 });
}
const origin = process.env.APP_ORIGIN;
if (!origin || request.headers.get("origin") !== origin) {
return Response.json({ error: "Origin not allowed" }, { status: 403 });
}
if (!process.env.MORPHLY_API_KEY) {
return Response.json({ error: "Server key is missing" }, { status: 503 });
}
let requested;
try { requested = await request.json(); }
catch { return Response.json({ error: "Invalid JSON" }, { status: 400 }); }
try {
const upstream = await fetch("https://api.morphly.fun/v1/realtime/sessions", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.MORPHLY_API_KEY}`,
"content-type": "application/json",
"idempotency-key": crypto.randomUUID(),
},
body: JSON.stringify({
model: requested.model || "morphly-realtime",
origin, // Use your configured origin, not an arbitrary caller's value.
max_session_seconds: requested.maxSessionSeconds ?? 300,
}),
cache: "no-store",
signal: AbortSignal.timeout(20000),
});
return Response.json(await upstream.json(), {
status: upstream.status,
headers: { "Cache-Control": "no-store" },
});
} catch {
return Response.json({ error: "Session service unavailable" }, { status: 502 });
}
}Expected: HTTP 201 with session_id, session_token, client_token, expires_at, model, max_session_seconds, and balance. Forward the complete JSON response and HTTP status. Treat credentials as opaque strings; do not decode, rename, cache, or log them.
Each new Start action creates a fresh idempotency key. If you add automatic retries to the upstream request, reuse that key: a 409 means the first creation already happened, and its credentials are not replayed. Do not keep creating replacement sessions on a retry loop.
Connect with the Morphly SDK
After adding the server route, save this complete page as public/morphly.html in Next.js. Sign in to your app, then visit http://localhost:3000/morphly.html. For another framework, serve the HTML from the same origin as your session route.
Click Start camera and allow camera access. Your transformed video appears in the output element. Change the prompt and click Apply prompt to update it without reconnecting. Click Stop to end the session.
<!-- Save as public/morphly.html in Next.js, or serve from your website. -->
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Morphly camera example</title>
<body>
<label>Visual prompt <input id="prompt" value="Cinematic anime style"></label>
<button id="start">Start camera</button>
<button id="update" disabled>Apply prompt</button>
<button id="stop" disabled>Stop</button>
<p id="status" role="status">Ready</p>
<video id="output" autoplay muted playsinline style="width:100%;max-width:720px"></video>
<script type="module">
import { createMorphlyClient } from "https://morphly.fun/sdk/morphly.js";
const $ = (id) => document.getElementById(id);
const client = createMorphlyClient({ tokenEndpoint: "/api/morphly-token" });
let session;
function reset() {
session = null;
$("start").disabled = false;
$("stop").disabled = $("update").disabled = true;
$("output").srcObject = null;
}
$("start").onclick = async () => {
$("start").disabled = true;
$("status").textContent = "Connecting… Allow camera access when asked.";
try {
session = await client.realtime.connectCamera({
prompt: $("prompt").value,
output: $("output"),
audio: false,
maxSessionSeconds: 300,
onStateChange: (state) => {
$("status").textContent = state;
if (state === "disconnected" || state === "failed") reset();
},
onError: (error) => { $("status").textContent = error.message; },
});
session.on("balance", (balance) => {
$("status").textContent = `Live · ${balance.available_credits} credits available`;
});
session.on("creditsExhausted", () => {
reset();
$("status").textContent = "Stream stopped. Add credits to continue.";
});
$("stop").disabled = $("update").disabled = false;
} catch (error) {
reset();
$("status").textContent = error.message;
}
};
$("update").onclick = async () => {
try { await session?.setPrompt($("prompt").value); }
catch (error) { $("status").textContent = error.message; }
};
$("stop").onclick = async () => {
const current = session;
reset();
await current?.disconnect();
$("status").textContent = "Stopped";
};
window.addEventListener("pagehide", () => { void session?.disconnect(); });
</script>
</body>
</html>Browser requirement: camera access works on HTTPS sites and on localhost. The user must grant camera permission.
When you supply output, the SDK attaches and plays the remote video. You can also supply onRemoteStream to observe or forward it. If you supply only the callback, attach the stream to your video element and call play() yourself. A connected session does not guarantee that a video frame has been decoded; wait for video data before drawing it to a canvas.
Update and close the session
The returned Morphly session lets your application change prompts, attach reference images, observe connection state, and disconnect cleanly.
// Update the prompt while streaming.
await session.setPrompt("Watercolor storybook style");
// Add <input id="reference" type="file" accept="image/*"> to your page.
const image = document.querySelector("#reference").files[0];
if (image) {
await session.set({ prompt: "Use this character's appearance", image });
}
session.on("lowCredit", ({ level }) => console.log("Low balance:", level));
session.on("creditsExhausted", () => console.log("Streaming stopped"));
// Release camera resources and settle the Morphly session.
await session.disconnect();Understand the credit balance
Streaming costs 2 Morphly credits per started billable second. Starting a session charges nothing upfront; usage is charged from your balance as the engine confirms it. Use a short maxSessionSeconds value for a short test.
The balance event reports available_credits (free to spend), charged_credits, and billable_seconds. reserved_credits remains for compatibility and is always 0. Usage updates can arrive in batches.
disconnect() closes the stream and requests settlement. A stop response can have state: "stopping" and stop: true while final usage is checked. Final confirmation may take the remaining credential lifetime plus a few minutes; if usage cannot be confirmed, the session stays pending for review. Always await disconnect; avoid starting replacement sessions in a retry loop.
The SDK stops the stream when billing authorization fails or billing requests repeatedly fail. Handle onError or the error event and offer a fresh Start action after the issue is resolved.
If disconnect() rejects with SESSION_STOP_PENDING, local media has closed but the server stop is unconfirmed. Keep that session object and call disconnect() again to retry. Show a pending state until confirmation succeeds.
Other inputs and frameworks
For React, Vue, or another browser framework, load the hosted module in browser code, start from a user click, and call disconnect() when the component unmounts. Keep DOM elements mounted for the session. The standalone HTML example also works inside these projects and is a useful first test.
For PHP, Python, Java, Express, or another backend, implement the same authenticated POST route: read your Morphly key from the server environment, call POST /v1/realtime/sessions with the fields above, and forward its JSON and status. Your frontend continues to use the same Morphly SDK.
// For screen sharing, canvas.captureStream(), or an existing camera stream:
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
const session = await client.realtime.connect(stream, {
prompt: "Hand-drawn animation",
onRemoteStream: (result) => { outputVideo.srcObject = result; },
});
// You own streams supplied to connect(); stop their tracks when finished.
await session.disconnect();
stream.getTracks().forEach((track) => track.stop());This SDK runs in browsers. A native mobile or desktop application needs a browser/WebView with camera, JavaScript modules, and WebRTC support, or a separately implemented native transport. A permanent Morphly key belongs on your server in either case.
Keep using the Morphly SDK
Load https://morphly.fun/sdk/morphly.js directly so you receive compatible SDK updates. Avoid copying or bundling a fixed SDK file if you want updates without rebuilding your app. Forward session credentials unchanged. Existing integrations using lucy-2.5 remain accepted; new integrations can omit model in the browser or use morphly-realtime.
The supported core flow is camera/custom-stream input, remote video, prompt and reference-image updates, balance events, and disconnect. Capture quality settings are preferences; actual output resolution and frame rate depend on the active model and connection.
Optional fields and events
enhancePrompt and enhance remain accepted for compatibility; automatic prompt enhancement is not guaranteed. viewerToken may be null, and connection-quality reports may be unavailable. Use connectionChange, error, balance, lowCredit, and creditsExhausted for the core session lifecycle. Preview mirroring may differ between models.
Find the failing layer
401The Morphly key is invalid, expired, or revoked.403The key lacks realtime scope or the application origin is not allowed.400The model, origin, or session duration is invalid.402Your workspace has insufficient credits. Add credits, then start again.409This idempotency key already created a session. Credentials are not replayed.429Too many requests. Wait before trying again.502Morphly authenticated the key but realtime session creation failed upstream.503New sessions are paused for maintenance or the realtime service is unavailable. Check the response code before retrying.REALTIME_SETUP_REQUIREDMorphly must complete a server update. Contact Morphly support; your integration does not need to change.CAMERA_UNAVAILABLEThe browser is not secure, unsupported, or camera permission was denied.VIDEO_PLAYBACK_BLOCKEDMute the output video or offer a Play button that calls video.play() from a user click.VIDEO_PLAYBACK_FAILEDCheck the output video element and browser media support.If the SDK fails to load, check the browser Network panel for a blocked module or Content Security Policy error. A restrictive policy must allow the hosted SDK and the media/upload destinations used by your session. For a black video, check camera access, autoplay muted playsinline, and your network’s WebRTC support. A 401 from your own token route usually means you must sign in to your app first.
Testing order: dashboard key validation, Postman key validation, server session route, then browser camera stream. This isolates authentication, server configuration, and WebRTC problems one layer at a time.
