skills/rw-integrate-characters/SKILL.md
Help users create Runway Characters (GWM-1 avatars) and integrate real-time conversational sessions into their apps
npx skillsauth add runwayml/skills rw-integrate-charactersInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
PREREQUISITES:
+rw-check-compatibility— Project must have a server-side component (API key must NEVER be exposed to the client)+rw-fetch-api-reference— Load the latest API reference from https://docs.dev.runwayml.com/api/ before integrating+rw-setup-api-key— API credentials must be configuredOPTIONAL DEPENDENCIES:
+rw-integrate-documents— Add a knowledge base to your character+rw-integrate-character-embed— Use the React SDK to embed the avatar call UI
Help users create Runway Characters — real-time conversational AI avatars powered by GWM-1.
Use this only when modifying a user's codebase. For direct avatar management or other one-off Runway account actions from the agent, use +use-runway-api instead.
Characters are generated from a single image (any visual style — photorealistic, animated, non-human) with full control over voice, personality, knowledge, and actions. No fine-tuning or training required.
| Concept | Description | |---------|-------------| | Avatar | A persistent persona with a defined appearance, voice, and personality. Created once, used many times. | | Session | A live WebRTC connection for real-time conversation. Connects one user to one avatar. Max duration: 5 minutes. |
┌───────────┐
┌──────────┤ NOT_READY ├──────────┐
│ └─────┬─────┘ │
│ │ │
▼ ▼ ▼
CANCELLED READY FAILED
┌──┴──┐
│ │
▼ ▼
RUNNING FAILED
┌──┴──┐
│ │
▼ ▼
COMPLETED CANCELLED
| Status | Description |
|--------|-------------|
| NOT_READY | Session is being provisioned. Poll until ready. |
| READY | Session is ready. The sessionKey is available. |
| RUNNING | WebRTC connection is active. Conversation in progress. |
| COMPLETED | Session ended normally. |
| FAILED | Error occurred. Check the failure field. |
| CANCELLED | Explicitly cancelled before completion. |
Important: Session credentials can only be consumed once. If the WebRTC connection fails after credentials are consumed, you must create a new Session.
The API key must stay server-side. The flow is:
Client (React) → Your Server → Runway API
↓
Client (React) ←─── WebRTC ───← Runway (realtime)
POST /v1/realtime_sessions)READY (GET /v1/realtime_sessions/:id)POST /v1/realtime_sessions/:id/consume)npm install @runwayml/sdk @runwayml/avatars-react
@runwayml/sdk — Server-side SDK (session creation, avatar management)@runwayml/avatars-react — Client-side React components (WebRTC, UI)Avatars can be created via the Developer Portal (UI) or the API (programmatic).
.txt files)8be4df61-93ca-11d2-aa0d-00e098032b8c)// Node.js
import RunwayML from '@runwayml/sdk';
const client = new RunwayML();
const avatar = await client.avatars.create({
name: 'Support Agent',
referenceImage: 'https://example.com/avatar.png',
voice: {
type: 'runway-live-preset',
presetId: 'clara',
},
personality: 'You are a helpful customer support agent for Acme Corp. You help users with billing questions and technical issues.',
});
console.log('Avatar ID:', avatar.id);
# Python
from runwayml import RunwayML
client = RunwayML()
avatar = client.avatars.create(
name='Support Agent',
reference_image='https://example.com/avatar.png',
voice={
'type': 'runway-live-preset',
'preset_id': 'clara',
},
personality='You are a helpful customer support agent for Acme Corp.',
)
print('Avatar ID:', avatar.id)
If the reference image is a local file, upload it first using +rw-integrate-uploads:
import fs from 'fs';
const upload = await client.uploads.createEphemeral(
fs.createReadStream('/path/to/avatar-image.png')
);
const avatar = await client.avatars.create({
name: 'Support Agent',
referenceImage: upload.runwayUri,
voice: { type: 'runway-live-preset', presetId: 'clara' },
personality: 'You are a helpful customer support agent...',
});
referenceImage is required when creating an avatar. It accepts three formats:
| Format | Limit | When to use |
|--------|-------|-------------|
| https://… URL | 2048 chars | Image already hosted publicly |
| data:image/…;base64,… | 5 MB (characters) | Small-to-medium local files (~3.5 MB raw max) |
| runway://… URI | 5000 chars | Large files uploaded via /v1/uploads first |
+rw-integrate-uploads) to get a runway:// URI instead of a data URI.For local files over ~3.5 MB, use the upload flow (+integrate-uploads) to get a runway:// URI instead of a data URI.
810dd3a (Improve CLI error details, auth fallback, and skill docs from testing):skills/integrate-characters/SKILL.md
| Preset ID | Name | Style |
|-----------|------|-------|
| clara | Clara | Soft, approachable |
| victoria | Victoria | Firm, professional |
| vincent | Vincent | Knowledgeable, authoritative |
Preview all voices in the Developer Portal.
This is the server-side API route that your client will call. It creates a session, polls until ready, consumes credentials, and returns them.
// app/api/avatar/session/route.ts
import RunwayML from '@runwayml/sdk';
const client = new RunwayML();
export async function POST(request: Request) {
const { avatarId } = await request.json();
// 1. Create session
const { id: sessionId } = await client.realtimeSessions.create({
model: 'gwm1_avatars',
avatar: { type: 'custom', avatarId },
});
// 2. Poll until ready
let sessionKey: string | undefined;
for (let i = 0; i < 60; i++) {
const session = await client.realtimeSessions.retrieve(sessionId);
if (session.status === 'READY') {
sessionKey = session.sessionKey;
break;
}
if (session.status === 'FAILED') {
return Response.json({ error: session.failure }, { status: 500 });
}
await new Promise(r => setTimeout(r, 1000));
}
if (!sessionKey) {
return Response.json({ error: 'Session timed out' }, { status: 504 });
}
// 3. Consume session to get WebRTC credentials
const consumeResponse = await fetch(
`${client.baseURL}/v1/realtime_sessions/${sessionId}/consume`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${sessionKey}`,
'X-Runway-Version': '2024-11-06',
},
}
);
const credentials = await consumeResponse.json();
return Response.json({
sessionId,
serverUrl: credentials.url,
token: credentials.token,
roomName: credentials.roomName,
});
}
import RunwayML from '@runwayml/sdk';
import express from 'express';
const client = new RunwayML();
const app = express();
app.use(express.json());
app.post('/api/avatar/session', async (req, res) => {
const { avatarId } = req.body;
try {
// 1. Create session
const { id: sessionId } = await client.realtimeSessions.create({
model: 'gwm1_avatars',
avatar: { type: 'custom', avatarId },
});
// 2. Poll until ready
let sessionKey: string | undefined;
for (let i = 0; i < 60; i++) {
const session = await client.realtimeSessions.retrieve(sessionId);
if (session.status === 'READY') {
sessionKey = session.sessionKey;
break;
}
if (session.status === 'FAILED') {
return res.status(500).json({ error: session.failure });
}
await new Promise(r => setTimeout(r, 1000));
}
if (!sessionKey) {
return res.status(504).json({ error: 'Session timed out' });
}
// 3. Consume credentials
const consumeResponse = await fetch(
`${client.baseURL}/v1/realtime_sessions/${sessionId}/consume`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${sessionKey}`,
'X-Runway-Version': '2024-11-06',
},
}
);
const credentials = await consumeResponse.json();
res.json({
sessionId,
serverUrl: credentials.url,
token: credentials.token,
roomName: credentials.roomName,
});
} catch (error) {
console.error('Session creation failed:', error);
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
import time
import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from runwayml import RunwayML
app = FastAPI()
client = RunwayML()
class SessionRequest(BaseModel):
avatar_id: str
@app.post("/api/avatar/session")
async def create_session(req: SessionRequest):
# 1. Create session
result = client.realtime_sessions.create(
model='gwm1_avatars',
avatar={'type': 'custom', 'avatar_id': req.avatar_id},
)
session_id = result.id
# 2. Poll until ready
session_key = None
for _ in range(60):
session = client.realtime_sessions.retrieve(session_id)
if session.status == 'READY':
session_key = session.session_key
break
if session.status == 'FAILED':
raise HTTPException(status_code=500, detail=str(session.failure))
time.sleep(1)
if not session_key:
raise HTTPException(status_code=504, detail='Session timed out')
# 3. Consume credentials
async with httpx.AsyncClient() as http:
resp = await http.post(
f"{client.base_url}/v1/realtime_sessions/{session_id}/consume",
headers={
"Authorization": f"Bearer {session_key}",
"X-Runway-Version": "2024-11-06",
},
)
credentials = resp.json()
return {
"session_id": session_id,
"server_url": credentials["url"],
"token": credentials["token"],
"room_name": credentials["roomName"],
}
See +rw-integrate-character-embed for the React SDK components that handle WebRTC connection and rendering. The simplest approach:
'use client';
import { AvatarCall } from '@runwayml/avatars-react';
import '@runwayml/avatars-react/styles.css';
export default function CharacterPage() {
return (
<AvatarCall
avatarId="your-avatar-id"
connectUrl="/api/avatar/session"
onEnd={() => console.log('Call ended')}
onError={(error) => console.error('Error:', error)}
/>
);
}
key_ followed by 128 hex chars. Ensure it's active.<AvatarCall
avatarId="your-avatar-id"
connectUrl="/api/avatar/session"
onError={(error) => {
console.error('Avatar error:', error);
console.error('Error name:', error.name);
console.error('Error message:', error.message);
if (error.cause) console.error('Cause:', error.cause);
}}
/>
import { useAvatarSession } from '@runwayml/avatars-react';
function DebugInfo() {
const { state, sessionId, error } = useAvatarSession();
return (
<pre>
{JSON.stringify({ state, sessionId, error: error?.message }, null, 2)}
</pre>
);
}
npx degit runwayml/avatars-sdk-react/examples/nextjs-simple test-app
cd test-app
npm install
# Add your API key to .env.local
npm run dev
| Browser | Minimum Version | |---------|-----------------| | Chrome | 74+ | | Firefox | 78+ | | Safari | 14.1+ | | Edge | 79+ |
Users must grant microphone permissions. Camera permissions needed if user video is enabled.
| Resource | Description | |----------|-------------| | Developer Portal | Manage avatars, view logs, access dashboard | | SDK Repository | Report bugs, view examples, check releases |
When reporting issues, include: browser/version, SDK version (npm list @runwayml/avatars-react), error messages, session ID, and steps to reproduce.
development
Directly use the Runway API from the agent to generate media, manage resources, and inspect account state
development
Guide users through obtaining and configuring a Runway API key
development
Complete Runway API setup: check compatibility, configure API key, and integrate generation endpoints
development
Help users integrate Runway video generation APIs (text-to-video, image-to-video, video-to-video)