AdShot Pipeline Architecture

Product URL to Meta Ad Campaign in 4 phases. Parallel extraction, LLM-powered creative direction, hybrid skeleton rendering with self-QA.

Phase 1
Extract
pipeline/extract.py
~8-15s
Phase 2
Reason
pipeline/reason.py
~3-5s
Phase 3
Create
pipeline/create.py
~8-20s
Phase 4
Deliver
main.py → API
instant
Phase 1

Extract

Parallel product extraction + brand intelligence

Inputs

  • Product URL (any e-commerce page)

Outputs

  • ExtractResult (ProductData + BrandLanguage + BrandBrief + CompetitiveSignals)
  • Downloaded font files (woff2/woff/ttf)
  • Validated product image URLs
  • Brand brief compiled from 7+ sources

Failure Modes

  • Product name = "Unknown Product" → pipeline abort
  • No product images found → pipeline abort
  • Gemini rate limited (429) → retry with backoff
  • Playwright timeout → falls back to direct fetch
1 Wave 1: Parallel Scraping asyncio.gather(shopify, direct_fetch, playwright)
S1

Shopify JSON API

Appends .json to product URL. Fastest, most reliable for Shopify stores. Returns structured product data: title, price, variants, images.

Timeout: 8s · httpx

S2

Direct HTTP Fetch

Raw HTML GET via httpx. Feeds the brand extraction LLM. Fallback content source when Shopify JSON isn't available.

Timeout: 15s · httpx

S3

Playwright Browser

Full JS rendering. Screenshots page, extracts DOM (price, title, images, CSS fonts, @font-face, logo, Instagram handle). Dismisses cookie banners. Intercepts font network responses. Clicks swatches to reveal price.

Timeout: 25s · Chromium headless

S4

Merger / Ranker

Deterministic priority ranking: Shopify > DOM > LLM > Vision for each field. Product name, price, images all have independent fallback chains. Deduplicates images, filters logos/icons.

2 Wave 2: Intelligence Gathering (8 parallel tasks) asyncio.gather(brand_extract, comp_search, img_validate, about, meta_ads, instagram, photo_palette, font_download)
LLM

Brand Extraction

Gemini 2.0 Flash analyzes HTML content. Extracts product data, brand personality, visual language, messaging pillars, key phrases, ad angles, review themes.

GS

Competitive Search

Gemini grounded search (Google Search tool). Returns competitor brands, messaging patterns, market gaps, trending themes.

HTTP

About Page Scrape

Tries /about, /about-us, /our-story, /pages/about, /pages/our-story. Strips HTML, returns plain text for brand voice analysis.

PW

Meta Ad Library Screenshot

Screenshots facebook.com/ads/library for the brand. Gemini vision analyzes ad visual style, color palette, typography, layout patterns.

PW

Instagram Grid Screenshot

Screenshots the brand's Instagram profile (if handle found). Gemini vision analyzes visual style, content themes, product presentation.

LLM

Photo Palette Analysis

Downloads first product image, sends to Gemini vision. Analyzes color palette, lighting style, background, mood.

HTTP

Font Download

Priority 1: Intercepted fonts from Playwright (already in memory). Priority 2: @font-face URL download. Maps fonts to roles: BrandHeading, BrandBody, BrandAccent.

HTTP

Image Validation

Parallel HEAD requests. Filters broken images, tiny images (<10KB), non-image MIME types.

BRAND_EXTRACT_SYSTEM (system prompt)
You are a world-class brand strategist and creative director who reverse-engineers brand identities from their digital presence. You don't just extract functional data — you decode the brand's SOUL. You understand that great advertising requires deep brand empathy: knowing not just what a brand sells, but how it makes people feel, what it stands for, and the visual/emotional language it uses to communicate. You think like a creative agency receiving a brief: you need to understand the brand deeply enough to create ads that feel authentically on-brand, as if the brand's own team made them. Return valid JSON matching the requested schema. Be specific and vivid in descriptions — generic answers like "modern and clean" are useless.
BRAND_EXTRACT_PROMPT (user prompt) — ~74 JSON fields requested
Analyze this product page as a brand strategist preparing to create Meta ads. Extract both the functional product data AND the deep brand identity. Return JSON with these exact fields: { "product_name": "exact product name", "price": "number only, no currency symbol (e.g. '45' not '$45')", "currency": "USD|GBP|EUR|etc", "description": "clean product description, 2-3 sentences", "value_props": ["top 3-5 selling points — be specific, not generic"], "specs": {"key": "value"}, "brand_name": "brand/store name", "seller_name": "retailer name if different from brand, else null", "tagline": "tagline or slogan if visible", "voice": "2-4 word voice essence (e.g. 'warm purposeful comfort' not 'friendly')", "brand_soul": "2-3 sentences: What does this brand STAND FOR at its deepest level?...", "messaging_pillars": ["the 3-5 themes they consistently emphasize"], "tone_markers": ["specific tone qualities"], "key_phrases": ["5-10 distinctive phrases copied VERBATIM from the page"], "content_style": "How they write — sentence structure, vocabulary level...", "review_themes": ["3-5 themes from customer reviews/testimonials"], "ad_messaging_angles": ["3-5 specific ad angles this brand ACTUALLY uses"], "visual_language": "overall visual feel as if briefing a photographer", "mood": "emotional atmosphere their visuals create", "visual_metaphors": ["conceptual metaphors in their visual language"], "palette_mood": "Describe the color feeling, not just the colors", "lighting_style": "How their product photography is lit", "texture_language": "What textures dominate their visuals", "cinematography": "How they frame/compose", "ad_style_description": "creative direction for a Meta feed ad", "primary_color": "#hex", "secondary_color": "#hex", "accent_color": "#hex", "font_family": "primary font name", "font_style": "font personality" } IMPORTANT: Do NOT be generic. "Modern and clean" describes 10,000 brands. Be specific to THIS brand. Page content: {content}
VISION_SYSTEM + VISION_PROMPT (screenshot fallback)
VISION_SYSTEM: You are a product page analyst with computer vision. You extract product information from screenshots with extreme precision. Only report what you can actually READ in the image. Never hallucinate. VISION_PROMPT: Look at this product page screenshot carefully. Extract ALL visible product information. Return JSON: { "product_name": "exact product name as shown", "price": "number only (e.g. '45' not '$45.00'). Look near Add to Cart, in sidebar, below product image.", "currency": "USD|GBP|EUR (infer from $ sign or domain)", "description": "product description if visible, 2-3 sentences max", "brand_name": "brand/store name from logo or header", "value_props": ["visible selling points, badges, feature callouts"], "primary_color": "#hex of dominant brand color (header/nav/logo)", "accent_color": "#hex of CTA button color", "rating": "star rating if visible (e.g. '4.8')", "review_count": "number of reviews if visible" } Be precise. Only report what you can READ.
COMPETITIVE_SYSTEM + COMPETITIVE_PROMPT (grounded search)
COMPETITIVE_SYSTEM: You are a competitive intelligence analyst for digital advertising. You research competitor ad strategies, messaging patterns, and market positioning. Return structured JSON with actionable competitive signals. COMPETITIVE_PROMPT: Research the competitive landscape for this product: Product: {product_name} Brand: {brand_name} Category: {category} Price: {price} {currency} Find and analyze: 1. What messaging angles do competitors use for similar products? 2. What are the dominant ad themes in this category? 3. What gaps exist in competitor messaging that this brand could exploit? Return JSON: { "competitor_brands": ["list of 3-5 competitor brand names"], "messaging_patterns": ["list of 3-5 common ad messaging patterns in this category"], "market_gaps": ["list of 2-3 underused messaging angles"], "search_themes": ["list of 3-5 trending topics/themes related to this product category"] }
3 Wave 3: Brand Brief Compilation + Vision Fallback asyncio.gather(compile_brief, vision_fallback)

