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 the table that applies to the connected workspace, never both, live, in both units and with the unit each cost applies to. Read it rather than hardcoding: prices move.

Accounts created on or after October 26, 2026

FeatureCreditsTokensPer
Video2424000minute
AI avatar300300000minute
Animated b-rolls375375000minute; High quality 1,125 a minute
AI image11000image
Script or description0.0880generation

The Video charge replaces the render, captions, scenes and premium voice charges of the older table, so renders and the premium voice cost nothing extra. A one-minute video built from stock footage comes to about 24 credits, and one in an AI image style to about 37.

Accounts created before October 26, 2026

These accounts keep the previous table:

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. On this table 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
maxSocialAccountsSocial accounts connected across all profiles. 0 means the workspace can download but not publish
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 monthAutomationsProfilesSocial accountsMax render lengthWatermarkPublishing
Free3None1None3 minutesYesNo, download only
StarterUncapped11110 minutesNoYes
ProUncapped31312 minutesNoYes
AdvancedUncapped62615 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:

CodeTitleActionBlocked
warning-trial-daily-limitDaily trial limit reachedtrialtrue
warning-trial-animated-limitAnimated trial limit reachedtrialtrue
warning-no-creditsOut of creditscreditstrue
warning-plan-durationVideo too long for your planupgradetrue
warning-plan-render-timeVideo too long to render on your planupgradetrue
warning-plan-rendersMonthly render limit reachedupgradetrue
warning-plan-generationsMonthly generation limit reachedupgradetrue
warning-plan-automationsAutomation limit reachedupgradetrue
warning-plan-publishPublishing needs a paid planupgradetrue
warning-plan-storageStorage limit reachedupgradetrue
warning-source-not-readySource video is not readynonetrue
warning-publish-not-configuredNo publishing destinationconfiguretrue
warning-publish-no-accountsNo connected accountconnecttrue
warning-publish-authReconnect your social accountconnecttrue
warning-publish-duplicateAlready postednonetrue
warning-publish-rate-limitedPosting limit reachedscheduletrue
warning-publish-rejectedPost refusednonetrue
voice-refusedThe voiceover couldn't be maderetryfalse
voice-unavailableVoiceover unavailableretryfalse
no-scenesNo scenesretryfalse
media-generation-failedVisuals failedretryfalse
unsupported-videoUnsupported videoretryfalse
timeoutTook too longretryfalse
media-stalledA scene didn't loadretryfalse
publish-timeoutPublishing timed outretryfalse
publish-unavailableAccounts unreachableretryfalse
internal-errorSomething went wrongretryfalse

schedule means the platform takes posts again at the time in retryAt, and retry means trying again may work. trial means the limit lifts when the plan starts: end the trial early from the billing page rather than upgrading.

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.

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

A failed job can also carry a warning-* code that isn't in the list, such as warning-invalid-input when a faceless video has no captions and transcribe off (see Videos). It comes with blocked: true, action: "none" and a message that says what to change.

Reference

On this page