Credits

Updated Oct 16, 2026 Open the tool

Credits are the one balance every paid tool on Zoov.dev draws from. A user holds two kinds at once, permanent credits that never expire and temporary plan credits that expire on a schedule, and the site shows them as a single total.

Permanent and temporary credits

Credits on Zoov.dev come in two kinds and the balance a user sees is the sum of both.

Permanent credits never expire. They come from the 50 credit sign-up bonus (recorded as signup_bonus; since 2026-10-06 it is paid once per person, so a new account made after deleting one with the same Discord account or email starts at 0, and since 2026-10-07 so does a brand-new account made in a browser that already holds an account or held a deleted one, see accounts-and-sign-in, "Deleting your account"), from credit packs bought in the store, from loyalty milestones, and from refunds of failed jobs. Permanent credits survive a cancelled subscription. Referral rewards (25 per friend who signs up through your link and uses a tool, reason referral:<referee id>, since 2026-10-01) are temporary credits with a 45-day expiry; see the referrals doc.

Temporary credits are the monthly allowance of a subscription plan, plus smaller awards such as team seat bonuses, claimed rewards and manual awards from staff. Every batch of temporary credits is a "grant" with its own expiry date in the credit_grants ledger. Plan grants expire at the end of the billing period plus a 3 day grace window. Grants that do not belong to a billing period (claims, team bonuses, manual awards) expire 45 days after they are given.

The pricing FAQ puts it this way: "Plan credits refresh on every billing date and unused ones do not carry over. Credits you buy as a pack are permanent and never expire."

Spend order and expiry rules

When a job charges credits, the database function spend_credits runs one atomic spend. It expires any overdue grants first, then drains temporary credits starting with the grant that expires soonest, and only then touches permanent credits. So plan credits are always used before pack credits, which is the order that loses the user the least.

On a monthly plan, each renewal issues a fresh grant and voids whatever was left of that plan's previous grant (use it or lose it). Grants from a different subscription, for example a monthly credit plan held alongside Pro, or a second Spark, are never touched by another subscription's renewal or cancellation (since 2026-10-09 each subscription has its own; before, two subscriptions on one product shared one and a renewal of either wiped both).

On a yearly plan (or any term of two months or more), the renewal webhook pays month 0 and the site pays months 1 to 11 as they come due. Each of those monthly grants expires at the next month boundary plus 3 days. The check runs when the user loads the site and again in the nightly sweep, so people who only use the API, the Discord bot or the Photoshop plugin are still topped up.

No credits are awarded while a subscription is in a Stripe trial. Cancelling a plan voids only that plan's remaining temporary credits; permanent credits and any other subscription's credits stay.

What each tool costs

The credit cost of every job comes from one table in src/lib/pricing.ts. Costs depend on the user's tier. Since 2026-09-30 Plugin Pass (lite) and Booster pay exactly Free's price on every row: only Standard and up are paid plans for pricing, and the Plugin Pass buys the plugin, not cheaper credits. Until then they paid one step under Free on the image rows (GPT Image 10 / 20 / 60, Nano Banana 20 / 22 / 42, AI Image legacy Standard 25). A Referrer pays the same as Free.

Tool Free Plugin Pass, Booster Standard Pro Max
Model to Prop (GLB, OBJ, FBX) 40 40 0 0 0
AI Remesh 60 60 50 50 50
Convert Game Files 25 25 20 0 0
Image to Prop (base) 105 105 50 50 50
Image to Prop + Texture (add-on) 25 25 25 25 25
Image to Prop + Priority (add-on) 40 40 30 30 30
Image to Prop V2 (base) 45 45 35 35 35
Image to Prop V2 + Texture (add-on) 25 25 20 20 20
Text to Patch 25 25 20 20 20
Image to Patch 50 50 40 40 40
GPT Image (Draft / Standard / High Quality) 15 / 25 / 70 15 / 25 / 70 8 / 15 / 50 6 / 12 / 40 6 / 12 / 40
Nano Banana (1K / 2K / 4K) 25 / 28 / 50 25 / 28 / 50 15 / 18 / 35 12 / 14 / 28 12 / 14 / 28
AI Image legacy (Draft / Standard / High) 10 / 30 / 60 10 / 30 / 60 8 / 20 / 50 8 / 20 / 50 8 / 20 / 50

