Skip to content
← STUDIO NOTES

SaaS engineering

How do you price credits in an AI video SaaS? Notes from 28 models

Viralance has 28 video models, 297 credit keys and one floor rule: credits = ceil(provider USD / 0.08). This is how the price table lives in code.

A showcase of the real Viralance mobile discovery screens and web platform
Viralance — Explore the real interfaces and project scope ↗

SHORT ANSWER

In Viralance, every new or repriced model tier derives its credit price from the provider’s dollar cost for that exact job: credits = ceil(USD / 0.08). A 7-second VEO 3.1 Fast extension costs $1.05 on fal, so it is 14 credits. Requiring each credit sold to cover at least $0.08 of provider cost protects margin while pack prices sit between $0.18 and $0.30 per credit.

The model catalogue lives in one file: 28 video models, 297 price keys

In the Viralance web app, lib/utils/constants.ts is the single source for models, endpoints and credit prices. Firebase Functions and the MCP server cannot import it directly, so a code generator, scripts/gen-model-catalog.ts, run through npm run sync:catalog, writes a committed copy into both packages, including functions/src/generated/model-catalog.ts. That generated catalogue lists 28 video models, 3 of them flagged retired: true. Retired models disappear from pickers but the server keeps accepting them, because older iOS builds, saved presets and API users can still send those ids.

The same file carries 297 video credit keys, 11 image credit keys and 6 image models. Each model entry declares its modes (text-to-video, image-to-video, reference-to-video), supported durations, resolutions and fal endpoints. Some models send 1080p to a different endpoint; Seedance 2.0 at 1080p, for example, routes to its Standard-tier endpoint.

The iOS app keeps its own mirror in src/constants/catalog.ts. Its comment is explicit: the server is authoritative and recomputes every charge; the mobile table exists only for the estimate shown before the user taps Generate, and it must build the same key as the web.

A credit key is built from model, mode, resolution and duration

Price is a lookup table, not a formula. After validating the request, the server function generateVideo builds a string called creditKey and looks it up in CREDIT_COSTS. The key shape depends on the model family:

  • duration only: e.g. kling-2.5-turbo-pro-5s
  • an extra segment for image-to-video: kling-3.0-pro-i2v-10s
  • resolution plus duration: wan-3.0-i2v-1080p-15s
  • fixed-length reference mode: veo3.1-fast-ref-720p-8s

If the key is missing, the request fails with Invalid credit configuration, so an unpriced combination never runs cheap or free by accident.

Two corrections happen before the key is built, and both came from a gap between what was charged and what was delivered. Hailuo 2.3’s 1080p endpoint takes no duration; in a live test on 13 July 2026 a 10-second request returned a 5.88-second video billed at the 10-second rate. The server now clamps 1080p to 6 seconds. VEO 3.1 reference mode only renders 8 seconds; a 4- or 6-second request used to be charged the shorter price for an 8-second render, and it is now always recorded as 8 seconds.

The web and iOS estimators copy the same key logic. Tests parse the server table out of functions/src/index.ts as text and compare it with the client table.

Provider cost sits in a separate file, as dollars per second

Sale price and cost are not in the same table. functions/src/fal-costs.ts estimates the dollar cost of every job, and the daily report uses it to total the day’s production spend. Its header says prices were read from public fal model pages in July 2026, with some rows re-checked in the 27 September 2026 unit-economics review. Models whose page could not be read are marked ESTIMATE. Where a model bills extra for audio, the price assumes audio on, because the generation flows enable it by default.

The lookup is layered: provider first (Google Vertex jobs have their own rows), then the stored endpoint (ENDPOINT_USD_PER_SEC overrides the model table), then a mode table (reference and image-to-video), and finally the model table. Resolution-dependent prices are stored as { default, 720p, 1080p }. A new model without a price falls back to conservative defaults: $0.12 per second, 6 seconds, $0.05 per image, so that a forgotten row does not make spend drop to zero in the report.

The comments record two mistakes. The Kling O3 Pro row used to say $0.28, double the page price; it was corrected, but credit prices set against it stayed. Seedance 2.5 had no row and fell back to the $0.12 default, under-reporting its spend by about 4x in cost analytics.

The floor rule: credits = ceil(USD / 0.08)

For new and repriced tiers the rule is one line. functions/src/product-models.ts defines FAL_USD_PER_CREDIT = 0.08 and computes Math.ceil(usd / FAL_USD_PER_CREDIT - 1e-9); the tiny epsilon stops floating-point noise from rounding an exact division up by one credit. The template discount repeats the same floor as TEMPLATE_OF_WEEK_CREDIT_USD_FLOOR.

Kling 4.0 shows the rule in use: $0.157 per second at 720p, so 5 seconds is $0.785 and 10 credits; 1080p for 30 seconds is $7.05 and 89 credits. Video upscale is priced per file: output megapixels, at least 30 fps and the duration at $0.001 per MP, divided by 0.08. A 5-second 9:16 clip comes out at 16 credits.

The 27 September 2026 review moved loss-making items onto this rule. The test lists them:

  • VEO 3.1 extend from 9 to 14 credits (old endpoint $2.80, new $1.05)
  • VEO 3.1 reference at 720p, 8 seconds, now 15 credits (it used to charge 12 while calling a $3.20 endpoint)
  • Grok Imagine 1.5 at 720p, 10 seconds, from 10 to 18 credits

The rule was not pushed back onto every older tier. Kling 2.5 Turbo Pro at 5 seconds is 3 credits; its comment says it stays profitable against a worst-case credit value of $0.139 (Business pack plus bonus). So the code carries two yardsticks, and the comments say which one each price was checked against.

