> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent templates

> Agent template routes — create and publish versions, read the catalog and manifest, install onto an agent, deploy a new agent, and uninstall

These routes publish and install [agent templates](/concepts/agent-templates), which freeze a whole agent so it can be installed on other agents or deployed as a new one: create a template, publish and inspect versions, read the catalog and the served manifest, install a version onto an existing agent, deploy a fresh agent in one call, preview an update, and uninstall. Rolling a version out to installed agents, the installs ledger, health, outcomes, and evals are on [Template fleet](/reference/rest/template-fleet); single skills listed on the marketplace are on [Marketplace skills](/reference/rest/marketplace-skills). The CLI twin is [`lua marketplace template`](/reference/cli/marketplace).

*Verified against lua-cli 3.33.0.*

## Base URL and authentication

Writes check `automations:write`, resolved against the template's creator organization, the template's source agent, or the target agent, as noted per route; reads check `marketplace:read` the same way. Ownership transfers check `org:manage`. Catalog reads need only an authenticated key and are visibility-filtered: a private template is visible only to its creator organization and answers `404` to anyone else. The host, the bearer header, the error envelope, and the catalog rate limits are on the [REST API overview](/reference/rest/overview).

## Template object

A template object carries `id, name, displayName, description, creatorId, creatorOrgId, sourceAgentId?, sourceBaseAgentId?, visibility, listed, latestVersion, latestApprovedVersion?, installCount, activeInstallCount?, workflowCount?, hasScheduledWorkflow?, hasJobTier?, hasScriptForm?, workspaceBackend?, firstParty?, uses?, createdAt, updatedAt`. `sourceBaseAgentId` is the base the source agent was created from and decides whether the template can be deployed as a new agent; the deploy route states the rule.

## Template version object

A version object carries `id, templateId, version, content?, envContract?, changelog?, sourceAgentVersion?, createdBy, createdAt, contentHash?, codeHash?, consentSurfaceHash?, approvalStatus?, approvalNotes?`, the authored sections including `installPolicy?`, `deprecated?`, and `warnings?`. The authored sections (`connections`, `personaTemplate`, `triggerPresets`, `paramsMeta`, `onInstall`, `onUninstall`, `installPolicy`) are documented on the [template manifest](/marketplace/manifest) and [lifecycle hooks](/marketplace/lifecycle-hooks) pages. The version object's `contentHash` is the publish-time digest; the install seal is the manifest's `contentHash`.

## Endpoints

### POST /marketplace/templates

Creates a template. Scope `automations:write` on `sourceAgentId` or on `creatorOrgId`; send exactly one of the two. Omit `sourceAgentId` for a declarative template composed from marketplace skills, and then `creatorOrgId` is required. Equivalent: `lua marketplace template create`.

<ParamField body="sourceAgentId" type="string">The source agent whose agent versions the template freezes.</ParamField>
<ParamField body="creatorOrgId" type="string">Creator organization, for a declarative template.</ParamField>
<ParamField body="name" type="string" required>Template name.</ParamField>
<ParamField body="displayName" type="string" required>Display name.</ParamField>
<ParamField body="description" type="string">Description.</ParamField>
<ParamField body="visibility" type="string">`public` or `private`.</ParamField>

**Response**

