Docs do Nodaro
DocumentaçãoReferência de nósModelosAgentes de IA (MCP)DesenvolvedoresSelf-hostingPesquisa
Incorporações

Incorporar um miniapp

Execute um miniapp do Nodaro no seu produto com o código de incorporação ou um formulário próprio na API REST: entradas, execuções, resultados e erros.

Incorporar um miniapp é executar um app do Nodaro já publicado a partir do seu próprio produto. Você pode colar o código de incorporação do app em uma página, ou criar seu próprio formulário sobre a API REST: ler as entradas do app, iniciar uma execução, consultá-la periodicamente e mostrar o resultado. O app em si fica no Nodaro; o seu produto coleta as entradas e exibe as saídas.

Duas formas de incorporar

Código de incorporaçãoSua própria interface
EsforçoColar uma tag iframe.Criar um formulário e uma pequena rota de servidor.
VisualA visualização de app do Nodaro.O seu próprio design.
Em nome de quem a execução é feitaDe quem está vendo a página, com a própria sessão do Nodaro.Da conta por trás do seu token.

Colar o código de incorporação

Abrir as configurações de incorporação do app

No Nodaro, abra Miniapps, escolha Meus miniapps e clique em Incorporar no card do app.

Permitir o seu domínio

Em Domínios permitidos para incorporação, digite a origem do seu site, como https://example.com, e clique em Adicionar. A incorporação fica bloqueada até que pelo menos um domínio seja adicionado.

Copiar o código para a sua página

Clique em Copiar código de incorporação e cole a tag na sua página:

<iframe src="https://app.nodaro.ai/embed/your-app-slug" width="100%" height="600" frameborder="0" allow="clipboard-write"></iframe>

Criar sua própria interface

O restante desta página cria uma interface web ou mobile personalizada para um app publicado, sobre a API REST. O conteúdo é autossuficiente, então você também pode entregá-lo a um gerador de código com IA: adicione .md à URL desta página para obter uma cópia em Markdown.

Três coisas para saber antes

URL basehttps://app.nodaro.ai no Nodaro Cloud, ou o endereço da sua própria instalação.
Identificador do appO slug: a última parte da URL do app publicado. Para https://app.nodaro.ai/app/my-cool-app, o slug é my-cool-app.
Formato da execuçãoAssíncrono. POST /v1/app/{slug}/run responde na hora com um runId, e você consulta GET /v1/app/{slug}/runs/{runId} periodicamente para obter o resultado. A execução não faz callback.

Dois endpoints públicos, que não precisam de token, informam tudo de que você precisa para o formulário:

  • GET /v1/app/{slug} retorna as entradas e os metadados do app.
  • GET /v1/nodes/{type} retorna os campos de um tipo de nó.

Etapa 1: ler as entradas do app

curl https://app.nodaro.ai/v1/app/<slug>

Os campos que importam para a sua interface:

{
  "id": "uuid",
  "name": "Headline Generator",
  "description": "...",
  "iconUrl": "https://...",
  "version": 3,                        // the latest version
  "estimatedCredits": 5,               // the credits one run costs
  "maxRunsPerUserPerDay": null,        // or a number
  "thumbnailNodeId": "node-abc",       // the node whose output is the main result
  "snapshotNodes": [                   // the workflow's nodes
    {
      "id": "node-abc",
      "type": "generate-image",        // use it in step 2
      "data": {
        "prompt": "default prompt",    // the current value of each field
        "aspectRatio": "1:1"
      }
    }
  ],
  "snapshotEdges": [ /* the connections, rarely needed by your interface */ ],
  "snapshotSettings": {
    "presentationSettings": {
      "inputItems": [                  // the form schema
        { "type": "field", "id": "item-1", "nodeId": "node-abc", "field": "prompt", "allowedValues": null },
        { "type": "field", "id": "item-2", "nodeId": "node-abc", "field": "aspectRatio", "allowedValues": ["1:1", "16:9", "9:16"] }
      ]
    }
  },
  "versions": [{ "version": 3, "id": "...", "createdAt": "..." }]
}

inputItems é o formulário que o publicador montou. Percorra a lista, entre em cada group e colete todos os itens field: esse é o seu formulário.