Synthesizes all 7+ intelligence sources into a single BrandBrief using gemini-2.5-flash (thinking model). Vision fallback only runs if product name OR price is still missing after Waves 1-2.

BRAND_BRIEF_SYSTEM (system prompt)
You are a world-class brand strategist compiling a definitive Brand Brief. You are synthesizing intelligence from multiple sources: the brand's website, their Meta ads, their Instagram, their about page, and competitive research. Your brief must be rich enough for a creative team to produce ads that are INDISTINGUISHABLE from ones the brand's own team would create. Be specific and vivid — generic descriptions are useless.
BRAND_BRIEF_PROMPT (user prompt) — compiles 7 intelligence sources
Compile a comprehensive Brand Brief from these intelligence sources: ## Page-Extracted Brand Data {brand_llm_data} ## About Page Content {about_text} ## Meta Ad Library Analysis {meta_ad_analysis} ## Instagram Visual Analysis {instagram_analysis} ## Product Photography Palette {photo_palette} ## Competitive Research {competitive_text} ## CSS-Detected Fonts {css_fonts} Return JSON: { "brand_name": "exact brand name", "brand_soul": "2-3 sentences: What this brand fundamentally believes...", "voice_description": "Detailed voice description...", "visual_identity": "Comprehensive visual identity...", "photography_style": "Specific photography direction...", "color_strategy": "The brand's REAL color palette...", "typography_strategy": "Font choices and why...", "ad_creative_direction": "How to create Meta ads for this brand...", "target_customer": "Detailed customer profile...", "competitive_positioning": "How this brand differentiates...", "key_phrases": ["8-12 distinctive phrases"], "resolved_fonts": ["2-3 font names to use"] }
Validation Gates (between Extract → Reason) main.py lines 129-132

Gate 1: Product Name

  • If product.name == "Unknown Product" → RuntimeError, pipeline abort

Gate 2: Product Images

  • If product.images is empty → RuntimeError, pipeline abort
Phase 2

Reason

Creative Director brief — single LLM call

Inputs

  • ExtractResult (product + brand + brand_brief + competitive)
  • Formatted brand brief text (compiled from all intelligence)

Outputs

  • ReasonResult with campaign_theme, target_audience, tone, visual_style, color_palette
  • 3x CreativeSpec with headline, subheadline, CTA, primary_text, description, angle
  • Each spec has layout_description + visual_direction (natural language for Create phase)

Failure Modes

  • Gemini returns non-dict → RuntimeError
  • <3 specs generated → warning (continues)
  • No creative_specs → pipeline abort at gate
  • Headlines truncated to 40 chars
LLM Creative Director Call gemini-2.0-flash · response_json=True

System prompt is dynamically templated with brand data from Phase 1. The LLM role-plays as the brand's own Creative Director.