`201` with the [template object](#template-object).

### POST /marketplace/templates/:templateId/versions

Publishes a version: freezes the source agent's active [agent version](/reference/rest/versions), or `sourceAgentVersion`, into a new template version. Scope `automations:write`, creator organization or source agent. Equivalent: `lua marketplace template publish`. An authored section that is omitted inherits the previous version's value; an empty array, empty object, or `null` clears it.

<ParamField body="sourceAgentVersion" type="number">Agent version to freeze. Defaults to the active version.</ParamField>
<ParamField body="changelog" type="string">What changed in this version.</ParamField>
<ParamField body="envContract" type="object">Env contract, keyed by variable name; each `{ description, required, example? }`.</ParamField>
<ParamField body="declaredTriggers" type="object[]">Triggers the version declares.</ParamField>
<ParamField body="marketplaceSkills" type="object[]">Marketplace skills a declarative template composes. At most 50.</ParamField>
<ParamField body="connections" type="object[]">Authored section; see the [template manifest](/marketplace/manifest).</ParamField>
<ParamField body="triggerPresets" type="object">Authored section; see the template manifest.</ParamField>
<ParamField body="personaTemplate" type="object">Authored section; see the template manifest.</ParamField>
<ParamField body="paramsMeta" type="object">Authored section; see the template manifest.</ParamField>
<ParamField body="onInstall" type="object">Authored section; see [lifecycle hooks](/marketplace/lifecycle-hooks).</ParamField>
<ParamField body="onUninstall" type="object">Authored section; see lifecycle hooks.</ParamField>
<ParamField body="installPolicy" type="object">`{ perWorkspace: "single" | "multiple" }`. `single` caps the template at one install per organization.</ParamField>
<ParamField body="outcomes" type="object">Outcome units the version records.</ParamField>
<ParamField body="channels" type="object[]">Channels the version declares.</ParamField>
<ParamField body="features" type="object[]">Features the version declares.</ParamField>
<ParamField body="referenceEvalRunId" type="string">Eval run to attach as the reference.</ParamField>
<ParamField body="skipAutoApply" type="boolean">Do not fan the publish out to installs that consented to creator updates.</ParamField>

**Response**

`201` with the [template version object](#template-version-object), plus `autoApplyRunId` and `autoApplyTargetCount` when the publish fanned out to installs that consented to creator updates. Poll that run on [Template fleet](/reference/rest/template-fleet).

### POST /templates/:templateId/versions/draft

Composes a proposed `template:` section for `lua.skill.yaml` from the frozen source and never persists it. Scope `automations:write`, creator organization or source agent. Equivalent: `lua marketplace template draft`.

<ParamField body="force" type="boolean">Replace existing authored sections wholesale instead of merging additively.</ParamField>
<ParamField body="aiAssist" type="boolean">Add advisory copy proposals from the model; they are never merged automatically.</ParamField>
<ParamField body="sourceAgentVersion" type="number">Agent version to draft from. Defaults to the active version.</ParamField>

**Response**

`201` with `{ templateId, draft, diff, proposals?, warnings? }`, where `warnings` lists pinned workflows or jobs that no longer exist on the source agent (`snapshot-workflow-missing`, `snapshot-job-missing`).

### PATCH /marketplace/templates/:templateId

Updates the template's listing text. Scope `automations:write`, creator organization.

<ParamField body="displayName" type="string">New display name.</ParamField>
<ParamField body="description" type="string">New description.</ParamField>

**Response**

`200`.

### PUT /marketplace/templates/:templateId/visibility

Sets the template's visibility. Scope `automations:write`, creator organization. Flipping private to public enqueues a review.

<ParamField body="visibility" type="string" required>`public` or `private`.</ParamField>

**Response**

`200`.

### PUT /marketplace/templates/:templateId/unlist

Unlists the template. Scope `automations:write`, creator organization or source agent.

**Response**

`200`.

### PUT /templates/:templateId/versions/:version/deprecate

Deprecates one version, which blocks new installs of that version only. Scope `automations:write`, creator organization.

<ParamField body="reason" type="string">Why the version is deprecated.</ParamField>

**Response**

`200`.

### PUT /templates/:templateId/owner

Transfers the template to another organization. Scope `org:manage` on both organizations.

<ParamField body="newOrgId" type="string" required>The receiving organization.</ParamField>

**Response**

`200`.

### GET /marketplace/templates

Lists the catalog. Any authenticated key; visibility-filtered.

<ParamField query="page" type="number">Page number.</ParamField>
<ParamField query="limit" type="number">Page size.</ParamField>
<ParamField query="firstParty" type="boolean">`true` filters to templates published by Lua.</ParamField>

**Response**

`200` with `{ templates, total }`, each entry a [template object](#template-object).

### GET /marketplace/templates/:templateId

Returns one template. Visibility-filtered; a scoped key needs `marketplace:read`. Equivalent: `lua marketplace template view`.

**Response**

`200` with the [template object](#template-object).

### GET /marketplace/templates/:templateId/versions

Lists a template's versions. Same access as the template read. Equivalent: `lua marketplace template versions`.

<ParamField query="page" type="number">Page number.</ParamField>
<ParamField query="limit" type="number">Page size.</ParamField>

**Response**

`200` with [template version objects](#template-version-object).

### GET /marketplace/templates/:templateId/versions/:version

Returns one version. Same access as the template read. Equivalent: `lua marketplace template view --version <n>`.

**Response**

`200` with the [template version object](#template-version-object).

### GET /marketplace/templates/:templateId/manifest

Returns the manifest of the latest version: the safe requirements view with no source code. Same access as the template read.

<ParamField query="agentId" type="string">Agent to reconcile against; adds `reconciliation` to the response.</ParamField>
<ParamField query="orgId" type="string">Organization to reconcile against instead of an agent.</ParamField>

**Response**

`200` with `{ id, name, displayName, description, version, latestVersion, visibility, contentHash?, agent: { model? }, skills[], connections[], triggers[], params[], workflows?[], personaTemplate?, channels?, features?, onInstall?, onUninstall?, installPolicy?, latestApprovedVersion?, installable?, deprecated?, reconciliation, policy? }`.

<ResponseField name="workflows" type="object[]">Present when the version froze a workflow. Each `{ key, displayName, description?, version, form: "graph" | "script", stepCount, hasApproval, hasSchedule, hasSignalWait, hasJobTier, jobSecondsMax?, inputSchema? }`.</ResponseField>
<ResponseField name="installPolicy" type="object">Present when the version declares one: `{ perWorkspace: "single" | "multiple" }`.</ResponseField>
<ResponseField name="reconciliation" type="object">Per connection requirement, whether the agent or organization in the query already satisfies it (`satisfied`, `multiple-matches`, `insufficient-scope`, `verify-access`, `unmet-required`, `unmet-optional`) and which existing connections are candidates.</ResponseField>
<ResponseField name="contentHash" type="string">The install seal: echo it as `contentHash` on install or deploy. The version object's `contentHash` is the publish-time digest and is not the seal.</ResponseField>

### GET /marketplace/templates/:templateId/versions/:version/manifest

Returns the manifest of one version, with the same query parameters and shape as the latest-version manifest. Same access as the template read.

### GET /marketplace/templates/:templateId/review

Returns the review status of every version, newest first. Scope `marketplace:read`, creator organization or source agent. Resubmitting for review means publishing a new version.

**Response**

`200` with `{ templateId, visibility, latestApprovedVersion?, versions: [{ version, approvalStatus?, approvalNotes?, approvedBy?, approvedAt?, provenanceAlerts?, holdout? }] }`.

### GET /templates/:templateId/eligible-targets

Lists the caller's own agents and whether each can take the template. Any authenticated key.

**Response**

`200` with `[{ agentId, name, orgId, installable, reason? }]`; `reason` is one of `already-installed`, `shared-agent-requires-org-admin`, `apply-in-progress`.

### POST /marketplace/templates/:templateId/install/:agentId

Applies a template version to an existing agent and answers synchronously with the [install result](#install-result). Scope `automations:write` on the agent. Equivalent: `lua marketplace template install`. `skipEnvCheck` is not accepted: unknown body fields are stripped, and the CLI's `--skip-env-check` therefore has no server-side effect. A refused install leaves no install record.

<ParamField body="version" type="number">Template version. Defaults to latest.</ParamField>
<ParamField body="contentHash" type="string">The manifest's `contentHash`, echoed verbatim. When the template changed in between, the install refuses with `409` `CONTENT_HASH_MISMATCH`. Omit to skip the seal.</ParamField>
<ParamField body="envValues" type="object">Env values written before validation, keyed by env contract key.</ParamField>
<ParamField body="connectionSelections" type="object">Capability key to `{ type, connectionId }`: which existing connection satisfies each requirement. Re-derived server-side.</ParamField>
<ParamField body="triggerOverrides" type="object">Trigger key to `{ enabled?, params?, input? }`. On a fresh install this is the sole source of the armed set: keys you omit are installed disarmed. On a re-install, keys you send win and omitted keys keep their current state. `params` sets declared editable schedule paths such as `preset.seconds`; `input` sets declared workflow-trigger input paths.</ParamField>
<ParamField body="personaVars" type="object">Answers for the persona template's declared variables, keyed by name.</ParamField>
<ParamField body="editedPersona" type="string | object">Installer-edited persona; legal only when the persona template is `editable`. Stored verbatim and never re-expanded.</ParamField>
<ParamField body="overwritePersona" type="boolean">Opt in to replacing a persona the agent already has.</ParamField>
<ParamField body="allowCreatorUpdates" type="boolean">Consent to the creator pushing future versions onto this install.</ParamField>
<ParamField body="acceptModifiedPrimitives" type="string[]">`kind/key` entries the update may overwrite even though they were modified since the last install (the answer to `MODIFIED_SINCE_INSTALL`).</ParamField>
<ParamField body="acceptWithdrawnParams" type="string[]">`key.path` entries whose stored value you accept dropping because the new version no longer declares them editable.</ParamField>
<ParamField body="migrateRuns" type="object">Move parked workflow runs onto the installed version: `{ strategy: "resume-on-new" | "cancel-and-reseed", keys?, onIncompatible?: "leave" | "cancel", reason?, include?: "suspended" | "all" }`. Installing organization only.</ParamField>
<ParamField body="model" type="string">Model code for the agent; `""` selects the default model instead of a specific one. Maximum 200 characters.</ParamField>

**Response**

`201` with the [install result](#install-result).

### POST /marketplace/templates/:templateId/deploy

Creates a fresh agent in your organization and installs the template on it in one call. The scope is checked on the target organization. Deploy is legal only for templates whose source is a base agent, that is, an agent created from the standard base rather than a Space or a catalog integration agent; the template object records this as `sourceBaseAgentId` equal to `baseAgent`, and any other source answers `409` `TEMPLATE_NOT_DEPLOYABLE`.

<ParamField header="Idempotency-Key" type="string" required>`[A-Za-z0-9_-]`, 16 to 128 characters. A retry with the same key returns the original outcome and never creates a second agent.</ParamField>
<ParamField body="version" type="number">Template version. Defaults to the latest approved version, or the latest version when none is approved.</ParamField>
<ParamField body="contentHash" type="string">The manifest's `contentHash`; same meaning as on install.</ParamField>
<ParamField body="agentDisplayName" type="string">Name for the new agent. Maximum 120 characters.</ParamField>
<ParamField body="orgId" type="string">Organization to create the agent in. Defaults to your default organization; a caller with no organization gets a personal one created.</ParamField>
<ParamField body="envValues, connectionSelections, triggerOverrides, personaVars, editedPersona, allowCreatorUpdates, acceptModifiedPrimitives, acceptWithdrawnParams, model">Same meaning as on install.</ParamField>

**Response**

`201` with the [deploy result](#deploy-result) when the deploy finishes within 10 seconds; otherwise `202` with `{ deployId }`, to poll on the deploys route.

### GET /marketplace/templates/deploys/:deployId

Polls a deploy that answered `202`. Scope `marketplace:read`, resolved from the deploy.

**Response**

`200` with `{ deployId, status: "running" | "succeeded" | "failed" | "rolled_back" | "rollback_pending", stage?, stages?, result?, error? }`; `result` is the [deploy result](#deploy-result) and `error` uses the envelope under [Errors](#errors).

### GET /marketplace/templates/:templateId/deploy-session

Returns the caller's deploy session for one template version: the draft answers and per-connection connect progress a wizard stores so it can resume. Signed-in user; the caller's own session only.

<ParamField query="version" type="number">Template version the session belongs to.</ParamField>

**Response**

`200` with `{ session, copy }`.

### PUT /marketplace/templates/:templateId/deploy-session

Deep-merges the body into the caller's deploy session. Signed-in user; the caller's own session only.

<ParamField query="version" type="number">Template version the session belongs to.</ParamField>
<ParamField body="answers" type="object">Draft answers.</ParamField>
<ParamField body="connectState" type="object">Per-connection connect progress.</ParamField>
<ParamField body="idempotencyKey" type="string">Idempotency key held for the deploy.</ParamField>

**Response**

`200` with `{ session, copy }`.

### GET /marketplace/templates/install-requests/mine

Lists the caller's install requests in an organization. Scope `marketplace:read` on the organization.

<ParamField query="orgId" type="string" required>The organization.</ParamField>

**Response**

`200`.

### GET /marketplace/templates/agent/:agentId

Lists the templates installed on an agent. Scope `marketplace:read` on the agent. Equivalent: `lua marketplace template installed`.

**Response**

`200` with `[{ templateId, displayName, installedVersion, appliedAt, installedBy?, status }]`.

### GET /agents/:agentId/templates

The rich form of the installed-templates list. Scope `marketplace:read` on the agent.

**Response**

`200` with the same entries plus `latestApprovedVersion?, updateAvailable, allowCreatorUpdates, lastError, appliedBy?, updatePendingConsent?, bindings[], triggers[], outcomes[], paramValues, params[], setup?, onInstallWorkflowRunId?, onInstallError?` per install; `setup` is `{ status: "running" | "ok" | "failed", error?, code?, workflowRunId? }` for the post-install hook.

### GET /agents/:agentId/templates/:templateId/update-preview

Answers the diff an update would apply. Scope `marketplace:read` on the agent.

<ParamField query="toVersion" type="number">Version to preview the update to.</ParamField>

**Response**

`200` with `{ fromVersion, toVersion, changelog?, sections, envContract, consentSurfaceChanged, codeChanged, scopeDelta, armedTriggersSurviving, driftedPrimitives, workflowRunsInFlight?, droppedEditableParams? }`.

### PATCH /agents/:agentId/templates/:templateId

Changes whether the creator may push updates onto this install; the only post-install way to change that consent. Scope `automations:write` on the agent.

<ParamField body="allowCreatorUpdates" type="boolean" required>Consent to creator updates.</ParamField>

**Response**

`200`.

### POST /agents/:agentId/templates/:templateId/setup/retry

Re-runs a failed post-install hook. Scope `automations:write` on the agent.

**Response**

`201` with `{ onInstallFired?, onInstall?, onInstallWorkflowRunId?, onInstallError?, startWorkflow }`, where `startWorkflow` is `{ outcome: "started", runId }`, `{ outcome: "already-started", runId? }`, `{ outcome: "failed", error }`, or `{ outcome: "not-declared" }`.

### GET /marketplace/templates/:templateId/installs/:agentId/removal-preview

Answers what deleting the agent through uninstall with `removeAgent=true` would affect. Scope `automations:write` on the agent.

**Response**

`200` with `{ agentId, agentName, minted, soleInstall, otherTemplates, members, memberTotal, workspaceRoleHolders, workspaceRoleHolderTotal, rooms, roomTotal, automations, connections, billing, warnings }`.

### DELETE /marketplace/templates/:templateId/installs/:agentId

Uninstalls the template: runs the version's `onUninstall` hook as the caller, removes the managed primitives, and promotes a new local agent version. Scope `automations:write` on the agent. Equivalent: `lua marketplace template uninstall`.

<ParamField query="removeAgent" type="boolean">Also delete the agent afterwards. Allowed only for an agent this template created through deploy; otherwise `409` `agent-not-template-minted`.</ParamField>

**Response**

`200` with `{ success, onUninstall?: { ran, ok?, error? }, agentRemoved? }`. The hook is fail-soft, so read `onUninstall` rather than `success` for its outcome.

## Install result

Returned by install, and on [Template fleet](/reference/rest/template-fleet) by the installs ledger and the creator retry.

<ResponseField name="templateId" type="string">The template.</ResponseField>
<ResponseField name="agentId" type="string">The agent installed on.</ResponseField>
<ResponseField name="orgId" type="string">The agent's organization.</ResponseField>
<ResponseField name="installedVersion" type="number">Template version applied.</ResponseField>
<ResponseField name="appliedAgentVersion" type="number">The agent version the install promoted.</ResponseField>
<ResponseField name="allowCreatorUpdates" type="boolean">Whether the creator may push updates onto this install.</ResponseField>
<ResponseField name="status" type="string">Install status, for example `installed`, `already_current`, `apply_failed`.</ResponseField>
<ResponseField name="lastError" type="string">Error of the last failed apply.</ResponseField>
<ResponseField name="appliedBy" type="string">User ID of the last apply.</ResponseField>
<ResponseField name="appliedAt" type="string">Timestamp of the last apply.</ResponseField>
<ResponseField name="installedBy" type="string">User ID of the first install; never rewritten.</ResponseField>
<ResponseField name="armedTriggers" type="string[]">Trigger keys this apply armed.</ResponseField>
<ResponseField name="blockedTriggers" type="object[]">Each `{ key, reason }`: triggers that could not be armed.</ResponseField>
<ResponseField name="unwatchedTriggers" type="object[]">Each `{ key, reason }`: armed schedules whose runs deposit no inbox card because the author marked them to run as the system rather than a user.</ResponseField>
<ResponseField name="triggerEndpoints" type="object[]">Each `{ key, url }`: per-install dispatch URLs for webhook-source triggers.</ResponseField>
<ResponseField name="hiddenParamsCleared" type="string[]">Params or persona variables hidden by a `showIf` condition under the effective values; a submitted or stored value for them was cleared without validation.</ResponseField>
<ResponseField name="personaSkipped" type="boolean">`true` when the template declares a persona but the write was skipped to preserve a user-edited persona.</ResponseField>
<ResponseField name="onInstallFired" type="boolean">`true` when this apply claimed and fired the post-install first run; absent otherwise.</ResponseField>
<ResponseField name="onInstall" type="object">`{ toolRan, ok? }`. `toolRan` is `true` when this apply claimed the version's `onInstall.tool`; the tool runs in the background and records its result on the install, readable as `setup` on the rich installed-templates read. A failed tool never fails the install.</ResponseField>
<ResponseField name="onInstallDeferred" type="boolean">`true` when the hooks were deferred to the caller, as the deploy path does; `onInstallFired` and `onInstall` then come from that later fire.</ResponseField>
<ResponseField name="onInstallWorkflowRunId" type="string">The run `onInstall.startWorkflow` started on the installed copy, or the run an earlier same-version install already started.</ResponseField>
<ResponseField name="onInstallError" type="object">Why `onInstall.startWorkflow` started no run; the install itself succeeded. `code` is one of `workflow-not-materialized`, `control-plane-unavailable`, `control-plane-dark`, `start-refused`, `start-failed`. Retry with `POST /agents/:agentId/templates/:templateId/setup/retry`.</ResponseField>
<ResponseField name="migration" type="object">`{ migrationIds, counts, pending?, skipped? }` per workflow key when `migrateRuns` was sent.</ResponseField>

## Deploy result

Returned by deploy with `201`, and as `result` on the poll route.

<ResponseField name="agentId" type="string">The created agent.</ResponseField>
<ResponseField name="orgId" type="string">Organization the agent was created in.</ResponseField>
<ResponseField name="agentVersion" type="number">The agent version the install promoted.</ResponseField>
<ResponseField name="status" type="string">`installed`, `already_current`, or `apply_failed`.</ResponseField>
<ResponseField name="armedTriggers" type="string[]">Trigger keys armed.</ResponseField>
<ResponseField name="triggerEndpoints" type="object[]">Each `{ key, url }`.</ResponseField>
<ResponseField name="blockedTriggers" type="object[]">Each `{ key, reason }`.</ResponseField>
<ResponseField name="unwatchedTriggers" type="object[]">Each `{ key, reason }`; same meaning as on the install result.</ResponseField>
<ResponseField name="personaSkipped" type="boolean">Same meaning as on the install result.</ResponseField>
<ResponseField name="lastError" type="string | null">Error of a failed apply.</ResponseField>
<ResponseField name="hiddenParamsCleared" type="string[]">Same meaning as on the install result.</ResponseField>
<ResponseField name="onInstallFired" type="boolean">`true` when the install inside this deploy fired the post-install first run.</ResponseField>
<ResponseField name="onInstall" type="object">`{ toolRan }`; the outcome is reported through `setup` on the rich installed-templates read.</ResponseField>
<ResponseField name="onInstallWorkflowRunId" type="string">The run `onInstall.startWorkflow` started on the deployed agent.</ResponseField>
<ResponseField name="onInstallError" type="object">Why no run started; same codes as on the install result.</ResponseField>

## Errors

Install, deploy, and the poll route's `error` share one envelope: `{ code, message, capabilityKey?, field?, allowedValues?, primitives?, reason?, missingScopes?, key?, blockedTriggers?, jobs?, sourceAgentId?, kind?, items?, issues?, reconciliation? }`. `reconciliation` is recomputed for every `409`, so it always reflects the agent's current connections.

| Status | Code or message                                                      | Meaning                                                                                                                                                                                                           | Fix                                                            |
| ------ | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `400`  | `VALIDATION_FAILED`                                                  | A body field is invalid; `field` names it and `issues[{ code, path }]` give detail, for example `trigger-override-unknown` at `triggerOverrides.<key>`. On deploy, also a missing or malformed `Idempotency-Key`. | Fix the field.                                                 |
| `409`  | `CONNECTIONS_UNSATISFIED`                                            | A required connection is missing or lacks scopes; `reason`, `capabilityKey`, and `missingScopes` say which.                                                                                                       | Connect the integration or pass `connectionSelections`.        |
| `409`  | `CONNECTION_PAUSED`                                                  | The selected connection is paused.                                                                                                                                                                                | Reconnect it.                                                  |
| `409`  | `TEMPLATE_UNLISTED`, `TEMPLATE_NOT_APPROVED`, `TEMPLATE_NO_VERSIONS` | The template cannot be installed in its current state.                                                                                                                                                            | Wait for approval or pick another version.                     |
| `409`  | `TEMPLATE_NOT_DEPLOYABLE`                                            | Deploy of a template whose source is not a base agent (`sourceBaseAgentId` is not `baseAgent`).                                                                                                                   | Install onto an existing agent instead.                        |
| `409`  | `RECONSENT_REQUIRED`                                                 | The new version's consent surface changed.                                                                                                                                                                        | Re-read the manifest and install again with explicit answers.  |
| `409`  | `ENV_CONFLICT`                                                       | An env value conflicts with the contract.                                                                                                                                                                         | Fix `envValues`.                                               |
| `409`  | `POLICY_BLOCKED`                                                     | The organization's template policy blocks the install; `message` explains.                                                                                                                                        | Ask an organization administrator.                             |
| `409`  | `MODIFIED_SINCE_INSTALL`                                             | Managed primitives were edited on the agent; `primitives[{ kind, key }]` lists them.                                                                                                                              | Re-send with `acceptModifiedPrimitives`.                       |
| `409`  | `AGENT_BUSY`                                                         | Another apply is in progress on the agent.                                                                                                                                                                        | Retry later.                                                   |
| `409`  | `CONTENT_HASH_MISMATCH`                                              | The template changed after you read the manifest.                                                                                                                                                                 | Re-read the manifest and send its new `contentHash`.           |
| `409`  | `TEMPLATE_TRIGGER_ARM_FAILED`                                        | A trigger could not be armed; `key`, `reason`, and `blockedTriggers`.                                                                                                                                             | Fix the cause or disarm it in `triggerOverrides`.              |
| `409`  | `TEMPLATE_JOB_CLASH`                                                 | A job on the agent shares a name with a template job; `jobs[{ key, name }]`.                                                                                                                                      | Rename or remove it on the agent, then install again.          |
| `409`  | `TEMPLATE_NAME_CLASH`                                                | A skill, webhook, processor, trigger, or workflow on the agent shares a name; `kind` and `items[{ key, name }]`.                                                                                                  | Same remedy.                                                   |
| `409`  | `TEMPLATE_SELF_INSTALL`                                              | The target is the template's own source agent (`sourceAgentId`).                                                                                                                                                  | Install onto another agent.                                    |
| `409`  | `agent-not-template-minted`                                          | `removeAgent=true` on an agent this template did not create.                                                                                                                                                      | Uninstall without `removeAgent`; delete the agent separately.  |
| `500`  | `INTERNAL`                                                           | Unexpected failure.                                                                                                                                                                                               | Retry; a deploy retry with the same `Idempotency-Key` is safe. |

## Example

Install a template on an existing agent with the hash seal, so a template that changed after you read its manifest is refused instead of applied.

<CodeGroup>
  ```bash CLI theme={null}
  lua marketplace template install --template-id <template-id> --env-vars SUPPORT_EMAIL=support@example.com --force
  ```

  ```ts TypeScript theme={null}
  interface TemplateManifest {
    version: number;
    contentHash?: string;
    installable?: boolean;
  }

  interface TemplateInstall {
    templateId: string;
    agentId: string;
    installedVersion: number;
    appliedAgentVersion?: number;
    status: string;
    armedTriggers?: string[];
    blockedTriggers?: { key: string; reason: string }[];
    unwatchedTriggers?: { key: string; reason: string }[];
    hiddenParamsCleared?: string[];
    onInstallFired?: boolean;
  }

  interface TemplateDeployError {
    code: string;
    message: string;
  }

  async function installTemplate(
    apiKey: string,
    templateId: string,
    agentId: string,
    envValues: Record<string, string>,
  ): Promise<TemplateInstall> {
    const base = `https://api.heylua.ai/marketplace/templates/${templateId}`;
    const headers = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' };

    const manifestResponse = await fetch(`${base}/manifest?agentId=${agentId}`, { headers });
    if (!manifestResponse.ok) {
      throw new Error(`Manifest read failed: ${manifestResponse.status}`);
    }
    const manifest = (await manifestResponse.json()) as TemplateManifest;

    const response = await fetch(`${base}/install/${agentId}`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        version: manifest.version,
        contentHash: manifest.contentHash,
        envValues,
        triggerOverrides: { 'daily-digest': { enabled: true, params: { 'preset.timezone': 'Europe/London' } } },
        allowCreatorUpdates: false,
      }),
    });
    if (response.status === 409 || response.status === 400) {
      const failure = (await response.json()) as TemplateDeployError;
      throw new Error(`${failure.code}: ${failure.message}`);
    }
    if (!response.ok) {
      throw new Error(`Install failed: ${response.status} ${await response.text()}`);
    }
    return (await response.json()) as TemplateInstall;
  }
  ```

  ```bash cURL theme={null}
  curl "https://api.heylua.ai/marketplace/templates/<<YOUR_TEMPLATE_ID>>/manifest?agentId=<<YOUR_AGENT_ID>>" \
    -H "Authorization: Bearer <<YOUR_API_KEY>>"

  curl -X POST "https://api.heylua.ai/marketplace/templates/<<YOUR_TEMPLATE_ID>>/install/<<YOUR_AGENT_ID>>" \
    -H "Authorization: Bearer <<YOUR_API_KEY>>" \
    -H "Content-Type: application/json" \
    -d '{
      "version": 3,
      "contentHash": "<<CONTENT_HASH>>",
      "envValues": { "SUPPORT_EMAIL": "support@example.com" },
      "triggerOverrides": { "daily-digest": { "enabled": true, "params": { "preset.timezone": "Europe/London" } } },
      "allowCreatorUpdates": false
    }'
  ```
</CodeGroup>

## See also

* [Template fleet](/reference/rest/template-fleet) — apply runs, the installs ledger, health, outcomes, evals
* [Marketplace skills](/reference/rest/marketplace-skills) — single skills listed on the marketplace
* [Install and apply a template](/marketplace/install-and-apply) — the installer's guide
* [Publish an agent template](/marketplace/publish-a-template) — the creator's guide
* [`lua marketplace`](/reference/cli/marketplace) — the CLI twin
