Presets
Create, update, import, reorder and delete your node presets over REST, read the public built-in catalog with example pictures, and manage preset favorites.
The Presets API reads and writes your node presets, their folders and your favorites, and reads the built-in preset catalog of each node type. A preset is a named node configuration, such as a model, a prompt, an aspect ratio and a quality, that you apply to a node in one step. A preset's data is the captured node configuration: merge it into a node's data to apply it.
The routes work on every edition. They take a bearer token: a personal API token (ndr_…), an OAuth app token, or your session token. An OAuth app token needs presets:read for the reads and assets:write for the writes. The built-in catalog, GET /v1/node-presets/factory, needs no token at all. A personal token limited to some workflows cannot reach your own presets (403 forbidden), but reads the catalog like anyone. See Authentication and Presets for what presets do in the editor.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v1/node-presets | Your custom presets, newest first. Optional nodeType filter. |
GET | /v1/node-presets/factory | The built-in catalog of one node type. nodeType is required. No token needed. |
GET | /v1/node-presets/factory/groups | The categories of the built-in catalog of one node type, without the presets. nodeType is required. No token needed. |
GET | /v1/node-presets/favorites | The preset ids you starred for one node type. nodeType is required. |
GET | /v1/node-preset-groups | Your preset folders and sections. Optional nodeType filter. |
POST | /v1/node-presets | Create a preset. |
PATCH | /v1/node-presets/:id | Rename a preset, replace its data, move it or set its picture. |
DELETE | /v1/node-presets/:id | Delete a preset and its favorite entries. |
POST | /v1/node-presets/import | Create up to 500 presets at once. |
POST | /v1/node-presets/reorder | Set positions, and move presets between folders. |
POST | /v1/node-presets/favorites | Star a preset. |
DELETE | /v1/node-presets/favorites | Remove a star. |
POST | /v1/node-preset-groups | Create a folder or a section. |
PATCH | /v1/node-preset-groups/:id | Rename or move a folder. |
DELETE | /v1/node-preset-groups/:id | Delete a folder. Its presets move out of it, and none are deleted. |
Read your presets
GET /v1/node-presets returns { data: NodePreset[] }, newest first. Pass nodeType, for example generate-image, to list the presets of one node type.
curl "https://app.nodaro.ai/v1/node-presets?nodeType=generate-image" \
-H "Authorization: Bearer $NODARO_API_KEY"import { createClient, StaticTokenAuth } from '@nodaro/sdk'
const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})
const presets = await client.presets.list('generate-image')
const cinematic = presets.find((p) => p.name === 'Cinematic Portrait')
const groups = await client.presets.listGroups('generate-image'){
"data": [
{
"id": "7e2c4a9f-1b3d-4f6a-8c5e-2d9b7a1f3c4e",
"nodeType": "generate-image",
"name": "Cinematic Portrait",
"description": "Warm key light, shallow depth of field",
"data": {
"provider": "nano-banana-pro",
"aspectRatio": "3:4",
"resolution": "2K",
"prompt": "cinematic portrait, warm key light, 85mm, shallow depth of field"
},
"groupId": null,
"tags": ["portrait"],
"sortOrder": 0,
"minReferences": 0,
"needsImage": false,
"previewUrl": null,
"createdAt": "2026-09-14T08:12:40Z",
"updatedAt": "2026-09-14T08:12:40Z"
}
]
}| Field | What it holds |
|---|---|
id | The preset's uuid. |
nodeType | The node type the preset belongs to. |
name, description | The preset's name and an optional description. |
data | The captured node configuration. This is what you apply. |
groupId | The folder the preset is in, from GET /v1/node-preset-groups, or null. |
tags, sortOrder | Your tags and the preset's position in its list. |
minReferences | How many connected reference images the preset works from. 0 means none. |
needsImage | true when minReferences is at least 1. |
previewUrl | The preset's own copy of the example picture you gave it, or null. |
createdAt, updatedAt | Timestamps. Send updatedAt back as expectedUpdatedAt on a write. |
Photos a preset works from
Run a preset whose minReferences is above 0 with that many reference images attached. Without them, the preset's prompt describes a photo that is not there. With presetId on POST /v1/generate-image, send the images as referenceImageUrls.
The Reference Sheet, Cast & Consistency, Edit by Name, Face Privacy, Edits, Portrait Transformations and Stylized Subject presets need one image, and Cast Mega Grid needs two. So does every template whose prompt uses {image:N} tokens: its count is the highest N. A preset of your own is counted the same way, from the {image:N} and {image:N:label} tokens in its prompt, its pre and post text and its negative prompt.
Read the built-in catalog
GET /v1/node-presets/factory?nodeType=generate-image returns { data: [...] }, the presets that ship with Nodaro for that node type. Each entry is { id, name, description?, group?, groupKind?, section?, platforms?, data, minReferences, needsImage, previewUrl, previewSrcSet?, previewWidth?, previewHeight? }. A factory id has the form <node-type>/<name>, for example generate-image/character-board or generate-video/orbit-360.
The catalog needs no token. It is the same for everyone and holds no user data, so a client app can show it to visitors who have not signed in, and ask for a sign-in only when they generate. The answer is sent with Cache-Control: public, max-age=300, and an invalid or expired token sent with the request is ignored.
A factory preset's previewUrl is an example picture on the server you asked, of the form https://<host>/preset-previews/<nodeType>/<slug>.<hash>.webp, or null while the preset has no picture. The file name changes whenever the picture does, so you can cache a URL for good.
# The presets that need a photo, and their example pictures. No token needed.
curl -s "https://app.nodaro.ai/v1/node-presets/factory?nodeType=generate-image" \
| jq '.data[] | select(.needsImage) | {id, minReferences, previewUrl}'const { data } = await client.presets.listFactory('generate-video')
const orbit = data.find((p) => p.id === 'generate-video/orbit-360')Where a factory preset is filed
groupis the folder, such as"YouTube","Shorts · TikTok","Social & Ads","Business","Life & Events","Layout"or"Text".section, when present, is a sub-heading inside the group: a YouTube topic such as"Finance & Crypto", or"Instagram feed".platforms, when present, is where the result is meant to be posted, most fitting first:["TikTok", "Reels", "Shorts"],["LinkedIn"]or["Print", "Etsy"].
A preset that has no sub-heading or no platform, such as a look, a layout or a studio folder, leaves the field out. Group and filter by section and platforms only when they are there.
List the categories only
GET /v1/node-presets/factory/groups?nodeType=generate-image returns the built-in catalog's categories without the presets. The answer is a few KB, for a menu or a shelf that lists the categories. It needs no token. The answer is { data: [{ group, shelf, count, previewUrl, previewUrls }] }:
| Field | What it holds |
|---|---|
group | The category's name, the same as a preset's group. |
shelf | The shelf a picker files the category under: video, marketing, photos, creative, life, pro, or more for a category that no shelf names yet. |
count | How many presets the category holds. |
previewUrl | The example picture of the category's first preset that has one, or null. |
previewUrls | The category's first three example pictures, in catalog order, each once. It is empty when no preset has a picture. |
The categories come in the order a picker shows them: shelf by shelf, and within a shelf, its named categories first in their design order, then the rest in catalog order.
Picture sizes
A factory preset's picture comes in two sizes: the tile picture in previewUrl, 512 px wide, and a full-screen picture up to 1024 px wide, under /preset-previews/1024/…. previewSrcSet names both with their real widths, ready for an <img srcset>, so the browser chooses by the room it has:
<img src="{previewUrl}" srcset="{previewSrcSet}" sizes="(min-width: 900px) 80vw, 100vw" alt="{name}">previewSrcSet is present only on a preset that has both files, and never on a preset of your own. The MCP listing leaves it out.
previewWidth and previewHeight are the tile picture's real width and height. Use them to draw each picture in a box of its own shape before it loads. A preset's data.aspectRatio is what it generates, which is not always its picture's shape. They are present on a factory preset with a picture, and never on a preset of your own.
Apply a preset
To generate one image with a preset, send its id as presetId on POST /v1/generate-image. The server applies the preset the way the editor applies it to a Generate Image node, and the fields you send override it. See Apply a preset on Generate Image.
To apply a preset in a workflow, merge its data into the node's data when you create or update the workflow. Values you set on the node after the merge win.
const node = {
id: 'portrait-1',
type: 'generate-image',
data: { ...cinematic.data, prompt: 'a lighthouse keeper at dawn' },
}A preset can also carry promptPrefix and promptSuffix: text that is added before and after the prompt when the node runs. See Prompt pre and post text.
Over MCP, find a preset with list_node_presets and read it with get_node_preset, or pass its presetId straight to a generation tool such as generate_image. The server then applies the preset, wraps your prompt with the preset's prefix and suffix, and lets any field you pass explicitly override it. See the MCP tools reference.
Favorites
Favorites put starred presets at the top of the editor's preset list. A favorite id is either a factory preset id or a custom preset's uuid. The read needs presets:read from an OAuth app token, and the writes need assets:write.
| Method | Path | Query or body | Returns |
|---|---|---|---|
GET | /v1/node-presets/favorites | nodeType (required) | { data: string[] }, the most recent first |
POST | /v1/node-presets/favorites | Body { nodeType, presetId } | { data: { success: true } }. Adding a favorite twice changes nothing. |
DELETE | /v1/node-presets/favorites | nodeType and presetId (both required) | { data: { success: true } }. Removing a favorite twice changes nothing. |
Factory ids contain a /, so URL-encode presetId in the DELETE query string:
curl -X DELETE "https://app.nodaro.ai/v1/node-presets/favorites?nodeType=generate-image&presetId=generate-image%2Fcharacter-board" \
-H "Authorization: Bearer $NODARO_API_KEY"Create and edit presets
| Method | Path | Body or query | What it does |
|---|---|---|---|
POST | /v1/node-presets | { nodeType, name, description?, data, groupId?, tags?, sortOrder?, previewUrl? } | Create a preset. Answers 201 with the preset. |
PATCH | /v1/node-presets/:id | Any of name, description, data, groupId, tags, sortOrder, previewUrl, and an optional expectedUpdatedAt | Update the fields you send. previewUrl: null removes the picture. |
DELETE | /v1/node-presets/:id | Optional expectedUpdatedAt query parameter | Delete a preset and its favorite entries. |
POST | /v1/node-presets/import | { presets: [{ nodeType, name, description?, data, previewUrl? }] }, up to 500 | Create many at once. Returns { data: { imported } }. |
POST | /v1/node-presets/reorder | { groups?: [{ id, sortOrder }], presets?: [{ id, groupId?, sortOrder }] } | Set positions and move presets between folders. Returns { data: { ok: true } }. |
POST | /v1/node-preset-groups | { nodeType, name, kind?, sortOrder? } | Create a folder (the default, collapsible) or a section (a flat header). |
PATCH | /v1/node-preset-groups/:id | { name?, sortOrder? } | Rename or move a folder. |
DELETE | /v1/node-preset-groups/:id | None | Delete a folder. Its presets move out of it. |
# Save a preset of your own, with one of your images as its picture
curl -s -X POST "https://app.nodaro.ai/v1/node-presets" \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"nodeType": "generate-image", "name": "Studio portrait", "data": {"provider": "gpt-image-2", "aspectRatio": "3:4", "prompt": "{image:1:person}, studio portrait, soft key light"}, "previewUrl": "https://cdn.nodaro.ai/images/<jobId>.png"}'
# Rename it, refusing if it changed since you read it
curl -s -X PATCH "https://app.nodaro.ai/v1/node-presets/<presetId>" \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Studio portrait (warm)", "expectedUpdatedAt": "<updatedAt>"}'const preset = await client.presets.create({
nodeType: 'generate-image',
name: 'Studio portrait',
data: { provider: 'gpt-image-2', aspectRatio: '3:4', prompt: '{image:1:person}, studio portrait' },
})
preset.needsImage // true: the prompt names {image:1}
await client.presets.addFavorite({ nodeType: 'generate-image', presetId: preset.id })The CLI has the same writes: nodaro presets create, update, delete, import, export, favorites, favorite, unfavorite, and nodaro presets groups with create, update and delete. Over MCP, save_node_preset, delete_node_preset and favorite_node_preset manage presets.
Rules for writes
- Names are unique per node type, ignoring case. Create and update answer
409 name_taken. Import keeps both presets and names the newcomer "… (imported 2)". datais at most 64 KB. Runtime keys, such as results, job ids and the node's label, are removed. An imported preset with nothing left is skipped.groupIdmust be one of your folders, or the write answers400 invalid_group.previewUrlmust be one of your own images on Nodaro: an upload or a finished generation result, as png, jpg, webp, gif or avif. The file must already exist, so set it once the job that makes the picture has completed. Anything else answers400 validation_errornaming the field, for examplepresets[3].previewUrlon an import.- The preset keeps its own copy of the picture. The answer's
previewUrlis the copy's address, not the one you sent, so deleting the original from your library never breaks the preset. expectedUpdatedAtmakes an update or a delete answer409 conflictwhen the preset changed since you read it. Read it again before you retry.- A preset or folder that is not yours, or an id that is not a uuid, answers
404 not_found. - Each account keeps at most 5,000 presets, 500 folders and 2,000 favorites. Past that, a create, an import or a favorite answers
409 limit_reachedand writes nothing. An import that does not fit whole imports none. - One reorder moves at most 500 presets and 100 folders from an API key or an OAuth app. More answers
400 validation_error: send the rest in another call. - Writes are limited to 60 a minute per credential, and imports to 10 a minute. Past that, the route answers
429 rate_limit_exceeded.
Recast render presets
The same library stores Recast generation settings under the recast-render namespace. It is not a node type: it saves a complete set of Recast render settings so you can reuse them. The factory entries are read-only, and your own entries stay private to you on every device.
A recast-render preset's data is a strict, complete snapshot:
{
"schemaVersion": 1,
"provider": "seedance-2-5",
"resolution": "480p",
"segmentSec": "max",
"renderMethod": "extend",
"anchorMode": "upfront",
"citeStyle": "bare",
"promptTiming": true,
"textOnly": false,
"interactive": true,
"anchorGates": false,
"musicGates": true,
"musicSource": "generated"
}| Field | Accepted values |
|---|---|
segmentSec | max, scenes-max (Long) or scenes (Short). |
resolution | 480p, 720p, 1080p or 4k. |
renderMethod | extend or keyframes. |
anchorMode | upfront, progressive or none. |
citeStyle | bare or rich. |
musicSource | generated, original or upload. |
promptTiming, textOnly, interactive, anchorGates, musicGates | true or false. |
Write them with the same routes as any preset. They need no photos and carry no picture (minReferences: 0, previewUrl: null). Unknown fields and unknown schema versions are refused on create, update and import. The snapshot never contains source media, cast references, prompts, uploaded tracks, rights confirmations or results. Applying a preset changes the settings and refreshes the price quote; it never starts a generation. An Original or Upload music choice uses the target project's own media. Check the chosen model's current capabilities before you generate.
Errors
| Status | Code | Meaning |
|---|---|---|
400 | validation_error | nodeType is missing where it is required, or a field is invalid. |
400 | invalid_group | groupId is not one of your folders. |
401 | unauthorized | The token is missing, invalid or revoked. |
403 | forbidden | A personal token limited to some workflows asked for your own presets. |
403 | insufficient_scope | An OAuth app token lacks presets:read for a read, or assets:write for a write. |
404 | not_found | The preset or folder is not yours, or the id is not a uuid. |
409 | name_taken | You already have a preset with that name for this node type. |
409 | conflict | The preset changed since expectedUpdatedAt. Read it again. |
409 | limit_reached | The account is at its limit of presets, folders or favorites. |
429 | rate_limit_exceeded | More than 60 writes a minute, or 10 imports a minute. |
Frequently asked questions
Can I create or edit presets with an API token?
Yes. A personal API token, or an OAuth app token with the assets:write scope, can create, update, delete, import and reorder presets, and manage folders and favorites.
Do I need a token to read the built-in preset catalog?
No. GET /v1/node-presets/factory is public. It holds no user data, so an app can show the catalog, with example pictures, to visitors who have not signed in.
How do I apply a preset from code?
Send presetId on POST /v1/generate-image, and the server applies the preset the way the editor does. For a workflow, merge the preset's data object into the node's data.
What does minReferences mean on a preset?
minReferences is how many connected reference images the preset works from, and needsImage is true when that number is at least one. Run such a preset with that many reference images attached.
Which OAuth scopes do the preset routes need?
Reads of your own presets need presets:read, and writes need assets:write. The built-in catalog needs no scope. Personal API tokens and session tokens need no scope.
Related
Presets
Prompt pre and post text
Nodes
Recast
MCP tools reference
Last updated on
Creatures
Create and manage animals and creatures over REST: generate main images, angle, pose and variation variants and motion clips, and give a creature a voice.
Community Library
Browse, search, favorite and clone shared characters, locations and objects from the Community Library over REST, and report a listing for moderation.