REASON_SYSTEM (system prompt — brand-personalized)
You are the Creative Director at {brand_name}. You've worked here for years. You know the brand inside out. BRAND DNA: - Soul: {brand_soul} - Voice: {voice_description} - Visual language: {visual_language} - Ad style: {ad_style} - Photography: {photography_style} You are designing a Meta feed ad campaign for {product_name}. These ads must be INDISTINGUISHABLE from ads your in-house team would create. You think like an art director: composition, visual weight, negative space, typography as design element, photography as hero. The product image IS the ad — text is a layer on top, not a separate zone. Study how {brand_name} actually advertises: - What does a {brand_name} ad LOOK like? (reference: {ad_style}) - What does a {brand_name} ad FEEL like? (reference: {brand_soul}) - How does {brand_name} use space? Typography? Color? CRITICAL CONSTRAINTS: - Headlines ≤40 characters. Count carefully. - Headlines must sound like YOUR brand wrote them — your voice, your wordplay, your vocabulary. - CTAs: "Shop Now", "Learn More", "Get Yours", "Try Now", "See More" - Primary text: hook first line (stop the scroll), benefit-led body. ~125 chars. - Each variant MUST use a completely different emotional angle AND different composition. - NEVER hallucinate product features. NEVER invent product lines or categories that don't exist. - ONLY reference the ACTUAL product being advertised. - If price is 0/missing, NEVER mention price. - The ad canvas is exactly 1080×1080 pixels. - Do NOT use emoji or special unicode characters in headlines or copy. - Each variant should use a DIFFERENT image_index (0, 1, 2) to create visual variety. - The layout_description should be EXTREMELY specific about pixel positions, sizes, and margins. COPY STRATEGY (CRITICAL): - If "Brand's ACTUAL Ad Angles" are provided, you MUST use those as the basis for headlines and copy. - If "Customer Review Themes" are provided, weave real customer language into at least one variant. - Generic copy like "Carry Smarter Not Harder" is FORBIDDEN. Be specific to THIS brand. - Each headline should reference a REAL product benefit, customer sentiment, or brand messaging angle.
REASON_PROMPT (user prompt — campaign brief request)
Design a 3-variant Meta feed ad campaign for this product. ## Product - Name: {product_name} - Price: {price} {currency} - Description: {description} - Value Props: {value_props} - Rating: {rating} ({review_count} reviews) ## Brand Intelligence {brand_brief_text} ## Available Product Images: {image_count} Return JSON: { "campaign_theme": "5-8 word theme capturing the brand's soul", "target_audience": { "summary": "...", "demographics": "...", "interests": ["5-8 Meta interest targeting categories"], "behaviors": ["3-5 purchase/digital behaviors"], "psychographics": "...", "lookalike_seed": "..." }, "tone": "...", "visual_style": "...", "color_palette": ["4 hex colors"], "validation_issues": [...], "enhanced_fields": [...], "creative_specs": [ { "variant_index": 0, "headline": "≤40 chars. Must sound like the BRAND wrote it.", "subheadline": "...", "cta": "Shop Now", "primary_text": "~125 char Meta ad text. First line = scroll-stopper.", "description": "~90 chars.", "angle": "specific emotional angle", "image_index": 0, "image_type": "lifestyle|on_model|studio|flat_lay", "value_props": [], "mood_direction": "...", "color_emphasis": "...", "layout_description": "DETAILED composition with pixel positions...", "visual_direction": "DETAILED visual instructions..." }, { "variant_index": 1, ... }, { "variant_index": 2, ... } ] } COMPOSITION STYLES TO CHOOSE FROM (mix across variants): 1. Full-bleed photo 2. Bold typography hero 3. Editorial split 4. Minimal product 5. Diagonal/dynamic 6. Text-on-image
Phase 3

Create

Skeleton rendering + Gemini styling + self-QA loop

Inputs

  • ExtractResult (images, fonts, colors, logo)
  • ReasonResult (creative_specs with layout_description + visual_direction)

Outputs

  • list[Creative] — PNG images (1080×1080)
  • Each Creative: variant_index, format, width, height, image_data (bytes)

Failure Modes

  • All image downloads fail → empty list returned
  • Skeleton assembly fails → falls back to V2 Jinja templates
  • QA score <9/10 → retry with different skeleton (max 2 retries)
  • Playwright render too small (<1KB) → fails variant
V3 Render Pipeline (per variant) skeleton + Gemini style + Playwright screenshot
1

Image Download

Downloads up to 6 product images in parallel. Converts to base64 data URIs. Scores images by size, type keywords (lifestyle, on-model, studio), match to spec's image_type.

2

Font Resolution

Priority: Downloaded brand fonts (BrandHeading/BrandBody roles) → CSS-detected fonts → Google Fonts URL → Brand brief resolved_fonts → FONT_MAP mapping → Bundled defaults (Satoshi/General Sans).

3

Skeleton Selection

Maps layout_description keywords to skeleton name. Keywords: "hero/product" → hero_product, "split/editorial" → editorial_split, "full bleed/photo" → full_bleed, "bold type/typography" → bold_type. Falls back to rotation by variant_index.

4

Gemini Style Generation

LLM returns JSON style object: bg, overlay_gradient, headline_color, cta_bg, cta_radius, h_size, etc. Merged over defaults. Brand colors injected into prompt.

5

HTML Assembly

Skeleton.format(**style_values) → inject font CSS (base64 @font-face) → inject product image data URI. Validate: must have <html> and <body> or <div>.

6

Playwright Screenshot

Render HTML in headless Chromium at 1080×1080. Semaphore limits to 2 concurrent renders. Returns PNG bytes.

STYLE_GEN_SYSTEM (system prompt)
You are a brand styling expert for Meta ads. Given a brand's visual identity and a layout skeleton name, output a JSON style object. You ONLY output valid JSON — no explanation, no markdown.
STYLE_GEN_PROMPT (user prompt)
Brand: {brand_name} Layout: {skeleton_name} Primary color: {brand_primary} Accent color: {brand_accent} Visual mood: {visual_direction} Return JSON with these EXACT keys (all values are CSS strings): { "bg": "<canvas background, e.g. #1a1a1a or {brand_primary}>", "left_bg": "<left panel bg for editorial_split>", "top_bg": "<top panel bg for bold_type>", "overlay_gradient": "<CSS gradient, e.g. linear-gradient(to right, {brand_primary}ee 0%, {brand_primary}00 100%)>", "logo_color": "<brand name text color>", "headline_color": "<headline color>", "sub_color": "<subheadline color>", "h_size": "<headline font-size 48-72px>", "cta_bg": "<CTA background>", "cta_color": "<CTA text color>", "cta_radius": "<0px for sharp, 4px for subtle, 30px for pill>" } Express {brand_name}'s DNA through these colors. Use brand colors prominently. JSON only:
QA Self-QA Scoring Loop Gemini vision · score ≥9/10 to pass · max 2 retries

After rendering, the PNG is sent back to Gemini for quality scoring. If score < 9/10, the critique is fed back into the style generation prompt and a different skeleton is tried. Max 2 retries before accepting.

QA_SCORE_PROMPT (vision scoring)
Score this 1080x1080 Meta feed ad for {brand_name}. Be STRICT. 1. Canvas fill (0 or 1): ENTIRE frame filled? NO white borders/gaps? 2. Brand authenticity (0-2): Looks like {brand_name}'s own ad team made it? 3. Product prominence (0-2): Product image large (60%+ of frame)? 4. Text readability (0 or 1): All text readable? No clipping/overflow? 5. Logo/brand mark (0 or 1): Brand name visible and properly sized (small, corner)? 6. Copy quality (0 or 1): Headline specific to the brand, not generic? 7. Typography (0 or 1): Correct weight and case? 8. Visual polish (0 or 1): Professional, no layout bugs? Return JSON: {"total": <int 0-10>, "breakdown": {"canvas_fill": <0-1>, "brand_auth": <0-2>, "product_prominence": <0-2>, "text_readability": <0-1>, "logo": <0-1>, "copy": <0-1>, "typography": <0-1>, "polish": <0-1>}, "critique": "<1-2 sentences>"}
Layout Skeletons (4 fixed HTML/CSS structures) Gemini only fills style values — structure is deterministic

