🎨 Higgsfield Pipeline

Fully automated browser-based image generation with stealth Playwright

📖 What This Is

A completely automated image generation pipeline for Higgsfield.ai that runs in a Docker container with stealth Playwright. It authenticates via 1Password + Gmail OTP (no human needed), automates the Higgsfield web UI, intercepts API responses to capture job IDs, and downloads full-resolution images from CloudFront CDN.

🔑 Key Features:
  • Self-service authentication (1Password → Browser → Gmail OTP → Session state)
  • Browser UI automation with response interception
  • Unlimited mode when available (no credit usage)
  • Reference image upload support
  • Automatic bonus job detection and download
  • Full-resolution PNG download with webp fallback

🏗️ Architecture Overview

System Flow

1
User / OpenClaw Agent

Calls generate.js with prompt, model, ratio, resolution

2
Docker Container

Runs on residential network with Xvfb virtual display

3
Stealth Playwright Browser

Loads authenticated session, navigates to higgsfield.ai/image

4
UI Automation

Selects model, ratio, resolution, enables Unlimited, types prompt, clicks Generate

5
API Interception

Captures POST /jobs/{model} response and polls GET /jobs/{id}/status

6
Job Completion Detection

Waits for primary job (matched by job_set_type), detects bonus jobs

7
CloudFront CDN Download

Extracts thumbnail URLs from page, tries full-res PNG then webp fallback

8
Output

Images saved to /srv/stacks/higgsfield/output/ (shared volume)

Docker Stack Layout

/srv/stacks/higgsfield/
├── docker-compose.yml      # Service definition
├── Dockerfile              # Playwright 1.52 + stealth + Xvfb
├── scripts/                # Mounted read-only at /app/scripts
│   ├── generate.js         # Main generation script
│   └── login.sh            # Authentication script
├── auth/                   # Mounted at /app/auth
│   └── authenticated.json  # Browser session state
└── output/                 # Mounted at /app/output
    ├── *.png / *.webp      # Generated images
    └── login-result.png    # Auth verification screenshot

Container Specs

Base Image

mcr.microsoft.com/playwright:v1.52.0-noble

Network

residential (external)

Display

Xvfb :99 @ 1920×1080×24

Shared Memory

2 GB

🔐 Authentication Flow

Fully automated self-service login via scripts/login.sh — no human interaction required.

Step-by-Step Process

1
Fetch Credentials

1Password CLI retrieves email + password from Higgsfield item in Bastion vault

2
Note Current Email ID

Queries Gmail via gog CLI to get latest Higgsfield message ID (to detect new OTP)

3
Browser Login

Stealth Chromium navigates to sign-in page, fills email + password, clicks submit

4
Clerk Auth Challenge

Higgsfield sends 6-digit verification code to Gmail (invalidates all previous codes)

5
Gmail OTP Extraction

Polls gog gmail messages search "from:higgsfield" until new message ID appears, extracts code via regex

6
Enter Code

Browser types 6-digit code into verify page, presses Enter

7
Save Session State

Playwright saves cookies + localStorage to /app/auth/authenticated.json

Critical Gotchas

Issue Solution
Cookie banner blocks clicks Block at network level: page.route("**/*cookiescript*", r => r.abort())
Submit button invisible Has opacity-0 invisible classes — use click({force: true})
Each login = new code NEVER re-login between getting code and entering it
networkidle times out Use waitUntil: "domcontentloaded" instead
Automation detected Stealth plugin required: playwright-extra + puppeteer-extra-plugin-stealth
Cookie elements re-inject Nuke DOM before every interaction + MutationObserver for persistent removal

Session Expiry

If generate.js detects a redirect to /auth/, it exits with code 2 to signal session expiry. Re-run login.sh to refresh authentication.

Manual Fallback

If gog CLI is unavailable, the browser script polls /app/output/code.txt for 90 seconds. Write the 6-digit code there manually or prompt the user.