The Nano Banana row was cut on 2026-09-14 (it was 35 / 40 / 68 on Free and 22 / 24 / 45 on Pro): the model behind it, Gemini 3.1 Flash Image, costs about $0.07 an image at 1K and 2K and $0.15 at 4K, while the old row had been priced from Gemini 3 Pro Image's $0.13 and $0.24.

Where the prices come from (2026-09-14)

Credits are sold at, per 100: Starter pack $3.25, Standard $2.00, Pro and Builder $1.67, Creator $1.50, Studio $1.25, Max $0.99, the monthly boosts $1.60 to $1.29 (yearly plans about 20% under their monthly figure). Provider cost per job, checked that day: a GPT Image standard picture about $0.05 plus about $0.02 an input image (so a texture job with its UV and a reference is about $0.09), a high one about $0.21; Nano Banana as above; a textured Tripo P1 prop 50 Tripo credits at $1 per 100 ($0.50); a textured Meshy prop 30 Meshy credits, about $0.45 to $0.60; a remesh 5; the Code Agent's LLM cost is what it charges, one credit per cent, times 0.8 on paid plans and 4 on Free; a model conversion is our own compute. At the Pro monthly rate every paid-tier price clears at least 1.8x its provider cost (Tripo textured 55 credits = $0.92 against $0.50 is the tightest; GPT standard 12 credits = $0.20 against $0.09); the Free-tier rows sit 1.3x to 2x above the paid ones, which is the discount a plan sells. The allowances were raised the same day because a Pro account was spending about 18% of its credits (half spent none), so the cost of the extra credits is small and the people it reaches are the ones who hit the cap.

AI image extras and free tools

AI image costs have two extras on top of the table above. Generating 2 images at once takes 10% off the base, 3 or more takes 15% off. GPT Image accepts 2 input images free; each extra input costs 6 (Free, Plugin Pass and Booster; Plugin Pass and Booster paid 5 until 2026-09-30), 4 (Standard) or 3 (Pro and Max) credits. Nano Banana input images are free. The Designer's Free limits (editing minutes, export size, YTD and DDS exports, 3D previews, saved designs) are not credits and cannot be bought with them; only a plan lifts them. A Roblox livery or clothing sheet in /app (every piece on one image, drawn at the largest size GPT Image 2.5 allows since 2026-09-23) costs twice the GPT Image 2.5 price for its quality; see the AI image tools doc, "Sheets".

An "AI image to textured prop" on the pricing page means Image to Prop V2 plus its texture add-on: 70 credits on Free, 55 on Standard, Pro and Max. The Code Agent charges at a rate of 4x on Free and 2x on paid plans (0.8x until 2026-10-12, when its cost started counting the prompt cache; code-agent.md); per-message costs are not in the pricing table.

Texture editing, optimizing, browsing models and viewing in 3D never cost credits. Free's day in the workspace (since 2026-09-27: 5 conversions or optimizations, 1 UV map, 1 vehicle, 1 ped (the Peds tool, since 2026-09-28), 1 mod and 20 Designer minutes a day, the Siren preview on Standard; DAY_LIMITS and DESIGNER_LIMITS in config/plans.ts) is counted, not charged: no credit pack extends it, only a plan does. The workspace asks for the sign-in before any tool is touched.

In the new workspace at /app (in progress), converting a model to a prop spends no credits either: free accounts get 3 conversions a day, any paid plan is unlimited, and converting a model made by an AI generation never counts against the day. The legacy /app/prop tool still charges the table above until it is redirected.

Refunds when a job fails

Every credit-charging generation route (image, texture, refine, tattoo, patch, 3D mesh) follows three rules: a successful generation is never refunded, a failed generation is always refunded (including empty provider responses, storage failures and crashes), and the user is always told.