hero_product

IMG
BRAND
Headline
Text
Subheadline
CTA

Product image 75% right, gradient overlay left, text zone bottom-left

heroproduct focusminimal product

editorial_split

BRAND
Bold
Headline
Subheadline text
CTA
IMG

45% text panel left, 55% image right, brand color background

spliteditorialside by sidediagonal

full_bleed

FULL IMG
BRAND
Headline
Subheadline
CTA

Full-bleed image, gradient overlay bottom 55%, text at bottom

full bleedphotolifestyletext on image

bold_type

BRAND
BIG
TYPE
IMG
CTA

38% top brand-color panel with big headline, 62% bottom image, CTA overlay

bold typetypographyheadline
V2 Template Fallback Jinja2 templates · 7 template variants

When USE_DYNAMIC_GEN=False or skeleton assembly fails, falls back to the V2 Jinja2 template system. Templates: full_bleed_overlay, editorial_split, bold_type, social_native, clean_hero, feature_callout, price_anchor. Each is an HTML file in backend/templates/, rendered by Jinja2 with brand colors, fonts, and copy injected.

Aa Font Resolution System Downloaded → CSS-detected → Google Fonts → Bundled → FONT_MAP → Defaults

Bundled Fonts

Satoshi (400/500/700/900), General Sans (400/500/600/700), Clash Display (500/600/700), Cabinet Grotesk (500/700/800). Embedded as base64 @font-face from backend/fonts/.

FONT_MAP

Maps common web fonts to bundled alternatives: Circular → Satoshi, Proxima Nova → General Sans, Futura → Cabinet Grotesk, Montserrat → Cabinet Grotesk, Poppins → Satoshi, Roboto → General Sans, etc.

Downloaded Fonts

Fonts intercepted from the brand's site during Playwright extraction. Injected as base64 @font-face with role names: BrandHeading, BrandBody, BrandAccent. Highest priority.

Font CSS Styles

CSS computed styles from DOM: font-weight, text-transform, letter-spacing, font-size per element (h1, h2, p, nav, cta). Used for h_weight, h_transform, h_letter_spacing, b_weight.

Phase 4

Deliver

API endpoints + frontend polling

API Endpoints

  • POST /campaign — start pipeline (returns campaign ID)
  • GET /campaign/{id} — poll status + progressive results
  • GET /campaign/{id}/creative/{idx} — download PNG
  • POST /campaign/{id}/cancel — request cancellation
  • GET /health — heartbeat

Frontend

  • Progressive disclosure: extract preview → brief → creatives
  • Polls /campaign/{id} every 1.5s
  • Lightbox for full-size creative viewing
  • Per-variant tabs with ad copy + creative image
  • Timing breakdown display

Concurrency

  • Global _busy flag — only 1 pipeline at a time
  • 409 Conflict if pipeline already running
  • Cancel via _cancel_requested set, checked between phases
  • Campaign objects stored in-memory dict (no persistence)
Reference

All LLM Calls

Complete inventory of Gemini API calls
Phase Call Model Method JSON? Conditional?
Extract Brand Extraction gemini-2.0-flash generate_text Yes Always
Extract Competitive Search gemini-2.0-flash + googleSearch grounded_search Yes (text-parsed) If product name available
Extract Meta Ad Library Analysis gemini-2.0-flash analyze_image No If brand name available
Extract Instagram Grid Analysis gemini-2.0-flash analyze_image No If Instagram handle found
Extract Photo Palette Analysis gemini-2.0-flash analyze_image No If product images exist
Extract Brand Brief Compilation gemini-2.5-flash generate_text Yes Always (Wave 3)
Extract Vision Fallback gemini-2.0-flash analyze_image Yes Only if name OR price missing
Reason Creative Director Brief gemini-2.0-flash generate_text Yes Always
Create Style Generation (×3 variants) gemini-2.0-flash generate_text Yes Per variant (USE_DYNAMIC_GEN)
Create QA Scoring (×3 variants, up to 2 retries each) gemini-2.0-flash analyze_image Yes Per variant (skipped on final attempt)

Total LLM calls per pipeline run: 8-17 calls (minimum: brand_extract + competitive + brief + reason + 3×style_gen + 3×QA = 11; more with retries and conditional vision calls).

Reference

Data Flow

How data models flow through the pipeline

BrandLanguage

models.py · 30+ fields
brand_name, voice, brand_soul, messaging_pillars, tone_markers, key_phrases, visual_language, mood, visual_metaphors, palette_mood, lighting_style, texture_language, primary_color, accent_color, font_family, css_fonts, downloaded_fonts, ad_messaging_angles, logo_url, logo_svg, meta_ad_style, instagram_style, photo_palette...

BrandBrief

models.py · compiled by gemini-2.5-flash
brand_soul, voice_description, visual_identity, photography_style, color_strategy, typography_strategy, ad_creative_direction, target_customer, competitive_positioning, key_phrases, resolved_fonts

CreativeSpec

models.py · 3 per campaign
variant_index, headline (≤40), subheadline, cta, primary_text, description, angle, image_index, image_type, layout_description, visual_direction, mood_direction, color_emphasis, template (V2 fallback)

Creative