How is the gap between pack price and floor protected?

Web credit packs are one-time and never expire: 30 credits for $9 ($0.30 per credit), 330 for $79 ($0.24), 1,650 for $299 ($0.18). What actually erodes the gap between pack price and the $0.08 floor is bonuses and discounts.

The code has three checks against that erosion:

  • The first-purchase bonus is 5–15% per pack inside a 24-hour window. Since 27 September 2026 it is capped at 15%; according to the comment, the earlier 30% Business bonus sold credits below fal cost on several models.
  • The template-of-the-week discount went from 20% to 10%, because at 20% most templates sold under cost. The discount is computed on the server and is dropped entirely when the discounted price would not cover the $0.08 floor.
  • profitability.test.ts automatically checks every new model’s price against a profitability floor.

Image generation costs far less per call than video, so image tiers are priced with a wider buffer. The AI helpers, captions and prompt enhancement, sit in the table at 0 credits.

One model, two providers: VEO tries Vertex AI first, then fal

Most Viralance generation runs on fal, but VEO 3.1 has a second path. According to the note in functions/src/google-veo.ts, Vertex AI charges $0.10 per second for Fast at 720p and $0.12 at 1080p, while fal bills the same model at $0.15. Lite costs the same on both. The note also records that fal turns off its VEO 3.1 endpoints on 17 November 2026.

The routing rule is “Vertex if it can render the order exactly”. googleVeoEligible only accepts 720p/1080p, 9:16 or 16:9, 4/6/8 seconds and no extra inputs; reference mode also requires 1–3 reference images and 8 seconds. An eligible job goes to Vertex, but the request it would have sent to fal is written to the video document as falFallback. Three cases are then handled separately:

  • a submit error (quota, outage, bad request): the job moves to fal in the same call and the user never sees the switch
  • Vertex finishes without a video for a non-policy reason: the stored fal request is submitted and polling continues through fal
  • a content-policy refusal: the job fails and is refunded; fal runs the same Google model, so a resubmit would be refused too

A 401, 402, 403 or 429 response puts the Vertex path on a 10-minute cooldown. On the cost side, fal-costs.ts recognises these jobs by provider: google and prices them from the veo3.1-fast-google row.

Why polling instead of webhooks? Intervals and timeouts

The code has signature-verified webhooks for payment providers, but none for video providers. After debiting credits, generateVideo drops the job on fal with queue.submit(), stores the request id and returns immediately. The comment explains why: waiting with subscribe() ran into the function timeout. The function itself is capped at 540 seconds, 9 minutes.

Two separate pollers collect the result:

  • Client: on the videos page a single shared timer checks every pending video in one wake-up. The interval grows through 3, 5, 10 and 15 seconds, each video gets at most 40 attempts, and nothing is sent while the tab is hidden. Each attempt calls pollVideoGeneration, which has a 120-second limit because a finished Google video is downloaded and copied to Storage inside that call.
  • Server: processVideosScheduled runs every minute and looks at up to 100 pending or processing videos per run. Anything older than 30 minutes is marked failed.

The cost of this setup is extra calls; the gain is not depending on a provider notification that might never arrive. Even if the user closes the browser, the scheduled job finishes the work. The same one-minute pattern repeats for other job types such as audio, actors, the ad multiplier and pro export.

Failure rates are a cost line in catalogue decisions

Adding a model to the catalogue is not decided on dollar price alone. The models available to accounts that have never bought a pack are fixed in TRIAL_VIDEO_MODELS, and the server enforces the list in generateVideo, Autopilot and MCP. It was chosen on 27 September 2026 from August–September failure data. The comment gives the numbers: VEO 3.1 Lite failed 2% of the time, Hailuo 2.3 0%. Left out were Grok Imagine (42% content-policy rejections), MiniMax H3 (41%), Grok Imagine 1.5 (73%) and WAN 2.7 (71%).

Some tiers are left out on purpose. MiniMax H3’s 480P tier, at $0.05 per second, lands on the same credit price as 768P; the comment says it would only ever be the worse of two identical bills. Gemini Omni Flash’s 360p tier is not offered either. Hunyuan 1.5 no longer advertises 720p, because fal only outputs 480p; this was caught in a live matrix test on 13 July 2026.

Retired models carry a cost too. VEO 3 Fast was retired when fal moved it to $0.40 per second; older clients still send it, and the server runs those requests on VEO 3.1 Fast 720p at the same credit price.

A checklist if you are building something similar

The practical rules that come out of the Viralance code:

  • Keep prices in one file, copy them into other packages with a code generator, and make the server authoritative. The client table is only an estimate.
  • Keep sale price and provider cost in separate tables. Record the check date and source next to each cost, and mark uncertain rows as estimates.
  • Make the default for an unpriced model a conservative number, not zero.
  • Derive credits from one rule; in Viralance it is ceil(USD / 0.08). Check bonuses, discounts and campaigns against the same floor.
  • Record cost at the endpoint level. The same model name on a different endpoint can cost more than 2.5x as much (VEO 3.1: $0.40 vs $0.15 per second).
  • Verify the duration the provider actually delivers, and build the price key after that.
  • If you can get the same model from two providers, store the fallback request up front and separate content refusals from technical errors.
  • Instead of trusting webhooks, combine a backing-off client poll with a per-minute server sweep, and set a clear timeout.
  • Tie price to cost with a test: credits × floor ≥ cost.

At AMG Software Studio we build multi-model, credit-based SaaS products like Viralance, web and mobile together.

APPLY IT TO YOUR PRODUCT

SaaS Development

Custom SaaS products with subscriptions, roles, background jobs and management tools. Web and mobile experiences planned together.