Install
openclaw skills install @riffkit/riffkitRiff winning short videos — give one source (a TikTok link, an uploaded video, or an analyzed template) and the backend riffs its emotion formula into your own AI video (post-ready short-form or UGC-style ad creative), with optional digital character, product placement, and language. You riff the formula, not the video. Triggers: the user says 'riff this video', 'turn this TikTok into mine', 'make a video with this product', 'make an ad' / 'make an ad creative' / 'a UGC ad for my product', 'make a promo / marketing video for my app or product', 'remake a viral video', 'generate a short video', 'riff', 'riffkit', or sends a product image / viral link wanting a short video.
openclaw skills install @riffkit/riffkitnode -v)? If riffkit --version fails or shows a version below 0.2.0, run npm i -g @riffkit/cli@latest. Then run riffkit login --agent: it prints the approval link and exits with code 12 while the user has not approved yet (or your command times out). Send the user the link exactly as printed, and run riffkit login --agent again after they say they approved (if a re-run prints a new link, send that one). From then on every Riffkit call is a riffkit command, never curl: each step of The flow names its command, riffkit help lists them all with their routes, and riffkit help <command> shows what a command does and its options.--yes, or an HTTP call that spends) only after the user approved the plan you restated and its quoted price. Never say a video is ready without the finished task's asset_id; hand it over with a link from riffkit get_video_link.curl -fsSL https://riffkit.ai/SKILL.md (save it to a file and read the file if your shell shows only a preview).Core stance: you riff the formula, not the video. Give one winning source; the backend analyzes the emotion formula that hijacks attention and migrates it onto your own content. The footage can be completely different as long as the viewer travels the same psychological path. Or keep the footage: a Swap re-shoots the source's own shots and changes only who or what is in them.
This skill makes short AI videos in exactly three modes: adapt riffs (analyze a source video's emotion formula and regenerate it as your own story), swap riffs (mode=swap: keep the source's shots, cuts and timing, and put your character, product or setting into them) and creation videos (an original video from a written creative direction, no source video). That is the entire product surface. If a user asks for something outside this — a different content format, or a feature this product doesn't have — say plainly that this product only makes riff (adapt / swap) and creation videos; don't call unrelated APIs and don't steer them elsewhere.
No staff/admin features are exposed. This skill covers only what a normal signed-in user can call. Building platform templates by analyzing new sources, publishing/unpublishing platform templates, cross-scope task search, manually granting/clawing back credits — all staff-only. This document never lists them and the agent never calls them.
Output follows the user's input language: reply in English to English, in Simplified Chinese to Chinese; for mixed input, follow the dominant language of the current message.
Always keep verbatim (do not translate): field IDs, commands and API paths, template_type (only pipeline), status enums (queued/running/completed/failed/dead/cancelled), product_visibility values (on_camera/off_camera/no_product), parameter names, the vee_session token.
Speak the app's words, not the tools'. With the user, say Adapt / Swap (改编 / 翻拍), Character (数字角色), Product (产品), Product placement: On-camera / Off-camera (产品植入方式: 入镜植入 / 画外植入), Language (语言), Aspect ratio (比例), Engine (引擎), Resolution (分辨率), Background music (背景音乐), How to render: In one go / Section by section (出片方式: 一次做完 / 一段段做), Video length (视频时长), Creative direction (创意方向) and, in a Swap, "What to change" (要改什么). Don't show a tool or parameter name, a status or step code, or an id unless the user asks.
Each step names its riffkit command; without the CLI, make the HTTP call it stands for (references/api.md, "Commands and routes"). A command that spends credits runs only with --yes.
A conversation follows the Riffkit app's screen, top to bottom. Ask only what the user hasn't said; apply every default silently and show it in the plan (step 6). Names in italics are the app's fields.
0. Which kind of video, then the mode. A link, a template or a video of the user's means a remake of an existing video (riffkit remake_video); only an idea, or a wanted length, means an original video (riffkit create_video: see "Create from an idea"); neither: ask. For a remake the mode comes before everything else, because it decides which questions exist:
Take the mode without asking only when the user says Adapt or Swap, or states the choice itself: "keep every shot", "the same video with me in it" = Swap; "a new story", "new footage, my own script" = Adapt. A request that only names a product, a character, a link or a template fits both modes: ask before any price: Keep the original shots and change who or what is in them (Swap), or keep what made it work and tell your own story (Adapt)? Send mode on every riffkit quote_remake and riffkit remake_video call: left out, the call runs as adapt.
1. Source: the one required input, exactly one.
riffkit list_templates (status analyzed; prefer public and most-used ones), and riffkit get_template to see what one does. Use only one whose analysis_prompt_is_latest is true: any other is refused at the price and at the submit, so offer another, or the original's TikTok link.riffkit quote_remake with it as soon as the mode is known: that reads its length and refuses a link that can't be used.riffkit create_upload_link makes a page where they add it. Give them its url as returned (anyone who holds it can add a video, for up to 90 minutes) and ask them to say when it is in; where the host shows apps, an upload box under the call takes it too and says it is in, for the user. Then riffkit get_upload; once it is ready, its upload_id is the source.riffkit remake_video --video ./clip.mp4, priced with riffkit quote_remake --upload_seconds = the file's length in seconds (without the CLI, send it as video on POST /api/riffs: references/api.md).user_hint may say what made the original work: it guides the analysis and is not the creative direction.max_render_duration in riffkit get_options is refused, never trimmed, and a Swap is exactly as long as its source.2. Who and what.
riffkit list_characters when the user names an account, a persona or a person to put in. One character unless the user asks for several: each is its own video, charged on its own. For a different person in a Swap, recommend a character: one described only in words can change face between shots. Before the price of a Swap with a character, make sure the character's photo is one clear, front-facing photo of one person, or one image with their close-up on the left and full body on the right (ask the user when you can't see it).riffkit list_references, sent as reference_ids; an @ name in the creative direction (mentions) says who wears one, where or when.riffkit list_products, or a new one once the user has confirmed its name and description: riffkit create_product. On camera it shows as many of its images as fit, in order; for particular ones, send product_image_ids.on_camera, the default: a physical thing shown on screen) or Off-camera (off_camera: an app, site or service, only spoken of and captioned). Say which and why in the plan. A Swap has no placement: its product is on camera, and only a product with images counts.riffkit create_image_upload_link makes the page they add it on, as in step 1; riffkit get_image_upload then gives its id.
With the image file on this machine, skip the link: riffkit add_product_image <product_id> --file ./photo.jpg, or riffkit add_reference --file ./outfit.jpg.3. Output.
default_video_pairs in riffkit get_options; offer only what video_backends there doesn't mark locked or list in locked_resolutions, and never mention plans; 480p is draft quality), and Background music, only for a template whose bgm_status is active or policy_violation: the original music by default, or, when it is active, a new track in its style (bgm_mode = source_ref).swap_recommended_video_backend: name it only when it isn't locked. Language, aspect ratio and sound are the source's: don't ask. A user who wants another language or a new story wants Adapt.delivery sections only when the user asks to see the start first (the first section now, the rest later on the same script). When staged_delivery.forced in riffkit get_options is sections, the account is held to the first section: each video is made in sections and only its first can be made, or made again. Say so in the plan; quote that section only.4. Creative direction (content_anchor), asked last.
5. Price, before the user decides. Call riffkit quote_remake with exactly what you will submit (mode, the source, engine and resolution, n_characters, in Adapt n_ratios, and delivery), and again whenever one of them changes; its description says how to read the answer. In sections: the first section's price, and about the whole (sections_estimate) unless held to the first section. locked true: this account can't submit that pair, so offer one that isn't locked (step 3). When fits is false, say the price and the balance and offer one way out: the first of fitting_templates (submitted with its own engine and resolution), else the most expensive pair in alternatives that still fits. An exact price that doesn't fit is not submitted; an approximate one may be, because the server checks again before anything is charged.
6. The plan and the go-ahead: the one confirmation before anything is charged. Restate every choice, defaults in words, in the app's order, then ask "Submit?".
7. Submit. riffkit remake_video with exactly what was quoted; keep its batch_id. When an answer is lost or unclear, look in riffkit list_tasks before submitting again.
8. Progress. After the submit, call riffkit get_batch once and say the video has started, usually takes 3 to 8 minutes and keeps going if the user leaves. If you can wait, check again no faster than every 30 seconds, for up to 15 minutes; if you can't, stop there and check when the user next asks. Never call it back to back.
With the CLI, riffkit wait <batch_id> does the waiting: it follows the batch for up to 9 minutes; exit code 10 = still running (run it again), 11 = it finished but a task made no video (read that task's error and result).
A new link or video shows two tasks: the analysis, then the video. Each extra aspect ratio appears as a further task once the first video finishes: the batch is done when every ratio submitted has its video. Queued over 2 minutes: the servers are busy and it starts by itself. A Swap on a Seedance engine first waits for a content review of the source, several minutes the first time. Before you call it done, check that a video came out (see "When something goes wrong").
9. Delivery. The finished task's result names the video (asset_id). Give the user its link from riffkit get_video_link, exactly as returned: say how long it works (seconds_valid), that anyone who holds it can open the video, that you get a fresh one whenever they ask, and that the video is also in their Riffkit Library.
On the free plan a finished video carries a small Riffkit watermark in its top-left corner, and the link says so (watermarked: true). Say it once when you hand the video over; any plan removes it from every video, the ones already made included.
To save the file itself: riffkit download <asset_id> (over HTTP the file needs the session cookie: references/details.md, "Delivery and files").
Then, from riffkit list_videos: its caption and asset_hashtags (the post text to publish with it); in a sentence or two, what was kept from the source and what the direction changed; what to try next time. A video made in sections: say which part is ready and, unless held to the first section (step 3), that the rest can be made later on the same script until staged.finish_by.
10. Next, on the finished video, in the app's order: the rest of a video made in sections (not when held to the first section), more aspect ratios, then caption fixes.
Create from an idea (riffkit create_video; no source, no mode). The app's order: Character, Reference images, Product, Product placement as in step 2 (On-camera needs a product with images) → Video length: fixed, 15 seconds by default (duration_mode = fixed, duration_seconds 4 to 45), or Auto length (duration_mode = smart: the engine decides, up to 45 seconds and to what the balance covers). Always send duration_mode, and duration_seconds with fixed: left out, the call runs as Auto length. A fixed length over one render (15 seconds; 30 on Seedance 2.5) is made in parts and the joins can show: say so → Language, Aspect ratio (one per video), Engine, Resolution and how to render as in step 3 → Creative direction, last and required: it is the whole script, so draft the scene, the spoken lines, the captions and the pacing with the user → the price: riffkit quote_create with the length, engine, resolution, number of characters and delivery you will submit, and again whenever one changes. locked true: offer a pair that isn't locked. fits false: say the price and the balance, and offer a shorter fixed length or a pair that costs less → the plan in that order, ending on length and price, "Submit?", and steps 7 to 10 with riffkit create_video.
Continue a video made in sections (its staged.pending is true in riffkit list_videos): Next section, Finish the rest or Redo this section (action next, rest, redo); held to the first section (step 3): only Redo this section. Say that one's price from staged.prices_shown and get the go-ahead, then riffkit continue_video with its staged.prices figure as credits and staged.delivered_through as delivered_through (left out, nothing starts); 409 price_changed: ask again at the price it gives. Each run is a new version of the same video: give a fresh link (riffkit get_video_link) when done.
Add aspect ratios to a finished vertical video (an Adapt, Create or Swap one; a video made in sections once all of it is made): the video (riffkit list_videos) → riffkit quote_ratios before you offer anything (the ratios it has, and the exact price of one more) → ask which ratios the user wants, from 9:16, 3:4, 1:1 and 4:5 less those that exist: never pick for them. Engine and resolution are the original's, and each ratio is its own charged video with the same shots, sound and on-screen text in a new frame → the plan (how many videos, the price of each, the total), "Submit?" → riffkit add_ratios, always with video_ratios = the ratios picked: left out, it makes a 9:16 video → say what was submitted and what was skipped and why, then steps 8 and 9.
Edit captions (free; a video made in sections once all of it is made): the burned-in lines the app calls "Subtitles" ("Caption & hashtags" is the post text). Read with riffkit get_subtitles (a 404 that mentions reconcile: riffkit reconcile_subtitles first, even when you only mean to add lines) → change only what was asked → riffkit save_subtitles with the complete list → riffkit burn_subtitles once, after all edits: it makes a new version of the same video, so when its task is done give the user a fresh link (riffkit get_video_link) to check it → undo with riffkit reset_subtitles, then render again. Text that is part of the picture itself, such as a Swap's original on-screen text, is in no list: it can't be changed, and typing it again would show it twice. To change it, make a new Swap and say so in What to change.
Before the burn, riffkit preview_subtitles renders a free preview (references/api.md, "Subtitle editing").
When something goes wrong
insufficient_credits): nothing was submitted. Say both numbers (required_credits_shown and available_credits_shown) and the one way out of step 5 (for a creation, a shorter fixed length); don't send the same request again.result.auto_generate_error made no video, even if that task reads completed: never report success. insufficient_credits: once the balance covers it, riffkit retry_task on that task goes on with the options it was submitted with (it spends: ask first). A swap code or source_video_too_long: nothing was charged; say what to fix.subscription_required): nothing was submitted. A locked engine or resolution: offer the same video with engine and resolution left out, or a pair that isn't locked. riffkit analyze_template: offer riffkit remake_video with the link. A product over the account's limit: use an existing one. Continuing a video: see step 3.detail is a sentence, or carries a message, that says why. Say the reason in the user's language, then the way forward. A character's photo in review (reviewing): try again in a minute; rejected (rejected): another character (the photo is changed in the web app). A Swap with nothing to change, or no person to replace: ask what should change. A Swap task that fails with a Chinese sentence naming seconds of the original and a code in brackets was refused by the content review of the source, before any charge: say it in the user's language, keep the code, and offer another source (the same one fails again).error in plain words. An error with "[1026]" or "input text sensitive": the engine refused the written direction before rendering, and a retry fails the same way, so rewrite the direction and submit again. One with "PolicyViolation" or "SensitiveContentDetected": the result was blocked as possibly copyrighted, so change the music, the direction or the source; for a Swap, offer a source the user shot or has the rights to. A run that made no video costs nothing; seconds already rendered stay charged. Before riffkit retry_task, say what it can cost: up to the full video's price (the quote for the same arguments).server_busy = nothing was created; wait 30 seconds, submit once more. Too many submits: wait a minute, submit once. A daily limit: pass the message on (its numbers are already credits) and stop until 00:00 UTC. "free_cost_limit" on a new link: offer an analyzed template. Never loop.*_shown fields (alternatives and fitting_templates too), as credits: credits_shown 1000 is "1,000 credits". Never turn credits into money. A Swap or an added ratio costs more per second than an Adapt or creation video on the same engine. On MiniMax H3 each reference image past 5 in one render is charged too, and quotes cover seconds only. riffkit cancel_task only when the user asks: what is already rendered stays charged.
Every error and how to handle it: references/errors.md.Beyond the three hard stops:
riffkit quote_remake, riffkit quote_create, riffkit quote_ratios and, to continue a video, staged.prices_shown, and the balance (riffkit get_credits) comes up only when a quote doesn't fit, on a 402, before a retry, or when the user askstopup_url verbatim, no retry, no silent failureWho does what. You judge and edit: pick the source, draft content_anchor and user_hint, fix subtitles, and decide whether a result does its job. The server measures and generates: the analysis, subtitle timing (align_status) and the video; read what it reports instead of guessing it. Converge: once the video does its job, deliver it and name what's still imperfect (a caption a beat late) instead of spending more renders chasing zero flaws.
The agent acts on the user's behalf and must be conservative, transparent, reversible:
/api/riffs, if suspicious (non-standard TikTok domain, possible phishing), gets a confirmation prompt first.Proactively flag anomalies (an undocumented error code / an internal field that shouldn't be exposed / the same task failing after 2 retries / balance dropping >10% in a minute for no reason).
The rest of this skill is in these files, in the references/ folder next to this file, or online at the address after each. Read one when the step you are on needs it.