models.py · final output
variant_index, format ("feed"), width (1080), height (1080), image_data (PNG bytes)
Complete Model Chain Campaign → ExtractResult → ReasonResult → Creative[]
Campaign
  └─ id: str (uuid hex[:12])
  └─ url: str
  └─ status: CampaignStatus (pending → extracting → reasoning → creating → done | error)
  └─ extract: ExtractResult
  │    └─ product: ProductData (name, price, currency, description, images[], specs, rating, review_count, reviews[], review_themes[], value_props[])
  │    └─ brand: BrandLanguage (30+ fields: identity, voice, visual, colors, fonts, intelligence signals)
  │    └─ brand_brief: BrandBrief | None (compiled from 7+ sources by gemini-2.5-flash)
  │    └─ competitive: CompetitiveSignals (competitor_brands[], messaging_patterns[], market_gaps[], search_themes[])
  │    └─ sources_used: list[str]
  │    ┌─ extraction_issues: list[str]
  └─ reason: ReasonResult
  │    └─ campaign_theme: str
  │    └─ target_audience: TargetAudience (summary, demographics, interests[], behaviors[], psychographics, lookalike_seed)
  │    └─ tone, visual_style, color_palette[]
  │    └─ creative_specs: list[CreativeSpec] (3x: headline, subheadline, cta, primary_text, description, angle, image_index, layout_description, visual_direction)
  │    ┌─ validation_issues[], enhanced_fields[]
  └─ creatives: list[Creative] (variant_index, format, width, height, image_data: bytes)
  └─ timing: dict (extract, reason, create, total)
  ┌─ warnings: list[str], error: str | None
Infrastructure

External Services

Runtime dependencies

Gemini API

Primary LLM. Three methods: generate_text (text-in/text-out), analyze_image (vision), grounded_search (Google Search tool).

Models: gemini-2.0-flash (fast), gemini-2.5-flash (thinking), gemini-3-pro-image-preview (unused).

Rate limit: 429 → exponential backoff (5s, 10s, 15s). Timeout: 90s text/vision, 30s grounded.

🌐

Playwright (Chromium)

Headless browser singleton. Used for: page scraping (extract), font interception, Meta Ad Library screenshots, Instagram screenshots, HTML-to-PNG rendering (create).

Pre-warmed at startup. Render semaphore: 2 concurrent. Launch args: --no-sandbox --disable-gpu --disable-dev-shm-usage

Fly.io

Hosting platform for the FastAPI backend. API base: adshot-api.fly.dev. Single instance (global _busy flag prevents concurrent pipelines).

Cloudflare Pages

Hosts the static frontend SPA (frontend/index.html). Connects to Fly.io API via fetch. No server-side rendering.

🔎

Google Search Grounding

Built into Gemini API via {"googleSearch": {}} tool. Used for competitive intelligence. Cannot use JSON response mode — parsed from text output.

🎨

Google Fonts CDN

Fallback font source when brand fonts aren't downloaded or bundled. Loaded via @import url() in generated HTML. Only used when font isn't in BUNDLED_FONTS or downloaded_fonts.

LIVE DATA

Sample Output — Allbirds Tree Runner

Actual word-for-word pipeline output from a live run. Extract: 25.5s · Reason: 10.5s · Create: 6.0s · Total: 42.0s

