Trends
Browse the Nodaro trend catalog, fill a trend's slots with your own pictures, quote the run for free and start it over REST, the SDK, the CLI or MCP.
Available on Nodaro Cloud
The Trends API runs ready-made Cast Swap recipes. A trend is a short clip with slots: the people, pets, products and place you can replace. Fill the slots with your own pictures and run the trend to get the clip back with them in it.
Trends are part of Nodaro Cloud. On a self-hosted install, every route answers 404. The routes take a bearer token. See Authentication.
Only published trends exist here. A draft, an unpublished or an unknown slug is a 404 on every route.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v1/trends | The catalog, in its own order: { data: TrendCard[], next }. Query: q (search), tag, favorites=1 (only yours), locale, cursor, limit (1 to 60, default 30). |
GET | /v1/trends/:slug | One trend: { data: TrendDetail }, with its slots, examples, preview length and resolutions. Query: locale. |
POST | /v1/trends/:slug/estimate | Quote a run. Free. Answers { data: { expectedCredits, holdCredits, breakdown } }. |
POST | /v1/trends/:slug/runs | Start a run. Answers 202 { data: { jobId } }. Follow the job with GET /v1/jobs/:id. |
GET | /v1/trends/runs | Your trend runs, newest first: { data: TrendRun[], next }. Query: trend (a slug), cursor, limit (1 to 60, default 30). |
PUT | /v1/trends/:slug/favorite | Add the trend to your favorites. Answers 204. |
DELETE | /v1/trends/:slug/favorite | Remove the trend from your favorites. Answers 204. |
Names, descriptions, slot labels and hints come in the language of ?locale= when it is a platform language. Otherwise they come in your profile's language, and then in English.
A run stays readable in GET /v1/trends/runs after its trend moves to a new version or is unpublished.
What a trend holds
GET /v1/trends/:slug returns:
slots: each slot'skey,label,kind(person,pet,productorlocation),required, an optionalhintand its default chips.examples: each example's result video and the pictures every slot used.previewSec:4when the trend offers a 4-second preview run, elsenull.resolutions: the resolutions the trend can run at.
A catalog card in GET /v1/trends carries the slug, name, tags, needs (how many slots of each kind), the length in seconds and isFavorite.
The run body
POST /v1/trends/:slug/estimate and POST /v1/trends/:slug/runs take the same body: { slots, example?, details?, preview?, resolution?, draft?, sessionId?, projectId? }.
| Field | What it does |
|---|---|
slots | Required. Slot key → { images?, …chips }, with 1 to 3 images of one subject per slot. Every required slot needs images. |
example | Optional. An example number, from 1. Its pictures fill the optional slots you leave out. The default is 1. |
details | Optional. Your own words, up to 1,000 characters. Mention a slot as {slot:<key>}. |
preview | Optional. true runs 4 seconds, when the trend's previewSec is 4. |
resolution | Optional. "480p", the default, "720p" or "1080p". |
draft | Optional. The default is true. A draft is available at 480p only. |
sessionId | Optional. Accepted and checked, but not applied yet: trend runs are not filed into sessions yet. |
projectId | Optional. Accepted, but not used by the run today. |
The chips are the slot kind's own traits. Each chip is "new", made from your pictures, or "original", kept as in the clip:
| Slot kind | Chips |
|---|---|
person | body, outfit, hair |
pet | body, coat |
product | shape, finish |
location | None |
{
"slots": {
"dancer": { "images": ["https://example.com/me.jpg"], "outfit": "original" }
},
"preview": true,
"resolution": "480p"
}The quote
The estimate answers expectedCredits, what the run is expected to be charged, and holdCredits, what is reserved when the run starts. breakdown lists the parts of the quote.
Repeats
POST /v1/trends/:slug/runs takes an Idempotency-Key header, 8 to 200 characters. A repeat with the same key answers with the first run's job. Nothing new is started or charged.
Permissions
An OAuth app needs:
jobs:readto read the catalog, a trend and your runs;workflows:executeto estimate or start a run, plusassets:writeto pass asessionId;assets:writefor favorites.
Errors
The body of a refusal is { error: { code, message } }. The slot errors also carry the slot's key as slot. The 400 refusals of the estimate and run routes come before anything is charged.
| Status | code | When |
|---|---|---|
400 | validation_error | The body or a query is not valid, the cursor is not one this list gave, or the Idempotency-Key is not 8 to 200 characters. |
400 | missing_slot | A required slot has no images, or an optional slot is left empty and the chosen example has no pictures for it. |
400 | unknown_slot | slots names a key the trend does not have. |
400 | invalid_choice | A chip that the slot's kind does not take, for example a coat on a person or any chip on a location. |
400 | example_out_of_range | example names an example the trend does not have, or an optional slot is left empty and the trend has no example to fill it. |
400 | preview_unavailable | preview: true on a trend that offers no preview. |
400 | invalid_slot_mention | details mentions {slot:<key>} with a key the trend does not have. |
400 | prompt_too_long | details is too long for this trend. Shorten it. |
401 | unauthorized | No authenticated user. |
404 | not_found | The trend is not published, or sessionId names a session you cannot use. |
502 | engine_error | The video engine answered in an unexpected way. |
503 | engine_unsupported | This platform version cannot run trends yet. |
503 | trend_unavailable | The trend cannot be run right now. |
503 | not_available | This server has no trend catalog yet. Every route except GET /v1/trends/runs. |
When the video engine refuses an estimate or a run, the trend routes answer with their own codes and fixed messages:
| Status | code | When |
|---|---|---|
400 | trend_run_rejected | The engine refused these inputs, for a reason not listed below. |
400 | clip_out_of_range, preview_out_of_range, video_unreadable | The trend's own clip cannot be run as it is. |
400 | unsafe_url | A picture is not a public http or https address. |
400 | raw_reference_token | details contains {image:, {video: or {audio:. |
402 | insufficient_credits | Your credits cannot cover the run. In a workspace, the limit that refused it can answer instead: budget_exceeded, member_cap_exceeded or user_allowance_exceeded. |
413 | storage_limit_exceeded | Your storage is full. |
422 | job_blocked | The deployment's job policy blocked the run. |
429 | rate_limited | Too many requests. Try again shortly. |
503 | engine_unsupported, estimate_unavailable | The run cannot be run or quoted right now. Try again shortly. |
502 | engine_error | Anything else. |
Runs that did not finish
A run in GET /v1/trends/runs that did not finish has error: { code, message }. The message is always the same fixed sentence, so branch on the code:
code | Meaning |
|---|---|
safety-block, policy-block | The job was blocked. |
credits | The run ran out of credits partway. It was refunded. Top up, then run it again. |
cancelled | The run was cancelled. |
failed | Any other failure. |
A run that did not fail and was not cancelled has error: null. A completed run carries videoUrl, posterUrl and creditsCharged. Each slot of the run says whether its pictures came from the example (fromExample).
From the SDK
client.trends wraps the routes. It is also importable as @nodaro/sdk/trends.
list(params?: ListTrendsParams): Promise<TrendPage<TrendCard>>
get(slug: string, params?: { locale?: string }): Promise<TrendDetail>
estimate(slug: string, request: TrendRunRequest): Promise<TrendEstimate>
run(slug: string, request: TrendRunRequest, options?: { idempotencyKey?: string }): Promise<{ jobId: string }>
runs(params?: ListTrendRunsParams): Promise<TrendPage<TrendRun>>
favorite(slug: string): Promise<void>
unfavorite(slug: string): Promise<void>list() and runs() resolve to the page as it arrives, { data, next }. Pass next back as cursor; it is null on the last page. get(), estimate() and run() resolve to the payload, without the { data } envelope. "runs" is never a slug: the methods that take a slug throw a TypeError for it before any request.
import type { TrendRunRequest } from "@nodaro/sdk"
const { data: cards } = await client.trends.list({ q: "dance" })
const trend = await client.trends.get(cards[0].slug)
const request: TrendRunRequest = {
// Every required slot needs 1–3 pictures; optional slots left out take the example's.
slots: Object.fromEntries(
trend.slots.filter((s) => s.required).map((s) => [s.key, { images: [myPhotoUrl] }]),
),
}
const quote = await client.trends.estimate(trend.slug, request)
console.log(`about ${quote.expectedCredits} credits, ${quote.holdCredits} held while it runs`)
const { jobId } = await client.trends.run(trend.slug, request, { idempotencyKey: crypto.randomUUID() })
const job = await client.jobs.get(jobId)From the CLI
nodaro trends list --search dance
nodaro trends get <slug>
nodaro trends estimate <slug> --slot dancer=https://example.com/me.jpg --choice dancer.outfit=original
nodaro trends run <slug> --slot dancer=https://example.com/me.jpg --idempotency-key <key>
nodaro trends runs --trend <slug>
nodaro trends favorite <slug>--slot repeats, with 1 to 3 pictures per slot. trends run spends credits, so run trends estimate first. See CLI commands.
From MCP
Four MCP tools cover trends: list_trends, get_trend, estimate_trend and run_trend. See MCP › Video. run_trend without confirm returns the quote and charges nothing. With confirm: true, after the user accepts the price, it starts the run and returns a job id.
Frequently asked questions
What is a trend in Nodaro?
A trend is a ready-made Cast Swap recipe. It is a short clip with slots for the people, pets, products and place you can replace. You fill the slots with your own pictures and get the clip back with them in it.
How do I know what a trend run costs?
Call POST /v1/trends/:slug/estimate with the same body you will run. It is free. It returns expectedCredits, what the run is expected to be charged, and holdCredits, what is reserved while it runs.
Do I have to fill every slot?
No. Every required slot needs 1 to 3 pictures. An optional slot that you leave out takes the pictures of the trend's example, the first one by default.
Can I retry a trend run safely?
Yes. Send the same Idempotency-Key header, 8 to 200 characters. A repeat with the same key answers with the first run's job, and nothing new is started or charged.
Do trends work on a self-hosted install?
No. Trends are part of Nodaro Cloud. On a self-hosted install, every trend route answers 404.
Related
Cast Swap
Jobs
Video
Commands
Last updated on
Cast Swap
Replace the people, pets, products or location in a 4 to 30 second video with your own images over REST, the SDK or MCP, keeping motion and sound.
Studio productions
Read and edit Studio productions over REST: apply atomic operations, generate stills and clips, land finished jobs, plan the export and share or copy a film.