Credits and limits

What each feature charges in credits, the ceilings every plan enforces, and the codes an API call returns when one of them refuses the work.

Two independent things can stop a job: the workspace's credit balance and the plan's ceilings. Both refuse before any work is queued, and both use the same warning-* vocabulary whether they arrive on a 400 or on a failed job.

Credits

Credits are the display unit and tokens the accounting unit: 1 credit is 1000 tokens. Every endpoint that reports a balance reports both, so a client never has to know the ratio:

curl https://app.shortfast.com/api/v1/workspace \
  -H "Authorization: Bearer sf_live_xxxxxxxxxxxxxxxxxxxxxxxx"
{
  "data": {
    "id": "k9WmT3xR2pQ7nV4bd",
    "plan": "pro",
    "credits": { "credits": 42500, "tokens": 42500000 },
    "limits": { },
    "usage": { }
  }
}

What each feature costs

GET /reference/credit-costs returns this table live, in both units and with the unit each cost applies to. Read it rather than hardcoding: prices move.

FeatureCreditsTokensPer
Video render1.81800minute
Subtitles and b-rolls1.21200minute
AI avatar300300000minute
AI image11000image
Script or description0.0880generation
Premium voice33000minute, on top of the standard voice

Duration-based costs are charged against the video's own length, so a 40 second render costs about two thirds of a minute's worth. The premium voice line is what voiceProvider: "gemini" adds over openai, per minute of narration.

Estimate before you queue: multiply the video's durationSeconds by the per- minute cost, add an AI image per scene for type: ai, and compare against credits.tokens. An empty balance refuses the call with warning-no-credits.

Plan limits

GET /workspace returns the ceilings for the connected workspace. A limit the plan does not cap is null, which means unlimited.

FieldWhat it caps
maxRendersPerMonthRenders started in a rolling 30 day window. It caps generations over the same window too
maxAutomationsBlueprints that can exist at once
maxProfilesPublishing profiles that can exist at once
maxVideoDurationSecondsHow long a source video may be to run through generation
maxRenderSecondsHow long a video may be to render
storageBytesTotal stored bytes across the library. Deleting a video credits its bytes back
watermarktrue when renders on this plan carry a watermark

Roughly, by plan:

PlanRenders and generations per monthAutomationsProfilesMax render lengthWatermarkPublishing
Free3None13 minutesYesNo, download only
StarterUncapped1110 minutesNoYes
ProUncapped3312 minutesNoYes
AdvancedUncapped3315 minutesNoYes

Treat that table as orientation, not as a contract. GET /workspace is the live answer for the workspace your key resolved to, including any storage ceiling, and it is the only thing that stays right when plans change.

Long videos (videoDuration: "long", 8 to 15 minutes) need an Advanced plan and are otherwise refused with warning-plan-duration. A workspace on a paid trial can also run into a daily cap of 5 generations, which arrives as warning-trial-daily-limit.

Current usage

GET /stats reports the counters against those ceilings, so you can throttle yourself instead of discovering the limit on a refused call:

{
  "data": {
    "plan": "pro",
    "videos": 37,
    "automations": { "used": 2, "limit": 3 },
    "clips": 118,
    "generations": { "used": 24, "limit": null, "since": "2026-07-09T11:42:03.000Z" },
    "renders": { "used": 19, "limit": null, "since": "2026-07-09T11:42:03.000Z" },
    "storage": { "used": 3187441664, "limit": 50000000000 },
    "credits": { "tokens": 42500000, "credits": 42500 },
    "maxRenderSeconds": 720,
    "maxDurationSeconds": 7200
  }
}

since is the start of the rolling window the generations and renders counters cover. A limit of null means the plan does not cap that counter.

How a limit surfaces

A refusal reaches you in one of two shapes, with the same code in both.

Synchronously, when the call is refused before the job is queued, as a 400:

{
  "error": {
    "status": 400,
    "code": "warning-plan-renders",
    "message": "You have reached the limit of 3 renders per month on your current plan. Upgrade to render more videos."
  }
}

Asynchronously, when a running job hits it, on the job document: process.error, render.error or publish.error, each carrying code, message, blocked and action. The same object rides on the video.generation_failed, video.render_failed and video.publish_failed events.

blocked: true marks an expected limit rather than a crash, so a client can render it as a quota instead of a red failure. action is the fix to offer:

CodeTitleAction
warning-no-creditsOut of creditscredits
warning-trial-daily-limitDaily trial limit reachedupgrade
warning-plan-generationsMonthly generation limit reachedupgrade
warning-plan-rendersMonthly render limit reachedupgrade
warning-plan-durationVideo too long for your planupgrade
warning-plan-render-timeVideo too long to render on your planupgrade
warning-plan-automationsAutomation limit reachedupgrade
warning-plan-publishPublishing needs a paid planupgrade
warning-source-not-readySource video is not readynone
warning-publish-not-configuredNo publishing destinationconfigure
warning-publish-no-accountsNo connected accountconnect
warning-publish-authReconnect your social accountconnect

GET /reference/failure-codes returns exactly this list, so a client can render an unfamiliar code by looking it up instead of falling back to a raw message.

One code sits outside that list: warning-plan-storage refuses a generation when the workspace is at its storage ceiling. Free space by deleting videos, which credits their bytes back to the quota.

Reference

On this page