typeRenderize como
fieldUm campo de formulário. Leia nodeId, field e o allowedValues opcional.
nodeO bloco padrão do nó inteiro. Ignore-o em um formulário personalizado, ou renderize todos os campos do nó.
outputUma prévia ao vivo de um resultado. Ignore-a enquanto monta o formulário e mostre-a depois da execução.
richtextMarkdown estático que o publicador escreveu. Renderize-o como está.
groupUm contêiner com os próprios items. Entre nele; os grupos não se aninham.

Etapa 2: descobrir o tipo de cada campo

inputItems fornece pares de nodeId e field, mas não o tipo do campo: texto, número, escolha. Busque o tipo de nó para saber mais:

curl https://app.nodaro.ai/v1/nodes/generate-image
{
  "data": {
    "type": "generate-image",
    "label": "Generate Image",
    "category": "ai-image",
    "description": "...",
    "outputType": "image",
    "providers": ["nano-banana-pro", "gpt-image-2", "flux"],   // shortened
    "capabilities": ["supports-reference-image", "supports-negative-prompt"]
  }
}

Nem todo descritor de nó tem um inputSchema completo. Quando ele estiver ausente, deduza o controle com estas regras, nesta ordem:

  1. O item de entrada tem allowedValues: um select com essas opções.
  2. O valor atual em snapshotNodes[i].data[field] é um booleano: um toggle.
  3. O valor atual é um número: um campo numérico, ou um slider quando o nome do campo termina em duration, intensity, strength, scale ou temperature.
  4. O valor atual é uma string, e o nome do campo é prompt, description, text, content, caption ou message: uma área de texto com várias linhas.
  5. O valor atual é uma URL, e o tipo de nó começa com upload-: um upload de arquivo que envia a URL pública do arquivo.
  6. Qualquer outra coisa: um campo de texto de uma linha.

Estes nomes de campo cobrem a maioria dos campos expostos:

Nome do campoControleObservações
prompt, negativePrompt, text, descriptionÁrea de texto
model, provider, voice, style, toneSelectAs opções vêm de allowedValues ou de providers.
aspectRatioSelectNormalmente 1:1, 16:9, 9:16, 4:3 e 3:4.
resolutionSelectNormalmente 1K, 2K e 4K, ou 720p, 1080p e 4k.
qualitySelectNormalmente medium e high.
duration, nFrames, seed, temperatureNúmero
enableTranslation, addAudio, headless, loopToggle
imageUrl, videoUrl, audioUrl, referenceImageURLFaça o upload do arquivo primeiro e depois envie a URL dele.

Campos de slot do Lottie

Um nó Gráficos animados (Motion Graphics) com o mecanismo Lottie pode expor os slots nomeados dele, como cores, textos e números, como entradas do app. Eles aparecem como itens de campo cujo field começa com slot:, como slot:primaryColor. Os usuários os alteram a cada execução por 0 créditos: quando um app expõe campos de slot, cada execução reaproveita a animação publicada e só troca os valores dos slots.

Valor do slotControleValor a enviar
Uma cor, como um array RGBA de números de 0 a 1Seletor de corUma string hexadecimal, como "#00ff00"
Uma stringCampo de textoA string
Um númeroSliderO número

Um valor de slot não é um campo comum do nó: ele fica em motionPlan.slotValues do nó. Para definir slots por inputOverrides diretos, substitua o motionPlan inteiro do nó por uma cópia do plano publicado cujo slotValues traga as suas alterações, com as cores como arrays RGBA. Um patch parcial de slotValues descartaria o restante do plano. O app do Nodaro e o SDK montam isso para você.

{
  "inputOverrides": {
    "node-mg1": {
      "motionPlan": {
        // ...the published node's motionPlan, unchanged...
        "slotValues": { "primaryColor": [0, 1, 0, 1], "nameText": "Acme Inc." }
      }
    }
  }
}

Etapa 3: montar o formulário

// Adapt to your framework.
type FormField = {
  nodeId: string
  field: string
  label: string                     // "aspectRatio" becomes "Aspect ratio"
  control: "text" | "textarea" | "number" | "toggle" | "select" | "upload"
  options?: Array<string | number | boolean>
  defaultValue: unknown
}

