REST API

Setup

Get an API key and make your first video with four HTTP calls.

Read as Markdown

Make your first video from a terminal: make a key, estimate, submit, then wait and download.

Base URL for every call:

Auth header on every call:

Header
Authorization: Bearer sk_live_...
  • Server side only. Never call the API from a browser.
  • You need a workspace on a paid plan or an active trial, plus curl and jq.

Make a key

Owners and admins make keys on the API keys page:

Tick Generate, or every submit fails with 403 insufficient_scope. The key starts with sk_live_ and is shown once, so copy it now.

Terminal
export RIFFADS_API_KEY="sk_live_..."

Estimate

The estimate is free and starts nothing. It returns two numbers:

  • credits: the expected price.
  • max_credits_needed: the number to send as max_credits on the submit.
POST/api/v1/estimatesAPI key
Terminal
REQ='{
  "capability_id": "kling_3_pro",
  "config": {
    "prompt": "Handheld close-up of a matte black water bottle on a gym bench, morning light",
    "aspect_ratio": "9:16",
    "duration": 5,
    "generate_audio": true
  }
}'

EST=$(curl -s -X POST https://app.riffads.com/api/v1/estimates \
  -H "Authorization: Bearer $RIFFADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$REQ")

echo "$EST" | jq '{credits, max_credits_needed}'
MAX=$(echo "$EST" | jq '.max_credits_needed')

This asks Kling 3 Pro for a 5 second vertical clip with sound.

Submit

Send the same request plus max_credits, taken from the estimate. The answer is 201 with a generation_id (gen_...). Save it.

POST/api/v1/generationsAPI key
Terminal
GEN=$(echo "$REQ" | jq --argjson max "$MAX" '. + {max_credits: $max}' \
  | curl -s -X POST https://app.riffads.com/api/v1/generations \
      -H "Authorization: Bearer $RIFFADS_API_KEY" \
      -H "Content-Type: application/json" \
      -d @- \
  | jq -r '.generation_id')

echo "$GEN"

The body is strict: a typo like maxCredits is a 400.

Wait and download

Each wait call blocks up to 20 seconds. Call it again while still_running is true (Wait for the result).

GET/api/v1/generations/{id}/waitAPI key
Terminal
while :; do
  BODY=$(curl -s "https://app.riffads.com/api/v1/generations/$GEN/wait" \
    -H "Authorization: Bearer $RIFFADS_API_KEY")
  [ "$(echo "$BODY" | jq -r '.still_running')" = "true" ] || break
done

echo "$BODY" | jq -r '.generation.status'
URL=$(echo "$BODY" | jq -r '.generation.outputs[0].url // empty')
[ -n "$URL" ] && curl -sL -o ad.mp4 "$URL"
You seeIt means
completedThe file is in ad.mp4
failedThere is no file. Read generation.error
An error objectThe call was refused. See Troubleshooting

Troubleshooting

Every error has code, message and retryable. All codes: Errors.

CodeWhat usually went wrongFix
not_authorizedThe key has quotes or a trailing newline, the header is not Bearer, or the key was revokedSend Authorization: Bearer sk_live_... exactly, with a live key
insufficient_scopeThe key was made without GenerateMake a new key with Generate ticked
max_credits_exceededYou sent the estimate's credits, or a number you pickedSend the estimate's max_credits_needed, or limit.required_credits from the refusal
invalid_configA field name is misspelled, or a value is not allowedThe message names the field. Check it against GET /capabilities/{id}
insufficient_creditsThe wallet is too low for this jobTop up in the app
spend_limit_exceededA limit a person set blocked itlimit.bound_by names which one. See Spend limits
submission_in_flightYou sent a second job while the first is still running on this keyWait for the first one, then submit
moderation_blockedThe script, prompt or file broke the content policyChange the wording or the file. The same one fails again
required_planThe plan does not include the API, or this model needs a higher planUpgrade the plan in the app. When the refusal has required_plan, it names the plan you need

Next

The machine-readable OpenAPI spec for every route:

On this page