# Apps e templates

> Explore e execute apps publicados do Nodaro em TypeScript, leia o histórico de execuções, clone templates de workflow em um projeto e liste os tutoriais.

Source: https://nodaro.ai/pt-BR/docs/developers/sdk/apps-and-templates

**`client.apps`** explora e executa apps publicados: workflows envolvidos em um formulário simples de entradas e saídas. **`client.templates`** explora o marketplace de templates e clona um template em um dos seus projetos, e **`client.tutorials`** lista os vídeos e os workflows de tutorial. Veja [Apps](https://nodaro.ai/docs/concepts/apps) e [Tutoriais e templates](https://nodaro.ai/docs/get-started/tutorials-and-templates) para os conceitos.

## Métodos
| Método | O que faz |
| --- | --- |
| [`apps.list(params?)`](#appslistparams) | Explora os apps publicados |
| [`apps.get(slug)`](#appsgetslug) | Lê um app, com as entradas e saídas dele |
| [`apps.run(slug, inputs?, opts?)`](#appsrunslug-inputs-opts) | Executa um app |
| [`apps.listRuns(slug, params?)`](#appslistrunsslug-params) | Lista as suas execuções de um app |
| [`apps.getRun(slug, runId)`](#appsgetrunslug-runid) | Lê uma execução |
| [`apps.deleteRun(slug, runId)`](#appsdeleterunslug-runid) | Arquiva uma execução |
| [`templates.browse(params?)`](#templatesbrowseparams) | Explora o marketplace de templates |
| [`templates.get(slug)`](#templatesgetslug) | Lê um template, com o workflow dele |
| [`templates.clone(slug, params)`](#templatescloneslug-params) | Copia um template para um projeto |
| [`tutorials.list()`](#tutorialslist) | Lista os tutoriais por categoria |

## client.apps
`list()` e `get()` são públicos. `run()` e o histórico de execuções agem como o usuário conectado que faz a chamada.

### apps.list(params?)
Explora os apps publicados, uma página por vez.

```ts
list(params?: { search?: string; category?: string; limit?: number; cursor?: string }): Promise<{
data: PublishedApp[]
nextCursor?: string | null
}>
```

<TypeTable
type={{
search: { type: 'string', description: "As palavras a buscar." },
category: { type: 'string', description: "Lista apenas esta categoria." },
limit: { type: 'number', description: "O tamanho da página, no máximo 50." },
cursor: { type: 'string', description: "O nextCursor da página anterior." },
}}
/>

```ts
const { data: apps, nextCursor } = await client.apps.list({ search: "headshot", limit: 20 })
```

Um `PublishedApp` tem `id`, `slug`, `name`, `description`, `creatorId`, `creatorName`, `thumbnailUrl`, `category`, `isFeatured`, `runCount`, `createdAt` e `updatedAt`.

### apps.get(slug)
Lê um app, com o `inputSchema`, os campos que os usuários finais preenchem, e `outputs`, o que o app retorna.

```ts
get(slug: string): Promise<{ data: PublishedAppDetail }>
```

<TypeTable
type={{
slug: { type: 'string', required: true, description: "O slug do app." },
}}
/>

```ts
const { data: app } = await client.apps.get("pro-headshot")
console.log(app.inputSchema, app.outputs) // outputs: [{ nodeId, label, type }]
```

### apps.run(slug, inputs?, opts?)
Executa um app e retorna na hora com `{ executionId, status, runId? }`. Consulte periodicamente [`client.executions.get(executionId)`](https://nodaro.ai/docs/developers/sdk/jobs-and-executions) para obter o resultado.

```ts
run(slug: string, inputs?: Record<string, unknown>, opts?: {
inputOverrides?: Record<string, Record<string, unknown>>
}): Promise<{ executionId: string; status: "pending" | "running"; runId?: string }>
```

<TypeTable
type={{
slug: { type: 'string', required: true, description: "O slug do app." },
inputs: { type: 'Record<string, unknown>', description: "Os valores de entrada do app. As chaves correspondem aos nomes dos campos em inputSchema." },
inputOverrides: { type: 'Record<string, Record<string, unknown>>', description: "Configurações brutas de nós, apenas para esta execução, por ID de nó, como { n1: { promptPrefix: '...' } }. Elas alcançam campos que o app não mostra aos usuários." },
}}
/>

```ts
const { executionId } = await client.apps.run("pro-headshot", { photo: photoUrl })

// Add hidden text before the app's prompt, for this run only
await client.apps.run(
"pro-headshot",
{ photo: photoUrl },
{ inputOverrides: { n1: { promptPrefix: "Studio portrait of" } } },
)
```

`inputOverrides` é uma opção avançada. Ela pode definir, por exemplo, o [texto antes e depois](https://nodaro.ai/docs/concepts/prompt-pre-post-text) de um nó (`promptPrefix` e `promptSuffix`). Ela não pode definir o destino de um nó de saída, como a URL de um nó **Saída de webhook** (Webhook Output), uma conta de publicação ou o alvo de um scraper. Uma substituição assim é recusada com `400 locked_field`.

### apps.listRuns(slug, params?)
Lista as suas execuções de um app, uma página por vez.

```ts
listRuns(slug: string, params?: { limit?: number; cursor?: string }): Promise<{ data: AppRun[]; nextCursor?: string | null }>
```

<TypeTable
type={{
slug: { type: 'string', required: true, description: "O slug do app." },
limit: { type: 'number', description: "O tamanho da página." },
cursor: { type: 'string', description: "O nextCursor da página anterior." },
}}
/>

```ts
const { data: runs } = await client.apps.listRuns("pro-headshot", { limit: 10 })
for (const run of runs) console.log(run.status, run.outputs)
```

Um `AppRun` tem `id`, `appSlug`, `executionId`, `status` (`pending`, `running`, `completed`, `failed` ou `cancelled`), `inputs`, `outputs` (cada um no formato `{ nodeId, type, url?, text? }`), `startedAt` e `finishedAt`.

### apps.getRun(slug, runId)
Lê uma execução de um app.

```ts
getRun(slug: string, runId: string): Promise<{ data: AppRun }>
```

<TypeTable
type={{
slug: { type: 'string', required: true, description: "O slug do app." },
runId: { type: 'string', required: true, description: "O ID da execução." },
}}
/>

```ts
const { data: run } = await client.apps.getRun("pro-headshot", runId)
```

### apps.deleteRun(slug, runId)
Arquiva uma execução. Restaurar uma execução e excluí-la de forma permanente só é possível no app do Nodaro, então um script não consegue destruir dados.

```ts
deleteRun(slug: string, runId: string): Promise<{ success: true; archived: true }>
```

<TypeTable
type={{
slug: { type: 'string', required: true, description: "O slug do app." },
runId: { type: 'string', required: true, description: "O ID da execução." },
}}
/>

```ts
await client.apps.deleteRun("pro-headshot", runId)
```

## client.templates
O marketplace de templates, público de propósito: explore-o, leia um template com o workflow inteiro e clone-o no seu projeto sem custo.

### templates.browse(params?)
Explora o marketplace, uma página por vez (`GET /v1/templates/browse`). Não é preciso entrar na conta.

```ts
browse(params?: BrowseTemplatesParams): Promise<{ data: TemplateBrowseCard[]; nextCursor: string | null }>
```

<TypeTable
type={{
search: { type: 'string', description: "Busca de texto completo." },
category: { type: 'string', description: "Lista apenas esta categoria." },
outputType: { type: 'string', description: "Apenas templates que produzem este tipo de saída." },
tag: { type: 'string', description: "Apenas templates com esta tag." },
nodeType: { type: 'string', description: "Apenas templates que usam este tipo de nó." },
provider: { type: 'string', description: "Apenas templates que usam este modelo." },
complexity: { type: 'string', description: "Apenas templates com esta complexidade." },
sort: { type: '"newest" | "popular" | "most-favorited"', default: '"newest"', description: "A ordem." },
limit: { type: 'number', description: "O tamanho da página." },
cursor: { type: 'string', description: "O nextCursor da página anterior." },
}}
/>

```ts
const page = await client.templates.browse({ sort: "popular", search: "trailer" })
```

### templates.get(slug)
Lê um template público com o snapshot completo do workflow: `snapshotNodes`, `snapshotEdges` e `snapshotSettings` (`GET /v1/templates/:slug`).

```ts
get(slug: string): Promise<Template>
```

<TypeTable
type={{
slug: { type: 'string', required: true, description: "O slug do template." },
}}
/>

```ts
const template = await client.templates.get("noir-trailer")
console.log(template.snapshotNodes.length)
```

Lança `NotFoundError` quando o slug é desconhecido ou quando o template não está listado ou está inativo.

### templates.clone(slug, params)
Copia um template para um dos seus projetos como um novo workflow (`POST /v1/templates/:slug/clone`). Não custa créditos.

```ts
clone(slug: string, params: { projectId: string; name?: string }): Promise<{ workflowId: string; projectId: string }>
```

<TypeTable
type={{
slug: { type: 'string', required: true, description: "O slug do template." },
projectId: { type: 'string', required: true, description: "O projeto para onde copiá-lo." },
name: { type: 'string', description: "O nome do novo workflow. O padrão é o nome do template." },
}}
/>

```ts
const { workflowId } = await client.templates.clone("noir-trailer", { projectId })
await client.workflows.run(workflowId)
```

## client.tutorials
### tutorials.list()
Lista todas as categorias de tutorial, com os vídeos e os workflows de tutorial de cada uma (`GET /v1/tutorials`). É público e somente leitura.

```ts
list(): Promise<{ categories: TutorialCategory[] }>
```

```ts
const { categories } = await client.tutorials.list()
for (const category of categories) {
console.log(category.name, category.videos.length, category.flows.length)
}
```

Cada categoria tem `id`, `name`, `slug`, `sortOrder`, `videos` e `flows`. Um vídeo tem um `title`, uma `videoUrl` e uma `thumbnailUrl`. Um flow é um template marcado como tutorial: tem um `title`, uma `complexity`, `estimatedCredits`, os tipos de nó e os modelos que usa, e um `slug` que você pode passar para `templates.get()` e `templates.clone()`.

## Frequently asked questions

### Como executar um app do Nodaro pelo código?

Chame client.apps.run com o slug do app e um objeto inputs cujas chaves correspondam aos campos de entrada do app. O método retorna um executionId; consulte client.executions.get(executionId) periodicamente para obter o resultado.

### Como descobrir as entradas que um app espera?

Chame client.apps.get(slug). O inputSchema lista os campos que os usuários finais preenchem, e outputs lista o que o app retorna.

### Clonar um template custa créditos?

Não. client.templates.clone copia o template para um dos seus projetos sem custo. Você só paga quando executa o novo workflow.

### inputOverrides pode mudar para onde um app envia os resultados?

Não. Uma substituição não pode definir um destino, como a URL de um nó Saída de webhook (Webhook Output) ou uma conta de publicação. A execução é recusada com 400 locked_field.
