Skip to main content
The Simulate API requires SDK version 1.0.0 or higher.
The Simulate API allows you to run scripted interactions asynchronously. Unlike the Interactive API where you connect and interact in real-time, simulations execute in the background and produce recordings you can retrieve when complete.

When to Use the Simulate API

Use CaseRecommended Approach
Real-time interactionInteractive API (connect() + startStream())
Batch video generationSimulate API
Pre-scripted sequencesSimulate API
Background processingSimulate API
User-driven interactionsInteractive API

Script Format

A simulation script is an array of entries that define the sequence of actions using timestamps:
interface ScriptEntry {
  timestamp_ms: number;           // When this action occurs (milliseconds from start)
  start?: {                       // Begin a new stream
    prompt: string;
    image?: File | Blob | string; // Optional image for image-to-video
  };
  interact?: {                    // Send an interaction
    prompt: string;
  };
  end?: Record<string, never>;    // End the stream (empty object)
}

Entry Types

ActionFieldsDescription
start{ prompt, image? }Begin a new stream with initial prompt
interact{ prompt }Send an interaction prompt
end{}End the current stream

Example Script

const script = [
  // Start a portrait video of a cat at t=0
  { timestamp_ms: 0, start: { prompt: 'A cat sitting by a window' } },

  // Interact at t=3000ms (3 seconds)
  { timestamp_ms: 3000, interact: { prompt: 'The cat looks outside' } },

  // Another interaction at t=6000ms (6 seconds)
  { timestamp_ms: 6000, interact: { prompt: 'The cat stretches' } },

  // End the stream at t=9000ms (9 seconds)
  { timestamp_ms: 9000, end: {} }
];

Basic Workflow

1. Create a Simulation

import { Odyssey } from '@odysseyml/odyssey';

const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });

const job = await client.simulate({
  script: [
    { timestamp_ms: 0, start: { prompt: 'A serene mountain landscape' } },
    { timestamp_ms: 5000, interact: { prompt: 'Clouds roll across the sky' } },
    { timestamp_ms: 10000, interact: { prompt: 'The sun begins to set' } },
    { timestamp_ms: 15000, end: {} }
  ],
  portrait: false
});

console.log('Simulation ID:', job.job_id);
console.log('Status:', job.status); // 'pending'

2. Poll for Completion

async function waitForCompletion(client, jobId) {
  while (true) {
    const status = await client.getSimulateStatus(jobId);

    if (status.status === 'completed') {
      return status;
    }

    if (status.status === 'failed') {
      throw new Error(`Simulation failed: ${status.error_message}`);
    }

    if (status.status === 'cancelled') {
      throw new Error('Simulation was cancelled');
    }

    // Wait 5 seconds before checking again
    await new Promise(resolve => setTimeout(resolve, 5000));
  }
}

const result = await waitForCompletion(client, job.job_id);
console.log('Simulation completed!');

3. Retrieve Recordings

// Get the stream IDs from the completed simulation
for (const stream of result.streams) {
  const recording = await client.getRecording(stream.stream_id);
  console.log('Video URL:', recording.video_url);
  console.log('Duration:', recording.duration_seconds, 'seconds');
}

Image-to-Video with the Simulate API

You can start a simulation with an image:
const imageFile = document.querySelector('input[type="file"]').files[0];

const job = await client.simulate({
  script: [
    {
      timestamp_ms: 0,
      start: {
        prompt: 'A cat',
        image: imageFile
      }
    },
    { timestamp_ms: 3000, interact: { prompt: 'The cat looks around' } },
    { timestamp_ms: 6000, end: {} }
  ],
  portrait: false
});
You can also use a base64 data URL string:
const job = await client.simulate({
  script: [
    {
      timestamp_ms: 0,
      start: {
        prompt: 'Robot dancing',
        image: 'data:image/png;base64,iVBORw0KGgo...'
      }
    },
    { timestamp_ms: 10000, end: {} }
  ]
});

Managing Simulation Jobs

List Your Simulation Jobs

const { jobs, total } = await client.listSimulations({ limit: 10 });

for (const sim of jobs) {
  console.log(`${sim.job_id}: ${sim.status} (created: ${sim.created_at})`);
}

console.log(`Showing ${jobs.length} of ${total} total simulations`);

Cancel a Simulation

// Cancel a pending or running simulation
await client.cancelSimulation(job.job_id);
console.log('Simulation cancelled');

Complete Example

import { Odyssey } from '@odysseyml/odyssey';

async function runSimulation() {
  const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });

  // Create simulation
  const job = await client.simulate({
    script: [
      { timestamp_ms: 0, start: { prompt: 'A cat sitting on a windowsill' } },
      { timestamp_ms: 3000, interact: { prompt: 'The cat watches a bird outside' } },
      { timestamp_ms: 6000, interact: { prompt: 'The cat stretches lazily' } },
      { timestamp_ms: 9000, interact: { prompt: 'The cat curls up to sleep' } },
      { timestamp_ms: 12000, end: {} }
    ],
    portrait: true
  });

  console.log('Started simulation:', job.job_id);

  // Poll for completion
  let status;
  do {
    await new Promise(resolve => setTimeout(resolve, 5000));
    status = await client.getSimulateStatus(job.job_id);
    console.log('Status:', status.status);
  } while (status.status === 'pending' || status.status === 'running');

  if (status.status === 'completed') {
    // Download recordings
    for (const stream of status.streams) {
      const recording = await client.getRecording(stream.stream_id);
      console.log('Recording ready:', recording.video_url);
    }
  } else {
    console.error('Simulation failed:', status.error_message);
  }
}

runSimulation();

Error Handling

try {
  const job = await client.simulate({
    script: [
      { timestamp_ms: 0, start: { prompt: 'A sunset over the ocean' } },
      { timestamp_ms: 5000, end: {} }
    ]
  });

  const status = await client.getSimulateStatus(job.job_id);

  if (status.status === 'failed') {
    console.error('Job failed:', status.error_message);

    // Check individual streams for errors
    for (const stream of status.streams) {
      if (stream.status === 'failed') {
        console.error(`Stream ${stream.stream_id} failed:`, stream.error_message);
      }
    }
  }
} catch (error) {
  console.error('API error:', error.message);
}