async function buildForm(slug: string, baseUrl: string): Promise<FormField[]> {
  const app = await fetch(`${baseUrl}/v1/app/${slug}`).then((r) => r.json())
  const nodesById = new Map(app.snapshotNodes.map((n) => [n.id, n]))

  const fields: FormField[] = []
  const walk = (items) => {
    for (const it of items ?? []) {
      if (it.type === "group") walk(it.items)
      if (it.type !== "field") continue
      const node = nodesById.get(it.nodeId)
      const current = node?.data?.[it.field]
      fields.push(toFormField(it, node, current))
    }
  }
  walk(app.snapshotSettings?.presentationSettings?.inputItems ?? [])
  return fields
}
  • Use o id do nó como chave dos valores, não o rótulo. O corpo da execução é { "inputOverrides": { "<nodeId>": { "<field>": value } } }.
  • O Nodaro aplica o allowedValues. Um valor fora da lista é recusado com 400 validation_error.
  • Preencha os valores padrão a partir de snapshotNodes[i].data, para que um usuário que não altere nada ainda tenha uma execução útil.
  • Mostre estimatedCredits, para que os usuários saibam o custo antes de executar.
  • Mostre o limite diário quando maxRunsPerUserPerDay estiver definido, como “2 de 5 execuções usadas hoje”, para evitar um 429 inesperado.

Etapa 4: iniciar uma execução e consultá-la periodicamente

Inicie a execução com um token:

curl -X POST https://app.nodaro.ai/v1/app/<slug>/run \
  -H "Authorization: Bearer $NODARO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "inputOverrides": {
      "node-abc": { "prompt": "a cat astronaut", "aspectRatio": "16:9" }
    }
  }'

A resposta é 202 Accepted:

{ "executionId": "exec-uuid", "runId": "run-uuid", "status": "pending" }
Campo opcional do corpoO que faz
versionExecuta uma versão específica do app. O padrão é a mais recente.
inputsAs entradas do app como um mapa plano de nomes de entrada para valores, do jeito que o SDK e a CLI as enviam. inputOverrides é aplicado sobre inputs, campo por campo, e prevalece onde os dois definem o mesmo campo.
runIdVincula a execução a um rascunho de execução que você criou antes. Você pode ignorar este campo em uma primeira versão.

inputOverrides também pode definir campos de nós que o app não expõe, como promptPrefix; veja Texto antes e depois do prompt. Ele nunca pode mudar para onde um nó externo envia dados ou de onde ele os busca, como o endereço de um nó Saída de webhook (Webhook Output): essa execução é recusada com 400 locked_field.

Depois, consulte a execução periodicamente:

curl https://app.nodaro.ai/v1/app/<slug>/runs/<runId> \
  -H "Authorization: Bearer $NODARO_API_TOKEN"
{
  "id": "run-uuid",
  "executionId": "exec-uuid",
  "status": "pending|running|completed|failed",
  "creditsUsed": 0,
  "thumbnailUrl": null,                       // set when the run completes
  "execution": {
    "status": "pending|running|completed|failed",
    "nodeStates": {
      "node-abc": {
        "status": "completed",
        "output": {                           // the shape depends on the node's outputType
          "url": "https://.../result.png",
          "imageUrl": "...",
          "videoUrl": "...",
          "audioUrl": "...",
          "resultUrl": "...",
          "text": "..."
        }
      }
    },
    "totalNodes": 5,
    "completedNodes": 5,
    "failedNodes": 0,
    "totalCreditsUsed": 5,
    "errorMessage": null,
    "completedAt": "2026-05-07T..."
  }
}
  • Consulte periodicamente, mais ou menos a cada 2 segundos. Pare quando execution.status for completed ou failed.
  • Mostre o progresso como completedNodes de totalNodes enquanto a execução estiver em andamento.
  • Em caso de falha, mostre execution.errorMessage.

Etapa 5: mostrar o resultado

O resultado principal é a saída do nó indicado por thumbnailNodeId. Leia a URL dele em execution.nodeStates[thumbnailNodeId].output, tentando estas chaves nesta ordem: url, imageUrl, videoUrl, audioUrl, resultUrl e, por fim, text para um resultado de texto, que você mostra como texto e não como link.

