| name | arcads-video-marketing |
| description | Generate AI marketing videos and static image ads using Arcads external API with Seedance 2.0, Sora 2, Veo 3.1, Kling, Nano Banana, and 37-template Meta image library |
| triggers | ["create an arcads video","generate ugc video with seedance","make a nano banana image","create ai influencer character sheet","generate meta image ad","animate product with veo","build pixar style ad","make claymation video campaign"] |
Arcads AI Video Marketing Skill
Skill by ara.so — Claude Code Skills collection.
Create AI marketing videos and static image ads using the Arcads external API. Supports the full creative stack: Seedance 2.0 (flagship video model), Sora 2, Veo 3.1, Kling 3.0, Grok Video, Nano Banana 2/Pro/Edit, ChatGPT Image 2, OmniHuman, and Audio-driven video generation, plus a 37-template Meta image-ad library and multi-step pipelines for Pixar-style and claymation animated ads.
Installation
1. Clone the repository
git clone https://github.com/krusemediallc/arcads-claude-code.git
cd arcads-claude-code
2. Run setup script
./scripts/setup.sh
This will:
- Prompt for your Arcads API key (get it at app.arcads.ai/settings/api)
- Create
.env file with your credentials
- Verify API connection
- Generate
MASTER_CONTEXT.md workspace file
3. Install optional dependencies (for multi-step pipelines)
brew install ffmpeg jq node
apt install ffmpeg jq nodejs python3
pip install openai-whisper
pip install -r shared/skills/meta-ad-builder/scripts/requirements.txt
Core API Structure
All API calls go through the Arcads base URL. The API key is stored in .env:
ARCADS_API_KEY=your_api_key_here
Basic request pattern (Python)
import os
import requests
import time
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("ARCADS_API_KEY")
base_url = "https://api.arcads.ai"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
response = requests.post(
f"{base_url}/v1/seedance-2/video",
headers=headers,
json={
"prompt": "Woman in kitchen reviewing product, natural iPhone aesthetic",
"duration": 12,
"aspectRatio": "9:16"
}
)
job_id = response.json()["jobId"]
while True:
status_resp = requests.get(
f"{base_url}/v1/jobs/{job_id}",
headers=headers
)
status = status_resp.json()
if status["status"] == "completed":
video_url = status["result"]["videoUrl"]
print(f"Video ready: {video_url}")
break
elif status["status"] == "failed":
print(f"Failed: {status['error']}")
break
time.sleep(5)
Video Generation Models
Seedance 2.0 (Flagship Model)
Endpoints:
POST /v1/seedance-2/video - Text or image-to-video (4-15s)
POST /v1/seedance-2/video-to-video - Video-to-video transformation
Key parameters:
prompt (string, required) - Scene description
duration (int, 4-15) - Video length in seconds
aspectRatio (string) - "16:9", "9:16", "1:1", "4:5"
startFrame (string, optional) - Base64 image to start from
referenceImages (array, optional) - Up to 3 base64 reference images
dialogue (string, optional) - Embedded speech
shotStyle (string, optional) - "static", "dynamic", "cinematic"
Example: UGC selfie-style product review
def generate_seedance_ugc(product_name, duration=12):
"""Generate UGC video using 9-layer Seedance formula"""
prompt = f"""
iPhone selfie video, woman in bright kitchen holding {product_name}.
Natural eye-contact breaks, authentic delivery, vertical frame.
Warm lighting, casual outfit, product visible in hand.
Looking directly at camera, slight hand gestures, genuine smile.
Background: real kitchen counter, soft focus.
"""
payload = {
"prompt": prompt.strip(),
"duration": duration,
"aspectRatio": "9:16",
"shotStyle": "static",
"dialogue": f"I used to buy [competitor] but this {product_name} is so much better"
}
response = requests.post(
f"{base_url}/v1/seedance-2/video",
headers=headers,
json=payload
)
return response.json()["jobId"]
Example: Premium product reveal (no person)
def generate_premium_reveal(product_name, features):
"""Dark void aesthetic with text narrative"""
prompt = f"""
{product_name} floating in dark void, dramatic spotlight from above.
Slow 360-degree rotation revealing product details.
Matte black background, professional studio lighting.
Text overlays appear: "{features}".
Cinematic depth, premium aesthetic, no person visible.
"""
payload = {
"prompt": prompt.strip(),
"duration": 10,
"aspectRatio": "1:1",
"shotStyle": "cinematic"
}
response = requests.post(
f"{base_url}/v1/seedance-2/video",
headers=headers,
json=payload
)
return response.json()["jobId"]
Example: Image-to-video with reference
import base64
def seedance_with_reference(prompt_text, image_path, duration=8):
"""Start from a static image and animate it"""
with open(image_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
payload = {
"prompt": prompt_text,
"duration": duration,
"aspectRatio": "9:16",
"startFrame": img_b64,
"shotStyle": "dynamic"
}
response = requests.post(
f"{base_url}/v1/seedance-2/video",
headers=headers,
json=payload
)
return response.json()["jobId"]
Sora 2 (Long-form text-to-video)
Endpoint: POST /v1/sora2/video
Parameters:
prompt (string, required)
duration (int, 4-20) - Up to 20 seconds
aspectRatio (string)
referenceImages (array, optional) - Style references
def generate_sora_video(scene_description, duration=16):
"""Sora 2 for longer-duration narrative videos"""
payload = {
"prompt": scene_description,
"duration": duration,
"aspectRatio": "16:9"
}
response = requests.post(
f"{base_url}/v1/sora2/video",
headers=headers,
json=payload
)
return response.json()["jobId"]
Veo 3.1 (Start-frame animation)
Endpoint: POST /v1/veo3/video
Best for: Animating static UGC images into video with dialogue
def animate_with_veo(image_path, dialogue_text, duration=8):
"""Animate a still image with embedded dialogue"""
with open(image_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
payload = {
"startFrame": img_b64,
"dialogue": dialogue_text,
"duration": duration,
"aspectRatio": "9:16",
"prompt": "Natural human motion, authentic delivery, maintain starting pose"
}
response = requests.post(
f"{base_url}/v1/veo3/video",
headers=headers,
json=payload
)
return response.json()["jobId"]
Kling 3.0 (B-roll and scenes)
Endpoints:
POST /v1/b-roll - Quick environmental clips
POST /v1/scene - Narrative scene generation
def generate_broll(scene_type, duration=5):
"""Generate b-roll footage"""
broll_prompts = {
"coffee": "Steam rising from fresh coffee in ceramic mug, morning sunlight, warm tones",
"hands": "Hands typing on laptop keyboard, overhead shot, modern workspace",
"product": "Product on clean white surface, soft shadows, professional lighting"
}
payload = {
"prompt": broll_prompts.get(scene_type, scene_type),
"duration": duration,
"aspectRatio": "16:9"
}
response = requests.post(
f"{base_url}/v1/b-roll",
headers=headers,
json=payload
)
return response.json()["jobId"]
Grok Video
Endpoint: POST /v2/videos/generate
def generate_grok_video(prompt_text, duration=10):
"""Generate video using Grok Video model"""
payload = {
"model": "grok-video",
"prompt": prompt_text,
"duration": duration,
"aspectRatio": "16:9"
}
response = requests.post(
f"{base_url}/v2/videos/generate",
headers=headers,
json=payload
)
return response.json()["jobId"]
Image Generation
Nano Banana (Character-consistent images)
Endpoints:
POST /v1/nano-banana-2 - Default model
POST /v1/nano-banana - Pro version (Gemini 3, tighter identity lock)
POST /v1/nano-banana-edit - Inpainting
Parameters:
prompt (string, required)
aspectRatio (string)
referenceImages (array, max 5) - For character consistency
refImageAsBase64 (string, optional) - Base reference
Example: Create AI influencer character sheet
def create_ai_influencer(description, output_dir="references/influencers/"):
"""Generate 10-image character sheet for consistent AI influencer"""
hero_prompt = f"""
{description}
Front-facing portrait, direct eye contact, natural smile.
Professional but approachable lighting, sharp focus on face.
Neutral background, shoulders visible, genuine expression.
"""
response = requests.post(
f"{base_url}/v1/nano-banana-2",
headers=headers,
json={
"prompt": hero_prompt.strip(),
"aspectRatio": "4:5"
}
)
hero_job_id = response.json()["jobId"]
hero_img = poll_and_download(hero_job_id)
print("Hero portrait ready. Generating 9 additional angles...")
with open(hero_img, "rb") as f:
hero_b64 = base64.b64encode(f.read()).decode()
angles = [
"3/4 profile view, slight turn to left",
"3/4 profile view, slight turn to right",
"Full profile view, side of face",
"Close-up of face, tighter crop",
"Laughing expression, natural joy",
"Serious expression, focused",
"Upper body, arms visible, casual pose",
"Holding phone, looking at camera",
"In different lighting, golden hour"
]
job_ids = []
for angle in angles:
payload = {
"prompt": f"{description}. {angle}",
"aspectRatio": "4:5",
"referenceImages": [hero_b64]
}
resp = requests.post(
f"{base_url}/v1/nano-banana-2",
headers=headers,
json=payload
)
job_ids.append(resp.json()["jobId"])
return hero_job_id, job_ids
Example: UGC product selfie still
def generate_ugc_selfie(character_ref_path, product_ref_path, setting="bedroom"):
"""Generate authentic UGC selfie with product"""
with open(character_ref_path, "rb") as f:
char_b64 = base64.b64encode(f.read()).decode()
with open(product_ref_path, "rb") as f:
prod_b64 = base64.b64encode(f.read()).decode()
aesthetic_refs = []
aesthetic_dir = "references/aesthetics/ugc-selfie/"
for img_file in os.listdir(aesthetic_dir)[:3]:
with open(os.path.join(aesthetic_dir, img_file), "rb") as f:
aesthetic_refs.append(base64.b64encode(f.read()).decode())
prompt = f"""
iPhone selfie in {setting}, natural lighting, authentic feel.
Person holding product at eye level, slight smile.
Visible skin texture, natural imperfections, not airbrushed.
Slightly off-center composition, casual framing.
Background: real {setting} environment, lived-in feel.
"""
payload = {
"prompt": prompt.strip(),
"aspectRatio": "4:5",
"referenceImages": [char_b64, prod_b64] + aesthetic_refs
}
response = requests.post(
f"{base_url}/v1/nano-banana-2",
headers=headers,
json=payload
)
return response.json()["jobId"]
ChatGPT Image 2 (Typography & UI-heavy)
Endpoint: POST /v1/chatgpt-image-2
Best for: UI mockups, text-heavy ads, screenshots
def generate_chatgpt_image(prompt_text, aspect_ratio="1:1"):
"""Generate image using GPT Image 2 (good for text/UI)"""
payload = {
"prompt": prompt_text,
"aspectRatio": aspect_ratio
}
response = requests.post(
f"{base_url}/v1/chatgpt-image-2",
headers=headers,
json=payload
)
return response.json()["jobId"]
Static Meta Image Ad Library (37 Templates)
Using the image-ad skills
The repo includes three specialized skills for static Meta image ads:
- chatgpt-image-ad - Typography/UI-heavy templates
- nano-banana-image-ad - Photoreal/lifestyle templates
- image-ad-clone - Reverse-engineer existing ads into templates
Template categories:
- UI mockups (Apple Notes, Slack, iMessage, ChatGPT conversation)
- Editorial (Forbes hero, magazine cover, newspaper)
- Comparison tables
- Fake Google search results
- Sticky-note flatlays
- Billboard/outdoor
- Museum exhibit
- Weather forecast UI
- Scratch-off ticket
- Founder letter
- Dating app card
- And 25+ more
Example: Generate Apple Notes-style ad
def generate_apple_notes_ad(product_name, benefits_list):
"""Create Apple Notes checklist-style ad"""
notes_text = f"{product_name} Benefits:\n"
for benefit in benefits_list:
notes_text += f"☐ {benefit}\n"
prompt = f"""
iPhone Notes app screenshot, light theme, Helvetica font.
Title: "{product_name} Benefits"
Checklist items:
{notes_text}
Clean iOS interface, realistic shadows, authentic Notes UI.
Yellow header bar, white background, black text.
"""
payload = {
"prompt": prompt.strip(),
"aspectRatio": "4:5"
}
response = requests.post(
f"{base_url}/v1/chatgpt-image-2",
headers=headers,
json=payload
)
return response.json()["jobId"]
Example: Comparison table ad
def generate_comparison_table(your_product, competitor, comparisons):
"""Generate side-by-side comparison table"""
table_prompt = f"""
Professional comparison table, clean design, centered layout.
Two columns: "{your_product}" vs "{competitor}"
Header row with product names in bold.
Comparison rows:
"""
for feature, your_val, comp_val in comparisons:
table_prompt += f"\n{feature}: {your_val} | {comp_val}"
table_prompt += """
Green checkmarks for your product, red X for competitor.
White background, subtle grid lines, modern sans-serif.
Bottom CTA: "Switch Today"
"""
payload = {
"prompt": table_prompt.strip(),
"aspectRatio": "1:1"
}
response = requests.post(
f"{base_url}/v1/chatgpt-image-2",
headers=headers,
json=payload
)
return response.json()["jobId"]
Clone existing ad workflow:
Job Polling and Status
Endpoint: GET /v1/jobs/{jobId}
Response statuses:
pending - Job queued
processing - Generation in progress
completed - Asset ready
failed - Error occurred
def poll_and_download(job_id, output_path="output/", poll_interval=5):
"""Poll job until complete and download result"""
while True:
response = requests.get(
f"{base_url}/v1/jobs/{job_id}",
headers=headers
)
status_data = response.json()
status = status_data["status"]
if status == "completed":
asset_url = status_data["result"].get("videoUrl") or status_data["result"].get("imageUrl")
filename = f"{job_id}.{asset_url.split('.')[-1]}"
asset_response = requests.get(asset_url)
output_file = os.path.join(output_path, filename)
with open(output_file, "wb") as f:
f.write(asset_response.content)
print(f"Downloaded: {output_file}")
return output_file
elif status == "failed":
error_msg = status_data.get("error", "Unknown error")
raise Exception(f"Job failed: {error_msg}")
time.sleep(poll_interval)
Multi-Step Pipelines
Pixar-Style 3D Animated Ad
Workflow:
- Lock character cast sheet (2-3 characters)
- Generate 8-beat storyboard stills (ChatGPT Image 2)
- Animate each beat with Seedance 2.0 (i2v)
- Stitch clips with ffmpeg
- Burn captions with HyperFrames
#!/bin/bash
PRODUCT_NAME="$1"
OUTPUT_DIR="output/pixar-ads/$(date +%s)"
mkdir -p "$OUTPUT_DIR"
python3 << EOF
import requests, os, base64
api_key = os.getenv("ARCADS_API_KEY")
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
# Hero character
hero_prompt = """
3D Pixar style character: anthropomorphized ${PRODUCT_NAME} mascot.
Large expressive eyes, friendly smile, glossy finish.
Round body shape, cute proportions, vibrant colors.
Full body view, T-pose, clean white background.
"""
resp = requests.post(
"https://api.arcads.ai/v1/chatgpt-image-2",
headers=headers,
json={"prompt": hero_prompt.strip(), "aspectRatio": "1:1"}
)
print(resp.json()["jobId"])
EOF
Claymation/Aardman-Style Ad
Similar to Pixar pipeline but with clay textures:
def generate_claymation_beat(beat_description, prior_frame_path=None):
"""Generate one beat of claymation animation"""
clay_prompt = f"""
Claymation style, sculpted plasticine characters.
Stop-motion aesthetic, visible fingerprints in clay.
Warm studio lighting, handcrafted set pieces.
Slightly imperfect textures, authentic claymation feel.
Scene: {beat_description}
"""
payload = {
"prompt": clay_prompt.strip(),
"aspectRatio": "16:9"
}
if prior_frame_path:
with open(prior_frame_path, "rb") as f:
payload["referenceImages"] = [base64.b64encode(f.read()).decode()]
still_resp = requests.post(
f"{base_url}/v1/chatgpt-image-2",
headers=headers,
json=payload
)
still_job = still_resp.json()["jobId"]
still_path = poll_and_download(still_job)
with open(still_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
video_resp = requests.post(
f"{base_url}/v1/seedance-2/video",
headers=headers,
json={
"startFrame": img_b64,
"prompt": "Subtle claymation motion, stop-motion jitter",
"duration": 5,
"aspectRatio": "16:9"
}
)
return video_resp.json()["jobId"]
Caption Burn-In Workflow
Dependencies: ffmpeg, whisper, hyperframes
#!/bin/bash
VIDEO_PATH="$1"
OUTPUT_PATH="${VIDEO_PATH%.mp4}_captioned.mp4"
ffmpeg -i "$VIDEO_PATH" -vn -acodec pcm_s16le -ar 16000 temp_audio.wav
whisper temp_audio.wav --model medium.en --output_format json
npx hyperframes \
--video "$VIDEO_PATH" \
--transcript temp_audio.json \
--output "$OUTPUT_PATH" \
--style modern \
--color "#FFFFFF" \
--bgcolor "#000000"
echo "Captioned video: $OUTPUT_PATH"
Configuration Files
.env structure
ARCADS_API_KEY=your_api_key_here
META_ACCESS_TOKEN=your_meta_token_here
META_AD_ACCOUNT_ID=act_123456789
MASTER_CONTEXT.md
Personal workspace file for tracking:
- Active campaigns
- Character reference paths
- Product catalog
- Brand guidelines
- Approved prompts
- Template library additions
Cost Management
The agent confirms estimated costs before expensive operations:
def estimate_cost(model, duration=None, count=1):
"""Estimate generation cost"""
price_table = {
"seedance-2": 0.15,
"sora-2": 0.20,
"veo-3": 0.18,
"kling-3": 0.12,
"nano-banana-2": 0.05,
"chatgpt-image-2": 0.03
}
unit_cost = price_table.get(model, 0.10)
total = unit_cost * count
print(f"Estimated cost: ${total:.2f} ({count}x {model})")
confirm = input("Proceed? (y/n): ")
return confirm.lower() == 'y'
Troubleshooting
Common Issues
1. Job stuck in "pending"
response = requests.get(f"{base_url}/v1/status", headers=headers)
print(response.json())
2. "Invalid aspect ratio" error
Supported ratios vary by model:
- Seedance 2.0: 16:9, 9:16, 1:1, 4:5
- Sora 2: 16:9, 9:16, 1:1
- Veo 3.1: 9:16, 16:9
- Nano Banana: 1:1, 4:5, 16:9
3. Character consistency issues
Use Nano Banana Pro instead of default:
payload = {
"model": "nano-banana",
"prompt": prompt,
"referenceImages": [hero_b64]
}
4. Dialogue not audible in Seedance/Veo
MANDATORY dialogue gate: agent must confirm dialogue text separately before video generation.
5. Image-ad template not matching reference
Use image-ad-clone skill for precise reverse-engineering:
- Agent analyzes reference with vision
- Generates detailed prompt template
- Tests against correct backend (chatgpt vs nano-banana)
- User validates before saving to library
File Organization
Standard output structure:
output/
├── videos/
│ ├── seedance/
│ ├── sora/
│ ├── veo/
│ └── kling/
├── images/
│ ├── nano-banana/
│ └── chatgpt/
├── campaigns/
│ └── {campaign_name}/
│ ├── stills/
│ ├── videos/
│ └── final/
└── references/
├── influencers/
├── products/
└── aesthetics/
Integration with Meta Ads
Publish static image ad to Meta (paused):
import subprocess
def publish_to_meta(image_path, ad_name, campaign_id):
"""Publish image ad to Meta Marketing API"""
script_path = "shared/skills/meta-ad-builder/scripts/create_ad.py"
result = subprocess.run([
"python3", script_path,
"--image", image_path,
"--name", ad_name,
"--campaign", campaign_id,
"--status", "PAUSED"
], capture_output=True, text=True)
if result.returncode == 0:
print(f"Ad published: {result.stdout}")
else:
print(f"Error: {result.stderr}")
Best Practices
- Always use references for character consistency - Generate hero shot first, then use as reference in all subsequent calls
- Confirm dialogue separately - Never skip the dialogue confirmation gate for Seedance/Veo
- Choose the right model - Seedance for flexibility, Veo for start-frame animation, Kling for b-roll
- Batch wisely - Don't fire 50 parallel jobs; queue 5-10 at a time
- Save approved prompts - Add working formulas to
MASTER_CONTEXT.md for reuse
- Test templates before scaling - Generate 1 proof-of-concept before batch production
- Use ChatGPT Image 2 for text-heavy - It handles typography/UI better than Nano Banana
- Use Nano Banana for photoreal - Better for humans, products, lifestyle shots
- Stitch in post - Multi-beat narratives (Pixar, claymation) need ffmpeg stitching
- Version your character sheets - Keep dated backups of influencer reference sets
Advanced Patterns
Batch generate 10 product angles
def generate_product_turnaround(product_image_path):
"""Generate 360-degree product shots"""
with open(product_image_path, "rb") as f:
prod_b64 = base64.b64encode(f.read()).decode()
angles = [0, 45, 90, 135, 180, 225, 270, 315, "top-down", "hero"]
job_ids = []
for angle in angles:
if isinstance(angle, int):
angle_desc = f"{angle} degree rotation"
else:
angle_desc = angle
prompt = f"Product photography, {angle_desc}, clean white background, professional studio lighting"
payload = {
"prompt": prompt,
"aspectRatio": "1:1",
"referenceImages": [prod_b64]
}
resp = requests.post(
f"{base_url}/v1/nano-banana-2",
headers=headers,
json=payload
)
job_ids.append((angle, resp.json()["jobId"]))
return job_ids
A/B test 5 thumbnail variations
def generate_thumbnail_variants(base_prompt, face_refs):