diff --git a/n8n-workflows/README.md b/n8n-workflows/README.md
new file mode 100644
index 0000000..377e37b
--- /dev/null
+++ b/n8n-workflows/README.md
@@ -0,0 +1,528 @@
+# n8n Workflows - InfiniteStories Marketing Automation
+
+Ce dossier contient les workflows n8n pour automatiser la création de contenu marketing TikTok pour InfiniteStories.
+
+---
+
+## Vue d'ensemble
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│ TikTok UGC Ads Generator │
+│ │
+│ 📝 Brief → 🤖 Script AI → 🎨 Images → 🎙️ Audio → 🎬 Vidéo │
+│ │
+│ OpenAI gpt-5-mini Flux 1.1 gpt-4o-mini Minimax/ │
+│ Form Pro -tts Kling │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+## Workflows Disponibles
+
+| Workflow | Fichier | Description | Coût |
+|----------|---------|-------------|------|
+| **Full Video** | `tiktok-ugc-ads-generator.json` | Script + Audio + Images + Vidéo | ~$2-5 |
+| **Script Only** | `tiktok-ugc-script-generator.json` | Script + Images (sans vidéo) | ~$0.05-2 |
+
+---
+
+# 🎬 TikTok UGC Ads Generator
+
+**Fichier:** `tiktok-ugc-ads-generator.json`
+
+Génère des publicités TikTok style UGC (User Generated Content) complètes à partir d'un simple brief.
+
+---
+
+## 📊 Architecture du Workflow
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ │
+│ ┌─────────────┐ │
+│ │ 📝 FORM │ Brief créatif avec tous les paramètres │
+│ │ TRIGGER │ (audience, problème, bénéfices, ton, durée...) │
+│ └──────┬──────┘ │
+│ │ │
+│ ▼ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ ÉTAPE 1: GÉNÉRATION DU SCRIPT │ │
+│ │ ┌─────────────┐ │ │
+│ │ │ OpenAI │ Modèle: gpt-5-mini │ │
+│ │ │ gpt-5-mini │ Output: Script JSON structuré │ │
+│ │ └─────────────┘ (hook, scenes[], voiceover, visual, emotion) │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ├────────────────────┬────────────────────┐ │
+│ │ │ │ │
+│ ▼ ▼ ▼ │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ ÉTAPE 2A │ │ ÉTAPE 2B │ │ ÉTAPE 2C │ │
+│ │ 🎙️ AUDIO │ │ 🎨 IMAGES │ │ 🎬 VIDÉO │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🔧 Étape 1: Génération du Script
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ 📝 SCRIPT GENERATION │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Modèle: OpenAI gpt-5-mini (Responses API) │
+│ Prompt: Structure XML optimisée │
+│ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ INPUT (Brief) │ │
+│ │ ───────────── │ │
+│ │ • Nom du produit: InfiniteStories │ │
+│ │ • Audience: Parents avec enfants 2-10 ans │ │
+│ │ • Problème: Fatigue le soir, pas d'énergie pour histoires │ │
+│ │ • Bénéfices: Histoires personnalisées, enfant = héros │ │
+│ │ • Ton: Chaleureux et authentique │ │
+│ │ • Hook type: problem-solution │ │
+│ │ • Durée: 30 secondes │ │
+│ │ • Langue: Français │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ OUTPUT (Script JSON) │ │
+│ │ ──────────────────── │ │
+│ │ { │ │
+│ │ "hook": "Vous en avez marre de...", │ │
+│ │ "scenes": [ │ │
+│ │ { │ │
+│ │ "id": 1, │ │
+│ │ "phase": "HOOK", │ │
+│ │ "timestamp": "0:00-0:03", │ │
+│ │ "voiceover": "Texte exact à dire", │ │
+│ │ "visual": "Description visuelle", │ │
+│ │ "emotion": "curiosité", │ │
+│ │ "camera": "selfie", │ │
+│ │ "video_prompt": "Prompt pour génération vidéo" │ │
+│ │ } │ │
+│ │ ], │ │
+│ │ "cta": "Télécharge maintenant!", │ │
+│ │ "hashtags": ["parenting", "bedtime"], │ │
+│ │ "music_mood": "Soft acoustic", │ │
+│ │ "filming_tips": ["Conseil 1", "Conseil 2"] │ │
+│ │ } │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+### Structure du Script UGC
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ STRUCTURE 30 SECONDES │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────┐ 0-3s HOOK │
+│ │ 🎯 │ Arrêter le scroll immédiatement │
+│ └─────────┘ "Vous en avez marre de répéter les mêmes..." │
+│ │ │
+│ ▼ │
+│ ┌─────────┐ 3-8s PROBLEM │
+│ │ 😫 │ Identifier le pain point │
+│ └─────────┘ "Chaque soir c'est la même galère..." │
+│ │ │
+│ ▼ │
+│ ┌─────────┐ 8-15s DISCOVERY │
+│ │ 💡 │ Présenter la solution │
+│ └─────────┘ "J'ai découvert cette app qui..." │
+│ │ │
+│ ▼ │
+│ ┌─────────┐ 15-25s BENEFITS │
+│ │ ✨ │ Démontrer les bénéfices │
+│ └─────────┘ "Mon fils adore être le héros..." │
+│ │ │
+│ ▼ │
+│ ┌─────────┐ 25-30s CTA │
+│ │ 📲 │ Call to action │
+│ └─────────┘ "Télécharge InfiniteStories - Lien en bio!" │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🎙️ Étape 2A: Génération Audio
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ 🎙️ AUDIO GENERATION │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Modèle: OpenAI gpt-4o-mini-tts │
+│ Format: MP3 │
+│ Vitesse: 0.9x (plus lent pour authenticité) │
+│ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ VOIX DISPONIBLES │ │
+│ │ ───────────────── │ │
+│ │ │ │
+│ │ • nova → Chaleureuse, douce (défaut) │ │
+│ │ • shimmer → Énergique, brillante │ │
+│ │ • alloy → Neutre, polyvalente │ │
+│ │ • ash → Confiante, forte │ │
+│ │ • ballad → Expressive, artistique │ │
+│ │ • coral → Joyeuse, enjouée │ │
+│ │ • sage → Calme, réfléchie │ │
+│ │ │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ INSTRUCTIONS VOCALES │ │
+│ │ ───────────────────── │ │
+│ │ │ │
+│ │ "Parle de manière naturelle et authentique, comme un parent │ │
+│ │ partageant une découverte avec un ami. Ton chaleureux. │ │
+│ │ Pauses naturelles, enthousiasme sincère." │ │
+│ │ │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ OUTPUT: Fichier MP3 binaire (~30 secondes) │
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🎨 Étape 2B: Génération des Images
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ 🎨 IMAGE GENERATION │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Modèle: Replicate Flux 1.1 Pro │
+│ Format: 9:16 (vertical TikTok) │
+│ Qualité: WebP 90% │
+│ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ PROCESSUS PAR SCÈNE │ │
+│ │ ───────────────────── │ │
+│ │ │ │
+│ │ Pour chaque scène du script: │ │
+│ │ │ │
+│ │ Scene 1 ─────► Replicate API ─────► Poll Status ─────► Image URL │ │
+│ │ │ │ │ │ │ │
+│ │ │ │ │ │ │ │
+│ │ Scene 2 ─────► Replicate API ─────► Poll Status ─────► Image URL │ │
+│ │ │ │ │ │ │ │
+│ │ │ │ │ │ │ │
+│ │ Scene N ─────► Replicate API ─────► Poll Status ─────► Image URL │ │
+│ │ │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ STYLE DU PROMPT │ │
+│ │ ─────────────── │ │
+│ │ │ │
+│ │ "UGC lifestyle photo for TikTok ad. │ │
+│ │ [Description visuelle de la scène]. │ │
+│ │ Emotion: [émotion]. Camera: [type de plan]. │ │
+│ │ Style: authentic smartphone photo, natural lighting, │ │
+│ │ warm tones, vertical 9:16, family-friendly. │ │
+│ │ Promoting InfiniteStories bedtime stories app." │ │
+│ │ │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ OUTPUT: Array d'URLs d'images (1 par scène) │
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🎬 Étape 2C: Génération Vidéo
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ 🎬 VIDEO GENERATION │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ MODÈLES DISPONIBLES (via Replicate) │ │
+│ │ ───────────────────────────────────── │ │
+│ │ │ │
+│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
+│ │ │ MINIMAX │ │ KLING │ │ RUNWAY │ │ │
+│ │ │ video-01 │ │ v1.6-pro │ │ Gen-3 │ │ │
+│ │ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ │
+│ │ │ ⚡ Rapide │ │ 🎨 Haute qualité│ │ 🌟 Premium │ │ │
+│ │ │ 💰 Économique │ │ 🎭 Réaliste │ │ 🎬 Cinématique │ │ │
+│ │ │ 📱 Bon pour UGC │ │ ⏱️ Plus lent │ │ 💎 Le meilleur │ │ │
+│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
+│ │ │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────────────────────────┐ │
+│ │ PROCESSUS DE GÉNÉRATION │ │
+│ │ ───────────────────────── │ │
+│ │ │ │
+│ │ Prompt + Config │ │
+│ │ │ │ │
+│ │ ▼ │ │
+│ │ ┌─────────────┐ │ │
+│ │ │ Replicate │ ──► Prediction créée │ │
+│ │ │ API │ │ │
+│ │ └─────────────┘ │ │
+│ │ │ │ │
+│ │ ▼ │ │
+│ │ ┌─────────────┐ ┌─────────────┐ │ │
+│ │ │ Poll │────►│ Wait 10s │──┐ │ │
+│ │ │ Status │ └─────────────┘ │ │ │
+│ │ └─────────────┘◄─────────────────────┘ │ │
+│ │ │ │ │
+│ │ ▼ (status = "succeeded") │ │
+│ │ ┌─────────────┐ │ │
+│ │ │ Video URL │ ──► MP4 9:16 prêt pour TikTok │ │
+│ │ └─────────────┘ │ │
+│ │ │ │
+│ └────────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ OUTPUT: URL de la vidéo MP4 │
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 📤 Output Final
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ 📤 RESPONSE STRUCTURE │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ { │
+│ "success": true, │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 📝 SCRIPT │ │
+│ │ ────────── │ │
+│ │ • hook: "Phrase d'accroche" │ │
+│ │ • scenes: [{ id, phase, timestamp, voiceover, visual, emotion }] │ │
+│ │ • cta: "Call to action" │ │
+│ │ • hashtags: ["tag1", "tag2"] │ │
+│ │ • music_mood: "Style musical suggéré" │ │
+│ │ • filming_tips: ["Conseil 1", "Conseil 2"] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 🎙️ AUDIO │ │
+│ │ ───────── │ │
+│ │ • generated: true/false │ │
+│ │ • format: "mp3" │ │
+│ │ • [binary data attaché à la réponse] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 🎨 IMAGES │ │
+│ │ ────────── │ │
+│ │ [ │ │
+│ │ { scene_id: 1, phase: "HOOK", image_url: "https://..." }, │ │
+│ │ { scene_id: 2, phase: "PROBLEM", image_url: "https://..." }, │ │
+│ │ ... │ │
+│ │ ] │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 🎬 VIDEO │ │
+│ │ ───────── │ │
+│ │ • generated: true/false │ │
+│ │ • url: "https://replicate.delivery/..." │ │
+│ │ • model: "minimax" | "kling" | "runway" │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────┐ │
+│ │ 📱 TIKTOK READY │ │
+│ │ ────────────── │ │
+│ │ • caption: "Hook + hashtags formatés" │ │
+│ │ • music: "Suggestion musicale" │ │
+│ └─────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ } │
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🔑 Credentials Requis
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ 🔑 CONFIGURATION DES CREDENTIALS │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────────┐│
+│ │ 1. OpenAI API ││
+│ │ ───────────── ││
+│ │ ││
+│ │ Credential ID: openai-api ││
+│ │ Type: Header Auth ││
+│ │ Header: Authorization ││
+│ │ Value: Bearer sk-xxxxx ││
+│ │ ││
+│ │ Utilisé pour: ││
+│ │ • gpt-5-mini (script generation) ││
+│ │ • gpt-4o-mini-tts (voiceover) ││
+│ │ ││
+│ └─────────────────────────────────────────────────────────────────────────┘│
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────────┐│
+│ │ 2. Replicate API ││
+│ │ ─────────────── ││
+│ │ ││
+│ │ Credential ID: replicate-api ││
+│ │ Type: Header Auth ││
+│ │ Header: Authorization ││
+│ │ Value: Bearer r8_xxxxx ││
+│ │ ││
+│ │ Utilisé pour: ││
+│ │ • flux-1.1-pro (images) ││
+│ │ • minimax/video-01 (vidéo) ││
+│ │ • kling-v1.6-pro (vidéo alternative) ││
+│ │ ││
+│ └─────────────────────────────────────────────────────────────────────────┘│
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 💰 Estimation des Coûts
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ 💰 COÛTS PAR MODE DE SORTIE │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────────┐│
+│ │ MODE: script_only ││
+│ │ ───────────────── ││
+│ │ ││
+│ │ OpenAI gpt-5-mini ........................... ~$0.05 ││
+│ │ ──────────────────────────────────────────────────── ││
+│ │ TOTAL ........................................ $0.05 ││
+│ │ ││
+│ └─────────────────────────────────────────────────────────────────────────┘│
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────────┐│
+│ │ MODE: script_audio ││
+│ │ ────────────────── ││
+│ │ ││
+│ │ OpenAI gpt-5-mini ........................... ~$0.05 ││
+│ │ OpenAI gpt-4o-mini-tts ...................... ~$0.30 ││
+│ │ ──────────────────────────────────────────────────── ││
+│ │ TOTAL ........................................ $0.35 ││
+│ │ ││
+│ └─────────────────────────────────────────────────────────────────────────┘│
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────────┐│
+│ │ MODE: script_images ││
+│ │ ─────────────────── ││
+│ │ ││
+│ │ OpenAI gpt-5-mini ........................... ~$0.05 ││
+│ │ Replicate Flux 1.1 Pro (5 images) ........... ~$0.50 ││
+│ │ ──────────────────────────────────────────────────── ││
+│ │ TOTAL ........................................ $0.55 ││
+│ │ ││
+│ └─────────────────────────────────────────────────────────────────────────┘│
+│ │
+│ ┌─────────────────────────────────────────────────────────────────────────┐│
+│ │ MODE: full_video ││
+│ │ ──────────────── ││
+│ │ ││
+│ │ OpenAI gpt-5-mini ........................... ~$0.05 ││
+│ │ OpenAI gpt-4o-mini-tts ...................... ~$0.30 ││
+│ │ Replicate Flux 1.1 Pro (5 images) ........... ~$0.50 ││
+│ │ Replicate Minimax/Kling (video) ............. ~$1-3 ││
+│ │ ──────────────────────────────────────────────────── ││
+│ │ TOTAL ........................................ $2-5 ││
+│ │ ││
+│ └─────────────────────────────────────────────────────────────────────────┘│
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🚀 Quick Start
+
+### 1. Importer le Workflow
+
+```bash
+n8n import:workflow --input=n8n-workflows/tiktok-ugc-ads-generator.json
+```
+
+### 2. Configurer les Credentials
+
+Dans n8n: **Settings** → **Credentials** → **Add Credential**
+
+- OpenAI API (Header Auth)
+- Replicate API (Header Auth)
+
+### 3. Activer et Tester
+
+```bash
+curl -X POST http://localhost:5678/webhook/tiktok-ad-replicate \
+ -H "Content-Type: application/json" \
+ -d '{
+ "Nom du produit": "InfiniteStories",
+ "Audience cible": "Parents fatigués le soir",
+ "Problème principal": "Plus d énergie pour raconter des histoires",
+ "Bénéfices clés (un par ligne)": "Histoires personnalisées\nEnfant héros",
+ "Ton": "Chaleureux et authentique",
+ "Type de hook": "problem-solution",
+ "Durée (secondes)": 30,
+ "Langue": "fr",
+ "Call to Action": "Lien en bio!",
+ "Output souhaité": "script_audio",
+ "Modèle vidéo": "minimax"
+ }'
+```
+
+---
+
+## 🎯 Types de Hook
+
+| Type | Description | Exemple |
+|------|-------------|---------|
+| `problem-solution` | Commence par le problème | "Vous en avez marre de..." |
+| `question` | Pose une question engageante | "Vous savez ce qui a changé nos soirées?" |
+| `story` | Raconte une mini-histoire | "L'autre soir, mon fils m'a dit..." |
+| `shocking` | Fait surprenant | "80% des parents sont épuisés le soir" |
+| `testimonial` | Témoignage personnel | "Je dois vous parler de cette app..." |
+
+---
+
+## 🌍 Langues Supportées
+
+| Code | Langue | Voix par défaut |
+|------|--------|-----------------|
+| `fr` | Français | nova |
+| `en` | English | nova |
+| `es` | Español | nova |
+| `de` | Deutsch | nova |
+| `it` | Italiano | nova |
+
+---
+
+## 🔧 Troubleshooting
+
+| Problème | Solution |
+|----------|----------|
+| Script non généré | Vérifier la clé OpenAI API |
+| Images échouent | Vérifier quota Replicate |
+| Vidéo timeout | Augmenter le temps de polling (10s → 30s) |
+| Audio vide | Vérifier que le voiceover_text n'est pas trop long |
diff --git a/n8n-workflows/tiktok-ugc-ads-generator.json b/n8n-workflows/tiktok-ugc-ads-generator.json
new file mode 100644
index 0000000..0e1ecff
--- /dev/null
+++ b/n8n-workflows/tiktok-ugc-ads-generator.json
@@ -0,0 +1,518 @@
+{
+ "name": "TikTok UGC Ads Generator - InfiniteStories (Replicate + OpenAI)",
+ "nodes": [
+ {
+ "parameters": {
+ "formTitle": "TikTok Ad Brief - InfiniteStories",
+ "formDescription": "Génère un script UGC TikTok complet avec audio, images et vidéo.",
+ "formFields": {
+ "values": [
+ {
+ "fieldLabel": "Nom du produit",
+ "fieldType": "text",
+ "requiredField": true,
+ "placeholder": "InfiniteStories"
+ },
+ {
+ "fieldLabel": "Audience cible",
+ "fieldType": "textarea",
+ "requiredField": true,
+ "placeholder": "Parents avec enfants de 2-10 ans, fatigués le soir"
+ },
+ {
+ "fieldLabel": "Problème principal",
+ "fieldType": "textarea",
+ "requiredField": true,
+ "placeholder": "Les parents sont épuisés et n'ont plus l'énergie de raconter des histoires"
+ },
+ {
+ "fieldLabel": "Bénéfices clés (un par ligne)",
+ "fieldType": "textarea",
+ "requiredField": true,
+ "placeholder": "Histoires personnalisées\nL'enfant devient le héros\nIllustrations synchronisées"
+ },
+ {
+ "fieldLabel": "Ton",
+ "fieldType": "dropdown",
+ "requiredField": true,
+ "fieldOptions": {
+ "values": [
+ { "option": "Chaleureux et authentique" },
+ { "option": "Enthousiaste et énergique" },
+ { "option": "Calme et rassurant" },
+ { "option": "Humoristique" }
+ ]
+ }
+ },
+ {
+ "fieldLabel": "Type de hook",
+ "fieldType": "dropdown",
+ "requiredField": true,
+ "fieldOptions": {
+ "values": [
+ { "option": "problem-solution" },
+ { "option": "question" },
+ { "option": "story" },
+ { "option": "shocking" },
+ { "option": "testimonial" }
+ ]
+ }
+ },
+ {
+ "fieldLabel": "Durée (secondes)",
+ "fieldType": "number",
+ "requiredField": true,
+ "placeholder": "30"
+ },
+ {
+ "fieldLabel": "Langue",
+ "fieldType": "dropdown",
+ "requiredField": true,
+ "fieldOptions": {
+ "values": [
+ { "option": "fr" },
+ { "option": "en" },
+ { "option": "es" },
+ { "option": "de" },
+ { "option": "it" }
+ ]
+ }
+ },
+ {
+ "fieldLabel": "Call to Action",
+ "fieldType": "text",
+ "requiredField": true,
+ "placeholder": "Télécharge InfiniteStories - Lien en bio!"
+ },
+ {
+ "fieldLabel": "Output souhaité",
+ "fieldType": "dropdown",
+ "requiredField": true,
+ "fieldOptions": {
+ "values": [
+ { "option": "script_only - Script uniquement" },
+ { "option": "script_audio - Script + Audio" },
+ { "option": "script_images - Script + Images" },
+ { "option": "full_video - Vidéo complète" }
+ ]
+ }
+ },
+ {
+ "fieldLabel": "Modèle vidéo",
+ "fieldType": "dropdown",
+ "requiredField": false,
+ "fieldOptions": {
+ "values": [
+ { "option": "minimax - Minimax Video (rapide)" },
+ { "option": "kling - Kling AI (qualité)" },
+ { "option": "runway - Runway Gen-3" }
+ ]
+ }
+ }
+ ]
+ },
+ "responseMode": "responseNode",
+ "options": {}
+ },
+ "id": "form-trigger",
+ "name": "Form - Brief",
+ "type": "n8n-nodes-base.formTrigger",
+ "typeVersion": 2.2,
+ "position": [200, 300],
+ "webhookId": "tiktok-ad-replicate"
+ },
+ {
+ "parameters": {
+ "jsCode": "const form = $input.first().json;\n\nconst outputMode = (form['Output souhaité'] || 'script_only').split(' - ')[0];\nconst videoModel = (form['Modèle vidéo'] || 'minimax').split(' - ')[0];\n\nconst brief = {\n product_name: form['Nom du produit'] || 'InfiniteStories',\n target_audience: form['Audience cible'],\n main_problem: form['Problème principal'],\n key_benefits: (form['Bénéfices clés (un par ligne)'] || '').split('\\n').filter(b => b.trim()),\n tone: form['Ton'] || 'Chaleureux et authentique',\n hook_type: form['Type de hook'] || 'problem-solution',\n duration_seconds: parseInt(form['Durée (secondes)']) || 30,\n language: form['Langue'] || 'fr',\n cta: form['Call to Action'],\n output_mode: outputMode,\n video_model: videoModel,\n generate_audio: ['script_audio', 'full_video'].includes(outputMode),\n generate_images: ['script_images', 'full_video'].includes(outputMode),\n generate_video: outputMode === 'full_video'\n};\n\nreturn [{ json: brief }];"
+ },
+ "id": "parse-form",
+ "name": "Parse Form",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [420, 300]
+ },
+ {
+ "parameters": {
+ "url": "https://api.openai.com/v1/responses",
+ "sendHeaders": true,
+ "headerParameters": {
+ "parameters": [
+ { "name": "Authorization", "value": "=Bearer {{ $credentials.openAiApi.apiKey }}" },
+ { "name": "Content-Type", "value": "application/json" }
+ ]
+ },
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={\n \"model\": \"gpt-5-mini\",\n \"input\": [\n {\n \"role\": \"system\",\n \"content\": \"Expert en scripts UGC TikTok pour apps parentales.\\n\\nCrée un script publicitaire UGC authentique de {{ $json.duration_seconds }}s.\\n\\n\\n1. HOOK (0-3s): Arrêter le scroll - type {{ $json.hook_type }}\\n2. PROBLEM (3-8s): Pain point parent\\n3. DISCOVERY (8-15s): Présenter la solution\\n4. BENEFITS (15-{{ $json.duration_seconds - 5 }}s): Démontrer les bénéfices\\n5. CTA ({{ $json.duration_seconds - 5 }}-{{ $json.duration_seconds }}s): Call to action\\n\\n\\n\\n\\n\"\n },\n {\n \"role\": \"user\",\n \"content\": \"\\nProduit: {{ $json.product_name }}\\nAudience: {{ $json.target_audience }}\\nProblème: {{ $json.main_problem }}\\nBénéfices: {{ $json.key_benefits.join(', ') }}\\nTon: {{ $json.tone }}\\nHook: {{ $json.hook_type }}\\nDurée: {{ $json.duration_seconds }}s\\nLangue: {{ $json.language }}\\nCTA: {{ $json.cta }}\\n\"\n }\n ],\n \"text\": { \"format\": { \"type\": \"json_object\" } }\n}",
+ "options": {}
+ },
+ "id": "generate-script",
+ "name": "OpenAI - Generate Script",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [640, 300],
+ "credentials": {
+ "httpHeaderAuth": { "id": "openai-api", "name": "OpenAI API" }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "const response = $input.first().json;\nconst brief = $('Parse Form').first().json;\n\nlet script;\ntry {\n const output = response.output || response;\n let content;\n if (Array.isArray(output)) {\n const msg = output.find(o => o.type === 'message');\n content = msg?.content?.find(c => c.type === 'output_text')?.text;\n } else if (response.choices) {\n content = response.choices[0]?.message?.content;\n }\n script = typeof content === 'string' ? JSON.parse(content) : content;\n} catch (e) {\n throw new Error('Script parse error: ' + e.message);\n}\n\nconst fullVoiceover = script.scenes.map(s => s.voiceover).join(' ');\n\nreturn [{\n json: {\n script,\n brief,\n fullVoiceover,\n scenes: script.scenes,\n wordCount: fullVoiceover.split(/\\s+/).length\n }\n}];"
+ },
+ "id": "parse-script",
+ "name": "Parse Script",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [860, 300]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [{ "id": "audio", "leftValue": "={{ $json.brief.generate_audio }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equals" } }]
+ }
+ },
+ "id": "check-audio",
+ "name": "Audio?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [1080, 150]
+ },
+ {
+ "parameters": {
+ "url": "https://api.openai.com/v1/audio/speech",
+ "sendHeaders": true,
+ "headerParameters": {
+ "parameters": [
+ { "name": "Authorization", "value": "=Bearer {{ $credentials.openAiApi.apiKey }}" },
+ { "name": "Content-Type", "value": "application/json" }
+ ]
+ },
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={\n \"model\": \"gpt-4o-mini-tts\",\n \"voice\": \"nova\",\n \"input\": \"{{ $('Parse Script').first().json.fullVoiceover.replace(/\\\"/g, '\\\\\"').replace(/\\n/g, ' ') }}\",\n \"response_format\": \"mp3\",\n \"speed\": 0.9,\n \"instructions\": \"Parle de manière naturelle et authentique, comme un parent partageant une découverte. Ton {{ $('Parse Form').first().json.tone }}. Pauses naturelles, enthousiasme sincère.\"\n}",
+ "options": { "response": { "response": { "responseFormat": "file" } } }
+ },
+ "id": "generate-audio",
+ "name": "OpenAI TTS",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [1300, 50],
+ "credentials": { "httpHeaderAuth": { "id": "openai-api", "name": "OpenAI API" } }
+ },
+ {
+ "parameters": { "jsCode": "return [{ json: { audio: { generated: true, format: 'mp3' } }, binary: $input.first().binary }];" },
+ "id": "process-audio",
+ "name": "Process Audio",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [1520, 50]
+ },
+ {
+ "parameters": { "jsCode": "return [{ json: { audio: { generated: false } } }];" },
+ "id": "skip-audio",
+ "name": "Skip Audio",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [1300, 200]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [{ "id": "images", "leftValue": "={{ $('Parse Script').first().json.brief.generate_images }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equals" } }]
+ }
+ },
+ "id": "check-images",
+ "name": "Images?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [1080, 400]
+ },
+ {
+ "parameters": { "fieldToSplitOut": "scenes", "options": {} },
+ "id": "split-scenes",
+ "name": "Split Scenes",
+ "type": "n8n-nodes-base.splitOut",
+ "typeVersion": 1,
+ "position": [1300, 350]
+ },
+ {
+ "parameters": {
+ "url": "https://api.replicate.com/v1/predictions",
+ "sendHeaders": true,
+ "headerParameters": {
+ "parameters": [
+ { "name": "Authorization", "value": "=Bearer {{ $credentials.replicateApi.apiKey }}" },
+ { "name": "Content-Type", "value": "application/json" }
+ ]
+ },
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={\n \"version\": \"black-forest-labs/flux-1.1-pro\",\n \"input\": {\n \"prompt\": \"UGC lifestyle photo for TikTok ad. {{ $json.visual }}. Emotion: {{ $json.emotion }}. Camera: {{ $json.camera }}. Style: authentic smartphone photo, natural lighting, warm tones, vertical 9:16, family-friendly. Promoting InfiniteStories bedtime stories app.\",\n \"aspect_ratio\": \"9:16\",\n \"output_format\": \"webp\",\n \"output_quality\": 90\n }\n}",
+ "options": {}
+ },
+ "id": "replicate-image",
+ "name": "Replicate - Flux Image",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [1520, 350],
+ "credentials": { "httpHeaderAuth": { "id": "replicate-api", "name": "Replicate API" } }
+ },
+ {
+ "parameters": {
+ "url": "={{ $json.urls.get }}",
+ "sendHeaders": true,
+ "headerParameters": {
+ "parameters": [{ "name": "Authorization", "value": "=Bearer {{ $credentials.replicateApi.apiKey }}" }]
+ },
+ "options": {}
+ },
+ "id": "poll-image",
+ "name": "Poll Image Status",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [1740, 350],
+ "credentials": { "httpHeaderAuth": { "id": "replicate-api", "name": "Replicate API" } }
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [{ "id": "done", "leftValue": "={{ $json.status }}", "rightValue": "succeeded", "operator": { "type": "string", "operation": "equals" } }]
+ }
+ },
+ "id": "check-image-done",
+ "name": "Image Done?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [1960, 350]
+ },
+ {
+ "parameters": { "amount": 2, "unit": "seconds" },
+ "id": "wait-image",
+ "name": "Wait 2s",
+ "type": "n8n-nodes-base.wait",
+ "typeVersion": 1.1,
+ "position": [2180, 450]
+ },
+ {
+ "parameters": {
+ "jsCode": "const scene = $('Split Scenes').first().json;\nconst result = $input.first().json;\nreturn [{\n json: {\n scene_id: scene.id,\n phase: scene.phase,\n timestamp: scene.timestamp,\n image_url: result.output,\n video_prompt: scene.video_prompt\n }\n}];"
+ },
+ "id": "collect-image",
+ "name": "Collect Image",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [2180, 300]
+ },
+ {
+ "parameters": { "aggregate": "aggregateAllItemData", "destinationFieldName": "images", "options": {} },
+ "id": "aggregate-images",
+ "name": "Aggregate Images",
+ "type": "n8n-nodes-base.aggregate",
+ "typeVersion": 1,
+ "position": [2400, 350]
+ },
+ {
+ "parameters": { "jsCode": "return [{ json: { images: [] } }];" },
+ "id": "skip-images",
+ "name": "Skip Images",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [1300, 500]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [{ "id": "video", "leftValue": "={{ $('Parse Script').first().json.brief.generate_video }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equals" } }]
+ }
+ },
+ "id": "check-video",
+ "name": "Video?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [2620, 350]
+ },
+ {
+ "parameters": {
+ "jsCode": "// Prepare video generation request based on model choice\nconst brief = $('Parse Script').first().json.brief;\nconst images = $input.first().json.images || [];\nconst script = $('Parse Script').first().json.script;\n\n// Build video prompts from scenes\nconst videoPrompts = script.scenes.map((scene, i) => ({\n scene_id: scene.id,\n prompt: scene.video_prompt || scene.visual,\n duration: 5, // seconds per scene\n image_url: images[i]?.image_url\n}));\n\nreturn [{\n json: {\n model: brief.video_model,\n prompts: videoPrompts,\n total_duration: brief.duration_seconds\n }\n}];"
+ },
+ "id": "prepare-video",
+ "name": "Prepare Video Request",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [2840, 300]
+ },
+ {
+ "parameters": {
+ "url": "https://api.replicate.com/v1/predictions",
+ "sendHeaders": true,
+ "headerParameters": {
+ "parameters": [
+ { "name": "Authorization", "value": "=Bearer {{ $credentials.replicateApi.apiKey }}" },
+ { "name": "Content-Type", "value": "application/json" }
+ ]
+ },
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={\n \"version\": \"{{ $json.model === 'kling' ? 'kwaivgi/kling-v1.6-pro' : 'minimax/video-01' }}\",\n \"input\": {\n \"prompt\": \"{{ $json.prompts[0].prompt }}. UGC style TikTok ad, authentic feel, warm tones, family-friendly content about bedtime stories app.\",\n \"duration\": {{ Math.min($json.total_duration, 10) }},\n \"aspect_ratio\": \"9:16\"\n }\n}",
+ "options": {}
+ },
+ "id": "replicate-video",
+ "name": "Replicate - Video Gen",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [3060, 300],
+ "credentials": { "httpHeaderAuth": { "id": "replicate-api", "name": "Replicate API" } }
+ },
+ {
+ "parameters": {
+ "url": "={{ $json.urls.get }}",
+ "sendHeaders": true,
+ "headerParameters": {
+ "parameters": [{ "name": "Authorization", "value": "=Bearer {{ $credentials.replicateApi.apiKey }}" }]
+ },
+ "options": {}
+ },
+ "id": "poll-video",
+ "name": "Poll Video Status",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [3280, 300],
+ "credentials": { "httpHeaderAuth": { "id": "replicate-api", "name": "Replicate API" } }
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [{ "id": "done", "leftValue": "={{ $json.status }}", "rightValue": "succeeded", "operator": { "type": "string", "operation": "equals" } }]
+ }
+ },
+ "id": "check-video-done",
+ "name": "Video Done?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [3500, 300]
+ },
+ {
+ "parameters": { "amount": 10, "unit": "seconds" },
+ "id": "wait-video",
+ "name": "Wait 10s",
+ "type": "n8n-nodes-base.wait",
+ "typeVersion": 1.1,
+ "position": [3720, 400]
+ },
+ {
+ "parameters": {
+ "jsCode": "const result = $input.first().json;\nreturn [{\n json: {\n video: {\n generated: true,\n url: result.output,\n model: $('Prepare Video Request').first().json.model\n }\n }\n}];"
+ },
+ "id": "process-video",
+ "name": "Process Video",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [3720, 250]
+ },
+ {
+ "parameters": { "jsCode": "return [{ json: { video: { generated: false } } }];" },
+ "id": "skip-video",
+ "name": "Skip Video",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [2840, 450]
+ },
+ {
+ "parameters": { "mode": "combine", "combineBy": "combineAll", "options": {} },
+ "id": "merge-audio",
+ "name": "Merge Audio",
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.1,
+ "position": [1740, 150]
+ },
+ {
+ "parameters": { "mode": "combine", "combineBy": "combineAll", "options": {} },
+ "id": "merge-images",
+ "name": "Merge Images",
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.1,
+ "position": [2620, 500]
+ },
+ {
+ "parameters": { "mode": "combine", "combineBy": "combineAll", "options": {} },
+ "id": "merge-video",
+ "name": "Merge Video",
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.1,
+ "position": [3940, 350]
+ },
+ {
+ "parameters": { "mode": "combine", "combineBy": "combineAll", "options": {} },
+ "id": "merge-all",
+ "name": "Merge All",
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.1,
+ "position": [4160, 300]
+ },
+ {
+ "parameters": {
+ "jsCode": "const items = $input.all();\nconst scriptData = $('Parse Script').first().json;\nconst brief = scriptData.brief;\n\nlet audio = items.find(i => i.json.audio)?.json.audio || { generated: false };\nlet images = items.find(i => i.json.images)?.json.images || [];\nlet video = items.find(i => i.json.video)?.json.video || { generated: false };\nlet audioBinary = items.find(i => i.binary?.data)?.binary;\n\nconst response = {\n success: true,\n \n script: {\n hook: scriptData.script.hook,\n cta: scriptData.script.cta,\n scenes: scriptData.script.scenes,\n hashtags: scriptData.script.hashtags,\n music_mood: scriptData.script.music_mood,\n filming_tips: scriptData.script.filming_tips\n },\n \n voiceover_text: scriptData.fullVoiceover,\n \n audio: audio,\n images: images,\n video: video,\n \n metadata: {\n product: brief.product_name,\n language: brief.language,\n duration: brief.duration_seconds,\n output_mode: brief.output_mode,\n models: {\n script: 'gpt-5-mini',\n audio: audio.generated ? 'gpt-4o-mini-tts' : null,\n images: images.length > 0 ? 'flux-1.1-pro' : null,\n video: video.generated ? brief.video_model : null\n },\n generated_at: new Date().toISOString()\n },\n \n tiktok_ready: {\n caption: scriptData.script.hook + '\\n\\n' + (scriptData.script.hashtags || []).map(h => '#' + h).join(' '),\n music: scriptData.script.music_mood\n }\n};\n\nif (audioBinary) {\n return [{ json: response, binary: audioBinary }];\n}\nreturn [{ json: response }];"
+ },
+ "id": "compile-response",
+ "name": "Compile Response",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [4380, 300]
+ },
+ {
+ "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": {} },
+ "id": "respond",
+ "name": "Respond",
+ "type": "n8n-nodes-base.respondToWebhook",
+ "typeVersion": 1.1,
+ "position": [4600, 300]
+ }
+ ],
+ "connections": {
+ "Form - Brief": { "main": [[{ "node": "Parse Form", "type": "main", "index": 0 }]] },
+ "Parse Form": { "main": [[{ "node": "OpenAI - Generate Script", "type": "main", "index": 0 }]] },
+ "OpenAI - Generate Script": { "main": [[{ "node": "Parse Script", "type": "main", "index": 0 }]] },
+ "Parse Script": { "main": [[{ "node": "Audio?", "type": "main", "index": 0 }, { "node": "Images?", "type": "main", "index": 0 }]] },
+ "Audio?": { "main": [[{ "node": "OpenAI TTS", "type": "main", "index": 0 }], [{ "node": "Skip Audio", "type": "main", "index": 0 }]] },
+ "OpenAI TTS": { "main": [[{ "node": "Process Audio", "type": "main", "index": 0 }]] },
+ "Process Audio": { "main": [[{ "node": "Merge Audio", "type": "main", "index": 0 }]] },
+ "Skip Audio": { "main": [[{ "node": "Merge Audio", "type": "main", "index": 1 }]] },
+ "Images?": { "main": [[{ "node": "Split Scenes", "type": "main", "index": 0 }], [{ "node": "Skip Images", "type": "main", "index": 0 }]] },
+ "Split Scenes": { "main": [[{ "node": "Replicate - Flux Image", "type": "main", "index": 0 }]] },
+ "Replicate - Flux Image": { "main": [[{ "node": "Poll Image Status", "type": "main", "index": 0 }]] },
+ "Poll Image Status": { "main": [[{ "node": "Image Done?", "type": "main", "index": 0 }]] },
+ "Image Done?": { "main": [[{ "node": "Collect Image", "type": "main", "index": 0 }], [{ "node": "Wait 2s", "type": "main", "index": 0 }]] },
+ "Wait 2s": { "main": [[{ "node": "Poll Image Status", "type": "main", "index": 0 }]] },
+ "Collect Image": { "main": [[{ "node": "Aggregate Images", "type": "main", "index": 0 }]] },
+ "Aggregate Images": { "main": [[{ "node": "Merge Images", "type": "main", "index": 0 }]] },
+ "Skip Images": { "main": [[{ "node": "Merge Images", "type": "main", "index": 1 }]] },
+ "Merge Audio": { "main": [[{ "node": "Merge All", "type": "main", "index": 0 }]] },
+ "Merge Images": { "main": [[{ "node": "Video?", "type": "main", "index": 0 }]] },
+ "Video?": { "main": [[{ "node": "Prepare Video Request", "type": "main", "index": 0 }], [{ "node": "Skip Video", "type": "main", "index": 0 }]] },
+ "Prepare Video Request": { "main": [[{ "node": "Replicate - Video Gen", "type": "main", "index": 0 }]] },
+ "Replicate - Video Gen": { "main": [[{ "node": "Poll Video Status", "type": "main", "index": 0 }]] },
+ "Poll Video Status": { "main": [[{ "node": "Video Done?", "type": "main", "index": 0 }]] },
+ "Video Done?": { "main": [[{ "node": "Process Video", "type": "main", "index": 0 }], [{ "node": "Wait 10s", "type": "main", "index": 0 }]] },
+ "Wait 10s": { "main": [[{ "node": "Poll Video Status", "type": "main", "index": 0 }]] },
+ "Process Video": { "main": [[{ "node": "Merge Video", "type": "main", "index": 0 }]] },
+ "Skip Video": { "main": [[{ "node": "Merge Video", "type": "main", "index": 1 }]] },
+ "Merge Video": { "main": [[{ "node": "Merge All", "type": "main", "index": 1 }]] },
+ "Merge All": { "main": [[{ "node": "Compile Response", "type": "main", "index": 0 }]] },
+ "Compile Response": { "main": [[{ "node": "Respond", "type": "main", "index": 0 }]] }
+ },
+ "active": false,
+ "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner" },
+ "versionId": "4",
+ "meta": { "instanceId": "infinite-stories-tiktok-replicate" },
+ "tags": [
+ { "name": "marketing", "id": "1" },
+ { "name": "tiktok", "id": "2" },
+ { "name": "replicate", "id": "3" },
+ { "name": "video", "id": "4" }
+ ]
+}
diff --git a/n8n-workflows/tiktok-ugc-script-generator.json b/n8n-workflows/tiktok-ugc-script-generator.json
new file mode 100644
index 0000000..4daf23b
--- /dev/null
+++ b/n8n-workflows/tiktok-ugc-script-generator.json
@@ -0,0 +1,407 @@
+{
+ "name": "TikTok UGC Script & Assets Generator (Simplified)",
+ "nodes": [
+ {
+ "parameters": {
+ "httpMethod": "POST",
+ "path": "generate-tiktok-script",
+ "responseMode": "responseNode",
+ "options": {}
+ },
+ "id": "webhook-trigger",
+ "name": "Webhook - Receive Briefing",
+ "type": "n8n-nodes-base.webhook",
+ "typeVersion": 2,
+ "position": [250, 300],
+ "webhookId": "tiktok-script-generator"
+ },
+ {
+ "parameters": {
+ "jsCode": "// Validate and structure the briefing input\nconst input = $input.first().json;\n\nconst briefing = {\n product_name: input.body?.product_name || 'InfiniteStories',\n target_audience: input.body?.target_audience || 'Parents avec enfants de 2-10 ans',\n key_benefits: input.body?.key_benefits || [\n 'Histoires personnalisees generees par IA',\n 'Votre enfant devient le heros',\n 'Illustrations synchronisees avec audio',\n 'Multi-langues',\n 'Contenu securise pour enfants'\n ],\n tone: input.body?.tone || 'chaleureux, authentique, relatable',\n duration_seconds: input.body?.duration_seconds || 30,\n language: input.body?.language || 'fr',\n hook_type: input.body?.hook_type || 'problem-solution',\n cta: input.body?.cta || 'Telecharge InfiniteStories maintenant!',\n custom_instructions: input.body?.custom_instructions || '',\n generate_images: input.body?.generate_images !== false,\n generate_audio: input.body?.generate_audio === true\n};\n\nreturn [{ json: briefing }];"
+ },
+ "id": "validate-input",
+ "name": "Validate & Structure Input",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [470, 300]
+ },
+ {
+ "parameters": {
+ "model": "gpt-4o",
+ "messages": {
+ "values": [
+ {
+ "role": "system",
+ "content": "Tu es un expert en creation de scripts publicitaires UGC (User Generated Content) pour TikTok specialise dans les apps pour parents.\n\nTon objectif: Creer des scripts qui semblent etre filmes par un vrai parent partageant sa decouverte avec ses amis.\n\nStructure obligatoire:\n1. HOOK (0-3s): Question ou affirmation qui arrete le scroll\n2. PROBLEME (3-8s): Identifier le pain point du parent\n3. DECOUVERTE (8-15s): Presenter l'app comme une solution trouvee\n4. DEMO/BENEFICES (15-25s): Montrer ce qui rend l'app geniale\n5. CTA (25-30s): Inciter au telechargement\n\nStyle UGC:\n- Parler comme a un ami, pas comme une pub\n- Utiliser \"je\", \"mon enfant\", \"vous savez quoi\"\n- Inclure des hesitations naturelles (\"euh\", \"genre\", \"vraiment\")\n- Emotions authentiques\n- Pas de jargon marketing\n\nFormat JSON requis:\n{\n \"hook\": \"Phrase d'accroche 3 sec max\",\n \"scenes\": [\n {\n \"id\": 1,\n \"timestamp\": \"0:00-0:03\",\n \"voiceover\": \"Texte exact a dire\",\n \"visual\": \"Description precise de ce qu'on filme\",\n \"emotion\": \"emotion/energie du moment\",\n \"camera\": \"type de plan (selfie, POV, produit)\"\n }\n ],\n \"cta\": \"Call to action\",\n \"captions\": [\"Textes a afficher sur la video\"],\n \"hashtags\": [\"sans le #\"],\n \"music_mood\": \"Description de l'ambiance musicale\",\n \"filming_tips\": [\"Conseils pour le tournage\"]\n}"
+ },
+ {
+ "role": "user",
+ "content": "=Cree un script UGC TikTok pour:\n\n**Produit:** {{ $json.product_name }}\n**Audience:** {{ $json.target_audience }}\n**Benefices cles:** \n{{ $json.key_benefits.map((b, i) => `${i+1}. ${b}`).join('\\n') }}\n**Ton:** {{ $json.tone }}\n**Duree:** {{ $json.duration_seconds }} secondes\n**Langue:** {{ $json.language }}\n**Type de hook:** {{ $json.hook_type }}\n**CTA:** {{ $json.cta }}\n\n**Instructions speciales:** {{ $json.custom_instructions || 'Aucune' }}\n\n---\n\nGenere un script UGC complet et authentique. Le spectateur doit croire que c'est un vrai parent qui partage son experience."
+ }
+ ]
+ },
+ "options": {
+ "temperature": 0.85,
+ "maxTokens": 2500,
+ "responseFormat": "json_object"
+ }
+ },
+ "id": "generate-script",
+ "name": "GPT-4o - Generate UGC Script",
+ "type": "@n8n/n8n-nodes-langchain.openAi",
+ "typeVersion": 1.8,
+ "position": [690, 300],
+ "credentials": {
+ "openAiApi": {
+ "id": "openai-credentials",
+ "name": "OpenAI API"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "// Parse the script\nconst response = $input.first().json;\nconst briefing = $('Validate & Structure Input').first().json;\n\nlet script;\ntry {\n const content = response.message?.content || response.content || response;\n script = typeof content === 'string' ? JSON.parse(content) : content;\n} catch (e) {\n throw new Error('Failed to parse script: ' + e.message);\n}\n\n// Generate full voiceover text for TTS\nconst fullVoiceover = script.scenes\n .map(s => s.voiceover)\n .join(' ');\n\nreturn [{\n json: {\n script,\n briefing,\n fullVoiceover,\n scenesForImages: briefing.generate_images ? script.scenes : []\n }\n}];"
+ },
+ "id": "parse-script",
+ "name": "Parse Script",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [910, 300]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": {
+ "caseSensitive": true,
+ "leftValue": "",
+ "typeValidation": "strict"
+ },
+ "conditions": [
+ {
+ "id": "should-generate-images",
+ "leftValue": "={{ $json.briefing.generate_images }}",
+ "rightValue": true,
+ "operator": {
+ "type": "boolean",
+ "operation": "equals"
+ }
+ }
+ ]
+ }
+ },
+ "id": "check-generate-images",
+ "name": "Generate Images?",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [1130, 300]
+ },
+ {
+ "parameters": {
+ "fieldToSplitOut": "scenesForImages",
+ "options": {}
+ },
+ "id": "split-scenes",
+ "name": "Split Scenes",
+ "type": "n8n-nodes-base.splitOut",
+ "typeVersion": 1,
+ "position": [1350, 200]
+ },
+ {
+ "parameters": {
+ "model": "gpt-4o",
+ "messages": {
+ "values": [
+ {
+ "role": "system",
+ "content": "Tu es un directeur artistique specialise en contenu UGC pour TikTok.\n\nTransforme la description de scene en un prompt DALL-E optimise pour generer une image style UGC/lifestyle.\n\nRegles:\n- Photo realiste, lumiere naturelle\n- Format vertical (pour 9:16)\n- Pas de texte ni logo\n- Ambiance chaleureuse, authentique\n- Adapte aux familles avec enfants\n\nReponds UNIQUEMENT avec le prompt en anglais, 1-2 phrases max."
+ },
+ {
+ "role": "user",
+ "content": "=Scene: {{ $json.visual }}\nEmotion: {{ $json.emotion }}\nCamera: {{ $json.camera }}\nContexte: App d'histoires pour enfants \"InfiniteStories\""
+ }
+ ]
+ },
+ "options": {
+ "temperature": 0.7,
+ "maxTokens": 200
+ }
+ },
+ "id": "generate-image-prompt",
+ "name": "Generate Image Prompt",
+ "type": "@n8n/n8n-nodes-langchain.openAi",
+ "typeVersion": 1.8,
+ "position": [1570, 200],
+ "credentials": {
+ "openAiApi": {
+ "id": "openai-credentials",
+ "name": "OpenAI API"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "operation": "generate",
+ "prompt": "={{ $json.message.content }}",
+ "options": {
+ "model": "dall-e-3",
+ "size": "1024x1792",
+ "quality": "standard",
+ "style": "natural"
+ }
+ },
+ "id": "generate-image",
+ "name": "DALL-E 3 - Generate Image",
+ "type": "@n8n/n8n-nodes-langchain.openAi",
+ "typeVersion": 1.8,
+ "position": [1790, 200],
+ "credentials": {
+ "openAiApi": {
+ "id": "openai-credentials",
+ "name": "OpenAI API"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "// Collect image result\nconst scene = $('Split Scenes').first().json;\nconst imageResult = $input.first().json;\nconst prompt = $('Generate Image Prompt').first().json.message?.content;\n\nreturn [{\n json: {\n scene_id: scene.id,\n timestamp: scene.timestamp,\n image_url: imageResult.data?.[0]?.url || imageResult.url,\n image_prompt: prompt,\n voiceover: scene.voiceover\n }\n}];"
+ },
+ "id": "collect-image",
+ "name": "Collect Image Data",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [2010, 200]
+ },
+ {
+ "parameters": {
+ "aggregate": "aggregateAllItemData",
+ "destinationFieldName": "images",
+ "options": {}
+ },
+ "id": "aggregate-images",
+ "name": "Aggregate Images",
+ "type": "n8n-nodes-base.aggregate",
+ "typeVersion": 1,
+ "position": [2230, 200]
+ },
+ {
+ "parameters": {
+ "jsCode": "// Compile response with images\nconst scriptData = $('Parse Script').first().json;\nconst images = $input.first().json.images || [];\n\nreturn [{\n json: {\n success: true,\n script: scriptData.script,\n generated_images: images,\n voiceover_text: scriptData.fullVoiceover,\n metadata: {\n product: scriptData.briefing.product_name,\n language: scriptData.briefing.language,\n duration_seconds: scriptData.briefing.duration_seconds,\n images_generated: images.length,\n generated_at: new Date().toISOString()\n },\n tiktok_post: {\n caption: scriptData.script.hook + '\\n\\n' + scriptData.script.hashtags.map(h => '#' + h).join(' '),\n captions_overlay: scriptData.script.captions,\n music_mood: scriptData.script.music_mood\n },\n filming_guide: scriptData.script.filming_tips\n }\n}];"
+ },
+ "id": "compile-with-images",
+ "name": "Compile Response (With Images)",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [2450, 200]
+ },
+ {
+ "parameters": {
+ "jsCode": "// Compile response without images\nconst scriptData = $('Parse Script').first().json;\n\nreturn [{\n json: {\n success: true,\n script: scriptData.script,\n generated_images: [],\n voiceover_text: scriptData.fullVoiceover,\n metadata: {\n product: scriptData.briefing.product_name,\n language: scriptData.briefing.language,\n duration_seconds: scriptData.briefing.duration_seconds,\n images_generated: 0,\n generated_at: new Date().toISOString()\n },\n tiktok_post: {\n caption: scriptData.script.hook + '\\n\\n' + scriptData.script.hashtags.map(h => '#' + h).join(' '),\n captions_overlay: scriptData.script.captions,\n music_mood: scriptData.script.music_mood\n },\n filming_guide: scriptData.script.filming_tips\n }\n}];"
+ },
+ "id": "compile-without-images",
+ "name": "Compile Response (Script Only)",
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [1350, 400]
+ },
+ {
+ "parameters": {
+ "mode": "combine",
+ "combineBy": "combineAll",
+ "options": {}
+ },
+ "id": "merge-results",
+ "name": "Merge Results",
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.1,
+ "position": [2670, 300]
+ },
+ {
+ "parameters": {
+ "respondWith": "json",
+ "responseBody": "={{ $json }}",
+ "options": {}
+ },
+ "id": "respond",
+ "name": "Respond",
+ "type": "n8n-nodes-base.respondToWebhook",
+ "typeVersion": 1.1,
+ "position": [2890, 300]
+ }
+ ],
+ "connections": {
+ "Webhook - Receive Briefing": {
+ "main": [
+ [
+ {
+ "node": "Validate & Structure Input",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Validate & Structure Input": {
+ "main": [
+ [
+ {
+ "node": "GPT-4o - Generate UGC Script",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "GPT-4o - Generate UGC Script": {
+ "main": [
+ [
+ {
+ "node": "Parse Script",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Parse Script": {
+ "main": [
+ [
+ {
+ "node": "Generate Images?",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Generate Images?": {
+ "main": [
+ [
+ {
+ "node": "Split Scenes",
+ "type": "main",
+ "index": 0
+ }
+ ],
+ [
+ {
+ "node": "Compile Response (Script Only)",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Split Scenes": {
+ "main": [
+ [
+ {
+ "node": "Generate Image Prompt",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Generate Image Prompt": {
+ "main": [
+ [
+ {
+ "node": "DALL-E 3 - Generate Image",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "DALL-E 3 - Generate Image": {
+ "main": [
+ [
+ {
+ "node": "Collect Image Data",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Collect Image Data": {
+ "main": [
+ [
+ {
+ "node": "Aggregate Images",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Aggregate Images": {
+ "main": [
+ [
+ {
+ "node": "Compile Response (With Images)",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Compile Response (With Images)": {
+ "main": [
+ [
+ {
+ "node": "Merge Results",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Compile Response (Script Only)": {
+ "main": [
+ [
+ {
+ "node": "Merge Results",
+ "type": "main",
+ "index": 1
+ }
+ ]
+ ]
+ },
+ "Merge Results": {
+ "main": [
+ [
+ {
+ "node": "Respond",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {
+ "executionOrder": "v1",
+ "saveManualExecutions": true,
+ "callerPolicy": "workflowsFromSameOwner"
+ },
+ "versionId": "1",
+ "meta": {
+ "instanceId": "infinite-stories-tiktok-script"
+ },
+ "tags": [
+ {
+ "name": "marketing",
+ "id": "1"
+ },
+ {
+ "name": "tiktok",
+ "id": "2"
+ },
+ {
+ "name": "content-generation",
+ "id": "3"
+ }
+ ]
+}
diff --git a/openspec/changes/add-n8n-tiktok-ads-workflow/design.md b/openspec/changes/add-n8n-tiktok-ads-workflow/design.md
new file mode 100644
index 0000000..3a10af6
--- /dev/null
+++ b/openspec/changes/add-n8n-tiktok-ads-workflow/design.md
@@ -0,0 +1,125 @@
+# Design: n8n TikTok UGC Ads Workflow
+
+## Context
+
+InfiniteStories is a children's bedtime story app that needs promotional content for TikTok. The team wants to leverage existing AI infrastructure (OpenAI models) while adding video generation capabilities. n8n was chosen as the workflow automation platform for its flexibility and self-hosting options.
+
+## Goals / Non-Goals
+
+**Goals:**
+- Automate TikTok ad generation from a simple briefing input
+- Maintain consistency with backend AI models (OpenAI)
+- Support multiple output modes for flexibility
+- Simplify billing through Replicate consolidation
+- Produce TikTok-optimized 9:16 vertical videos
+
+**Non-Goals:**
+- Direct TikTok posting (handled separately)
+- A/B testing infrastructure
+- Real-time video editing
+- User-facing interface (n8n internal only)
+
+## Decisions
+
+### Decision 1: Use n8n for workflow orchestration
+- **What**: n8n handles the entire pipeline from briefing to video output
+- **Why**: Visual workflow editor, self-hostable, good API integration support
+- **Alternatives**: Zapier (expensive), custom code (maintenance overhead), Make (less flexible)
+
+### Decision 2: OpenAI for script and audio generation
+- **What**: Use `gpt-5-mini` for scripts, `gpt-4o-mini-tts` for voiceover
+- **Why**: Consistency with backend models, proven quality, existing billing relationship
+- **Alternatives**: Claude (no TTS), ElevenLabs (separate billing), Anthropic (no audio)
+
+### Decision 3: Replicate for image and video generation
+- **What**: Use Replicate API with `flux-1.1-pro` for images, `minimax/video-01` for video
+- **Why**: Single billing platform for compute-heavy media generation, model flexibility
+- **Alternatives**:
+ - DALL-E 3 (expensive at scale)
+ - Runway (separate subscription)
+ - Pika (less API-friendly)
+
+### Decision 4: UGC-style script structure
+- **What**: HOOK (0-3s) -> PROBLEM (3-8s) -> DISCOVERY (8-15s) -> BENEFITS (15-25s) -> CTA (25-30s)
+- **Why**: Proven TikTok format for engagement, fits platform algorithm preferences
+- **Alternatives**: Testimonial format, demo format (less engaging for app promotion)
+
+### Decision 5: Conditional output modes
+- **What**: 4 output modes (script_only, script_audio, script_images, full_video)
+- **Why**: Allow iterative review, cost control, faster turnaround for script-only needs
+- **Alternatives**: Single full output (wastes resources if only script needed)
+
+## Technical Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ n8n WORKFLOW │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
+│ │ Form │───▶│ OpenAI │───▶│ Conditional │ │
+│ │ Trigger │ │ gpt-5-mini │ │ Branching │ │
+│ └──────────┘ │ (Script) │ └──────────┬─────────────┘ │
+│ └──────────────┘ │ │
+│ ▼ │
+│ ┌──────────────────────────────┴─────────┐ │
+│ │ │ │
+│ ┌────────▼────────┐ ┌─────────▼─────────┐ │ │
+│ │ OpenAI TTS │ │ Replicate │ │ │
+│ │ gpt-4o-mini-tts │ │ flux-1.1-pro │ │ │
+│ │ (Audio) │ │ (Images x5) │ │ │
+│ └────────┬────────┘ └─────────┬─────────┘ │ │
+│ │ │ │ │
+│ └──────────┬──────────┘ │ │
+│ ▼ │ │
+│ ┌──────────────────────┐ │ │
+│ │ Replicate │ │ │
+│ │ minimax/video-01 │ │ │
+│ │ (Video Assembly) │ │ │
+│ └──────────┬───────────┘ │ │
+│ ▼ │ │
+│ ┌──────────────────────┐ │ │
+│ │ JSON Response │ │ │
+│ │ (All Assets) │ │ │
+│ └──────────────────────┘ │ │
+│ │ │
+└──────────────────────────────────────────────────────────┴─────┘
+```
+
+## API Models Reference
+
+| Component | Provider | Model | Purpose |
+|-----------|----------|-------|---------|
+| Script | OpenAI | gpt-5-mini | UGC script generation |
+| Audio | OpenAI | gpt-4o-mini-tts | Voiceover synthesis |
+| Images | Replicate | flux-1.1-pro | Scene illustrations |
+| Video | Replicate | minimax/video-01 | Video assembly |
+
+## Risks / Trade-offs
+
+- **Risk**: Replicate model availability/deprecation
+ - Mitigation: Abstract model selection, easy to swap alternatives
+
+- **Risk**: Cost overruns on full video generation
+ - Mitigation: Output modes allow script-only for review before committing to video
+
+- **Risk**: Video quality inconsistency
+ - Mitigation: Use proven models, image-to-video ensures visual consistency
+
+- **Trade-off**: Async polling vs webhooks for Replicate
+ - Chose polling for simplicity; webhooks for production at scale
+
+## Migration Plan
+
+1. Deploy n8n instance
+2. Configure API credentials
+3. Import workflow JSON
+4. Test with sample briefings
+5. Create production briefing templates
+6. Train team on workflow usage
+
+## Open Questions
+
+- Q1: Should we add automatic TikTok posting integration?
+- Q2: Do we need A/B testing for different script styles?
+- Q3: Should we support other platforms (Instagram Reels, YouTube Shorts)?
diff --git a/openspec/changes/add-n8n-tiktok-ads-workflow/proposal.md b/openspec/changes/add-n8n-tiktok-ads-workflow/proposal.md
new file mode 100644
index 0000000..6a0e7ce
--- /dev/null
+++ b/openspec/changes/add-n8n-tiktok-ads-workflow/proposal.md
@@ -0,0 +1,23 @@
+# Change: Add n8n TikTok UGC Ads Workflow
+
+## Why
+
+InfiniteStories needs a scalable, automated solution to generate TikTok UGC-style promotional videos. Manual ad creation is time-consuming and expensive. By leveraging the same AI models used in the app backend (OpenAI) combined with Replicate for video generation, we can produce high-quality ads quickly with simplified billing.
+
+## What Changes
+
+- Add new `n8n-workflows/` directory for automation workflows
+- Create `tiktok-ugc-ads-generator.json` - Main n8n workflow for generating TikTok ads
+- Create `README.md` - Visual documentation with architecture diagrams
+- Integrate with:
+ - OpenAI `gpt-5-mini` for script generation (same as story generation)
+ - OpenAI `gpt-4o-mini-tts` for voiceover synthesis
+ - Replicate `flux-1.1-pro` for image generation
+ - Replicate `minimax/video-01` or `kling-v1.6-pro` for video generation
+
+## Impact
+
+- Affected specs: None (new capability)
+- Affected code: New `n8n-workflows/` directory
+- New external dependencies: Replicate API
+- Infrastructure: Requires n8n instance (self-hosted or cloud)
diff --git a/openspec/changes/add-n8n-tiktok-ads-workflow/specs/n8n-workflow/spec.md b/openspec/changes/add-n8n-tiktok-ads-workflow/specs/n8n-workflow/spec.md
new file mode 100644
index 0000000..929a17b
--- /dev/null
+++ b/openspec/changes/add-n8n-tiktok-ads-workflow/specs/n8n-workflow/spec.md
@@ -0,0 +1,124 @@
+# n8n Workflow Specification
+
+## ADDED Requirements
+
+### Requirement: TikTok UGC Script Generation
+The workflow SHALL generate UGC-style scripts from a briefing input using OpenAI gpt-5-mini.
+
+The script MUST follow the structure:
+- HOOK (0-3 seconds): Attention grabber
+- PROBLEM (3-8 seconds): Pain point identification
+- DISCOVERY (8-15 seconds): Solution introduction
+- BENEFITS (15-25 seconds): Feature highlights
+- CTA (25-30 seconds): Call to action
+
+#### Scenario: Script generation from briefing
+- **GIVEN** a user provides a briefing with target audience, key message, and tone
+- **WHEN** the workflow executes with output_mode containing "script"
+- **THEN** the system SHALL generate a structured UGC script with all 5 sections
+- **AND** the script SHALL be optimized for TikTok's 9:16 vertical format
+
+#### Scenario: Script includes visual directions
+- **GIVEN** a script is generated
+- **WHEN** the output is returned
+- **THEN** each section SHALL include visual_direction for image/video generation
+- **AND** each section SHALL include timing information
+
+---
+
+### Requirement: Audio Voiceover Generation
+The workflow SHALL generate audio voiceover from the script using OpenAI gpt-4o-mini-tts.
+
+#### Scenario: Audio generation from script
+- **GIVEN** a generated script
+- **WHEN** the workflow executes with output_mode "script_audio" or "full_video"
+- **THEN** the system SHALL generate MP3 audio for the complete script
+- **AND** the voice SHALL match the requested tone (energetic, warm, professional)
+
+#### Scenario: Audio timing matches script sections
+- **GIVEN** audio is generated
+- **WHEN** the output is returned
+- **THEN** the audio duration SHALL approximately match the script timing (25-30 seconds)
+
+---
+
+### Requirement: Scene Image Generation
+The workflow SHALL generate scene images from visual directions using Replicate flux-1.1-pro.
+
+#### Scenario: Image generation for each section
+- **GIVEN** a script with 5 sections and visual_directions
+- **WHEN** the workflow executes with output_mode "script_images" or "full_video"
+- **THEN** the system SHALL generate one image per section (5 images total)
+- **AND** images SHALL be 1024x1792 (9:16 aspect ratio)
+
+#### Scenario: Image style consistency
+- **GIVEN** images are generated for a script
+- **WHEN** multiple images are created
+- **THEN** all images SHALL maintain consistent style based on the briefing tone
+
+---
+
+### Requirement: Video Assembly
+The workflow SHALL assemble final video from audio and images using Replicate video models.
+
+#### Scenario: Full video generation
+- **GIVEN** generated audio and images
+- **WHEN** the workflow executes with output_mode "full_video"
+- **THEN** the system SHALL generate a 9:16 vertical video
+- **AND** video duration SHALL be 25-30 seconds
+- **AND** images SHALL be animated with transitions
+
+#### Scenario: Video uses image-to-video model
+- **GIVEN** the video generation step
+- **WHEN** Replicate is called
+- **THEN** the system SHALL use minimax/video-01 or kling-v1.6-pro model
+- **AND** the system SHALL poll for completion with exponential backoff
+
+---
+
+### Requirement: Conditional Output Modes
+The workflow SHALL support multiple output modes to control generation scope.
+
+#### Scenario: Script only mode
+- **GIVEN** output_mode is "script_only"
+- **WHEN** the workflow completes
+- **THEN** only the script JSON SHALL be returned
+- **AND** no audio, images, or video SHALL be generated
+
+#### Scenario: Script with audio mode
+- **GIVEN** output_mode is "script_audio"
+- **WHEN** the workflow completes
+- **THEN** script and audio_url SHALL be returned
+- **AND** no images or video SHALL be generated
+
+#### Scenario: Script with images mode
+- **GIVEN** output_mode is "script_images"
+- **WHEN** the workflow completes
+- **THEN** script and image_urls array SHALL be returned
+- **AND** no video SHALL be generated
+
+#### Scenario: Full video mode
+- **GIVEN** output_mode is "full_video"
+- **WHEN** the workflow completes
+- **THEN** script, audio_url, image_urls, and video_url SHALL be returned
+
+---
+
+### Requirement: Form-Based Input Interface
+The workflow SHALL provide a form-based interface for briefing input.
+
+#### Scenario: Form trigger with required fields
+- **GIVEN** a user accesses the workflow
+- **WHEN** the form is displayed
+- **THEN** the form SHALL include:
+ - target_audience (required, text)
+ - key_message (required, text)
+ - tone (required, dropdown: energetic/warm/professional)
+ - output_mode (required, dropdown)
+ - additional_context (optional, text)
+
+#### Scenario: Form validation
+- **GIVEN** a user submits the form
+- **WHEN** required fields are missing
+- **THEN** the form SHALL display validation errors
+- **AND** the workflow SHALL NOT execute
diff --git a/openspec/changes/add-n8n-tiktok-ads-workflow/tasks.md b/openspec/changes/add-n8n-tiktok-ads-workflow/tasks.md
new file mode 100644
index 0000000..0e60213
--- /dev/null
+++ b/openspec/changes/add-n8n-tiktok-ads-workflow/tasks.md
@@ -0,0 +1,27 @@
+# Tasks: Add n8n TikTok UGC Ads Workflow
+
+## 1. Implementation
+
+- [x] 1.1 Create `n8n-workflows/` directory structure
+- [x] 1.2 Design workflow architecture (Form Trigger -> AI Nodes -> Output)
+- [x] 1.3 Implement script generation node with OpenAI `gpt-5-mini`
+- [x] 1.4 Implement audio generation node with OpenAI `gpt-4o-mini-tts`
+- [x] 1.5 Implement image generation node with Replicate `flux-1.1-pro`
+- [x] 1.6 Implement video generation node with Replicate video models
+- [x] 1.7 Add conditional output modes (script_only, script_audio, script_images, full_video)
+- [x] 1.8 Create comprehensive README with visual documentation
+
+## 2. Documentation
+
+- [x] 2.1 Document workflow architecture with ASCII diagrams
+- [x] 2.2 Document each workflow step (script, audio, images, video)
+- [x] 2.3 Document credentials configuration
+- [x] 2.4 Document cost breakdown per output mode
+
+## 3. Deployment (Pending)
+
+- [ ] 3.1 Deploy n8n instance (self-hosted or cloud)
+- [ ] 3.2 Configure OpenAI API credentials
+- [ ] 3.3 Configure Replicate API credentials
+- [ ] 3.4 Import and test workflow
+- [ ] 3.5 Create production briefing templates