fal-studio-app-patterns

/home/avalon/.hermes/skills/software-development/fal-studio-app-patterns/SKILL.md · raw

fal-studio App Patterns

Overview

Use this for fal-studio-class apps: Vite + React frontend, Express backend, user-supplied model/provider keys, generated image/video outputs, persisted galleries, and a fast-moving model catalog.

This umbrella replaces narrower one-session skills. The right abstraction is not "auth migration" or "dynamic uploads" alone, but the broader app class: a multi-user media-generation studio whose UI and storage model must stay resilient as providers, models, and asset flows change.

When to Use

Core architecture

1. Auth + persistence + storage migration

When converting a single-user prototype into a real app: - add signup/login - store per-user fal keys - persist gallery rows in SQLite - upload final outputs immediately to durable storage - prefer saved asset URLs over provider preview URLs everywhere user-visible

Recommended tables: - users(id, email, password_hash, fal_key, created_at) - assets(id, user_id, model, prompt, result_kind, storage_key, storage_url, params_json, created_at)

Safe user shape:

{ id, email, hasFalKey: !!fal_key }

2. Schema-driven model input UI

Do not hardcode model assumptions from memory.

For each endpoint: 1. inspect the real OpenAPI schema 2. derive required fields, enums, defaults, upload requirements, and output kind from that schema 3. use human docs only as a supplement for descriptions/pricing/prompt hints

Represent uploads by semantic section keys, not one generic image input: - generalReferences - firstFrame - lastFrame - storyboardReference

Backend should use multer.any() and group files by field name before mapping them to API params.

3. Concurrency and model-switch resilience

A single global generating boolean is too weak for model-comparison workflows.

Use a lightweight frontend job queue: - queued - running - completed - failed

Preserve only compatible upload buckets across model switches. Users should not lose valid references just because they compare two nearby models.

4. Error normalization

Provider errors often arrive as nested objects, arrays, moderation payloads, or opaque strings.

Normalize in shared helpers on both backend and frontend: - unwrap error, detail, message, msg, reason - join validation arrays clearly - map status classes to user-facing categories: - 401 invalid/missing key - 403 safety/policy or restricted capability - 422 invalid params or blocked request - 429 rate limit - 5xx provider-side failure

Treat persisted assets as the source of truth.

Rules: 1. save metadata at write time 2. return stable asset URLs from the backend 3. derive absolute share URLs server-side 4. gallery and result viewers should prefer persisted URLs over preview URLs 5. never rely on temporary provider URLs for long-lived gallery/share behavior

Useful metadata to persist: - result_kind - storage_mode - inference_time - output_duration - request params/model name/prompt

6. fal-specific operational lessons

7. Marketing-only capabilities and upstream native routes

A provider product page can advertise a workflow that its generic public wrapper schema does not expose. Do not force the advertised semantics through superficially similar fields.

Seedream 5.0 Pro native layers are the concrete warning case (last verified 2026-07-23): - fal's num_images requests independent completed generations; it is not a semantic layer count. - Prompting fal's ordinary edit endpoint for layers while sending num_images: 1 returned one flattened image in two observed completed requests, even though the prompt asked for transparent layers. - Do not substitute the public BytePlus ModelArk image endpoint. Its current capability table explicitly documents seedream-5-0-pro as single-image generation, says sequential generation is unsupported, and says passing sequential_image_generation causes an error. - The often-quoted 15-image combined input/output ceiling belongs to Seedream 5.0 Lite / 4.5 / 4.0 sequential generation, not Pro. It cannot be used as a Pro billing cap. - A top-level BytePlus data[] response shape does not prove that Pro can return multiple semantic layers; the documented Pro contract can return a one-item array. - Until an exact public layer endpoint, request field, provider-enforced output ceiling, and observed transparent-layer response are established, fail closed before upload or paid provider activity. Preserve the local compositor/demo separately.

General rule: establish the exact endpoint, request switch, response fixture, billing ceiling, and output semantics from official docs or an observed provider response before implementing a marketing claim. A provider page or demo can advertise a private/orchestrated workflow that neither its wrapper nor upstream public API exposes.

8. Account bootstrap, funding, and assisted setup

Keep fal out of zero-field product signup and request it just in time when paid media work needs it. Use one Connect fal entry for both new and existing users because fal itself combines login and signup through Google, GitHub, or SSO. The fastest supported direct flow is human OAuth/terms → select account context → paste one ADMIN setup key → broker verifies username/balance → broker creates an API-only runtime key.

Agentcard Companies (agentcard.sh, not agentcard.ai) can provide embedded per-user connection, KYC, user-funded wallet, multi-use cards, and approvals. For browser-assisted fal funding, keep Hermes as planner/proposer while a restricted deterministic payment runner injects card details outside model context. Human turns remain OAuth identity, terms, CAPTCHA, KYC, 3DS, OTP, and exact spend approval. Obtain fal written authorization or a first-party partner provisioning path before production dashboard automation.

See references/fal-account-bootstrap-agentcard-onboarding.md for exact provider facts, connector states, two-key design, Agentcard funding flow, restricted browser runner boundary, managed-fal alternative, source links, and pitfalls.

Common Pitfalls

  1. Using a global provider key in app state instead of the authenticated user's key
  2. Modeling uploads as one imageFile instead of semantic upload buckets
  3. Preferring previewUrl over saved URLs in gallery/share flows
  4. Blocking the whole app with one global spinner when users want multiple jobs
  5. Passing raw provider error objects to the UI and showing [object Object]
  6. Forgetting credentials: 'include' for cookie-authenticated fetches

Verification Checklist