Renderize o resultado de acordo com o outputType do nó, vindo de GET /v1/nodes/{type}:

outputTypeRenderize como
imageUma imagem
videoUm player de vídeo com controles
audioUm player de áudio com controles
textTexto pré-formatado ou Markdown
dataUm visualizador de JSON, ou dados opacos

Quando thumbnailNodeId for null, use o último nó na ordem do workflow, ou mostre todas as saídas que não estejam vazias.

Excluir uma execução

curl -X DELETE https://app.nodaro.ai/v1/app/<slug>/runs/<runId> \
  -H "Authorization: Bearer $NODARO_API_TOKEN"

Uma exclusão move a execução para os itens arquivados do usuário e responde { "success": true, "archived": true }. Nada é destruído: uma automação que exclua a execução errada não consegue perder os dados do usuário. Restaurar uma execução e excluí-la de vez só são possíveis no app do Nodaro, então a sua integração não deve tentar nenhuma das duas coisas.

Autenticação

Toda execução precisa de um token bearer. Escolha o tipo de acordo com quem deve ser dono das execuções e pagar por elas:

Você está criandoUse
Uma ferramenta pessoal, um painel de agência ou uma automação internaUm token de API pessoal.
Um SaaS em que os clientes conectam as próprias contas do NodaroOAuth.
Uma ferramenta pública que executa um app para visitantes anônimosUm token de API pessoal. Quem paga é você, então limite as execuções por visitante do seu lado.

Token de API pessoal. A sua conta é dona de todas as execuções e paga por elas. Crie o token em Configurações › Tokens de API com Criar token, copie-o uma vez e guarde-o como um segredo do servidor. Ele vale até você revogá-lo. Veja Autenticação.

OAuth. A conta de cada usuário é dona das execuções dele e paga por elas. Registre um app de desenvolvedor e envie o usuário à tela de consentimento com os escopos workflows:execute, para iniciar execuções, e jobs:read, para consultá-las periodicamente. No seu servidor, troque o código por um token de acesso que vale 90 dias. Se uma chamada responder 403 insufficient_scope, envie o usuário de novo pela tela de consentimento com os escopos mais amplos.

Manter o token no seu servidor

O token nunca deve chegar ao navegador. Os bundlers embutem toda variável de ambiente com prefixo VITE_ ou NEXT_PUBLIC_ no JavaScript que entregam, onde qualquer pessoa com as ferramentas de desenvolvedor pode lê-la e gastar os seus créditos.

[Browser]  --HTTPS-->  [Your server or edge function]  --HTTPS-->  Nodaro API
                         (holds NODARO_API_TOKEN)
StackOnde o token fica
Lovable com SupabaseUm segredo de Edge Function do Supabase: supabase secrets set NODARO_API_TOKEN=...
Next.jsUm route handler de servidor, com process.env.NODARO_API_TOKEN e sem o prefixo NEXT_PUBLIC_
SvelteKit, Remix, NuxtUma variável de ambiente só do servidor, como $env/static/private no SvelteKit
Edge function da Vercel ou da NetlifyUma variável de ambiente do projeto que não é exposta ao cliente
Cloudflare WorkerUm segredo do Worker: wrangler secret put NODARO_API_TOKEN

Como o código no navegador só se comunica com o seu próprio servidor, você não precisa adicionar o seu domínio às origens permitidas do Nodaro. Se você chamar o Nodaro direto do navegador com OAuth, registre a sua origem em Origens permitidas, no app de desenvolvedor.

Erros

Os erros têm o formato { "error": { "code": "...", "message": "..." } }.

StatusCódigoCausaO que fazer
400validation_errorUm inputOverrides malformado, um valor fora de allowedValues ou um slug malformado.Corrija o corpo da requisição.
400locked_fieldUma substituição indica o destino de um nó externo, como o endereço de uma Saída de webhook.Remova a substituição. O app decide para onde envia e de onde busca.
401unauthorizedO token está ausente, expirado ou revogado.Crie um novo token, ou autorize de novo.
402insufficient_app_creditsA conta por trás do token tem créditos insuficientes.Adicione créditos ou mude de plano.
403insufficient_scopeFalta ao token OAuth um escopo, indicado em missingScope.Autorize de novo com os escopos mais amplos.
404not_foundO slug ou a execução não existe, ou o app está desativado.Confira o slug e mostre “App indisponível”.
429rate_limit_exceededO limite diário de execuções do app por usuário foi atingido.Mostre “Limite diário atingido”.
500internal_errorUma falha no servidor.Tente de novo uma vez depois de uma pausa, e relate o problema se ele persistir.

