# Apps and templates

> Browse and run published Nodaro apps from TypeScript, read their run history, clone workflow templates into a project, and list the tutorials.

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

**`client.apps`** browses and runs published apps: workflows wrapped in a simple form of inputs and outputs. **`client.templates`** browses the template marketplace and clones a template into one of your projects, and **`client.tutorials`** lists the tutorial videos and tutorial workflows. See [Apps](https://nodaro.ai/docs/concepts/apps) and [Tutorials and templates](https://nodaro.ai/docs/get-started/tutorials-and-templates) for the concepts.

## Methods

| Method | What it does |
| --- | --- |
| [`apps.list(params?)`](#appslistparams) | Browse published apps |
| [`apps.get(slug)`](#appsgetslug) | Read an app with its inputs and outputs |
| [`apps.run(slug, inputs?, opts?)`](#appsrunslug-inputs-opts) | Run an app |
| [`apps.listRuns(slug, params?)`](#appslistrunsslug-params) | List your runs of an app |
| [`apps.getRun(slug, runId)`](#appsgetrunslug-runid) | Read one run |
| [`apps.deleteRun(slug, runId)`](#appsdeleterunslug-runid) | Archive a run |
| [`templates.browse(params?)`](#templatesbrowseparams) | Browse the template marketplace |
| [`templates.get(slug)`](#templatesgetslug) | Read a template with its workflow |
| [`templates.clone(slug, params)`](#templatescloneslug-params) | Copy a template into a project |
| [`tutorials.list()`](#tutorialslist) | List the tutorials by category |

## client.apps

`list()` and `get()` are public. `run()` and the run history act as the signed-in caller.

### apps.list(params?)

Browses published apps, a page at a time.

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

<TypeTable
type={{
search: { type: 'string', description: "Words to search for." },
category: { type: 'string', description: "Only one category." },
limit: { type: 'number', description: "The page size, at most 50." },
cursor: { type: 'string', description: "The nextCursor of the previous page." },
}}
/>

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

A `PublishedApp` has `id`, `slug`, `name`, `description`, `creatorId`, `creatorName`, `thumbnailUrl`, `category`, `isFeatured`, `runCount`, `createdAt` and `updatedAt`.

### apps.get(slug)

Reads one app, with its `inputSchema`, the fields end users fill in, and `outputs`, what it returns.

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

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

```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?)

Runs an app and returns at once with `{ executionId, status, runId? }`. Poll [`client.executions.get(executionId)`](https://nodaro.ai/docs/developers/sdk/jobs-and-executions) for the result.

```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: "The app's slug." },
inputs: { type: 'Record<string, unknown>', description: "The app's input values. The keys match the field names in inputSchema." },
inputOverrides: { type: 'Record<string, Record<string, unknown>>', description: "Raw node settings for this run only, by node id, such as { n1: { promptPrefix: '...' } }. They reach fields the app does not show its users." },
}}
/>

```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` is an advanced option. It can set, for example, a node's [pre and post text](https://nodaro.ai/docs/concepts/prompt-pre-post-text) (`promptPrefix` and `promptSuffix`). It cannot set a destination of an output node, such as a Webhook Output URL, a publishing account or a scraper's target. Such an override is refused with `400 locked_field`.

### apps.listRuns(slug, params?)

Lists your runs of an app, a page at a time.

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

<TypeTable
type={{
slug: { type: 'string', required: true, description: "The app's slug." },
limit: { type: 'number', description: "The page size." },
cursor: { type: 'string', description: "The nextCursor of the previous page." },
}}
/>

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

An `AppRun` has `id`, `appSlug`, `executionId`, `status` (`pending`, `running`, `completed`, `failed` or `cancelled`), `inputs`, `outputs` (each `{ nodeId, type, url?, text? }`), `startedAt` and `finishedAt`.

### apps.getRun(slug, runId)

Reads one run of an app.

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

<TypeTable
type={{
slug: { type: 'string', required: true, description: "The app's slug." },
runId: { type: 'string', required: true, description: "The run id." },
}}
/>

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

### apps.deleteRun(slug, runId)

Archives a run. Restoring a run and deleting it permanently are only available in the Nodaro app, so a script cannot destroy data.

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

<TypeTable
type={{
slug: { type: 'string', required: true, description: "The app's slug." },
runId: { type: 'string', required: true, description: "The run id." },
}}
/>

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

## client.templates

The template marketplace, public by design: browse it, read a template with its whole workflow, and clone it into your project for free.

### templates.browse(params?)

Browses the marketplace, a page at a time (`GET /v1/templates/browse`). No sign-in is needed.

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

<TypeTable
type={{
search: { type: 'string', description: "Full-text search." },
category: { type: 'string', description: "Only one category." },
outputType: { type: 'string', description: "Only templates that produce this kind of output." },
tag: { type: 'string', description: "Only templates with this tag." },
nodeType: { type: 'string', description: "Only templates that use this node type." },
provider: { type: 'string', description: "Only templates that use this model." },
complexity: { type: 'string', description: "Only templates of this complexity." },
sort: { type: '"newest" | "popular" | "most-favorited"', default: '"newest"', description: "The order." },
limit: { type: 'number', description: "The page size." },
cursor: { type: 'string', description: "The nextCursor of the previous page." },
}}
/>

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

### templates.get(slug)

Reads one public template with its full workflow snapshot: `snapshotNodes`, `snapshotEdges` and `snapshotSettings` (`GET /v1/templates/:slug`).

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

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

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

Throws `NotFoundError` when the slug is unknown, unlisted or inactive.

### templates.clone(slug, params)

Copies a template into one of your projects as a new workflow (`POST /v1/templates/:slug/clone`). It costs no credits.

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

<TypeTable
type={{
slug: { type: 'string', required: true, description: "The template's slug." },
projectId: { type: 'string', required: true, description: "The project to copy it into." },
name: { type: 'string', description: "The new workflow's name. The default is the template's name." },
}}
/>

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

## client.tutorials

### tutorials.list()

Lists every tutorial category with its tutorial videos and tutorial workflows (`GET /v1/tutorials`). It is public and read-only.

```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)
}
```

Each category has `id`, `name`, `slug`, `sortOrder`, `videos` and `flows`. A video has a `title`, a `videoUrl` and a `thumbnailUrl`. A flow is a template marked as a tutorial: it has a `title`, a `complexity`, `estimatedCredits`, the node types and models it uses, and a `slug` you can pass to `templates.get()` and `templates.clone()`.

## Frequently asked questions

### How do I run a Nodaro app from code?

Call client.apps.run with the app's slug and an inputs object whose keys match the app's input fields. It returns an executionId; poll client.executions.get(executionId) for the result.

### How do I find the inputs an app expects?

Call client.apps.get(slug). Its inputSchema lists the fields end users fill in, and outputs lists what the app returns.

### Does cloning a template cost credits?

No. client.templates.clone copies the template into one of your projects for free. You pay only when you run the new workflow.

### Can inputOverrides change where an app sends its results?

No. An override cannot set a destination, such as a Webhook Output URL or a publishing account. The run is refused with 400 locked_field.
