REST API Tutorial

Step-by-step guide to using the OpenCue REST API


This tutorial walks you through using the OpenCue REST API to interact with your render farm programmatically. You’ll learn to authenticate, query shows and jobs, monitor rendering progress, and integrate OpenCue into web applications.

Prerequisites

  • OpenCue REST Gateway deployed and running
  • Basic understanding of HTTP/REST APIs
  • Command-line tools: curl, jq (optional for JSON formatting)
  • Text editor for creating scripts

Quick Setup: If you don’t have OpenCue running yet, start it with Docker:

Important: The REST Gateway is not included in OpenCue’s main docker-compose.yml and must be deployed separately.

# From OpenCue repository root
# Start OpenCue stack first
docker compose up -d

# Deploy REST Gateway separately
export JWT_SECRET=$(openssl rand -base64 32)
docker build -f rest_gateway/Dockerfile -t opencue-rest-gateway:latest .
docker run -d --name opencue-rest-gateway \
  --network opencue_default \
  -p 8448:8448 \
  -e CUEBOT_ENDPOINT=cuebot:8443 \
  -e JWT_SECRET="$JWT_SECRET" \
  opencue-rest-gateway:latest

# This will make the REST Gateway available at http://localhost:8448

Step 1: Setup and Authentication

Environment Setup

First, set up your environment variables:

# Gateway endpoint
export OPENCUE_REST_GATEWAY_URL="http://localhost:8448"

# Use the JWT secret from your Docker Compose setup
# (or generate a new one if running manually)
export JWT_SECRET="your-secret-key"

Generate JWT Token

Create a JWT token for authentication. You can use Python:

# generate_token.py
import jwt
import datetime
import os

secret = os.getenv('JWT_SECRET', 'your-secret-key')
payload = {
    "user": "api-tutorial",
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}

token = jwt.encode(payload, secret, algorithm="HS256")
print(f"export JWT_TOKEN='{token}'")

Run the script and export the token:

python3 generate_token.py
# Copy and paste the export command from output

Test Connection

Verify your setup by testing service connectivity:

# Test if service is responding (expects 401 - confirms service is running)
response=$(curl -s -o /dev/null -w "%{http_code}" "$OPENCUE_REST_GATEWAY_URL/")
if [ "$response" = "401" ]; then
    echo "✓ Gateway is running and requiring authentication (as expected)"
else
    echo "✗ Gateway may not be running (got HTTP $response)"
fi

Note: The REST Gateway requires JWT authentication for ALL endpoints - there are no public health endpoints.

Step 2: Exploring Shows

List All Shows

Get all shows in your OpenCue system:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/show.ShowInterface/GetShows" \
     -d '{}' | jq .

Response:

{
  "shows": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "demo_show",
      "active": true,
      "default_min_cores": 1.0,
      "default_max_cores": 10.0
    }
  ]
}

Get Specific Show Details

Retrieve detailed information about a show:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/show.ShowInterface/GetShow" \
     -d '{"name": "demo_show"}' | jq .

Step 3: Working with Jobs

List Jobs for a Show

Get all jobs in a specific show:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/GetJobs" \
     -d '{"r": {"show": "demo_show"}}' | jq .

Response:

{
  "jobs": [
    {
      "id": "job-550e8400-e29b-41d4-a716-446655440001",
      "name": "render_job_001",
      "show": "demo_show",
      "user": "artist1",
      "state": "PENDING",
      "total_frames": 100,
      "stats": {
        "pending_frames": 90,
        "running_frames": 10,
        "succeeded_frames": 0,
        "dead_frames": 0
      }
    }
  ]
}

Get Job Details

Retrieve detailed information about a specific job:

JOB_ID="job-550e8400-e29b-41d4-a716-446655440001"

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/GetJob" \
     -d "{\"id\": \"$JOB_ID\"}" | jq .

Filter Jobs by User

Find jobs submitted by a specific user:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/GetJobs" \
     -d '{"r": {"show": "demo_show", "user": "artist1"}}' | jq .

Step 4: Monitoring Frame Progress

Get Frames for a Job

List all frames in a job:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/frame.FrameInterface/GetFrames" \
     -d "{\"r\": {\"job\": \"$JOB_ID\"}}" | jq .

Filter Frames by State

Get only failed frames:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/frame.FrameInterface/GetFrames" \
     -d "{\"r\": {\"job\": \"$JOB_ID\", \"state\": [\"DEAD\"]}}" | jq .

Get Frame Details

Get detailed information about a specific frame:

FRAME_ID="frame-550e8400-e29b-41d4-a716-446655440002"

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/frame.FrameInterface/GetFrame" \
     -d "{\"id\": \"$FRAME_ID\"}" | jq .

Step 5: Host and Resource Monitoring

List Rendering Hosts

Get all rendering hosts:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/host.HostInterface/GetHosts" \
     -d '{"r": {}}' | jq .

Filter Hosts by State

Get only online hosts:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/host.HostInterface/GetHosts" \
     -d '{"r": {"state": ["UP"]}}' | jq .

Get Host Details

Get detailed information about a specific host:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/host.HostInterface/GetHost" \
     -d '{"name": "render01"}' | jq .

Step 6: Job Management Operations

Pause a Job

Pause a running job:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/Pause" \
     -d "{\"job\": {\"id\": \"$JOB_ID\"}}" | jq .

Resume a Job

Resume a paused job:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/Resume" \
     -d "{\"job\": {\"id\": \"$JOB_ID\"}}" | jq .

Set Job Priority

Change job priority (higher numbers = higher priority):

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/SetPriority" \
     -d "{\"job\": {\"id\": \"$JOB_ID\"}, \"priority\": 75}" | jq .

Retry Failed Frame

Retry a failed frame:

curl -s -H "Authorization: Bearer $JWT_TOKEN" \
     -H "Content-Type: application/json" \
     -X POST \
     "$OPENCUE_REST_GATEWAY_URL/frame.FrameInterface/Retry" \
     -d "{\"frame\": {\"id\": \"$FRAME_ID\"}}" | jq .

Step 7: Creating a Simple Monitoring Script

Basic Monitoring Script

Create a simple bash script to monitor job progress:

#!/bin/bash
# monitor_job.sh

JOB_ID="$1"
if [ -z "$JOB_ID" ]; then
    echo "Usage: $0 <job-id>"
    exit 1
fi

echo "Monitoring job: $JOB_ID"
echo "Press Ctrl+C to stop"

while true; do
    echo "$(date): Checking job status..."
    
    # Get job stats
    RESPONSE=$(curl -s -H "Authorization: Bearer $JWT_TOKEN" \
                    -H "Content-Type: application/json" \
                    -X POST \
                    "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/GetJob" \
                    -d "{\"id\": \"$JOB_ID\"}")
    
    if echo "$RESPONSE" | jq -e '.job' > /dev/null; then
        STATS=$(echo "$RESPONSE" | jq -r '.job.stats')
        PENDING=$(echo "$STATS" | jq -r '.pending_frames')
        RUNNING=$(echo "$STATS" | jq -r '.running_frames')
        SUCCEEDED=$(echo "$STATS" | jq -r '.succeeded_frames')
        DEAD=$(echo "$STATS" | jq -r '.dead_frames')
        
        echo "  Pending: $PENDING, Running: $RUNNING, Succeeded: $SUCCEEDED, Dead: $DEAD"
    else
        echo "  Error getting job status"
    fi
    
    sleep 30
done

Make it executable and run:

chmod +x monitor_job.sh
./monitor_job.sh "your-job-id-here"

Step 8: Python Integration Example

Complete Python Class

Create a comprehensive Python client:

# opencue_client.py
import requests
import json
import time
from datetime import datetime, timedelta

class OpenCueClient:
    def __init__(self, base_url, jwt_token):
        self.base_url = base_url.rstrip('/')
        self.headers = {
            'Authorization': f'Bearer {jwt_token}',
            'Content-Type': 'application/json'
        }
        self.session = requests.Session()
        self.session.headers.update(self.headers)
    
    def _post(self, endpoint, data=None):
        """Make POST request to REST API"""
        url = f"{self.base_url}/{endpoint}"
        response = self.session.post(url, json=data or {})
        response.raise_for_status()
        return response.json()
    
    # Show operations
    def get_shows(self):
        return self._post('show.ShowInterface/GetShows')
    
    def get_show(self, name):
        return self._post('show.ShowInterface/GetShow', {'name': name})
    
    # Job operations
    def get_jobs(self, show=None, user=None, state=None):
        filter_data = {}
        if show:
            filter_data['show'] = show
        if user:
            filter_data['user'] = user
        if state:
            filter_data['state'] = state
        return self._post('job.JobInterface/GetJobs', {'r': filter_data})
    
    def get_job(self, job_id):
        return self._post('job.JobInterface/GetJob', {'id': job_id})
    
    def pause_job(self, job_id):
        return self._post('job.JobInterface/Pause', {'job': {'id': job_id}})
    
    def resume_job(self, job_id):
        return self._post('job.JobInterface/Resume', {'job': {'id': job_id}})
    
    def set_job_priority(self, job_id, priority):
        return self._post('job.JobInterface/SetPriority', {
            'job': {'id': job_id}, 
            'priority': priority
        })
    
    # Frame operations
    def get_frames(self, job_id, state=None):
        filter_data = {'job': job_id}
        if state:
            filter_data['state'] = state
        return self._post('frame.FrameInterface/GetFrames', {'r': filter_data})
    
    def retry_frame(self, frame_id):
        return self._post('frame.FrameInterface/Retry', {'frame': {'id': frame_id}})
    
    # Host operations
    def get_hosts(self, state=None):
        filter_data = {}
        if state:
            filter_data['state'] = state
        return self._post('host.HostInterface/GetHosts', {'r': filter_data})
    
    # Utility methods
    def monitor_job_progress(self, job_id, interval=30):
        """Monitor job progress with periodic updates"""
        print(f"Monitoring job {job_id}...")
        
        while True:
            try:
                job_data = self.get_job(job_id)
                stats = job_data['job']['stats']
                
                print(f"{datetime.now()}: "
                      f"Pending: {stats['pending_frames']}, "
                      f"Running: {stats['running_frames']}, "
                      f"Succeeded: {stats['succeeded_frames']}, "
                      f"Dead: {stats['dead_frames']}")
                
                # Check if job is complete
                total = (stats['pending_frames'] + stats['running_frames'] + 
                        stats['succeeded_frames'] + stats['dead_frames'])
                if stats['pending_frames'] == 0 and stats['running_frames'] == 0:
                    print("Job completed!")
                    break
                    
                time.sleep(interval)
                
            except KeyboardInterrupt:
                print("\nMonitoring stopped")
                break
            except Exception as e:
                print(f"Error: {e}")
                time.sleep(interval)

# Usage example
if __name__ == "__main__":
    import os
    
    client = OpenCueClient(
        base_url=os.getenv('OPENCUE_REST_GATEWAY_URL', 'http://localhost:8448'),
        jwt_token=os.getenv('JWT_TOKEN')
    )
    
    # List shows
    shows = client.get_shows()
    print(f"Found {len(shows['shows'])} shows")
    
    # Get jobs for first show
    if shows['shows']:
        show_name = shows['shows'][0]['name']
        jobs = client.get_jobs(show=show_name)
        print(f"Found {len(jobs['jobs'])} jobs in {show_name}")
        
        # Monitor first job if available
        if jobs['jobs']:
            job_id = jobs['jobs'][0]['id']
            client.monitor_job_progress(job_id)

Run the Python client:

python3 opencue_client.py

Step 9: Web Integration Example

JavaScript/HTML Dashboard

Create a simple web dashboard:

<!DOCTYPE html>
<html>
<head>
    <title>OpenCue Dashboard</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .job { border: 1px solid #ccc; margin: 10px 0; padding: 15px; }
        .stats { display: flex; gap: 20px; }
        .stat { background: #f0f0f0; padding: 10px; border-radius: 5px; }
        button { padding: 5px 10px; margin: 2px; }
    </style>
</head>
<body>
    <h1>OpenCue Dashboard</h1>
    
    <div>
        <label>Show: </label>
        <select id="showSelect"></select>
        <button onclick="loadJobs()">Load Jobs</button>
    </div>
    
    <div id="jobsList"></div>

    <script>
        const API_BASE = 'http://localhost:8448';
        const JWT_TOKEN = 'your-jwt-token-here'; // Set your token
        
        const headers = {
            'Authorization': `Bearer ${JWT_TOKEN}`,
            'Content-Type': 'application/json'
        };
        
        async function apiPost(endpoint, data = {}) {
            const response = await fetch(`${API_BASE}/${endpoint}`, {
                method: 'POST',
                headers: headers,
                body: JSON.stringify(data)
            });
            return response.json();
        }
        
        async function loadShows() {
            try {
                const data = await apiPost('show.ShowInterface/GetShows');
                const select = document.getElementById('showSelect');
                select.innerHTML = '';
                
                data.shows.forEach(show => {
                    const option = document.createElement('option');
                    option.value = show.name;
                    option.textContent = show.name;
                    select.appendChild(option);
                });
            } catch (error) {
                console.error('Error loading shows:', error);
            }
        }
        
        async function loadJobs() {
            const showName = document.getElementById('showSelect').value;
            if (!showName) return;
            
            try {
                const data = await apiPost('job.JobInterface/GetJobs', {
                    r: { show: showName }
                });
                
                const container = document.getElementById('jobsList');
                container.innerHTML = '';
                
                data.jobs.forEach(job => {
                    const jobDiv = document.createElement('div');
                    jobDiv.className = 'job';
                    jobDiv.innerHTML = `
                        <h3>${job.name}</h3>
                        <p>User: ${job.user} | State: ${job.state}</p>
                        <div class="stats">
                            <div class="stat">Pending: ${job.stats.pending_frames}</div>
                            <div class="stat">Running: ${job.stats.running_frames}</div>
                            <div class="stat">Succeeded: ${job.stats.succeeded_frames}</div>
                            <div class="stat">Dead: ${job.stats.dead_frames}</div>
                        </div>
                        <button onclick="pauseJob('${job.id}')">Pause</button>
                        <button onclick="resumeJob('${job.id}')">Resume</button>
                    `;
                    container.appendChild(jobDiv);
                });
            } catch (error) {
                console.error('Error loading jobs:', error);
            }
        }
        
        async function pauseJob(jobId) {
            try {
                await apiPost('job.JobInterface/Pause', { job: { id: jobId } });
                loadJobs(); // Refresh
            } catch (error) {
                console.error('Error pausing job:', error);
            }
        }
        
        async function resumeJob(jobId) {
            try {
                await apiPost('job.JobInterface/Resume', { job: { id: jobId } });
                loadJobs(); // Refresh
            } catch (error) {
                console.error('Error resuming job:', error);
            }
        }
        
        // Load shows on page load
        loadShows();
    </script>
</body>
</html>

Step 10: Best Practices and Tips

Error Handling

Always implement proper error handling:

# Bash example with error handling
response=$(curl -s -H "Authorization: Bearer $JWT_TOKEN" \
               -H "Content-Type: application/json" \
               -X POST \
               "$OPENCUE_REST_GATEWAY_URL/job.JobInterface/GetJob" \
               -d "{\"id\": \"$JOB_ID\"}")

if echo "$response" | jq -e '.error' > /dev/null; then
    echo "API Error: $(echo "$response" | jq -r '.message')"
    exit 1
fi

Rate Limiting

Implement reasonable delays between requests:

import time

# Add delay between rapid API calls
for job_id in job_ids:
    job_data = client.get_job(job_id)
    process_job_data(job_data)
    time.sleep(0.1)  # 100ms delay

Token Management

Implement token renewal:

def is_token_expired(token):
    try:
        payload = jwt.decode(token, options={"verify_signature": False})
        exp = payload.get('exp')
        return datetime.utcfromtimestamp(exp) < datetime.utcnow()
    except:
        return True

def renew_token_if_needed():
    if is_token_expired(current_token):
        current_token = generate_new_token()
        client.update_token(current_token)

Troubleshooting

Common Issues

401 Unauthorized:

  • Check JWT token is valid and not expired
  • Verify JWT secret matches gateway configuration

Connection refused:

  • Verify gateway is running: Check for 401 response on curl http://localhost:8448/
  • Check network connectivity and firewall rules

Invalid JSON:

  • Ensure Content-Type header is set to application/json
  • Validate JSON syntax with tools like jq

Empty responses:

  • Check if data exists (e.g., jobs in the show)
  • Verify filter parameters are correct

What’s next?


Back to top

Page last modified: Sep 18 2025 at 12:19 PM.