Generate an API key and make your first call in <3 minutes.
Magic Hour is an AI video and image generation platform. You submit a job, we render it, and you download the result. This guide gets you to your first output in a few minutes.What you’ll accomplish:
Create an API key - Get credentials to authenticate with our API
Set up your development environment - Install SDK and create project files
Generate your first output - Make an API call and download the result
Credit Cost: Pick the example that matches your goal. The image tab costs about 5 credits; the
face swap video tab costs about 200 credits for the sample clip. New accounts get 400 free credits
plus 100 daily credits if you claim them in the web app.
# Create and navigate to project directorymkdir magic-hour-quickstartcd magic-hour-quickstart# Create your Python filetouch main.py
# Create and navigate to project directorymkdir magic-hour-quickstartcd magic-hour-quickstart# Initialize npm projectnpm init -y# Enable ES module support (required for the SDK)echo '{"type":"module"}' > package.json# Create your JavaScript filetouch main.js
# Create and navigate to project directorymkdir magic-hour-quickstartcd magic-hour-quickstart# Initialize Go modulego mod init magic-hour-quickstart# Create your Go filetouch main.go
# Create new Rust projectcargo new magic-hour-quickstartcd magic-hour-quickstart
What we’re doing: Submit a generation job, wait for Magic Hour to render it, then download the
result. Choose the example that matches what you want to build:
SDK version required for generate(): Use Python SDK v0.36.0+ or Node SDK v0.37.0+. Older
versions do not include this helper. Go and Rust use the manual create/poll/download pattern shown
below.
Choose an example tab, then copy the code from your preferred language tab below.
Generate image (~5 credits)
Face swap video (~200 credits)
Create an image from a text prompt. This is the cheapest way to verify your setup end to end.Why these parameters:
image_count: 1 - Generate one image (costs 5 credits)
aspect_ratio: "16:9" - Widescreen (landscape) output; also supports 1:1 and 9:16
resolution: "1k" - Request an explicit supported resolution instead of deprecated auto
wait_for_completion: true - SDK polls until done
download_outputs: true - Automatically download to local disk
download_directory: "." - Save to the current directory
from magic_hour import Clientimport os# Use environment variable for securityclient = Client(token=os.getenv("MAGIC_HOUR_API_KEY"))result = client.v1.ai_image_generator.generate( image_count=1, aspect_ratio="16:9", resolution="1k", style={ "prompt": "Epic anime art of wizard casting a cosmic spell in the sky that says 'Magic Hour'" }, wait_for_completion=True, # wait for the render to complete download_outputs=True, # download the outputs to local disk download_directory=".", # save the outputs to the current directory)print(f"created image with id {result.id}, spent {result.credits_charged} credits. Outputs are saved at {result.downloaded_paths}")
import { Client } from "magic-hour";// Use environment variable for securityconst client = new Client({ token: process.env.MAGIC_HOUR_API_KEY });async function main() { const createRes = await client.v1.aiImageGenerator.generate( { imageCount: 1, aspectRatio: "16:9", resolution: "1k", style: { prompt: "Epic anime art of wizard casting a cosmic spell in the sky that says 'Magic Hour'", }, }, { waitForCompletion: true, // wait for the render to complete downloadOutputs: true, // download the outputs to local disk downloadDirectory: ".", // save the outputs to the current directory } ); console.log( `created image with id ${createRes.id}, spent ${createRes.creditsCharged} credits. Outputs are saved at ${createRes.downloadedPaths}` );}main();
package mainimport ( "fmt" "io" "net/http" "os" "time" sdk "github.com/magichourhq/magic-hour-go/client" nullable "github.com/magichourhq/magic-hour-go/nullable" "github.com/magichourhq/magic-hour-go/resources/v1/ai_image_generator" "github.com/magichourhq/magic-hour-go/resources/v1/image_projects" "github.com/magichourhq/magic-hour-go/types")func main() { // Use environment variable for security client := sdk.NewClient(sdk.WithBearerAuth(os.Getenv("MAGIC_HOUR_API_KEY"))) createRes, err := client.V1.AiImageGenerator.Create(ai_image_generator.CreateRequest{ ImageCount: 1, AspectRatio: nullable.NewValue(types.V1AiImageGeneratorCreateBodyAspectRatioEnum169), Resolution: nullable.NewValue(types.V1AiImageGeneratorCreateBodyResolutionEnum1k), Style: types.V1AiImageGeneratorCreateBodyStyle{ Prompt: "Epic anime art of wizard casting a cosmic spell in the sky that says 'Magic Hour'", }, }) if err != nil { fmt.Println(err) return } fmt.Printf("queued image with id %s, spent %d credits\n", createRes.Id, createRes.CreditsCharged) for { res, err := client.V1.ImageProjects.Get(image_projects.GetRequest{Id: createRes.Id}) if err != nil { fmt.Println(err) return } if res.Status == "complete" { println("render complete!") url := res.Downloads[0].Url outputFile := "output.png" resp, err := http.Get(url) if err != nil { fmt.Println(err) return } defer resp.Body.Close() out, err := os.Create(outputFile) if err != nil { fmt.Println(err) return } defer out.Close() _, err = io.Copy(out, resp.Body) if err != nil { fmt.Println(err) return } fmt.Printf("file downloaded successfully to %s\n", outputFile) break } else if res.Status == "error" { println("render failed") break } else { fmt.Printf("render in progress: %s\n", res.Status) time.Sleep(1 * time.Second) } }}
use magic_hour;use reqwest;use std::io::Read;#[tokio::main]async fn main() { // Use environment variable for security let api_key = std::env::var("MAGIC_HOUR_API_KEY") .expect("MAGIC_HOUR_API_KEY environment variable not set"); let mut client = magic_hour::Client::default().with_bearer_auth(&api_key); let create_res = client .v1() .ai_image_generator() .create(magic_hour::resources::v1::ai_image_generator::CreateRequest { image_count: 1, aspect_ratio: Some(magic_hour::models::V1AiImageGeneratorCreateBodyAspectRatioEnum::Enum169), resolution: Some(magic_hour::models::V1AiImageGeneratorCreateBodyResolutionEnum::Enum1k), style: magic_hour::models::V1AiImageGeneratorCreateBodyStyle { prompt: "Epic anime art of wizard casting a cosmic spell in the sky that says 'Magic Hour'".to_string(), ..Default::default() }, ..Default::default() }) .await .unwrap(); let project_id = create_res.id; let credits_charged = create_res.credits_charged; println!("queued image with id {project_id}, spent {credits_charged} credits"); loop { let res = client .v1() .image_projects() .get(magic_hour::resources::v1::image_projects::GetRequest { id: project_id.clone(), }) .await .unwrap(); match res.status { magic_hour::models::GetV1ImageProjectsIdResponseStatusEnum::Complete => { println!("render complete!"); let url = res.downloads[0].url.clone(); tokio::task::block_in_place(move || { let response = reqwest::blocking::get(url).unwrap(); let output_path = "output.png"; if response.status().is_success() { let mut output_file = std::fs::File::create(output_path).unwrap(); std::io::copy(&mut response, &mut output_file).unwrap(); println!("file downloaded successfully to {}", output_path); } else { println!("failed to download file: {}", response.status()); } }); return; } magic_hour::models::GetV1ImageProjectsIdResponseStatusEnum::Error => { println!("render failed"); return; } _ => { println!("render in progress: {}", res.status); std::thread::sleep(std::time::Duration::from_secs(1)); } } }}
#!/bin/bashset -eURL="https://api.magichour.ai/v1/ai-image-generator"STATUS_URL="https://api.magichour.ai/v1/image-projects"# Use environment variable for securityAPI_KEY="$MAGIC_HOUR_API_KEY"OUTPUT_PATH="output.png"create_response=$(curl -s $URL \ --request POST \ --header "Content-Type: application/json" \ --header "Authorization: Bearer $API_KEY" \ --data '{ "image_count": 1, "aspect_ratio": "16:9", "resolution": "1k", "style": { "prompt": "Epic anime art of wizard casting a cosmic spell in the sky that says \"Magic Hour\"" } }')project_id=$(echo $create_response | jq -r '.id')credits_charged=$(echo $create_response | jq -r '.credits_charged')echo "queued image with id $project_id, spent $credits_charged credits"while true; do status_response=$(curl -s $STATUS_URL/$project_id --header "Authorization: Bearer $API_KEY") status=$(echo $status_response | jq -r '.status') if [ "$status" == "complete" ]; then echo "render complete!" download_url=$(echo $status_response | jq -r '.downloads[0].url') echo "downloading image from $download_url..." curl -s $download_url -o $OUTPUT_PATH echo "file downloaded successfully to $OUTPUT_PATH" break elif [ "$status" == "error" ]; then echo "render failed" break else echo "render in progress" sleep 1 fidone
Expected output for Python and Node.js:
created image with id clx1234567890, spent 5 credits. Outputs are saved at ['./output-0.png']
Expected output for Go, Rust, and cURL:
queued image with id clx1234567890, spent 5 creditsrender complete!file downloaded successfully to output.png
Swap a face into a video clip. This is our most popular API path. The sample uses a 6.2-second
segment (start_seconds to end_seconds). Video pricing is duration-based, so longer clips cost
more.
from magic_hour import Clientimport os# Use environment variable for securityclient = Client(token=os.getenv("MAGIC_HOUR_API_KEY"))result = client.v1.face_swap.generate( name="Swap Tom Cruise into Iron Man scene", assets={ "image_file_path": "https://videos.magichour.ai/api-assets/sample/tom-cruise.png", "video_file_path": "https://videos.magichour.ai/api-assets/sample/iron-man.mp4", "video_source": "file", }, start_seconds=2.3, end_seconds=8.5, wait_for_completion=True, # wait for the render to complete download_outputs=True, # download the outputs to local disk download_directory=".", # save the outputs to the current directory)print(f"created face swap video with id {result.id}, spent {result.credits_charged} credits. Outputs are saved at {result.downloaded_paths}")
import { Client } from "magic-hour";// Use environment variable for securityconst client = new Client({ token: process.env.MAGIC_HOUR_API_KEY });async function main() { const createRes = await client.v1.faceSwap.generate( { name: "Swap Tom Cruise into Iron Man scene", assets: { imageFilePath: "https://videos.magichour.ai/api-assets/sample/tom-cruise.png", videoFilePath: "https://videos.magichour.ai/api-assets/sample/iron-man.mp4", videoSource: "file", }, startSeconds: 2.3, endSeconds: 8.5, }, { waitForCompletion: true, // wait for the render to complete downloadOutputs: true, // download the outputs to local disk downloadDirectory: ".", // save the outputs to the current directory } ); console.log( `created face swap video with id ${createRes.id}, spent ${createRes.creditsCharged} credits. Outputs are saved at ${createRes.downloadedPaths}` );}main();
package mainimport ( "fmt" "io" "net/http" "os" "time" sdk "github.com/magichourhq/magic-hour-go/client" nullable "github.com/magichourhq/magic-hour-go/nullable" "github.com/magichourhq/magic-hour-go/resources/v1/face_swap" "github.com/magichourhq/magic-hour-go/resources/v1/video_projects" "github.com/magichourhq/magic-hour-go/types")func main() { // Use environment variable for security client := sdk.NewClient(sdk.WithBearerAuth(os.Getenv("MAGIC_HOUR_API_KEY"))) createRes, err := client.V1.FaceSwap.Create(face_swap.CreateRequest{ Name: nullable.NewValue("Swap Tom Cruise into Iron Man scene"), Assets: types.PostV1FaceSwapBodyAssets{ ImageFilePath: "https://videos.magichour.ai/api-assets/sample/tom-cruise.png", VideoFilePath: nullable.NewValue("https://videos.magichour.ai/api-assets/sample/iron-man.mp4"), VideoSource: types.PostV1FaceSwapBodyAssetsVideoSourceEnumFile, }, StartSeconds: 2.3, EndSeconds: 8.5, Height: 288, Width: 512, }) if err != nil { fmt.Println(err) return } fmt.Printf("queued video with id %s, spent %d credits based on 30fps. This value will be adjusted after render completes if fps is different.\n", createRes.Id, createRes.CreditsCharged) for { res, err := client.V1.VideoProjects.Get(video_projects.GetRequest{Id: createRes.Id}) if err != nil { fmt.Println(err) return } if res.Status == "complete" { fmt.Printf("render complete! Final credits charged is %d, actual fps is %f.\n", res.CreditsCharged, res.Fps) url := res.Downloads[0].Url outputFile := "output.mp4" resp, err := http.Get(url) if err != nil { fmt.Println(err) return } defer resp.Body.Close() out, err := os.Create(outputFile) if err != nil { fmt.Println(err) return } defer out.Close() _, err = io.Copy(out, resp.Body) if err != nil { fmt.Println(err) return } fmt.Printf("file downloaded successfully to %s\n", outputFile) break } else if res.Status == "error" { println("render failed") break } else { fmt.Printf("render in progress: %s\n", res.Status) time.Sleep(1 * time.Second) } }}
use magic_hour;use reqwest;#[tokio::main]async fn main() { // Use environment variable for security let api_key = std::env::var("MAGIC_HOUR_API_KEY") .expect("MAGIC_HOUR_API_KEY environment variable not set"); let mut client = magic_hour::Client::default().with_bearer_auth(&api_key); let create_res = client .v1() .face_swap() .create(magic_hour::resources::v1::face_swap::CreateRequest { name: Some("Swap Tom Cruise into Iron Man scene".to_string()), assets: magic_hour::models::PostV1FaceSwapBodyAssets { image_file_path: "https://videos.magichour.ai/api-assets/sample/tom-cruise.png" .to_string(), video_file_path: Some( "https://videos.magichour.ai/api-assets/sample/iron-man.mp4".to_string(), ), video_source: magic_hour::models::PostV1FaceSwapBodyAssetsVideoSourceEnum::File, ..Default::default() }, start_seconds: 2.3, end_seconds: 8.5, height: 288, width: 512, ..Default::default() }) .await .unwrap(); let project_id = create_res.id; let credits_charged = create_res.credits_charged; println!("queued face swap video with id {project_id}, spent {credits_charged} credits based on 30fps. This value will be updated after render completes if fps is different."); loop { let res = client .v1() .video_projects() .get(magic_hour::resources::v1::video_projects::GetRequest { id: project_id.clone(), }) .await .unwrap(); match res.status { magic_hour::models::GetV1VideoProjectsIdResponseStatusEnum::Complete => { println!( "render complete! Final credit charged is {}, actual fps is {}", res.credits_charged, res.fps ); let url = res.downloads[0].url.clone(); tokio::task::block_in_place(move || { let response = reqwest::blocking::get(url).unwrap(); let output_path = "output.mp4"; if response.status().is_success() { let mut output_file = std::fs::File::create(output_path).unwrap(); std::io::copy(&mut response, &mut output_file).unwrap(); println!("file downloaded successfully to {}", output_path); } else { println!("failed to download file: {}", response.status()); } }); return; } magic_hour::models::GetV1VideoProjectsIdResponseStatusEnum::Error => { println!("render failed"); return; } _ => { println!("render in progress: {}", res.status); std::thread::sleep(std::time::Duration::from_secs(1)); } } }}
#!/bin/bashset -eURL="https://api.magichour.ai/v1/face-swap"STATUS_URL="https://api.magichour.ai/v1/video-projects"# Use environment variable for securityAPI_KEY="$MAGIC_HOUR_API_KEY"OUTPUT_PATH="output.mp4"create_response=$(curl -s $URL \ --request POST \ --header "Content-Type: application/json" \ --header "Authorization: Bearer $API_KEY" \ --data '{ "name": "Swap Tom Cruise into Iron Man scene", "assets": { "image_file_path": "https://videos.magichour.ai/api-assets/sample/tom-cruise.png", "video_file_path": "https://videos.magichour.ai/api-assets/sample/iron-man.mp4", "video_source": "file" }, "start_seconds": 2.3, "end_seconds": 8.5, "height": 288, "width": 512 }')project_id=$(echo $create_response | jq -r '.id')credits_charged=$(echo $create_response | jq -r '.credits_charged')echo "queued face swap video with id $project_id, spent an estimated $credits_charged credits based on 30fps. This value will be adjusted after render completes if fps is different."while true; do status_response=$(curl -s $STATUS_URL/$project_id --header "Authorization: Bearer $API_KEY") status=$(echo $status_response | jq -r '.status') if [ "$status" == "complete" ]; then credits_charged=$(echo $status_response | jq -r '.credits_charged') fps=$(echo $status_response | jq -r '.fps') download_url=$(echo $status_response | jq -r '.downloads[0].url') echo "render complete! Final credit charged is $credits_charged, actual fps is $fps." echo "downloading video from $download_url..." curl -s $download_url -o $OUTPUT_PATH echo "file downloaded successfully to $OUTPUT_PATH" break elif [ "$status" == "error" ]; then echo "render failed" break else echo "render in progress: $status" sleep 3 fidone
Expected output for Python and Node.js:
created face swap video with id clx1234567890, spent 186 credits. Outputs are saved at ['./output.mp4']
Paste it into your file (main.py, main.js, main.go, etc.)
Run the code:
# Make sure you're in your project directorycd magic-hour-quickstart# Run the scriptpython main.py
# Make sure you're in your project directorycd magic-hour-quickstart# Run the scriptnode main.js
# Make sure you're in your project directorycd magic-hour-quickstart# Run the programgo run main.go
# Make sure you're in your project directorycd magic-hour-quickstart# Run the programcargo run
FileNotFoundError when using a custom download_directoryThe SDK saves outputs into download_directory but does not create the folder for you. The default (".") always works. If you point it at a folder that doesn’t exist yet (e.g. "outputs"), create it first:
mkdir outputs
Error: MAGIC_HOUR_API_KEY environment variable not setSolution: Set your API key as an environment variable. See
Environment Variables in the
Authentication guide for platform-specific setup steps.
Explore the API Reference: Learn how to generate and edit videos and images programmatically. API Reference →
Explore Face Swap Video: See parameter details, pricing, and production patterns in the
Face Swap Video guide.
Try All APIs in Google Colab:Run our complete cookbook with sample code for all 22 APIs. Just add your API key and start experimenting.
Use the Web App: Try more tools and experiment interactively at magichour.ai.
Handle Results at Scale: Set up webhooks to process results async and avoid polling.
Join the Community: Get help, share projects, and see what others are building in Discord.
Stay Updated: Check out the Changelog for new products and API updates.
Prefer fast iteration inside your editor? Install this documentation as an MCP server to get
contextual help while integrating the Magic Hour API. Learn
more.