Labsco
runwayml logo

rw-integrate-characters

โ˜… 55

by runwayml ยท part of runwayml/skills

Help users create Runway Characters (GWM-1 avatars) and integrate real-time conversational sessions into their apps

๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅโœ“ VerifiedFreeQuick setup
๐Ÿงฉ One of 7 skills in the runwayml/skills package โ€” works on its own, and pairs well with its siblings.

Help users create Runway Characters (GWM-1 avatars) and integrate real-time conversational sessions into their apps

Inspect the full instructions your agent will receiveExpand

This is the exact playbook injected into your agent when the skill activates โ€” shown here so you can audit it before installing. You don't need to read it to use the skill.

by runwayml

Help users create Runway Characters (GWM-1 avatars) and integrate real-time conversational sessions into their apps npx skills add https://github.com/runwayml/skills --skill rw-integrate-characters Download ZIPGitHub55

Integrate Characters (GWM-1 Avatars)

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 configured

OPTIONAL 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.

Key Concepts

Avatars vs Sessions

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.

Session Lifecycle

Copy & paste โ€” that's it
 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค 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.

Architecture

The API key must stay server-side. The flow is:

Copy & paste โ€” that's it
Client (React) โ†’ Your Server โ†’ Runway API
 โ†“
Client (React) โ†โ”€โ”€โ”€ WebRTC โ”€โ”€โ”€โ† Runway (realtime)
  • Client requests a session from your server

  • Your server calls Runway API to create a session (POST /v1/realtime_sessions)

  • Your server polls until session is READY (GET /v1/realtime_sessions/:id)

  • Your server consumes credentials (POST /v1/realtime_sessions/:id/consume)

  • Your server returns credentials to the client

  • Client establishes a direct WebRTC connection to Runway

Step 2: Create an Avatar

Avatars can be created via the Developer Portal (UI) or the API (programmatic).

Option A: Developer Portal (Recommended for first time)

  • Go to https://dev.runwayml.com/ โ†’ Characters tab

  • Click Create a Character

  • Upload a reference image (tips below)

  • Choose a voice preset

  • Write personality instructions (e.g., "You are a helpful customer support agent for Acme Corp...")

  • Optionally add a starting script (what the character says first)

  • Optionally upload knowledge documents (.txt files)

  • Click Create Character

  • Copy the Avatar ID (a UUID like 8be4df61-93ca-11d2-aa0d-00e098032b8c)

Option B: API (Programmatic)

Copy & paste โ€” that's it
// 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);
Copy & paste โ€” that's it
# 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:

Copy & paste โ€” that's it
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...',
});

Reference Image (Required)

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

<<<<<<< HEAD:skills/rw-integrate-characters/SKILL.md

For local files over ~3.5 MB, use the upload flow (+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

Reference Image Guidelines

  • Any visual style works: photorealistic humans, animated mascots, stylized brand characters

  • Use high-quality images with good lighting

  • Face must be clearly visible and centered โ€” images without a recognizable face will fail processing

  • Avoid images with multiple people or obstructions

  • Recommended aspect ratio: 1088ร—704

Voice Presets

Preset ID Name Style clara Clara Soft, approachable victoria Victoria Firm, professional vincent Vincent Knowledgeable, authoritative

Preview all voices in the Developer Portal.

Step 3: Create a Session (Server-Side)

This is the server-side API route that your client will call. It creates a session, polls until ready, consumes credentials, and returns them.

Next.js App Router

Copy & paste โ€” that's it
// 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 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,
 });
}

Express.js

Copy & paste โ€” that's it
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 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' });
 }
});

FastAPI (Python)

Copy & paste โ€” that's it
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"],
 }

Step 4: Connect from the Client

See +rw-integrate-character-embed for the React SDK components that handle WebRTC connection and rendering. The simplest approach:

Copy & paste โ€” that's it
'use client';
import { AvatarCall } from '@runwayml/avatars-react';
import '@runwayml/avatars-react/styles.css';

export default function CharacterPage() {
 return (
 console.log('Call ended')}
 onError={(error) => console.error('Error:', error)}
 />
 );
}

Browser Support

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.

Getting Help

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.