URL: allbirds.com/products/mens-tree-runners
Brand: Allbirds
Product: Men's Tree Runner - Jet Black (White Sole)
Price: $100
Phase 1: Extract
Phase 2: Reason
{
  "product": {
    "name": "Men's Tree Runner - Jet Black (White Sole)",
    "price": "100",
    "currency": "USD",
    "description": "The Allbirds Tree Runner is a breathable and lightweight sneaker made with responsibly sourced eucalyptus tree fiber that feels silky smooth and cool on your skin. These shoes are perfect for everyday casual wear, walking, and warmer weather.",
    "images": [
      "https://cdn.shopify.com/s/files/1/1104/4168/files/TR3MJBW080_SHOE_LEFT_GLOBAL_MENS_TREE_RUNNER_JET_BLACK_WHITE.png?v=1751165486",
      "https://cdn.shopify.com/s/files/1/1104/4168/files/TR3MJBW080_SHOE_BACK_GLOBAL_MENS_TREE_RUNNER_JET_BLACK_WHITE.png?v=1751165486",
      "https://cdn.shopify.com/s/files/1/1104/4168/files/TR3MJBW080_SHOE_TOP_GLOBAL_MENS_TREE_RUNNER_JET_BLACK_WHITE.png?v=1751165486",
      "https://cdn.shopify.com/s/files/1/1104/4168/files/TR3MJBW080_SHOE_BOTTOM_GLOBAL_MENS_TREE_RUNNER_JET_BLACK_WHITE.png?v=1751165486",
      "http://www.allbirds.com/cdn/shop/files/TR3MJBW080_SHOE_LEFT_GLOBAL_MENS_TREE_RUNNER_JET_BLACK_WHITE.png?v=1751165486",
      "https://www.allbirds.com/cdn/shop/products/SKU-SOCKS-GLOBAL-ANKLE-BLIZZARD-1_Resize_0000_SKU-SOCKS-GLOBAL-NOSHOW-NATURAL-BLACK-1-min.png?v=1676567902&width=400"
    ],
    "specs": {},
    "rating": null,
    "review_count": null,
    "reviews": [],
    "review_themes": [],
    "value_props": [
      "Breathable eucalyptus tree fiber for silky smooth feel",
      "Lightweight design for all-day comfort",
      "Responsibly sourced materials",
      "Perfect for casual wear, walking, and warm weather"
    ]
  },
  "brand": {
    "brand_name": "Allbirds",
    "seller_name": null,
    "tagline": null,
    "logo_url": "https://cdn.cookielaw.org/logos/0b9f74e8-b9c3-40a2-9197-b5a394dea570/18541889-8a69-4580-9b85-5511b6f023a8/5920bccc-8213-4567-ae6d-da69691ca4f8/logo_with_padding_(1).png",
    "logo_svg": null,
    "voice": "Sustainable effortless comfort",
    "brand_soul": "Allbirds believes in creating better things in a better way. They are driven by a commitment to sustainability and ethical production, aiming to minimize their environmental impact while maximizing comfort and style. They envision a world where fashion is both eco-friendly and enjoyable.",
    "messaging_pillars": [
      "Sustainability: Responsibly sourced materials",
      "Comfort: Lightweight and breathable design",
      "Versatility: Suitable for everyday wear and various activities",
      "Quality: Durable construction for long-lasting use"
    ],
    "tone_markers": [
      "Informative and clear",
      "Eco-conscious",
      "Friendly and approachable",
      "Understated confidence"
    ],
    "key_phrases": [
      "breathable",
      "lightweight",
      "responsibly sourced",
      "silky smooth",
      "cool on your skin",
      "everyday casual wear",
      "walking",
      "warmer weather"
    ],
    "content_style": "Straightforward and descriptive, using clear and concise language to highlight the product's benefits. Focuses on the materials, comfort, and sustainability aspects.",
    "visual_language": "Clean, minimalist product photography with a focus on natural light and soft textures. The shoes are often presented in lifestyle settings, showcasing their versatility and comfort. Scenery implies outdoor activity and eco-consciousness.",
    "mood": "Relaxed, natural, and effortlessly stylish, promoting a sense of well-being and environmental awareness.",
    "visual_metaphors": [
      "Comfort = natural materials",
      "Sustainability = visible, responsible sourcing",
      "Versatility = lifestyle scenes"
    ],
    "palette_mood": "Neutral and muted tones, with an emphasis on natural colors like white, black, and earth tones. Creates a sense of understated elegance and environmental consciousness.",
    "lighting_style": "Soft, diffused natural light to highlight the texture and details of the shoes, with minimal shadows to create a clean and inviting look.",
    "texture_language": "Focus on the smooth, natural texture of the eucalyptus fiber, emphasizing the shoe's comfort and breathability. Evokes feelings of lightness and freshness.",
    "cinematography": "Close-up shots of the shoe's materials and construction, as well as lifestyle shots showing the shoes being worn in various casual settings. The framing is simple and uncluttered.",
    "ad_style_description": "Ads would feature clean layouts with a focus on product photography and concise messaging. Emphasis on the sustainable materials and comfort benefits, with a call to action to explore the collection. High-quality photography and minimal text to create a sophisticated and eco-conscious feel.",
    "primary_color": "#ece9e2",
    "secondary_color": null,
    "accent_color": null,
    "font_family": null,
    "font_style": null,
    "css_fonts": [
      "\"Self Modern\", ui-serif, serif",
      "Geograph, system-ui, sans-serif",
      "Geograph, system-ui, sans-serif",
      "Geograph, system-ui, sans-serif",
      "Geograph, system-ui, sans-serif",
      "Geograph, system-ui, sans-serif",
      "Geograph, system-ui, sans-serif"
    ],
    "css_font_faces": [],
    "google_fonts_url": null,
    "downloaded_font_names": [
      "Self Modern",
      "Geograph"
    ],
    "css_font_styles": {
      "h1": {
        "family": "\"Self Modern\", ui-serif, serif",
        "weight": "400",
        "style": "normal",
        "size": "30px",
        "letterSpacing": "normal",
        "textTransform": "none"
      },
      "h2": {
        "family": "Geograph, system-ui, sans-serif",
        "weight": "500",
        "style": "normal",
        "size": "12px",
        "letterSpacing": "normal",
        "textTransform": "uppercase"
      },
      "h3": {
        "family": "Geograph, system-ui, sans-serif",
        "weight": "500",
        "style": "normal",
        "size": "12px",
        "letterSpacing": "0.6px",
        "textTransform": "uppercase"
      },
      "body": {
        "family": "Geograph, system-ui, sans-serif",
        "weight": "400",
        "style": "normal",
        "size": "16px",
        "letterSpacing": "normal",
        "textTransform": "none"
      },
      "nav": {
        "family": "Geograph, system-ui, sans-serif",
        "weight": "400",
        "style": "normal",
        "size": "16px",
        "letterSpacing": "normal",
        "textTransform": "none"
      },
      "body_text": {
        "family": "Geograph, system-ui, sans-serif",
        "weight": "400",
        "style": "normal",
        "size": "12px",
        "letterSpacing": "0.6px",
        "textTransform": "none"
      },
      "cta": {
        "family": "Geograph, system-ui, sans-serif",
        "weight": "500",
        "style": "normal",
        "size": "12px",
        "letterSpacing": "0.6px",
        "textTransform": "uppercase"
      }
    },
    "ad_messaging_angles": [
      "Highlight the sustainable materials used in the shoes.",
      "Emphasize the breathability and lightweight feel for summer comfort.",
      "Showcase the versatility of the shoes for various casual activities.",
      "Focus on the smooth, cool feel of the eucalyptus fiber."
    ],
    "meta_ad_style": null,
    "instagram_style": null,
    "about_page_text": "Our Story | Sustainable Shoes &amp; Apparel | Allbirds Get 30% Off When You Spend $150 or More. Discount Automatically Applied at Checkout. Skip to Content Men Women Sale About ReRun Shoes Socks & Apparel Sale Dasher NZ Collection Terralux\u2122 Collection Varsity Collection New Arrivals Bestsellers Men's Shoes Shop All Sneakers Slip Ons Slippers All-Weather Sandals Customer Favorites Tree Runner NZ The Original Tree Runner Varsity Dasher NZ Wool Cruiser Top Add-Ons Socks Apparel Bags Gift Cards Sock",
    "photo_palette": "Okay, here's an analysis of the product photography:\n\n**1. Color Palette:**\n\n*   **Dominant Colors:** The primary colors are *black* and *white*. The shoe upper, laces, and detailing are a shade of dark gray, while the sole is a bright, clean white.\n*   **Mood:** The palette creates a modern, minimalist, and neutral mood. The contrast between the black upper and white sole evokes a sense of sleekness and clarity.\n\n**2. Lighting Style:**\n\n*   **Soft Lighting:** The lighting appears to be soft and"
  },
  "brand_brief": {
    "brand_name": "Allbirds",
    "brand_soul": "Allbirds fundamentally believes in creating better things in a better way. They are driven by an unwavering commitment to sustainability and ethical production, striving to minimize their environmental footprint while maximizing comfort and style. They envision a world where fashion seamlessly blends eco-consciousness with enjoyable, everyday wear, making responsible choices effortless for their customers.",
    "voice_description": "Allbirds speaks with an understated confidence that is informative, clear, and eco-conscious. Their vocabulary is accessible but precise, focusing on material science and comfort benefits without being overly technical or academic. Sentences are generally concise and direct, promoting clarity and ease of understanding. The emotional register is friendly and approachable, never overtly playful or overly serious, maintaining a relaxed and natural tone. There's a subtle pride in their sustainable innovation. Example phrases: \"responsibly sourced eucalyptus tree fiber that feels silky smooth and cool on your skin,\" \"lightweight design for all-day comfort,\" \"creating better things in a better way,\" \"perfect for everyday casual wear, walking, and warmer weather.\"",
    "visual_identity": "The visual identity is clean, minimalist, and effortlessly natural. It evokes a sense of well-being and environmental awareness through soft textures, natural light, and an emphasis on the materials themselves. The overall aesthetic is one of understated elegance and practical style, suggesting a relaxed yet conscious lifestyle. Composition often features products in serene, natural settings or against clean, high-contrast backgrounds, highlighting their simplicity and comfort. Whitespace is generous, allowing products to breathe and conveying a sense of calm and clarity.",
    "photography_style": "Photography employs soft, diffused natural light, designed to highlight the texture and details of the shoes without harsh shadows. When showcasing the product specifically, the lighting tends to be cool and crisp, often against a pure black or clean white background to create high contrast and a sleek, modern feel. For lifestyle shots, the shoes are presented in relaxed, natural settings that imply outdoor activity, casual wear, and eco-consciousness, with a focus on genuine human interaction or serene landscapes. Props are minimal, reinforcing the product's natural origins and functional simplicity.",
    "color_strategy": "The brand's primary color strategy revolves around neutral and muted tones, emphasizing natural colors. For UI and branding, this includes a blend of earth tones, subtle greens, and grays that suggest nature and sustainability. In product photography, a core palette of high-contrast black and clean white is frequently used for standalone product shots, where the shoe upper (often a dark gray or black) stands out against a bright white sole and a pure black background. For lifestyle and advertising, the palette expands to include natural blues, muted greens, and earthy browns, aligning with the \"natural light\" and \"eco-conscious\" mood. Ads should leverage these natural, calming colors for lifestyle imagery and use crisp black/white for product focus, reinforcing both sustainability and sleek design.",
    "typography_strategy": "The typography strategy balances classic sophistication with modern clarity. \"Self Modern\" is reserved for headings (h1), conveying a sense of established quality and subtle elegance through its serif structure. This choice adds a touch of refinement without being overtly traditional. For body text, navigation, and CTAs, \"Geograph\" (a clean, legible sans-serif) is used. This modern sans-serif ensures excellent readability and projects a contemporary, approachable, and efficient personality. The combination signifies a brand that is thoughtful and established (serif) yet forward-thinking and user-friendly (sans-serif), aligning with their 'better things in a better way' ethos. Weight preferences lean towards medium to regular for readability, with bolder options for emphasis.",
    "ad_creative_direction": "Allbirds Meta ads should be visually clean and uncluttered, prioritizing compelling imagery over heavy text overlays. The layout should feel balanced and natural, with visual weight evenly distributed, leading the eye smoothly through the ad. To stop the scroll, ads should feature stunning, high-quality photography or short, engaging video clips that immediately highlight the shoe's key benefits: the silky-smooth eucalyptus texture, its featherlight feel, or its versatility in an aspirational, natural setting. Messaging should be concise and focus on \"breathable comfort for warmer weather,\" \"responsibly sourced materials,\" and \"everyday wearability.\" Ads should evoke a sense of calm, well-being, and effortless style, using natural light and colors to create an inviting atmosphere that feels authentic to the brand's sustainable ethos. Text-to-image ratio should be low, allowing the visuals to speak volumes, complemented by direct, benefit-driven headlines.",
    "target_customer": "The Allbirds target customer is a conscious consumer, typically urban or suburban, aged 25-55, with a moderate to high disposable income. They value comfort, quality, and sustainability equally. This individual leads an active, yet often relaxed, lifestyle, seeking versatile products that fit seamlessly into their daily routine\u2014from commuting and casual walks to weekend errands. They are environmentally aware, make purchasing decisions based on ethical production and material sourcing, and appreciate minimalist aesthetics. They prioritize natural materials and feel good about supporting brands that align with their eco-conscious values, seeking products that simplify their lives without compromising style or impact.",
    "competitive_positioning": "Allbirds differentiates itself through its deep commitment to specific, innovative natural materials, particularly eucalyptus tree fiber (for Tree Runners) and merino wool (for other lines), making sustainability a core product feature rather than just a marketing claim. While competitors like Rothy's and Veja also champion recycled materials and eco-friendliness, Allbirds stands out by emphasizing the *natural comfort* and *silky-smooth feel* derived directly from its unique bio-based materials. Their ads are visually distinct through a consistent aesthetic of understated confidence, natural light, and a minimalist design that highlights genuine comfort and eco-consciousness, avoiding overt trendiness. Their brand soul is deeply integrated into every product and communication, offering a truly 'better way' to experience footwear.",
    "key_phrases": [
      "responsibly sourced eucalyptus tree fiber",
      "silky smooth and cool on your skin",
      "lightweight design for all-day comfort",
      "perfect for everyday casual wear",
      "walking and warmer weather",
      "creating better things in a better way",
      "sustainable effortless comfort",
      "natural materials",
      "breathable and lightweight sneaker",
      "minimal environmental impact",
      "eco-friendly and enjoyable",
      "understated confidence"
    ],
    "resolved_fonts": [
      "Self Modern",
      "Geograph"
    ]
  },
  "competitive": {
    "competitor_brands": [
      "Allbirds",
      "Rothy's",
      "Veja",
      "TOMS"
    ],
    "messaging_patterns": [
      "Sustainability and eco-friendliness (recycled materials, carbon footprint reduction)",
      "Comfort and lightweight design (ideal for daily wear, travel)",
      "Versatility (suitable for various activities, from running to casual outings)",
      "Style and design (minimalist aesthetics, trendy colors)",
      "Machine washable and easy care"
    ],
    "market_gaps": [
      "Specific activity focus (e.g., trail running in sustainable materials)",
      "Targeting specific foot conditions (e.g., wide feet, plantar fasciitis) with sustainable solutions"
    ],
    "search_themes": [
      "Sustainable sneakers",
      "Eco-friendly running shoes",
      "Comfortable walking shoes",
      "Everyday sneakers for men",
      "Black sneakers with white sole"
    ],
    "ad_landscape": null
  },
  "sources_used": [
    "shopify_json",
    "playwright",
    "brand_brief_2.5flash",
    "gemini_grounded_search",
    "about_page",
    "photo_palette"
  ],
  "extraction_issues": []
}
{
  "campaign_theme": "Comfort meets conscious design: Tree Runners",
  "target_audience": {
    "summary": "Eco-conscious urban dwellers seeking comfortable, versatile footwear.",
    "demographics": "Ages 25-55, all genders, income $60k+, located in urban and suburban areas",
    "interests": [
      "Sustainable fashion",
      "Eco-friendly products",
      "Minimalist lifestyle",
      "Outdoor activities",
      "Comfort footwear",
      "Walking",
      "Travel",
      "Ethical brands"
    ],
    "behaviors": [
      "Online purchases of apparel",
      "Frequent walkers/commuters",
      "Socially responsible spending"
    ],
    "psychographics": "Values sustainability, comfort, and quality. Motivated by ethical consumption and a desire to reduce their environmental footprint. Appreciates minimalist aesthetics and versatile products that fit their active lifestyle.",
    "lookalike_seed": "Allbirds existing customer list, focusing on those who have purchased Tree Runners or similar eco-friendly products."
  },
  "tone": "Informative, eco-conscious, friendly, and understated.",
  "visual_style": "Clean, minimalist, and natural, emphasizing the comfort and sustainability of the shoes through soft lighting, natural textures, and serene settings.",
  "color_palette": [
    "#ece9e2",
    "#232323",
    "#FFFFFF",
    "#a9b7b3"
  ],
  "creative_specs": [
    {
      "variant_index": 0,
      "headline": "Cool Comfort. Conscious Choice.",
      "subheadline": "Men's Tree Runner",
      "cta": "Shop Now",
      "primary_text": "Breathe easy this season. Tree Runners are crafted with eucalyptus tree fiber for breezy comfort and a smaller footprint. Feels silky smooth.",
      "description": "Experience the perfect blend of comfort and sustainability with our Tree Runners.",
      "angle": "Guilt-free comfort",
      "image_index": 0,
      "image_type": "studio",
      "value_props": [
        "Breathable eucalyptus tree fiber",
        "Responsibly sourced materials"
      ],
      "layout_description": "Full-bleed product photo fills the entire 1080\u00d71080 frame. The shoe is centered, shot against a pure black background with soft shadows. Brand logo in white, 24px, top-left corner with 40px margin. Headline in display font, 48px, white, bottom-left with 50px margin from edges. Subheadline in sans-serif font, 24px, bottom left, 90px above the headline. CTA as a pill-shaped button, 180\u00d748px, #ece9e2 fill, bottom-right, text in black.",
      "visual_direction": "Crisp, high-contrast studio shot with cool lighting. The focus is on the texture and detail of the shoe. The typography is clean and elegant. Minimal distractions, allowing the product to shine.",
      "template": "full_bleed_overlay",
      "mood_direction": "Calm and refreshing",
      "color_emphasis": "White"
    },
    {
      "variant_index": 1,
      "headline": "Your Everyday Eco-Adventure",
      "subheadline": "Men's Tree Runner",
      "cta": "Learn More",
      "primary_text": "Step into sustainable style. Responsibly sourced eucalyptus and a lightweight design mean all-day comfort, wherever you roam. Shop now!",
      "description": "Discover the versatility of Tree Runners, perfect for everyday casual wear and warmer weather.",
      "angle": "Versatile eco-style",
      "image_index": 1,
      "image_type": "lifestyle",
      "value_props": [
        "Lightweight design",
        "Perfect for casual wear"
      ],
      "layout_description": "Editorial split: Lifestyle image fills 70% of the frame (top). A brand-colored bar (#ece9e2) fills the bottom 30% of the frame. Headline in display font, 40px, black, centered in the #ece9e2 zone. Primary text in sans-serif font, 20px, black, below the headline. CTA as a pill-shaped button, 160x40px, #232323 fill, bottom right, text in white.",
      "visual_direction": "Warm, natural lighting in a casual, outdoor setting. The image showcases the shoes being worn during a relaxed activity (e.g., walking in a park). The overall feel is inviting and authentic, emphasizing the shoe's versatility.",
      "template": "full_bleed_overlay",
      "mood_direction": "Relaxed and adventurous",
      "color_emphasis": "Earth Tones"
    },
    {
      "variant_index": 2,
      "headline": "Silky Smooth, Seriously Sustainable",
      "subheadline": "Men's Tree Runner",
      "cta": "Get Yours",
      "primary_text": "Feel the difference. Our Tree Runners combine breezy eucalyptus fiber with a sleek design for a shoe that's as good for the planet as it feels.",
      "description": "Experience the unmatched comfort and eco-consciousness of Allbirds Tree Runners.",
      "angle": "Sustainable luxury",
      "image_index": 2,
      "image_type": "flat_lay",
      "value_props": [
        "Silky smooth feel",
        "Suitable for warmer weather"
      ],
      "layout_description": "Minimal product: Clean white background, product centered at 60% of the frame. Headline in display font, 32px, black, centered below the product image. Subheadline in sans-serif font, 18px, grey (#a9b7b3), centered below the headline. CTA as a pill-shaped button, 140x36px, #232323 fill, bottom center, text in white.",
      "visual_direction": "Clean, minimalist product shot on a bright white background. Soft, diffused lighting to highlight the texture of the eucalyptus fiber. The focus is on the shoe's sleek design and premium materials. Typography is understated and elegant.",
      "template": "full_bleed_overlay",
      "mood_direction": "Sophisticated and conscious",
      "color_emphasis": "Black"
    }
  ],
  "validation_issues": [],
  "enhanced_fields": []
}