⚙️ Generation Pipeline

Complete Automation Flow

The generate.js script automates the entire Higgsfield web UI workflow:

0
Load Authenticated Session

Reads /app/auth/authenticated.json, launches stealth Chromium, navigates to higgsfield.ai/image

1
Select Model

Clicks toolbar model button, types search term in filter input, clicks matching dropdown option

// Find model button, click, filter, select
await p.evaluate(() => {
  [...document.querySelectorAll("button")]
    .find(e => e.innerText.match(/Nano|Seedream|.../) && e.getBoundingClientRect().top > 550)?.click();
});
2
Select Aspect Ratio

Clicks ratio button in toolbar, selects target ratio from popover (1:1, 3:4, 16:9, etc.)

3
Select Resolution

Clicks resolution button, selects 1K/2K/4K from "Select quality" popover (falls back to default if unavailable)

4
Enable Unlimited Mode

Checks if Unlimited toggle exists for selected model, enables if available (avoids credit usage)

Note: Not all models support Unlimited. Script detects availability and warns if credits will be used.
5
Type Prompt

Finds [contenteditable=true][role=textbox], clears existing text via Ctrl+A → Backspace, types new prompt character-by-character

6
Click Generate

Finds "Generate" button, clicks it (via evaluate for reliability when layout shifts)

7
Intercept API Response

Response listener captures POST /jobs/{model} to extract job_sets array

p.on("response", async (resp) => {
  const body = await resp.json();
  if (body.job_sets) {
    console.log(`Submitted: ${body.job_sets.map(js => js.type).join(", ")}`);
  }
});
8
Poll for Completion

Intercepts GET /jobs/{id}/status responses, tracks completion by matching job_set_type to primary model

if (url.includes("/status")) {
  const body = await resp.json();
  if (body.status === "completed" && body.job_set_type === primaryType) {
    primaryDone = true;
  }
}
9
Extract CloudFront URLs

Scrapes page for thumbnail <img> elements containing job IDs, extracts CloudFront URLs from url= query param

10
Download Full-Resolution Images

Tries PNG (replace _min.webp with .png), falls back to webp if PNG 403s. Downloads via curl (no auth needed for CloudFront)

const pngUrl = thumbUrl.replace("_min.webp", ".png");
execSync(`curl -sS -f -o "${filepath}" "${pngUrl}"`);

Response Interception Strategy

Instead of polling external API endpoints (would need auth headers), the script intercepts browser network responses to capture job IDs and completion status in real-time.

Submit Detection

POST fnf.higgsfield.ai/jobs/{model}

Response body contains job_sets[] array with job types

Status Polling

GET fnf.higgsfield.ai/jobs/{id}/status

Browser polls automatically — we just listen for status: "completed"

🖼️ Reference Image Upload

How It Works

Reference images are passed via --ref /app/output/filename.png. The file must exist inside the container's shared volume.

Two-Phase Upload Mechanism

Phase 1: Filechooser Creates Slot

Clicking the first input[type=file] triggers a filechooser event. Setting files here creates a reference image slot in the UI.

Phase 2: Inner Input Uploads to CDN

A new input[type=file] appears for the slot. Triggering its filechooser uploads the image to CloudFront CDN and displays the thumbnail.

Known Issue: Duplicate References

⚠️ Known Bug

The upload mechanism sometimes creates 2 reference slots instead of 1. The script attempts to detect and clear extras by:

  1. Clearing all existing references before upload
  2. Counting uploaded refs via img[alt="object image"]
  3. Removing extras if count > 1

Impact: Generation still works — it just uses 2 copies of the same reference. Not ideal, but functional.

Usage Example

# 1. Copy reference image into shared volume
cp ~/cat.png /srv/stacks/higgsfield/output/ref-cat.png

# 2. Pass container path to generate.js
docker exec -e DISPLAY=:99 higgsfield-browser node /app/scripts/generate.js \
  --prompt "reimagine as watercolor painting" \
  --ref /app/output/ref-cat.png \
  --model nano-banana-pro