Templates de referência

Uma Edge Function do Supabase que faz proxy do app

// supabase/functions/nodaro-run/index.ts
import { serve } from "https://deno.land/std@0.224.0/http/server.ts"

const BASE = Deno.env.get("NODARO_BASE_URL")!       // for example https://app.nodaro.ai
const TOKEN = Deno.env.get("NODARO_API_TOKEN")!     // ndr_...
const SLUG = Deno.env.get("NODARO_APP_SLUG")!       // my-cool-app

serve(async (req) => {
  const url = new URL(req.url)

  // Public probe: no token is sent, because the endpoint is public.
  if (req.method === "GET" && url.pathname.endsWith("/schema")) {
    const r = await fetch(`${BASE}/v1/app/${SLUG}`)
    return new Response(await r.text(), { status: r.status, headers: { "content-type": "application/json" } })
  }

  // Start a run.
  if (req.method === "POST" && url.pathname.endsWith("/run")) {
    const { inputs } = await req.json()
    const r = await fetch(`${BASE}/v1/app/${SLUG}/run`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ inputOverrides: inputs }),
    })
    return new Response(await r.text(), { status: r.status, headers: { "content-type": "application/json" } })
  }

  // Poll a run.
  const m = url.pathname.match(/\/runs\/([0-9a-f-]{36})$/)
  if (req.method === "GET" && m) {
    const r = await fetch(`${BASE}/v1/app/${SLUG}/runs/${m[1]}`, {
      headers: { "Authorization": `Bearer ${TOKEN}` },
    })
    return new Response(await r.text(), { status: r.status, headers: { "content-type": "application/json" } })
  }

  return new Response("Not found", { status: 404 })
})

Código do navegador: ler, executar, consultar periodicamente

// 1. On mount: read the schema and build the form (steps 1 to 3).
const schema = await fetch("/functions/v1/nodaro-run/schema").then((r) => r.json())
const inputItems = schema.snapshotSettings?.presentationSettings?.inputItems ?? []

// 2. On submit: send the values keyed by node id.
const start = await fetch("/functions/v1/nodaro-run/run", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    inputs: { "node-abc": { prompt: form.prompt, aspectRatio: form.aspectRatio } },
  }),
}).then((r) => r.json())

// 3. Poll every 2 seconds until the run ends.
let last = start
while (last.status !== "completed" && last.execution?.status !== "completed"
       && last.status !== "failed" && last.execution?.status !== "failed") {
  await new Promise((res) => setTimeout(res, 2000))
  last = await fetch(`/functions/v1/nodaro-run/runs/${start.runId}`).then((r) => r.json())
}

// 4. Read the result.
const out = last.execution?.nodeStates?.[schema.thumbnailNodeId]?.output ?? {}
const heroUrl = out.url ?? out.imageUrl ?? out.videoUrl ?? out.audioUrl ?? out.resultUrl
const heroText = out.text

Neste template, o navegador envia valores com o id do nó como chave, e a edge function os repassa como inputOverrides.

Checklist antes de gerar o código da interface

  • GET {BASE}/v1/app/{slug} retornou o schema de entrada e os valores padrão.
  • Para cada nodeId em inputItems, você sabe o type dele a partir de snapshotNodes.
  • Para cada tipo de nó, GET {BASE}/v1/nodes/{type} informou a categoria, o outputType e os modelos dele.
  • Você montou a lista de campos do formulário com as regras da etapa 2.
  • Você sabe qual é o nó do resultado, thumbnailNodeId ou o último nó, e o outputType dele.
  • Você tem um token de API pessoal ou as credenciais de um app de desenvolvedor.
  • O segredo fica no seu servidor.

Perguntas frequentes

Última atualização

Nesta página