This guide walks you through integrating Magic Hour APIs into your production application. You’ll learn the complete workflow from making API calls to handling responses, errors, and file management.

What you’ll accomplish

By the end of this guide, you’ll have:
  • ✅ Secure API integration setup
  • ✅ Complete create → poll → download workflow
  • ✅ Robust error handling
  • ✅ Production-ready patterns

Before you start

Complete the Quick Start Guide first to get familiar with basic API calls.
Prerequisites:
  • API key from Developer Hub
  • Your preferred SDK installed or direct HTTP client ready
Security: Never expose your API key in client-side code. Always keep it secure on your server to prevent unauthorized usage.

Integration overview

Magic Hour APIs follow a simple 3-step pattern:
  1. Create - Submit your job (image/video generation)
  2. Poll or webhook - Check status
  3. Download - Retrieve your generated content

Complete integration example

Here’s a full end-to-end example that demonstrates the complete workflow:
This example uses the create function to better demonstrate the full workflow. You can also use the generate function in Python and Node SDKs if you want a simpler workflow.
from magic_hour import Client
import time

client = Client(token="YOUR_API_KEY")

# 1. Create job
print("Creating face swap job...")
create_res = client.v1.face_swap.create(
    name="My face swap",
    assets={
        "image_file_path": "https://example.com/face.jpg",
        "video_file_path": "https://example.com/video.mp4",
        "video_source": "file"
    },
    start_seconds=0.0,
    end_seconds=10.0,
)

project_id = create_res.id
print(f"Job created: {project_id}")

# 2. Poll for completion
print("Waiting for completion...")
while True:
    status_res = client.v1.video_projects.get(id=project_id)
    print(f"Status: {status_res.status}")

    if status_res.status == "complete":
        print("✅ Render complete!")

        # 3. Download result
        download_url = status_res.downloads[0].url
        import requests
        with open("result.mp4", "wb") as file:
            response = requests.get(download_url)
            file.write(response.content)
        print("📁 File downloaded: result.mp4")
        break

    elif status_res.status == "error":
        print(f"❌ Error: {status_res.error}")
        break

    time.sleep(5)  # Wait 5 seconds before next check

Working with input files

Magic Hour accepts input files in two ways: The simplest approach is to pass file URLs:
{
  "assets": {
    "image_file_path": "https://cdn.yourwebsite.com/face.jpg",
    "video_file_path": "https://cdn.yourwebsite.com/video.mp4"
  }
}
Supported formats:
  • Images: PNG, JPG, JPEG, WEBP, AVIF
  • Videos: MP4, MOV, WEBM
  • Audio: MP3, WAV, AAC, FLAC

Option 2: Upload to Magic Hour

For secure or temporary files, upload directly to Magic Hour storage:

Input Files Guide

Complete guide to file uploads, formats, and storage options

Understanding job status

Every job goes through these states:
StatusDescriptionAction Required
queuedJob is waiting for available server⏳ Keep polling
renderingJob is being processed⏳ Keep polling
completeJob finished successfully✅ Download result
errorJob failed during processing❌ Handle error
canceledJob was manually canceled🛑 Job stopped
Recommended polling intervals:
  • Images: Check every 2-3 seconds (usually complete within 30-60 seconds)
  • Videos: Check every 5-10 seconds (can take 2-5 minutes depending on length)

Error handling

When a job fails (status: "error"), the response includes detailed error information:
{
  "status": "error",
  "error": {
    "code": "no_source_face",
    "message": "Please use an image with a detectable face"
  }
}

Error handling example

try:
    create_res = client.v1.face_swap.create(...)

    # Poll with error handling
    while True:
        status_res = client.v1.video_projects.get(id=create_res.id)

        if status_res.status == "complete":
            # Success - download result
            break
        elif status_res.status == "error":
            error_code = status_res.error.get("code", "unknown")
            error_msg = status_res.error.get("message", "Unknown error")

            # Handle specific errors
            if error_code == "no_source_face":
                print("❌ No face detected. Please use a different image.")
            else:
                print(f"❌ Error ({error_code}): {error_msg}")
            break

        time.sleep(5)