🤖 Model Support

Available Models

Use hyphenated slugs with --model parameter:

Slug Display Name job_set_type Best For Unlimited
nano-banana-pro Nano Banana Pro nano_banana_2 General-purpose (default)
nano-banana-2 Nano Banana 2 nano_banana_flash Flash speed
nano-banana Nano Banana nano_banana Legacy
seedream-4.5 Seedream 4.5 seedream_v4_5 Photorealistic 4K ?
seedream-5.0-lite Seedream 5.0 lite seedream_v5_lite Visual reasoning ?
flux-2-pro FLUX.2 Pro flux_2_pro Speed-optimized detail
flux-2-flex FLUX.2 Flex flux_2 Next-gen generation
flux-2-max FLUX.2 Max flux_2 Ultimate precision
gpt-image-1.5 GPT Image 1.5 gpt_image_1_5 Text rendering ?
z-image Z-Image z_image Instant lifelike portraits ?
kling-o1 Kling O1 kling_o1 Photorealistic ?
wan-2.2 WAN 2.2 wan_2_2 Cinematic visuals ?
auto Auto image_auto Best model for prompt

Resolution Support

Note: Not all resolutions are available for all models. For example, FLUX.2 models only support 1K and 2K. The script attempts to select the requested resolution and falls back to default if unavailable.

Unlimited Mode Availability

The script automatically detects whether the selected model supports Unlimited mode by checking for the toggle switch in the UI. If unavailable, it warns that credits will be used.

🔌 API Details

Endpoints (fnf.higgsfield.ai)

POST /jobs/{model}

Submit a generation job. The browser makes this call when you click Generate.

// Response body:
{
  "job_sets": [
    { "id": "...", "type": "nano_banana_2", ... },
    { "id": "...", "type": "seedream_v5_lite", ... }  // bonus job
  ]
}

GET /jobs/{id}/status

Poll job status. The browser calls this automatically (~1-2s intervals).

// Response body:
{
  "id": "abc123",
  "job_set_type": "nano_banana_2",
  "status": "completed",
  ...
}

GET /jobs/{id}

Full job result (not used by script — we extract URLs from page thumbnails instead).

Two-Level Job IDs

Critical Detail: Higgsfield uses different IDs for job_set (POST response) vs job (status polls). The script matches jobs by job_set_type instead of IDs to correctly identify which job completed.

Bonus Jobs

Higgsfield may spawn additional free generations alongside your requested model. For example, requesting Nano Banana Pro might also generate Seedream 4.5 and Seedream 5.0 lite at no cost.

Primary Job

The model you requested. Script blocks until this completes.

Bonus Jobs

Higgsfield freebies (not related to Unlimited mode). Script downloads these too if they complete.

CloudFront CDN URLs

Images are served from d8j0ntlcm91z4.cloudfront.net and don't require authentication.

URL Pattern

https://d8j0ntlcm91z4.cloudfront.net/user_{userId}/hf_{timestamp}_{jobId}_min.webp

Resolution Variants

Suffix Format Resolution Availability
_min.webp WebP 1K (e.g., 896×1200 @ 3:4) Always
.png PNG Higher-res (depends on model/settings) Sometimes

Download Strategy: Script tries PNG first (replace _min.webp with .png), falls back to webp if PNG returns 403.

📁 Volume Mounts & File Paths

Host ↔ Container Mapping

Host Path Container Path Mode Purpose
/srv/stacks/higgsfield/scripts/ /app/scripts Read-only Generation and auth scripts
/srv/stacks/higgsfield/output/ /app/output Read-write Generated images + reference images
/srv/stacks/higgsfield/auth/ /app/auth Read-write Browser session state

Key Files

authenticated.json

/app/auth/authenticated.json

Playwright storage state (cookies + localStorage). Created by login.sh, read by generate.js.