When a refund works, the error message the user sees ends with "have been refunded" followed by "you were not charged", for example "Your 35 credits have been refunded". When the refund itself fails, the message says "We could not automatically refund your 35 credits; our team has been alerted and will restore them" and gives a request ID to quote to support. Ask the user for that request ID.

Refunded credits are added back as permanent credits, even if the original charge was taken from temporary plan credits. This is a small upside for the user, not a bug.

Some AI paths that have already started producing output are allowed to push the permanent balance slightly negative, up to a debt of 50 credits. The error at that limit is "Debt limit reached (max 50)." and the debt is cleared by the next credit purchase or award. A plain shortfall returns "Insufficient credits."

What a failed generation says (2026-09-23)

A failed generation now explains itself in one sentence, whatever the provider wrote. Every AI provider (Meshy, Tripo, OpenAI, Gemini) answers a refusal in its own wording, and that wording used to reach the user as it came ("Failed to start generation: moderation_blocked") or be replaced by a flat "try again". One shared classifier (src/lib/app/server/generation-refusals.ts) sorts the provider's text and HTTP status into three causes and gives each one sentence. The provider's own words are never shown, because they carry request ids and rate-limit hints; a cause the classifier does not recognise keeps the fallback sentence the route already had.

For a 3D generation the sentences are: "That image was rejected by the 3D generator for content reasons. Try a different picture." (content), "The 3D generator could not read a model from that image. Use a single subject, in focus, on a plain background." (the picture itself), and "The 3D generator is unavailable right now. Try again in a few minutes." (us, not the user).

For an image, a tattoo or a texture they are: "That request was rejected for content reasons. Change the picture or the wording and try again.", "The picture could not be read. Try another one." and "The image generator is busy right now. Try again in a minute."

None of this changes what is charged. The refund rules above are the same for all three causes: a refusal means no model and no image, so the credits come back and the sentence is followed by "Your {n} credits have been refunded" or the alerted line. A content refusal is worth repeating to a user in support, because it is the one cause they can fix themselves by changing the picture or the prompt.

Where the balance shows

The credit balance appears in the site header badge on every page. The badge opens the credit store (aria label "Open the credit store") and has a "Buy credits" button. It updates live through a Supabase Realtime subscription on the credits table, so a purchase or a charge shows within a second or two without a reload.

Inside the workspace a "Running low on credits" nudge appears with the line "You're out of credits, top up to keep generating and exporting." or "{balance} left, get the most out of Zoov.dev by topping up your credits." and a "Top up credits" button.

In the workspace at /app the balance is a pill in the top bar: the coin, the number, and a "+" that opens the plans dialog at the credit packs. The number is read from the credits table when the page loads and asked for again (GET /api/v2/credits/summary) after every action that moves it: a generation, a texture, a refine, their refunds, a purchase back from Stripe, opening the credits dialog, or a tab back in view after half a minute away. No realtime socket (2026-09-14); the legacy pages keep theirs. Clicking the number opens the Credits dialog (2026-09-14): the balance split into permanent and plan credits, every unexpired batch of plan credits with the day it expires ("900 expire 3 Oct"), this month's Spent, Refunded and Added totals with the job count, the full history newest first (12, 25 or 50 a page: the date, the type such as "AI texture · Vehicle", "AI image · Logo", "Code agent", "Model to prop", "Refund · AI texture", "Credit pack", "Plan credits", "Welcome bonus", "Team credits", and the signed amount; rows that moved no credits, such as plan renewals, are left out), the plan with its monthly credits and "renews 3 Oct" or "ends 3 Oct" when it was cancelled, and a Credit packs card listing the one-time packs, each with its own Buy (with a card on file the first press arms "Charge ···4242 · $24.99" and the second pays without leaving the dialog; otherwise the press goes to Stripe Checkout, or since 2026-10-10, for staff only until it is switched on for everyone, turns the dialog into a payment step with Stripe's card form inside it; see the billing doc, "Paying inside the credits dialog"), plus a Monthly boosts link to the plans dialog. Manage opens Stripe's customer portal. Signed out it says "Sign in to see your balance". The legacy pages still show the total only.

