-
-
A simple interface that needs only the bare minimum to output a professionally directed AI video
-
A pre-generation checklist to ensure the video won't be rejected by Kling which avoids resubmission
-
A payment gateway on a per generation basis. No calls to Kling without a successful payment keeping the workflow tight and efficient
-
A no-touch professional shot-by-shot breakdown producing high quality output
-
A straightforward end result with a video player and download button
Inspiration
Every AI video generation tool I've tried before building CAGE gates the output behind a monthly subscription. OpenArt, Runway, Kling direct, Pika.. all of them assume that you commit to a recurring plan and learn how to create AI video. For a small business owner who needs a single 15-second ad for a product launch, that's the wrong model. They need just one ad. And they want to pay for just that ad.
The problem is that pay-per-use is hard to do safely with generative video. Generated content costs real money in compute. If users pay after the video renders, they can capture it from the browser before the watermark is applied, or screen-record it and never pay. If users pay before generation, you need a payment infrastructure that gates the actual API call and a userbase patient enough to learn.
CAGE solves this by making payment a structural part of the pipeline rather than an afterthought. The Stripe payment must succeed before the Kling generation request is ever submitted. No subscription, no watermark, direct download. The price is $3.50 as a deliberate reference to the South Park "tree fiddy" Loch Ness Monster bit, because pricing should be memorable and a $50 monthly commitment is exactly the friction to eliminate.
What made the build viable was MeDo's plugin ecosystem. I could chain three plugins - an LLM (for policy review and director-of-photography brief generation), Stripe (for one-time payment), and Kling Omni (for video generation) into a single user flow without writing a payment processor, an LLM gateway, or a video generation backend myself.
What it does
CAGE is a four-screen pipeline that turns a structured creative brief into a generated 15-second video ad.
Screen 1 - Brief Builder The user fills out a six-field form;
- What they're promoting (with brand name guidance built into the placeholder)
- Aspect ratio (9:16 or 16:9)
- Visual tone (Cinematic, Energetic, Minimal, Luxury, or Gritty)
- Key message or CTA
- Visual style (Live Action, Product Close-up, Lifestyle, or Abstract), and
- An optional reference image. There's no chat interface, every field is structured so the downstream LLM has clean inputs.
Screen 2 - Pre-Flight Check An LLM evaluates each field against Kling's content policy and returns per-field green/amber/red status with reasons. The check is context-aware, it understands that "a golden apple" is a fruit, not a brand reference, and that "an apple a day keeps the doctor away" is a cultural idiom, not a misleading medical claim. The panel is read-only. If a field fails, the user goes back to the brief and edits there. This prevents the cached-state bugs that come from trying to edit-and-revalidate inline.
Screen 3 - Payment + Generation A persistent red banner warns the user not to navigate away. Stripe Elements collects the $3.50 payment. Once the payment is confirmed client-side, the LLM generates a six-shot Kling 3.0 Omni brief using a five-layer per-shot structure (Scene/Subject/Action/Camera/Audio) with a persistent scene anchor, temporal motion descriptions, and attributed VO. The brief is displayed in a JetBrains Mono monospace block while Kling renders the video, giving the user something to read during the four-minute wait.
Screen 4 - Result + Download The video plays in an aspect-ratio-aware container. A "Download your ad" button uses a fetch-and-blob approach to save the file directly without navigating away. There's no watermark as the payment gate replaces it.
How I built it
Platform
MeDo (Baidu's no-code AI platform) running a React 19 + TypeScript + Vite frontend, TailwindCSS 3 with shadcn/ui components, Zustand for state, Framer Motion for transitions, and Supabase Edge Functions (Deno) for the backend. Space Grotesk for display typography, JetBrains Mono for the brief output. Dark theme: #0a0a0a background, #f5a623 amber accent.
Plugin chaining
Three MeDo plugins, sequenced through five Edge Functions:
policy-checkcalls the MeDo LLM gateway (Gemini 2.5 Flash) via SSE to evaluate the brief fields against a context-aware content policy with disambiguation rules for brand names, medical idioms, generic roles, and narrative violence.create-payment-intentserver-side StripePaymentIntentcreation. ReadsSTRIPE_SECRET_KEYfrom Deno env, calls Stripe's REST API withautomatic_payment_methodsenabled, returns theclientSecretto the frontend for Stripe Elements confirmation.generate-shot-briefcalls the LLM with the optimized Kling 3.0 Omni prompt template. Produces a 6-shot brief with persistent scene anchor, identical subject description across shots, temporal camera language, and attributed VO lines([ANNOUNCER, warm tone]: spoken line).generate-videosubmits the brief to Kling Omni. Includes a pre-processing step that strips redundant SCENE/SUBJECT lines from shots 2–6 to fit Kling's 2,500-character prompt limit while preserving the full brief in the user-facing display.query-video-statuspolls Kling for completion and returns the rendered video URL.
Architecture decisions worth calling out.
Stripe payment runs as server-side PaymentIntent creation with client-side Stripe.js confirmation, not a webhook-verified flow. At the $3.50 price point I accepted the spoofing risk of a client-attested success callback. The data model is session-based, no user accounts, which kept the auth surface minimal and let's me ship the entire flow without identity infrastructure.
The Kling brief prompt. The single highest-leverage piece of engineering in CAGE. Kling 3.0 Omni produces dramatically better output when given a structured five-layer prompt with a persistent scene anchor than when given free-form text. The system instruction I wrote enforces:
- One scene anchor sentence at the top establishing persistent environment, lighting, and mood
- Six shots, each with explicit SCENE / SUBJECT / ACTION / CAMERA / AUDIO sections
- Temporal motion in the CAMERA section (no single-word camera descriptions like "pan" — must describe how the shot starts, moves, and ends)
- VO lines capped at 8 spoken words and attributed with a tone tag for native audio synthesis
- Total duration locked to 15 seconds The output looks like a professional shot list and reads like a director wrote it.
Challenges I ran into
The Stripe plugin configuration panel didn't surface.
MeDo's documented "Configure Stripe" button never appeared in the editor for our app. I logged a bug in the MeDo Support Discord and worked with MeDo support to find a workaround to bypass the plugin UI entirely and add the Stripe keys directly to Backend Services → Secrets, where the deployed create-payment-intent Edge Function reads STRIPE_SECRET_KEY from the Deno environment. The frontend reads VITE_STRIPE_PUBLISHABLE_KEY from a .env file injected at build time. Once I found the path, the integration worked cleanly but identifying it took several days of iteration.
Vite environment variables are baked at build time.
This produced a subtle and persistent bug. I could update the publishable key in MeDo Secrets, see the new value reflected in the editor, republish, and still observe the old key being sent in browser requests. The cause was that the .env file in the project root held a stale value and was taking precedence over the Secrets injection. Until I explicitly overwrote the .env file contents and rebuilt, the bundle kept shipping the original (and eventually expired) key. The diagnostic moment came when Stripe returned a code: api_key_expired error a much more informative response than the initial generic 401s which let me trace the actual key in use back to the stale file.
Kling's 2,500-character prompt cap.
The optimized brief generation prompt produces excellent output by repeating SCENE and SUBJECT lines in every shot for visual consistency. The unintended consequence was that briefs ran 2,800–3,000 characters which is over Kling's hard limit. I added a server-side trimming pass in generate-video that drops the redundant SCENE/SUBJECT lines from shots 2–6 before submission while keeping the full brief in the user-facing display. The user sees the polished, full-detail brief; Kling receives a compact version that fits the API constraint. Two different versions of the same content for two different audiences.
Pre-flight policy check was over-flagging.
Early test runs rejected innocuous inputs like "a golden apple" (the LLM saw a capitalized A and treated it as Apple Inc.) and "an apple a day keeps the doctor away" (the LLM flagged it as a "misleading medical claim"). I rewrote the policy check system instruction with explicit disambiguation rules: brand-name flags require intent to reference a brand, not just a token match; medical-claim flags require specific falsifiable claims, with idioms and proverbs explicitly excluded; generic roles like "a doctor" or "a chef" are not flagged as named real people. After the update, both test cases passed green, and the check still catches actual policy violations.
Pre-flight inline editing introduced a stale-state bug.
The original spec allowed users to edit field values inside the pre-flight panel and click a Re-check button. The Re-check kept evaluating against a cached copy of the original values, not the edited inline values. Rather than fix the caching, I removed the inline editing entirely. The pre-flight panel is now diagnostic only. If a field fails, the user goes back to the brief and fixes it there. Simpler, more predictable, and structurally impossible to reintroduce the same bug.
Accomplishments I'm proud of
A working three-plugin chain.
LLM → Stripe → LLM → Kling Omni, sequenced through five Edge Functions, with proper failure handling at each stage. Each plugin earns its place and none of it is decorative integration.
A Kling prompt that produces real director-quality output.
The five-layer structure with scene anchor, temporal camera language, and attributed VO is something I'd happily hand to a human cinematographer and ask them to shoot. The fact that Kling can render it credibly is a testament to both the model and the prompt engineering.
Pay-per-use as the differentiator.
Every competitor in this space gates AI video behind a monthly subscription. CAGE is the only tool I know of that lets a user generate one ad, pay $3.50, and walk away. The pricing model isn't a gimmick as it's the whole product.
A polished, distinctive UI.
Dark theme with a single amber accent, Space Grotesk display type, JetBrains Mono for the brief output, persistent navigation warnings and confetti on completion. The aesthetic makes the tool feel intentional, not generic.
What I learned
Stripe's API treats publishable and secret keys as a matched pair from the same account.
I knew this in theory but did not appreciate how often this can break in practice, When sandboxes are introduced, when keys are rolled, when one half is updated without the other, when an environment variable is updated in one location but not another. Days of debugging traced back to mismatched account identifiers between the two keys.
Build-time vs runtime environment variables are not interchangeable.
The Vite frontend reads import.meta.env at build time and bakes values into the bundle. Backend Edge Functions read Deno.env.get at runtime. Updating Secrets affects the latter immediately and the former only after a rebuild. Internalizing that distinction would have saved hours.
Diagnostic specificity beats diagnostic noise.
The most useful debugging moment of the entire project was when Stripe returned code: api_key_expired instead of a generic 401. One specific error message replaced six hypotheses. Good APIs fail loudly and specifically, and I tried to do the same in my own functions. Every Edge Function logs the full upstream response on failure so the next debug pass starts with real data, not guesses.
LLM prompts for structured output benefit from explicit disambiguation rules.
"Don't flag brand names" is ambiguous as every common noun is a brand somewhere. "Only flag a word as a brand if context makes it clear the brand itself is being referenced; capitalisation alone is not evidence" is unambiguous and produces consistent output. The same pattern applied to the Kling brief prompt: "Cinematic camera move" is ambiguous; "Slow dolly-in from wide to close-up over 2.5 seconds" is unambiguous and renders correctly.
MeDo's plugin model genuinely accelerated the build. I didn't write Stripe API calls, didn't provision an LLM gateway and did not stand up a video generation backend. I focused only on the prompt engineering, the user flow, the failure modes, and the UI. The parts where the actual product lives. That's what good infrastructure feels like.
What's next for CAGE
Multi-image conditioning via Kling Omni's first-frame + last-frame mode.
The current single reference image becomes a "starting frame" anchor. I'd add a second "ending frame" upload and Kling generates the cinematic transition between them. This is where Kling Omni's actual strengths live, the kinds of "window passthrough" and seamless interpolation effects that text-to-video can't reach. A natural fit for product reveal ads (closed-box → opened product, before → after, exterior → interior).
Server-side webhook verification for Stripe.
The current flow is client-attested for payment success. At $3.50 the spoofing risk is acceptable, but a production deployment would benefit from a webhook handler listening for payment_intent.succeeded from Stripe's servers, verifying settlement before generation rather than trusting the browser callback.
Constrained brief editing.
Per-shot edit affordances for VO line, camera direction (dropdown of valid Kling moves), and shot length (with auto-rebalance to keep total = 15s). Gives users meaningful directorial control without letting them break the structured prompt that makes Kling produce good output. The challenge here is UX, not engineering. Too much control creates decision fatigue at exactly the wrong moment in the flow.
Logo overlay via post-processing.
Kling cannot reliably render specific logos from text descriptions. Adding a post-generation FFmpeg compositing step would let users specify logo placement, size, and entry timing as a final layer over the generated video. Out of scope for this hackathon, but the obvious next step for production use.
Refactor Stripe to a webhook-verified, retry-safe payment flow.
Including idempotency keys on PaymentIntent creation, dead-letter handling for failed video generations, and automatic refund on Kling failure. The current flow assumes the happy path, a real product needs to handle the partial-failure cases where the user paid but Kling errored.
Built With
- claude
- kling
- medo
- stripe

Log in or sign up for Devpost to join the conversation.