Generated Images

/app/output/{jobId}.png

/app/output/{jobId}.webp

Full-resolution downloads. Script prints FIRST_IMAGE= path on last line.

Reference Images

/app/output/*.png

Copy reference images here before passing to --ref.

Login Screenshot

/app/output/login-result.png

Verification screenshot taken after OTP entry.

Passing Files Between Host & Container

# Host → Container (reference image)
cp ~/my-ref.png /srv/stacks/higgsfield/output/ref.png
docker exec ... --ref /app/output/ref.png

# Container → Host (generated image)
# Automatic — generated files appear in /srv/stacks/higgsfield/output/

Quick Reference

Common Commands

Authentication

# Self-service login (automated)
bash ~/.openclaw/skills/higgsfield/scripts/login.sh

# Check auth status
docker exec -e DISPLAY=:99 higgsfield-browser node -e '
const { chromium } = require("playwright-extra");
const s = require("puppeteer-extra-plugin-stealth");
chromium.use(s());
(async () => {
  const b = await chromium.launch({headless:false,args:["--no-sandbox"]});
  const c = await b.newContext({storageState:"/app/auth/authenticated.json"});
  const p = await c.newPage();
  await p.goto("https://higgsfield.ai",{timeout:30000,waitUntil:"domcontentloaded"});
  await p.waitForTimeout(3000);
  console.log(p.url().includes("auth") ? "EXPIRED" : "VALID");
  await b.close();
})()'

Image Generation

# Basic generation (Unlimited mode when available)
docker exec -e DISPLAY=:99 higgsfield-browser node /app/scripts/generate.js \
  --prompt "a cat floating in space, oil painting" \
  --model nano-banana-pro --ratio 1:1 --res 1k

# With reference image
cp ~/ref.png /srv/stacks/higgsfield/output/ref.png
docker exec -e DISPLAY=:99 higgsfield-browser node /app/scripts/generate.js \
  --prompt "reimagine as watercolor" \
  --ref /app/output/ref.png \
  --model nano-banana-pro --ratio 3:4 --res 2k

# Different model + aspect ratio
docker exec -e DISPLAY=:99 higgsfield-browser node /app/scripts/generate.js \
  --prompt "cinematic movie poster" \
  --model flux-2-pro --ratio 2:3 --res 4k

Container Management

# Start/rebuild container
cd /srv/stacks/higgsfield && docker compose up -d --build

# Check status
docker ps | grep higgsfield

# View logs
docker logs higgsfield-browser

# Stop container
docker compose down

Exit Codes

Code Meaning Action
0 Success Image(s) downloaded successfully
1 Error Login failed, generation failed, or no images downloaded
2 Session expired Re-run login.sh to re-authenticate

Troubleshooting

Session Expired

Symptom: Script exits with code 2, "Session expired — re-authenticate with login.sh"
Fix: Run bash ~/.openclaw/skills/higgsfield/scripts/login.sh

Model Not Found

Symptom: "Model: FAILED" in output
Fix: Check model slug spelling, ensure it exists in MODEL_SEARCH_MAP

Resolution Not Available

Symptom: "Resolution 4K: not available for this model, using default"
Fix: Normal behavior — some models don't support 4K. Try 2K or 1K.

Unlimited Mode Unavailable

Symptom: "Unlimited: not available for this model (credits will be used)"
Fix: Expected for FLUX.2 and some other models. Generation proceeds with credits.

Reference Image Duplicates

Symptom: 2+ copies of reference image appear in UI
Fix: Known issue. Script attempts cleanup, generation still works.

No Images Downloaded

Symptom: "DONE: 0 image(s)", exit code 1
Fix: Check if primary job completed (look for "Primary (model) completed!" in output). If not, job may have timed out or failed.

🎨 Higgsfield Pipeline Documentation
Automated browser-based image generation with stealth Playwright
Built with ❤️ for seamless AI image generation