NodaroAccount
Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare
REST API

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

MethodPathWhat it does
GET/v1/node-presetsYour custom presets, newest first. Optional nodeType filter.
GET/v1/node-presets/factoryThe built-in catalog of one node type. nodeType is required. No token needed.
GET/v1/node-presets/factory/groupsThe categories of the built-in catalog of one node type, without the presets. nodeType is required. No token needed.
GET/v1/node-presets/favoritesThe preset ids you starred for one node type. nodeType is required.
GET/v1/node-preset-groupsYour preset folders and sections. Optional nodeType filter.
POST/v1/node-presetsCreate a preset.
PATCH/v1/node-presets/:idRename a preset, replace its data, move it or set its picture.
DELETE/v1/node-presets/:idDelete a preset and its favorite entries.
POST/v1/node-presets/importCreate up to 500 presets at once.
POST/v1/node-presets/reorderSet positions, and move presets between folders.
POST/v1/node-presets/favoritesStar a preset.
DELETE/v1/node-presets/favoritesRemove a star.
POST/v1/node-preset-groupsCreate a folder or a section.
PATCH/v1/node-preset-groups/:idRename or move a folder.
DELETE/v1/node-preset-groups/:idDelete 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"
    }
  ]
}
FieldWhat it holds
idThe preset's uuid.
nodeTypeThe node type the preset belongs to.
name, descriptionThe preset's name and an optional description.
dataThe captured node configuration. This is what you apply.
groupIdThe folder the preset is in, from GET /v1/node-preset-groups, or null.
tags, sortOrderYour tags and the preset's position in its list.
minReferencesHow many connected reference images the preset works from. 0 means none.
needsImagetrue when minReferences is at least 1.
previewUrlThe preset's own copy of the example picture you gave it, or null.
createdAt, updatedAtTimestamps. 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

  • group is 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 }] }:

FieldWhat it holds
groupThe category's name, the same as a preset's group.
shelfThe shelf a picker files the category under: video, marketing, photos, creative, life, pro, or more for a category that no shelf names yet.
countHow many presets the category holds.
previewUrlThe example picture of the category's first preset that has one, or null.
previewUrlsThe 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.

MethodPathQuery or bodyReturns
GET/v1/node-presets/favoritesnodeType (required){ data: string[] }, the most recent first
POST/v1/node-presets/favoritesBody { nodeType, presetId }{ data: { success: true } }. Adding a favorite twice changes nothing.
DELETE/v1/node-presets/favoritesnodeType 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

MethodPathBody or queryWhat 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/:idAny of name, description, data, groupId, tags, sortOrder, previewUrl, and an optional expectedUpdatedAtUpdate the fields you send. previewUrl: null removes the picture.
DELETE/v1/node-presets/:idOptional expectedUpdatedAt query parameterDelete a preset and its favorite entries.
POST/v1/node-presets/import{ presets: [{ nodeType, name, description?, data, previewUrl? }] }, up to 500Create 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/:idNoneDelete 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)".
  • data is 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.
  • groupId must be one of your folders, or the write answers 400 invalid_group.
  • previewUrl must 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 answers 400 validation_error naming the field, for example presets[3].previewUrl on an import.
  • The preset keeps its own copy of the picture. The answer's previewUrl is the copy's address, not the one you sent, so deleting the original from your library never breaks the preset.
  • expectedUpdatedAt makes an update or a delete answer 409 conflict when 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_reached and 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"
}
FieldAccepted values
segmentSecmax, scenes-max (Long) or scenes (Short).
resolution480p, 720p, 1080p or 4k.
renderMethodextend or keyframes.
anchorModeupfront, progressive or none.
citeStylebare or rich.
musicSourcegenerated, original or upload.
promptTiming, textOnly, interactive, anchorGates, musicGatestrue 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

StatusCodeMeaning
400validation_errornodeType is missing where it is required, or a field is invalid.
400invalid_groupgroupId is not one of your folders.
401unauthorizedThe token is missing, invalid or revoked.
403forbiddenA personal token limited to some workflows asked for your own presets.
403insufficient_scopeAn OAuth app token lacks presets:read for a read, or assets:write for a write.
404not_foundThe preset or folder is not yours, or the id is not a uuid.
409name_takenYou already have a preset with that name for this node type.
409conflictThe preset changed since expectedUpdatedAt. Read it again.
409limit_reachedThe account is at its limit of presets, folders or favorites.
429rate_limit_exceededMore 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.

Last updated on

On this page