Credits and limits
Every render costs credits, and the cost is calculated from your request before it runs — so it is never a surprise after the fact.
Two currencies#
Image and video credits are separate balances. A poster costs us milliseconds of CPU; a 4K minute costs minutes. One shared unit would either make posters absurdly expensive or make video effectively free.
Each balance has two buckets:
| Bucket | Where it comes from | Expiry |
|---|---|---|
granted | Your plan’s monthly allowance | Resets at the start of each period — use it or lose it |
topup | Credit packs you buy | Never expires while your account is open |
What a render costs#
Images are priced by output megapixels, in bands — the cost is dominated by painting a page, not by the pixels.
| Output size | Credits |
|---|---|
| Up to 2.5 MP (1080×1080, 1920×1080) | 1 |
| Up to 9 MP (4K poster) | 2 |
| Up to 20 MP | 4 |
| Above 20 MP | 8 |
| 1.5× the image rate |
Video is priced per output second, scaled by resolution, frame rate and codec. Minimum 5 credits per video.
| Example | Credits |
|---|---|
| 6-second reel, 1080p30 | 6 |
| 15-second story, 1080p30 | 15 |
| 30-second ad, 1080p60 | 60 |
| 30-second ad, 4K30 | 90 |
| Audio-only export (mp3/wav) | 1 |
The scale multiplier on stills counts towards megapixels — a 1080×1080 render at scale: 2 is 2160×2160, which is 4.7 MP and therefore 2 credits.
Check before you commit
POST /v1/render/estimate prices a render without running it, and costs nothing. Use it to show cost in your own UI, or to guard a bulk job before it starts.Hold, settle, refund#
Credits move in three steps, and the order is deliberate.
- Hold — when a render is accepted, its cost is moved out of your balance. This is what stops a thousand queued renders against a balance of ten.
- Settle — on success, the hold becomes a spend. The balance already reflected it, so nothing further changes.
- Refund — on failure or cancellation, the exact credits are returned to the buckets they came from. A refunded grant is still a grant.
This means you are never charged for a render you did not receive, and no action is needed on your side.
GET /api/credits
{
"credits": {
"image": { "granted": 380, "topup": 500, "available": 880, "held": 2 },
"video": { "granted": 96, "topup": 250, "available": 346, "held": 15 }
},
"grants": { "imageCredits": 500, "videoCredits": 150 },
"periodEnd": "2026-09-01T00:00:00.000Z",
"planCode": "starter"
}held is reserved by renders in flight. It has already been deducted from available — it is reported separately so you can explain a temporary dip to a user.
Every movement is recorded in an append-only ledger at GET /api/credits/history, including which render caused it.
Running out#
A render that cannot be afforded returns 402 before any work is done:
{
"success": false,
"message": "Not enough video credits: this render needs 15, the workspace has 4.",
"code": "INSUFFICIENT_CREDITS",
"required": 15,
"available": 4,
"creditKind": "video",
"upgradeRequired": true,
"upgradeUrl": "https://pixbix.app/dashboard/billing"
}Do not retry a 402
It will fail identically until credits are added. Surface it to a human, or pause the job queue and alert. Subscribe tocredits.low to get warned before you hit zero.Plan limits#
Separate from credits, plans cap what a single render may ask for. These are checked before credits are held, so hitting one costs nothing.
| Limit | Error code |
|---|---|
| Max video duration | LIMIT_VIDEO_DURATION |
| Max video resolution | LIMIT_VIDEO_RESOLUTION |
| Max frame rate | LIMIT_VIDEO_FPS |
| Max image dimension | LIMIT_IMAGE_SIZE |
| Max still scale | LIMIT_IMAGE_SCALE |
| Renders queued or running at once | LIMIT_QUEUE_DEPTH |
| Video rendering not on plan | FEATURE_VIDEOENABLED |
| Requests per minute | RATE_LIMIT_EXCEEDED |
Concurrent renders is not in that table, and that is deliberate.Your plan’s concurrency is how many of your renders run at the same time — it is a speed limit, not a submission limit. Send a thousand renders on a plan that runs three at a time and all thousand are accepted; three run, the rest sit at queued with a stage telling you why, and each one starts the moment a slot frees. Nothing needs retrying.
LIMIT_QUEUE_DEPTH is the ceiling that does refuse, and only a runaway loop should ever meet it: it means the workspace already holds the most unfinished renders your plan will keep at once. Like the others it clears on its own as the queue drains, so a bulk job that hits it should back off rather than treat it as fatal.
Rendering for free#
Test keys (pk_test_) never consume credits, whatever the plan. They run the same pipeline and produce a real file, always watermarked.
Build, debug and load-test your integration on a test key. Switch to pk_live_ only when the output needs to be usable.