The old GET /api/v1/credits/balance was removed on 2026-10-13 because nothing called it; the balance is GET /api/v2/credits/balance. The workspace reads GET /api/v2/credits/summary (balance, grants, month, plan; the packs are GET /api/v2/billing/packs) and GET /api/v2/credits/history?page=&pageSize= (12, 25 or 50; newest first).

Loyalty credits

Loyalty credits reward subscription tenure. They are permanent and are kept after cancellation. The milestones are 1 month: 50, 3 months: 150, 6 months: 400, 1 year: 1,000, 18 months: 500, 2 years: 1,000, a total of 3,100 credits over two years.

Months are counted from the start date of the user's most recent active subscription, so a gap resets the count. The check runs when the user loads the site and each milestone is awarded once per subscription. The user gets a Discord DM headed "Thanks for staying with Zoov.dev, your loyalty credits are here" with a "View My Credits" button.

Nightly sweep

A Vercel cron job calls /api/v1/cron/subscription-credits every day at 06:00 UTC. It does three things in order. First it expires every overdue grant with sweep_expired_credit_grants and resyncs the cached balance for the affected users. Second it finds every yearly or multi-month subscription that is owed this month's credits and awards them, at most 500 subscriptions per run. Third it settles every active team so each member holds the current per-seat allowance.

Expiry also happens inside every spend, so a balance is correct before the sweep runs; the sweep mainly fixes the header number for people who have not spent anything since a grant expired.

Common questions

Why did my credits drop overnight? Plan credits expire. A monthly plan's allowance is replaced at each renewal and the unused part is voided; a yearly plan's monthly grant expires at the next month boundary plus 3 days. Pack credits never expire. In the workspace, click the credits pill in the top bar: the dialog lists what is left of each plan batch and the day it expires, and the history shows every movement. If the drop does not match, ask staff to check the grants ledger.

Which credits are used first? Temporary plan credits, soonest-expiring first, then permanent pack credits.

A generation failed. Do I get my credits back? Yes. Failed generations are refunded automatically and the error message says so. If the message says the refund could not be made automatically, send support the request ID from the message.

It says my image was rejected for content reasons. Was I charged? No. A refusal means nothing was made, so the credits are refunded and the message says so. Content refusals come from the AI provider's own rules; change the picture or the wording and try again.

I only use the plugin or the API. Do I still get my yearly plan's monthly credits? Yes. The nightly sweep awards them at 06:00 UTC even if you never open the site.

How much does an AI prop cost? Image to Prop V2 with texture is 70 credits on Free and 55 credits on Standard, Pro and Max. Model to Prop conversions are free from Standard up.

Languages

Since 2026-09-20 this part of the site is available in English, French, German, Spanish, Portuguese, Polish and Dutch. It follows the browser's language on a first visit, and the language menu (the icon beside the profile in /app, or in the top navigation on public pages) changes it. Nothing about how it works, what it costs or what a plan includes differs by language; only the words do. File names, format names (GLB, YDR, YTD), product and model names, code, and some error lines that come from the server stay in English. Details and known gaps: Languages and translation.

Faster charges (2026-09-28)

A paid action now starts as soon as the credits are taken and the ledger row is written. The Discord log of the charge and the copy of the balance kept in the sign-in session are written a moment after, so a support log can arrive a second or two after the generation started, and anything that reads the balance from the session (not the workspace, which reads the table) can lag by the same. The top bar's balance is read from GET /api/v2/credits/balance (one row) after every action; the credits dialog still reads the full summary.

Internal: the history and month stats read credit_transactions through credit_transactions_user_created_idx (user_id, created_at desc). It was created on production with CONCURRENTLY and recorded in public._migrations_applied; the migration file holds the plain create index if not exists form, because the migrations workflow runs every file in one transaction, where CONCURRENTLY is refused (its first push failed the workflow on that).

Topics creditsbalanceexpirygrantsrefundcostpricingsweep

Something missing? Ask on Discord