except Exception as e:
    print(f"❌ Request failed: {e}")
For unknown_error codes, contact support@magichour.ai with your project ID for investigation.

Status monitoring strategies

Choose the right approach based on your application’s needs: Get real-time notifications when jobs complete. Best for:
  • ✅ Production applications
  • ✅ Video processing (longer render times)
  • ✅ Multiple concurrent jobs
  • ✅ Better server resource usage

Webhook Setup Guide

Complete webhook implementation guide with examples

Option 2: Polling (Good for simple use cases)

Periodically check job status. Best for:
  • ✅ Simple integrations
  • ✅ Single job processing
  • ✅ Image generation (quick results)
Smart polling example:
import time

def wait_for_completion(client, project_id, project_type="video"):
    """Smart polling with exponential backoff"""

    get_method = (client.v1.video_projects.get if project_type == "video"
                 else client.v1.image_projects.get)

    while True:
        try:
            res = get_method(id=project_id)
            print(f"Status: {res.status}")

            if res.status == "complete":
                return res  # Success!
            elif res.status == "error":
                raise Exception(f"Job failed: {res.error}")
            time.sleep(3)
        except Exception as e:
            print(f"Error checking status: {e}")

    raise TimeoutError("Job did not complete within timeout")

Downloading results

When a job completes, the downloads array is populated with secure, time-limited URLs:
{
  "status": "complete",
  "downloads": [
    {
      "url": "https://video.magichour.ai/id/output.mp4?auth-token=1234",
      "expires_at": "2024-10-19T05:16:19.027Z"
    }
  ]
}
Download URLs expire after 24 hours. Download files immediately after job completion.

Download outputs

import requests
import os
from pathlib import Path

def download_result(download_info, output_dir="./downloads"):
    # Create output directory
    Path(output_dir).mkdir(exist_ok=True)

    # Generate filename from URL or use timestamp
    url = download_info["url"]
    filename = f"result_{int(time.time())}.mp4"  # or extract from URL
    filepath = Path(output_dir) / filename

    try:
        print(f"Downloading to {filepath}...")
        response = requests.get(url, stream=True, timeout=60)
        response.raise_for_status()

        # Stream download for large files
        with open(filepath, "wb") as file:
            for chunk in response.iter_content(chunk_size=8192):
                file.write(chunk)

        print(f"✅ Downloaded: {filepath} ({filepath.stat().st_size} bytes)")
        return str(filepath)

    except requests.exceptions.RequestException as e:
        print(f"❌ Download failed: {e}")
        return None

Multiple outputs handling

Some tools generate multiple files (e.g., multiple images):
# Handle multiple downloads
for i, download in enumerate(status_res.downloads):
    filename = f"output_{i+1}.{'mp4' if 'video' in download.url else 'jpg'}"
    download_result(download, filename)

File management

Cleaning up storage

Generated files are stored indefinitely. Clean up completed jobs to manage storage:
client.v1.video_projects.delete(id="cuid")
)
Deletion is permanent and cannot be undone. Only delete after confirming successful download.

Development and testing

Free testing with mock server

Avoid credit charges during development by using the mock API server:
The mock server returns realistic sample data without processing jobs or charging credits.
from magic_hour import Client
from magic_hour.environment import Environment

# Use mock server for development
client = Client(
    token="API_TOKEN",
    environment=Environment.MOCK_SERVER
)

# All API calls will return mock data
result = client.v1.face_swap.create(...)  # No credits charged

Job cancellation

Cancel video jobs (with full refund) via the web dashboard:
1

Open video details

Visit: https://magichour.ai/my-library?videoId={project_id}
2

Click cancel render

Cancel Render Button
3

Confirm cancellation

Confirm Cancel Button
4

Cancellation complete

Cancel Success
Notes:
  • Image jobs cannot be cancelled (they complete too quickly)
  • API-based cancellation is not currently available
  • Full credit refund is provided for cancelled video jobs

Next steps


Need help? Join our Discord community or email support@magichour.ai