CLI commands
Every riffads command and flag, JSON output, exit codes and CI use.
Read as MarkdownCLI: not distributed yet
14 commands. Each wraps REST API calls. Setup first: CLI setup.
| Command | Starts paid work | What it does |
|---|---|---|
login | no | Checks a key, then saves it |
logout | no | Deletes the saved key file |
whoami | no | Which key and host it will use, and whether they work |
credits | no | Available credits and the agent spend ceiling |
capabilities | no | What you can generate. show <id> adds schema and example |
actors | no | Actors (act_) |
voices | no | Voices (voc_) |
estimate | no | The number for --max-credits. Reserves nothing |
generate | yes | Starts a job, waits, saves the file |
status | no | Reads one generation (gen_) |
batch | no | Reads every variant of one submit (bg_) |
download | no | Saves outputs to disk |
upload | no | Uploads a file, prints its ast_ id |
workflows | on invoke | Lists templates, starts and follows runs |
Global options
Put them before or after the command.
| Flag | Short | Default | What it does |
|---|---|---|---|
--json | off | Full API response as JSON on stdout, signed links included | |
--quiet | -q | off | Hide progress and notes. Errors and warnings still print |
--api-key <key> | none | Use this key over the env var and saved login | |
--base-url <url> | https://riffads.com | Host. Set https://app.riffads.com | |
--help | -h | off | Help for the command |
- Aliases:
lsrunscapabilities,balancerunscredits,getrunsstatus. riffads --version,-vorriffads versionprints the version.- Write
--jsonand--quietbare.--json=truedoes not switch output. - Accepted forms:
--name value,--name=value,-s value,--no-flag,--to stop parsing. No bundled short flags (-abc). - Unknown options are refused, with a hint:
Unknown option "--max-credit". Did you mean --max-credits?
Account
login, logout, whoami
riffads login [--api-key <key>] [--base-url <url>]
riffads logout
riffads whoamiloginexits 0 saved, 2 no key given, 3 key refused. stdout stays empty.logoutmakes no network call. Always exits 0.whoami --jsonaddsbase_url_source,api_key_source(flag,env,file, ordefaultfor the host),credits_availableandagent_limits.
Details: CLI setup.
credits
Alias balance. Output goes to stdout, so riffads credits | jq works with --json.
riffads credits --json | jq '{available: .credits_available, ceiling: .agent_limits.most_one_generation_may_cost, bound_by: .agent_limits.bound_by}'most_one_generation_may_cost is the highest --max-credits a job can pass right now. bound_by names the cap that sets it.
Browse
Read commands share a limit of 120 requests a minute.
capabilities
Alias ls.
riffads capabilities [show <capability-id>]| Flag | Default | What it does |
|---|---|---|
--limit <n> | server default | Rows per page |
--cursor <cursor> | none | Continue from next_cursor |
--all | off | Follow every page |
--schema | off | With show: only the JSON config schema |
--example | off | With show: only a valid example config |
riffads capabilities show actor_ultra --example > brief.json
riffads capabilities show actor_ultra --schema | jq '.properties | keys'- List lines: id, output kind, status, name, and
needs <plan>when gated. showis required:riffads capabilities actor_ultraexits 2.- The CLI never validates config. The server names the bad field.
Every id: capabilities.
actors and voices
riffads actors --search casual --limit 20
riffads actors --all --json | jq -r '.actors[].actor_id'
riffads voices --language en --search warm --json | jq -r '.voices[] | "\(.voice_id)\t\(.name)"'| Flag | Short | On | What it does |
|---|---|---|---|
--search <text> | -s | both | Match name, description, tags |
--language <code> | voices | Filter by language, for example en | |
--limit <n> | both | Rows per page | |
--cursor <cursor> | both | Continue from next_cursor | |
--all | both | Follow every page |
actors --json adds default_voice_id, default_voice_name, is_platform.
Generate
estimate
riffads estimate --capability <id> [--config <json|@file|->] [--set k=v]Spends nothing. Limit: 60 a minute. Takes the same --capability, --config, --set, --set-string flags as generate.
stdout is a bare integer. Details go to stderr.
EST=$(riffads estimate --capability veo_31 --config @look.json)
MAX=$(( (EST * 11 + 9) / 10 )) # ceil(estimate * 1.1)at most on stderr means is_ceiling is true. --json gives credits, is_ceiling, breakdown, and note and seconds when present.
generate
riffads generate --capability <id> --config <json> --max-credits <n>| Flag | Short | Required | Default | What it does |
|---|---|---|---|---|
--capability <id> | -c | yes | Capability to run | |
--config <json|@file|-> | no | {} | Capability settings | |
--set <key=value> | no, repeatable | One field, coerced | ||
--set-string <key=value> | no, repeatable | One field, kept as a string | ||
--max-credits <n> | -m | yes | none | Most this job may cost, all variants |
--variants <n> | no | server default | Several takes of one config, up to 4, each charged | |
--actor <actor-id> | no | Library actor | ||
--actor-image-asset <asset-id> | no | Uploaded image as the face | ||
--voice <voice-id> | no | Voice | ||
--approved-voice-generation <generation-id> | no | Finished tts generation for a talking actor | ||
--no-wait | no | off | Print the id and exit 0 | |
--timeout <seconds> | no | 1800 | Stop waiting after this | |
--output <dir> | -o | no | Save outputs here |
Send ceil(estimate * 1.1) as --max-credits
The bare estimate is refused with max_credits_exceeded, exit 4. --max-credits has no default: leaving it out exits 2.
What it does:
- Submits. The id prints to stdout first, so a broken wait still leaves you the id.
- Waits on the server's wait endpoint. Retryable refusals during the wait are slept off, up to 5 in a row.
- Saves outputs with
-oas<generation_id>-<index><ext>. Absolute paths go to stdout, one per line.
Veo 3.1: 484 credits held for 1 output
gen_8c31fa: completed, 1 of 1 delivered
held 484 credits, charged 462 credits (captured)
[0] video https://files.riffads.com/org/gen_8c31fa-0.mp4 (signed link hidden: use --json or riffads download)- Wait timeout: exit 5. Nothing is canceled.
- Ctrl+C stops the wait, not the job. Exit 130.
generate -ohas no--force. An existing file warns and exits 2 even if the job worked. Useriffads download <id> -o ./out --force.- One job at a time per key. A second gets
submission_in_flight, exit 5. run_idnull printsQueued, starting shortly.Not a failure.
Talking actor: two generations
Voice first. Its id drives the video.
SCRIPT='Three reasons these sneakers sold out twice.'
VOICE=$(riffads generate -c tts \
--config "{\"script\":\"$SCRIPT\"}" \
-m "$VOICE_MAX" --voice voc_ada_en --no-wait)
riffads status "$VOICE" --wait
riffads generate -c actor_ultra \
--config "{\"script\":\"$SCRIPT\"}" \
-m "$ACTOR_MAX" \
--actor act_7f2a91 --voice voc_ada_en \
--approved-voice-generation "$VOICE" \
--output ./outttsneeds--voice.actor_ultrahas noaspect_ratio. Output is 720p, start frame shape, up to 60 seconds of audio.
More: talking actor.
Passing a config
estimate and generate parse config the same way.
--config '{"script":"..."}' # inline
--config @brief.json # a file
--config - # stdin- Must be a JSON object. Omitted:
{}. --setruns after--config. Dotted paths nest:--set voice.speed=1.2gives{"voice":{"speed":1.2}}.--setcoercestrue,false,nulland numbers.--set-stringnever coerces and wins on the same path.
jq -n --arg s "$SCRIPT_TEXT" '{script:$s}' \
| riffads generate -c tts --voice voc_ada_en --config - -m "$MAX"Follow and download
status
Alias get.
riffads status <generation-id> [--wait] [--timeout <seconds>]| Flag | Short | Default | What it does |
|---|---|---|---|
--wait | -w | off | Block until the job ends |
--timeout <seconds> | 1800 | Stop waiting after this |
- stdout:
queued,rendering,post_processing,completedorfailed. A canceled job readsfailed. - An unsettled charge prints
not settled yet, never0.
batch
riffads batch <batch-group-id> [--wait] [--timeout <seconds>]Same flags as status. stdout: running, completed, failed or partial. Exit code is the worst variant's.
download
riffads download <generation-id> [--output <dir>] [--index <n>] [--force]| Flag | Short | Default | What it does |
|---|---|---|---|
--output <dir> | -o | . | Target folder, created if missing |
--index <n> | all | Only this output | |
--force | -f | off | Overwrite existing files |
for f in $(riffads download gen_8c31fa -o ./out); do ffprobe "$f"; done- Takes an id, never a URL. It fetches fresh signed links itself (links last 600 seconds).
- Absolute paths on stdout, one per line.
- Writes
<file>.part, renames when complete. A file is complete or absent. - Up to 3 attempts per call.
- Existing file without
--force: warning, exit 2. Nothing delivered yet: exit 7.
upload
riffads upload <file> [--content-type <type>]Uploads and finalizes. Only the ast_ id goes to stdout.
| Extensions | Type |
|---|---|
.png, .jpg, .jpeg, .webp, .gif | image |
.mp4, .mov, .webm | video |
.mp3, .wav, .ogg | audio |
Other extensions are refused. --content-type overrides the guess. Size limits: uploads.
riffads generate -c actor_ultra \
--actor-image-asset "$(riffads upload face.png)" \
--voice voc_ada_en --approved-voice-generation "$VOICE" \
--config @brief.json -m "$MAX"Exit 0 only when the file finalizes as usable: true. Otherwise 2.
Workflows
riffads workflows <templates|invoke|run> [...]| Flag | Short | Default | What it does |
|---|---|---|---|
--template <key> | none | With invoke: run a template, not a saved workflow | |
--max-credits <n> | -m | none, required to invoke | Most the whole run may cost |
--input <node.field=value> | -i | none | One input on one node, repeatable |
--wait | -w | off | Follow until done |
--timeout <seconds> | 3600 | Stop following after this |
workflows templates
Lists templates: key, step count, name on stdout. Each input as the -i flag to pass on stderr. No subcommand runs this one.
product_demo 4 steps Product demoThere is no list of saved workflows. Copy the workflow id from the app.
workflows invoke
riffads workflows invoke <workflow-id> -m <n> [-i node.field=value] [--wait]
riffads workflows invoke --template <key> -m <n> [-i node.field=value] [--wait]riffads workflows invoke --template product_demo \
--max-credits 900 \
-i script.text='new sneakers, upbeat' \
--wait --timeout 5400- Exactly one of workflow id or
--template. --inputsplits on the first=and the last.before it. Values coercetrue,falseand numbers. Everything else is a string.- stdout: the run id (
wfr_...).
workflows run
riffads workflows run <workflow-run-id> [--wait] [--timeout <seconds>]Reads a run once, or with --wait checks every 30 seconds. stdout: the run status. stderr: one line per node.
| Run state | Exit |
|---|---|
| Still running at timeout | 5 |
| Partial | 7 |
| Failed or canceled, nothing charged | 6 |
| Failed after some steps charged | 7 |
| Otherwise | 0 |
More: workflows.
Output streams
| Stream | Carries |
|---|---|
| stdout | Data: ids, file paths, asset ids, status words, --json bodies |
| stderr | People: spinner, costs, hints, warnings, errors |
GEN=$(riffads generate -c tts --config '{"script":"hello there"}' --voice voc_ada_en -m 40 --no-wait)
ASSET=$(riffads upload face.png)
STATE=$(riffads status "$GEN")Signed links never print to stdout unless you pass --json.
JSON output
--json prints the response body on stdout. Failures print too:
{
"error": {
"ok": false,
"code": "max_credits_exceeded",
"message": "This generation costs more than the max_credits you set (400 credits). This one needs 462 credits. Raise max_credits, or ask for something cheaper, such as a shorter or lower resolution render.",
"retryable": false,
"credits_charged": 0,
"limit": { "bound_by": "max_credits", "limit_credits": 400, "required_credits": 462 }
}
}generate --json does not pipe straight into jq
The id line prints before the JSON. Split submit from read instead:
GEN=$(riffads generate -c tts --config @brief.json --voice voc_ada_en -m 60 --no-wait)
riffads status "$GEN" --wait --json | jq| Call | JSON body | Has |
|---|---|---|
generate --no-wait --json | Submit body | generation_ids |
generate --json, one output, finished | Wait body plus settled generation | no generation_ids |
generate --json with --variants | Batch body | generations |
status --json | Generation body | |
batch --json | Batch body | generations |
login, logout and download print no JSON on success.
credits_charged is null until the job settles. Never read null as 0.
Exit codes
| Exit | Meaning | Error codes |
|---|---|---|
| 0 | Worked | |
| 1 | Something broke. Tell a human | internal_error |
| 2 | Change the request | invalid_config, capability_unavailable, not_found, moderation_blocked, request_blocked |
| 3 | Fix key, scope, plan or workspace | not_authorized, no_workspace, connection_not_configured, workspace_unavailable, required_plan, read_only_connection, insufficient_scope |
| 4 | Not enough credits, or a spend cap | insufficient_credits, max_credits_exceeded, spend_limit_exceeded, not_priced, quota_exceeded |
| 5 | Refused, safe to send again | input_not_ready, estimate_changed, pricing_unavailable, moderation_unavailable, rate_limited, submission_in_flight, provider_unavailable |
| 6 | Job failed, nothing charged | |
| 7 | Job delivered only part of what was asked | |
| 130 | Ctrl+C. The job was not canceled |
- 6 and 7 are job outcomes. A refused submit never exits 6 or 7, and prints no id.
internal_errorisretryable: trueon the wire but exits 1, so CI does not retry an outage.- Batches exit with the worst variant. Order, least to worst:
0 < 6 < 7 < 5 < 2 < 4 < 3 < 1.
Meaning of each code: errors.
Retries
- Inside a wait, the CLI retries for you: up to 5 in a row, honouring
retry_after_seconds, else 1, 2, 4, 8, 16 seconds. - A submit is never retried.
rate_limited,submission_in_flightandprovider_unavailableon submit exit 5 at once. - One job in flight per key. Use one key per parallel lane.
- Resending after the first job finished starts a second paid job.
- Timeouts cancel nothing. No render time is promised: budget generously, or
--no-waitand follow later.
CI script
#!/usr/bin/env bash
set -uo pipefail
export RIFFADS_API_KEY="${RIFFADS_API_KEY:?set the key}"
export RIFFADS_BASE_URL=https://app.riffads.com
# 1. Check key and host. Exits 3 if wrong.
riffads whoami || exit $?
# 2. Start from a valid example.
riffads capabilities show tts --example > brief.json
jq --arg s "Three reasons these sneakers sold out twice." \
'.script = $s' brief.json > brief.tmp && mv brief.tmp brief.json
VOICE=$(riffads voices --language en --json | jq -r '.voices[0].voice_id') || exit $?
# 3. Estimate. Free.
EST=$(riffads estimate --capability tts --config @brief.json) || exit $?
# 4. max_credits: ceil(estimate * 1.1).
MAX=$(( (EST * 11 + 9) / 10 ))
# 5. Submit and save. stdout: id on line 1, then one path per line.
riffads generate \
--capability tts \
--config @brief.json \
--voice "$VOICE" \
--max-credits "$MAX" \
--output ./out > gen.out
CODE=$?
GEN=$(head -n 1 gen.out)
case "$CODE" in
0) echo "delivered: $GEN" ;;
2) echo "fix the request or the content"; exit 2 ;;
3) echo "fix the key, its scope, the plan or the workspace"; exit 3 ;;
4) echo "top up in the app, or ask for less"; exit 4 ;;
5) echo "retryable: sleep and send the same call again"; exit 5 ;;
6) echo "the job failed and nothing was charged"; exit 6 ;;
7) echo "only part of it arrived"; riffads status "$GEN"; exit 7 ;;
*) echo "broken, tell a human"; exit 1 ;;
esac- No
set -e. It would exit before thecaseruns. - Do not capture
generate -oin a variable. stdout has the id, then paths. Redirect and take line 1. - Derive
MAXevery run. Never hardcode it.
Not in the CLI
- No cancel, logs or publish command.
- No list of past generations or saved workflows. Keep your ids.
- No outbound webhooks.
- No workflow authoring.
- Nothing posts to Meta, TikTok, YouTube or X.