> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# FPO templates

> Define reusable Full Product Object templates with user-fillable variables

An FPO template wraps a full product object (FPO). Use it to share a reusable product definition and collect values from each person who imports it.

Use an FPO template when you need to:

* Keep the product structure stable.
* Collect names, URLs, or environment-specific values during import.
* Ask for the credentials that the product's tools need.
* Preview the product on the dashboard import page (`use.runtype.com/now`) before you create it.

## Template shape

An FPO template uses these top-level keys:

* `version` sets the template document format version. Use `1.0` or `1.1`.
* `productObject` contains the FPO.
* `template.variables` contains metadata for each variable that the FPO references.

Set `productObject.version` to `1.0`, `1.1`, or `2.0`. Use `2.0` for new templates. In `2.0`, place inline agent runtime fields under `agent.config`. In `1.0` and `1.1`, place those fields directly on the agent object.

The following table lists the top-level keys of `productObject`:

| Key            | Required | Limit | Purpose                                                                                          |
| -------------- | -------- | ----- | ------------------------------------------------------------------------------------------------ |
| `version`      | Yes      | None  | Set the FPO version: `1.0`, `1.1`, or `2.0`.                                                     |
| `product`      | Yes      | None  | Set the product `name` and `description`. Both are required. `icon` and `metadata` are optional. |
| `capabilities` | Yes      | 50    | Define the flows and agents that the product runs.                                               |
| `tools`        | Yes      | 100   | Define the tools that capabilities use. Send an empty array when the product has no tools.       |
| `surfaces`     | Yes      | 50    | Define where users reach the capabilities, such as a chat surface.                               |
| `_meta`        | Yes      | None  | Describe how the FPO was produced.                                                               |
| `records`      | No       | 500   | Seed records that the import creates.                                                            |
| `schedules`    | No       | 50    | Run capabilities on a schedule. See [Add scheduled jobs](#add-scheduled-jobs).                   |
| `secrets`      | No       | 50    | Declare secrets that the product needs.                                                          |
| `evals`        | No       | 50    | Add eval suites. See [Add eval suites](#add-eval-suites).                                        |
| `skills`       | No       | 20    | Add agent skills. See [Add skills](#add-skills).                                                 |

The `_meta` object needs five non-empty strings: `schemaVersion`, `catalogVersion`, `generatedAt`, `generatorVersion`, and `planHash`. When you write a template by hand, fill them with descriptive values, as the [example template](#example-template) does.

Each capability sets exactly one of `flow`, `agent`, `existingFlowId`, or `existingAgentId`.

> **Warning**
>
> An `existingFlowId` or `existingAgentId` must belong to the account that imports the template. For
> any other account, the import fails with a "not found or not owned by current account" error. In a
> template that you share with other accounts, define each capability inline with `flow` or `agent`.

Use variable references in string values within the FPO:

**`Variable reference`**

```json title="Variable reference"
{
  "name": "{{productName}}"
}
```

## Variable manifest

Declare each variable in `template.variables` with the following fields:

| Field          | Required    | Notes                                                                                                                                                             |
| -------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`          | Yes         | Reference this key with `{{key}}` inside `productObject`. Each key must be unique.                                                                                |
| `label`        | Yes         | Use this value as the form label.                                                                                                                                 |
| `description`  | No          | Show this value in preview UIs.                                                                                                                                   |
| `inputType`    | Yes         | Set to `text`, `textarea`, `url`, `secret`, `select`, `number`, or `boolean`.                                                                                     |
| `required`     | Yes         | Set whether the importer must supply a value.                                                                                                                     |
| `defaultValue` | No          | Set a string, number, or boolean. Secret variables can't have a default.                                                                                          |
| `placeholder`  | No          | Provide a hint for the input field.                                                                                                                               |
| `options`      | When select | Required, with at least one `{label, value}` entry, when `inputType` is `select`. Not allowed for other input types. `value` can be a string, number, or boolean. |

A template can declare up to 50 variables.

> **Warning**
>
> An `inputType: "secret"` variable is substituted literally into the resolved product object, so
> Runtype stores the supplied value in the product record. Validation reports a
> `TEMPLATE_VARIABLE_SECRET_IS_UNSAFE` warning for every secret variable. To collect a credential,
> declare it in the tool's `auth.secrets` and reference it as `{{secret:KEY}}`. For more
> information, see [Collect credentials](#collect-credentials). Never put live secrets in template
> files.

## Resolution rules

Runtype resolves a template with these rules:

* Every `{{variableName}}` reference must be declared in the manifest.
* Inline defaults such as `{{apiKey|default}}` are not allowed. Define defaults with `template.variables[].defaultValue`.
* A template must declare at least one variable or at least one pending secret: a tool with `auth.setupRequired: true` and one or more `auth.secrets` entries, a top-level `secrets[]` entry, or a `{{secret:NAME}}` reference.
* References to `{{secret:NAME}}` and to system variables, such as `{{_now.iso}}` or `{{userMessage}}`, don't need a declaration. Runtype leaves them for run time.
* Host-resolved variables don't need a declaration either: `{{_identity.token}}` in a tool or MCP header, and `{{_agent.id}}`, `{{_agent.alias}}`, or `{{_organization.id}}` in a memory `profileTemplate`, including fallbacks such as `{{_agent.alias || "live"}}`.
* A typed field, such as a URL, number, boolean, enum, or datetime, can hold a variable. Runtype checks that field against the product schema after substitution, so validation reports a bad default and import rejects a bad value.
* Every other `{{...}}` token counts as a template variable, including a runtime token in a step prompt or a Liquid template. To keep a runtime token, declare a template variable whose `defaultValue` is the token, and reference that variable in its place.
* A value supplied for a `number` variable is converted to a number. A non-numeric value counts as missing.
* A value supplied for a `boolean` variable is converted from the strings `true` and `false`. Any other value counts as missing.
* When a field value is exactly `{{variableName}}` and the variable is a `number` or `boolean`, the field keeps that scalar type.
* Every other substitution resolves to a string.

## Collect credentials

To ask the importer for an API key or token, declare it as a pending secret on the tool that uses it. Reference the secret in the tool configuration with `{{secret:KEY}}`. Runtype stores the value in your secrets, not in the product object.

If the credential belongs to the product rather than one tool, such as an MCP server header or an external agent's auth, declare it in the top-level `secrets[]` array with an `id`, a `key`, and an optional `description` and `required`. Reference it with `{{secret:KEY}}` wherever it's used. The import lists these secrets with the tool secrets.

The following fields define a tool's `auth` object:

| Field               | Required | Notes                                                                                                                                                                                                                                                |
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`              | Yes      | Set to `oauth`, `api_key`, `none`, or `user_provided`.                                                                                                                                                                                               |
| `setupRequired`     | Yes      | Set to `true` so the import lists the tool's secrets.                                                                                                                                                                                                |
| `secrets`           | Yes      | List up to 20 secrets. Each entry has a `key` and `required`, plus an optional `description` and `placeholder`. Write each `key` with 2 to 100 uppercase letters, digits, and underscores. Start it with a letter and end it with a letter or digit. |
| `setupInstructions` | Yes      | Set a `summary` and a `steps` array. Optionally add a `docsUrl`, which must be an absolute `https` URL.                                                                                                                                              |

Use the following structure to reference a pending secret in a tool:

**`Pending secret`**

```json title="Pending secret"
{
  "id": "tool_search",
  "type": "integration",
  "provider": "firecrawl",
  "name": "Knowledge Search",
  "config": {
    "headers": {
      "Authorization": "Bearer {{secret:SEARCH_API_KEY}}"
    }
  },
  "auth": {
    "type": "api_key",
    "setupRequired": true,
    "secrets": [
      {
        "key": "SEARCH_API_KEY",
        "required": true,
        "description": "API key for knowledge searches."
      }
    ],
    "setupInstructions": {
      "summary": "Add your search provider API key.",
      "steps": ["Create an API key with your search provider.", "Add it as a secret after import."],
      "docsUrl": "https://example.com/docs/api-keys"
    }
  }
}
```

The import preview returns these entries in `pendingSecrets`. The dashboard import page lists them under **Secrets to configure after import**, with each secret's key, tool name, description, and a **Get key** link to `docsUrl`. When you create the product, Runtype adds each secret to your account without a value. If your account already has a secret with the same key, the product uses that secret instead. Validation doesn't check the key format, and the import skips any secret whose key breaks the format rule in the preceding table. To set a value, see [Managing secrets](/user-guide/settings/managing-secrets).

## Example template

Use this complete template as a starting point:

**`Full FPO template`**

```json title="Full FPO template"
{
  "version": "1.0",
  "productObject": {
    "version": "2.0",
    "product": {
      "name": "{{productName}}",
      "description": "Support automation for {{companyName}}",
      "metadata": {
        "requireEscalationApproval": "{{requireEscalationApproval}}",
        "responseTimeoutMinutes": "{{responseTimeoutMinutes}}"
      }
    },
    "capabilities": [
      {
        "id": "cap_support",
        "name": "Support Agent",
        "description": "Handle incoming support requests.",
        "agent": {
          "name": "{{productName}} Agent",
          "description": "Handle support requests for {{companyName}}.",
          "config": {
            "model": "claude-sonnet-5",
            "systemPrompt": "Use a {{brandVoice}} tone when responding."
          }
        }
      }
    ],
    "tools": [
      {
        "id": "tool_search",
        "type": "integration",
        "provider": "{{searchProvider}}",
        "name": "Knowledge Search",
        "config": {
          "baseUrl": "{{knowledgeBaseUrl}}",
          "headers": {
            "Authorization": "Bearer {{secret:SEARCH_API_KEY}}"
          }
        },
        "auth": {
          "type": "api_key",
          "setupRequired": true,
          "secrets": [
            {
              "key": "SEARCH_API_KEY",
              "required": true,
              "description": "API key for knowledge searches."
            }
          ],
          "setupInstructions": {
            "summary": "Add your search provider API key.",
            "steps": ["Create an API key with your search provider.", "Add it as a secret after import."]
          }
        }
      }
    ],
    "surfaces": [
      {
        "id": "surface_chat",
        "name": "{{productName}} Chat",
        "type": "chat",
        "config": {},
        "routes": [
          {
            "capabilityId": "cap_support"
          }
        ]
      }
    ],
    "_meta": {
      "schemaVersion": "2.0",
      "catalogVersion": "1.0",
      "generatedAt": "2026-03-07T00:00:00.000Z",
      "generatorVersion": "1.0.0",
      "planHash": "template-example"
    }
  },
  "template": {
    "variables": [
      {
        "key": "productName",
        "label": "Product Name",
        "inputType": "text",
        "required": true,
        "defaultValue": "Example Organization Support Copilot"
      },
      {
        "key": "companyName",
        "label": "Company Name",
        "inputType": "text",
        "required": true
      },
      {
        "key": "knowledgeBaseUrl",
        "label": "Knowledge Base URL",
        "inputType": "url",
        "required": true
      },
      {
        "key": "searchProvider",
        "label": "Search Provider",
        "inputType": "select",
        "required": true,
        "defaultValue": "firecrawl",
        "options": [
          { "label": "Firecrawl", "value": "firecrawl" },
          { "label": "Exa", "value": "exa" }
        ]
      },
      {
        "key": "requireEscalationApproval",
        "label": "Require Escalation Approval",
        "inputType": "boolean",
        "required": true,
        "defaultValue": true
      },
      {
        "key": "responseTimeoutMinutes",
        "label": "Response Timeout Minutes",
        "inputType": "number",
        "required": true,
        "defaultValue": 15
      },
      {
        "key": "brandVoice",
        "label": "Brand Voice",
        "inputType": "textarea",
        "required": false,
        "defaultValue": "Calm and direct."
      }
    ]
  }
}
```

## Add scheduled jobs

Use the top-level `productObject.schedules[]` array for cron or one-time automation. Each schedule targets a capability through `capabilityId`. Importing the FPO creates the schedule.

A capability that a schedule targets doesn't need a surface route. Don't add a `type: "schedule"` surface to trigger the schedule. Add a schedule surface only when users or operators need a dedicated surface to inspect and manage schedule behavior.

The following fields define a schedule in an FPO:

| Field              | Required       | Notes                                                                                                                                                                                                                                                                                                                                            |
| ------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`               | Yes            | Set a template-local schedule ID.                                                                                                                                                                                                                                                                                                                |
| `capabilityId`     | Yes            | Reference the `capabilities[].id` to run.                                                                                                                                                                                                                                                                                                        |
| `triggerType`      | Yes            | Set to `cron` or `one_time`.                                                                                                                                                                                                                                                                                                                     |
| `cron`             | For `cron`     | Set the cron expression, such as `0 9 * * MON`.                                                                                                                                                                                                                                                                                                  |
| `runAt`            | For `one_time` | Set the run time as an ISO 8601 timestamp with an offset, such as `2027-01-19T09:00:00Z`.                                                                                                                                                                                                                                                        |
| `name`             | No             | Set the schedule display name.                                                                                                                                                                                                                                                                                                                   |
| `timezone`         | No             | Set the time zone for the cron expression, such as `UTC`.                                                                                                                                                                                                                                                                                        |
| `enabled`          | No             | Set whether the schedule runs.                                                                                                                                                                                                                                                                                                                   |
| `recordIds`        | No             | Run the capability once for each listed record. Each entry is a `records[].id` from the same FPO or a saved `record_…` ID.                                                                                                                                                                                                                       |
| `recordType`       | No             | Run the capability once for each record of this type.                                                                                                                                                                                                                                                                                            |
| `recordFilter`     | No             | Run the capability once for each record that matches this filter. It takes priority over `recordType`.                                                                                                                                                                                                                                           |
| `messages`         | No             | Send messages to each run: either one list of `role` and `content` messages, or named message sets (`{ "name", "messages" }`), each run once per fire. A flow-backed schedule runs one conversation per fire, so it accepts at most one message set. Content can be text or content parts such as `image` and `file`.                            |
| `inputs`           | No             | Pass these flow inputs to each run. Keys can't start with `_`.                                                                                                                                                                                                                                                                                   |
| `executionOptions` | No             | Set batch options for each run: `chunkSize` (1–250), `recordConcurrency` (1–50), `progressFlushSize` (1–100), `timeoutMs` (60,000–1,800,000), `flowTimeoutMs` (1,000–900,000), `stepTimeoutMs` (1,000–600,000), `maxRetries` (0–10), `skipCacheWarm`, `modelOverride`, and `loggingPolicy`. Validation warns about a value outside these bounds. |

When an FPO that you import again leaves out `messages`, `inputs`, `recordIds`, `executionOptions`, `name`, or `enabled`, the live schedule keeps its current value. A live `recordIds` list is cleared when the FPO changes `recordType` or `recordFilter`.

Use the following structure to define a scheduled capability:

**`Scheduled capability`**

```json title="Scheduled capability"
{
  "productObject": {
    "capabilities": [
      {
        "id": "cap_support",
        "name": "Support Agent",
        "description": "Answer user questions.",
        "agent": {
          "name": "Support Agent",
          "description": "Answer user questions.",
          "config": { "model": "claude-sonnet-5" }
        }
      },
      {
        "id": "cap_weekly_digest",
        "name": "Weekly Digest",
        "description": "Summarize new records each Monday.",
        "agent": {
          "name": "Weekly Digest",
          "description": "Summarize new records.",
          "config": { "model": "claude-sonnet-5" }
        }
      }
    ],
    "surfaces": [
      {
        "id": "surface_chat",
        "name": "Support Chat",
        "type": "chat",
        "config": {},
        "routes": [{ "capabilityId": "cap_support" }]
      }
    ],
    "schedules": [
      {
        "id": "weekly-digest",
        "capabilityId": "cap_weekly_digest",
        "triggerType": "cron",
        "cron": "0 9 * * MON",
        "timezone": "UTC",
        "enabled": true
      }
    ]
  }
}
```

## Add eval suites

Use the optional top-level `productObject.evals[]` array to include eval suites in a product. Each suite targets a capability through `capabilityId`. Importing the FPO creates the suite, its graders, and its example cases with the capability's flow or agent.

The following fields define an eval suite in an FPO:

| Field                         | Required | Notes                                                                                                                                                                                                                                                                                                                                                                                |
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                          | Yes      | Set a template-local suite ID for duplicate detection.                                                                                                                                                                                                                                                                                                                               |
| `name`                        | Yes      | Set the suite display name.                                                                                                                                                                                                                                                                                                                                                          |
| `capabilityId`                | Yes      | Reference a `capabilities[].id` for the flow or agent that the suite tests.                                                                                                                                                                                                                                                                                                          |
| `graders`                     | Yes      | Add one to 20 graders. Use deterministic checks such as `contains` or `json_field`, trace checks such as `called_tool`, or an AI judge with `kind: "ai"`.                                                                                                                                                                                                                            |
| `description`                 | No       | Describe what the suite tests.                                                                                                                                                                                                                                                                                                                                                       |
| `cases`                       | No       | Add up to 200 example cases. Each case can include a `name`, `input.variables`, `input.messages`, `input.endUser`, `input.tenant`, an `expected` block, `graders`, `notes`, and `enabled`. Case `graders` replace the suite graders for that case. A captured case can also carry `input.checkpoint`, `input.toolMocks`, and recorded `toolCalls` and `toolResults` on its messages. |
| `recordedToolMode`            | No       | Set how a captured case replays recorded tool calls: `next_step` or `continue`.                                                                                                                                                                                                                                                                                                      |
| `recordedToolUnmatchedPolicy` | No       | Set what happens when a replayed run makes an unrecorded tool call: `fail` or `stub`.                                                                                                                                                                                                                                                                                                |
| `isDefault`                   | No       | Make this suite the default for its flow or agent. Only one suite per flow or agent can set it, including capabilities that share a saved flow or agent. When the FPO leaves it out, the live flag stays.                                                                                                                                                                            |
| `identityDefaults`            | No       | Set the `endUser` and `tenant` that every case runs as unless the case sets its own. Required, or a per-case identity, when the capability's agent sets a tenancy strategy. See [Grade an agent that runs as an end user](/user-guide/evals/manage-evals-as-code#grade-an-agent-that-runs-as-an-end-user).                                                                           |

Use the following structure to add an eval suite:

**`Eval suite`**

```json title="Eval suite"
{
  "productObject": {
    "capabilities": [
      {
        "id": "cap_support",
        "name": "Support Agent",
        "description": "Answer user questions.",
        "agent": {
          "name": "Support Agent",
          "description": "Answer user questions.",
          "config": { "model": "claude-sonnet-5" }
        }
      }
    ],
    "evals": [
      {
        "id": "eval_support_basics",
        "name": "Support basics",
        "capabilityId": "cap_support",
        "graders": [
          {
            "kind": "ai",
            "criteria": "The response directly addresses what the user asked, without dodging or answering a different question."
          }
        ],
        "cases": [
          {
            "name": "Refund policy question",
            "input": {
              "messages": [{ "role": "user", "content": "What is your refund policy?" }]
            },
            "expected": { "facts": ["Refunds are available within 30 days"] },
            "enabled": true
          }
        ]
      }
    ]
  }
}
```

> **Note**
>
> When you re-import the product, Runtype replaces only the cases that came from the FPO. Cases
> that you author in the dashboard or save from a run stay in the suite, and Runtype doesn't add
> them to the FPO when you export the product definition.

For grader and case concepts, see [What are Evals?](/user-guide/evals/what-are-evals). Use [Managing eval suites](/user-guide/evals/managing-eval-suites) to edit imported suites in the dashboard.

## Add skills

Use the optional top-level `productObject.skills[]` array to include agent skills in a product. A skill is a loadable context bundle with a markdown body and optional capabilities that it activates when loaded. Importing the FPO publishes each skill and binds it to the agent-backed capabilities listed in `bindTo`.

The following fields define a skill in an FPO:

| Field          | Required | Notes                                                                                                                                  |
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `id`           | Yes      | Set a template-local skill ID for duplicate detection.                                                                                 |
| `name`         | Yes      | Set the skill display name.                                                                                                            |
| `description`  | Yes      | Describe when to load the skill.                                                                                                       |
| `content`      | Yes      | Provide the `SKILL.md` markdown body that loads into the agent's context when the skill fires.                                         |
| `slug`         | No       | Use a lowercase identifier that starts with a letter and contains letters, digits, underscores, or hyphens. Limit it to 64 characters. |
| `trustLevel`   | No       | Set to `org`, `imported`, or `community`.                                                                                              |
| `version`      | No       | Set the `SKILL.md` frontmatter version label, such as `1.2.0`. This label isn't the published version number that `bindTo` pins.       |
| `license`      | No       | Set the `SKILL.md` frontmatter license.                                                                                                |
| `tags`         | No       | Set the `SKILL.md` frontmatter tags.                                                                                                   |
| `capabilities` | No       | Bind FPO capabilities with `capabilityRefs`, saved tools with `toolIds`, or inline tools with `inlineTools`.                           |
| `bindTo`       | No       | List the agent-backed capabilities that the skill binds to. See [Pin a skill version](#pin-a-skill-version).                           |

Do not declare `mcpServers` in a skill's `capabilities`. The validator rejects the key with an explicit error. Bind MCP tools with a saved tool ID or an inline tool.

Use the following structure to add a skill:

**`Skill definition`**

```json title="Skill definition"
{
  "productObject": {
    "capabilities": [
      {
        "id": "cap_support",
        "name": "Support Agent",
        "description": "Answer user questions.",
        "agent": {
          "name": "Support Agent",
          "description": "Answer user questions.",
          "config": { "model": "claude-sonnet-5" }
        }
      }
    ],
    "skills": [
      {
        "id": "skill_refund_policy",
        "name": "Refund policy",
        "slug": "refund-policy",
        "description": "Load when the user asks about refunds, returns, or exchanges.",
        "content": "# Refund policy\n\nRefunds are available within 30 days of purchase. Always confirm the order number before promising a refund.",
        "bindTo": ["cap_support"]
      }
    ]
  }
}
```

### Pin a skill version

Each `bindTo` entry is either an FPO capability ID or an object with `capability` and `version`. A capability ID binds the skill so that the agent follows the skill's published version. An object pins the binding to the skill version with that version number:

```json
"bindTo": ["cap_support", { "capability": "cap_escalations", "version": 2 }]
```

Importing the FPO publishes the skill content as a new version, so an agent that follows the published version starts using the new content. A pinned agent keeps the version it names until you change the pin. If the skill has no version with that number, product ensure reports the skill as failed and doesn't write that binding. Pulling a product returns each pinned binding in the object form.

## Add saved tools

To ship a saved tool with the product, add a `definition` to its entry in `productObject.tools[]`. Importing the FPO creates or updates a saved tool whose identity is the entry's `name`, the same way `POST /v1/tools/ensure` does. The saved tool's config is the entry's `config`.

| Field                         | Required | Notes                                                                                      |
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------ |
| `definition.description`      | Yes      | Describe the tool for the model. Limit it to 5000 characters.                              |
| `definition.toolType`         | Yes      | Set to `custom`, `external`, `graphql`, `flow`, `mcp`, `local`, or `subagent`.             |
| `definition.parametersSchema` | Yes      | Provide the JSON Schema of the tool's input.                                               |
| `auth`                        | No       | Omit it when the tool needs no setup. Use `auth.secrets` to collect credentials at import. |

Reference the saved tool as `tool:<name>` from an agent's `config.tools.toolIds` or a flow step. An agent `toolIds` entry that names the FPO tool `id` is rewritten to the same `tool:<name>` reference. Put `{{secret:NAME}}` references only in an `external` tool's config. A `custom` tool lists the bare secret names in `config.secrets` instead.

A `flow` or `subagent` saved tool points at its flow or agent by raw ID in `config.flowId` or `config.agentId`. Pull keeps that ID and adds a warning, because the ID doesn't exist in another workspace. After you import the FPO into a different workspace, point the tool at the new flow or agent.

**`Saved tool`**

```json title="Saved tool"
{
  "id": "lookup",
  "type": "custom",
  "name": "lookup_order",
  "config": { "code": "return { status: 'shipped' }" },
  "definition": {
    "description": "Look up an order by its number.",
    "toolType": "custom",
    "parametersSchema": {
      "type": "object",
      "properties": { "orderNumber": { "type": "string" } },
      "required": ["orderNumber"]
    }
  }
}
```

## Add record collections

Use the optional top-level `productObject.collections[]` array to give a record type a schema. A collection's `slug` is the record `type` it governs. Importing the FPO creates or updates each collection by slug, the same way `POST /v1/collections/ensure` does, before it writes any seed `records[]`.

| Field            | Required | Notes                                                                                 |
| ---------------- | -------- | ------------------------------------------------------------------------------------- |
| `slug`           | Yes      | Set the record type. Use letters, digits, underscores, dots, or hyphens.              |
| `displayName`    | Yes      | Set the collection's display name.                                                    |
| `schema`         | No       | Provide a root `object` schema with string, number, boolean, array, or object fields. |
| `validationMode` | No       | Set to `off` (default), `warn`, or `enforce`.                                         |
| `description`    | No       | Describe the collection.                                                              |
| `icon`           | No       | Set an icon.                                                                          |

With `validationMode: "enforce"`, a seed record whose metadata does not match the schema fails to import, and record steps reject nonconforming writes at run time.

## Add question sets

Use the optional top-level `productObject.questionSets[]` array to ship a judgment rubric for `builtin:decision_judge`. Importing the FPO creates or updates each set by `slug` and publishes it. Question sets belong to an organization, so importing one needs an organization context.

| Field          | Required | Notes                                                             |
| -------------- | -------- | ----------------------------------------------------------------- |
| `slug`         | Yes      | Use lowercase letters, digits, `-`, and `_`, up to 64 characters. |
| `name`         | Yes      | Set the display name.                                             |
| `questions`    | Yes      | Define the judgment questions, keyed by question ID.              |
| `inputMapping` | No       | Map each `state` field to the value that fills it.                |
| `model`        | No       | Set the decision model. Omit it to use the default.               |

Point a judge configuration at the set with `"questionSetId": "questionSet:<slug>"`. Import rewrites the reference to the saved set's ID:

**`Question set reference`**

```json title="Question set reference"
"toolConfigs": {
  "builtin:decision_judge#triage": { "questionSetId": "questionSet:ticket-triage" }
}
```

An imported set is published, not calibrated. Calibrate it before an automated policy acts on its answers.

Pulling a product returns the saved tools, collections, and question sets that its agents, flows, and schedules reference. Product prune never deletes them, because they are account-scoped and other products can share them.

## Validate a template

Validate a template before you share it. The `runtype` CLI validates locally without an API call. The API endpoint is `POST /v1/public/products/validate-template`.

Save the [example template](#example-template) as `customer-support-fpo-template.json`, then validate it with one of the following samples.

#### CLI

**`CLI`**

```bash title="CLI"
runtype validate-product ./customer-support-fpo-template.json --template --json
```

The command exits with code `0` when the template is valid. It exits with code `1` when validation finds errors, when the defaults resolve to an invalid FPO, or when the command can't read or parse the input. A variable without a default is reported but doesn't fail the command. To read the template from standard input, pass `-` as the path.

#### TypeScript

**`TypeScript`**

```typescript title="TypeScript"
import { readFile } from 'node:fs/promises'

const template = JSON.parse(await readFile('customer-support-fpo-template.json', 'utf8'))

const validation = await fetch('https://api.runtype.com/v1/public/products/validate-template', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.RUNTYPE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(template),
}).then((response) => response.json())

console.log(validation.valid)
console.log(validation.errors)
console.log(validation.warnings)
console.log(validation.defaultsSufficient)
console.log(validation.defaultsValidation.errors)
```

#### cURL

**`cURL`**

```bash title="cURL"
curl https://api.runtype.com/v1/public/products/validate-template \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d @customer-support-fpo-template.json
```

Replace `YOUR_API_KEY` with a Runtype API key.

The endpoint returns `400` with the code `INVALID_JSON` when the request body isn't valid JSON. Otherwise, it returns the following fields:

| Field                    | Description                                                                                           |
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
| `valid`                  | `true` when the template has no errors.                                                               |
| `errors`                 | Structural and variable errors. Each entry has a `code`, `message`, `path`, and `severity`.           |
| `warnings`               | Issues that don't block import, such as unused variables.                                             |
| `recommendations`        | Suggested improvements.                                                                               |
| `normalizedVariables`    | The parsed variable manifest.                                                                         |
| `referencedVariableKeys` | The variable keys that the FPO references.                                                            |
| `defaultsSufficient`     | `true` when the template resolves to a valid FPO with defaults alone, without extra input.            |
| `defaultsValidation`     | The validation result for the FPO resolved with defaults. It explains a `false` `defaultsSufficient`. |

A variable without a default leaves `valid` set to `true`. It sets `defaultsSufficient` to `false` and adds a `MISSING_TEMPLATE_VARIABLE` entry to `defaultsValidation.errors`. The importer supplies that value during import.

The following table lists the template-specific issue codes:

| Code                                  | Severity                      | Cause                                                            | Fix                                                                                                  |
| ------------------------------------- | ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `DUPLICATE_TEMPLATE_VARIABLE`         | Error                         | Two manifest entries share a `key`.                              | Rename or remove one entry.                                                                          |
| `INLINE_TEMPLATE_DEFAULT_NOT_ALLOWED` | Error                         | A reference adds an inline default after a pipe character.       | Remove the inline default and set `defaultValue` in the manifest.                                    |
| `UNDECLARED_TEMPLATE_VARIABLE`        | Error                         | The FPO references a variable that the manifest doesn't declare. | Declare the variable, or wrap a runtime token as described in [Resolution rules](#resolution-rules). |
| `UNUSED_TEMPLATE_VARIABLE`            | Warning                       | The manifest declares a variable that the FPO doesn't use.       | Reference the variable or remove it.                                                                 |
| `TEMPLATE_VARIABLE_SECRET_IS_UNSAFE`  | Warning                       | A variable uses `inputType: "secret"`.                           | Move the credential to a pending secret. See [Collect credentials](#collect-credentials).            |
| `MISSING_TEMPLATE_VARIABLE`           | Error in `defaultsValidation` | A variable has no default value.                                 | Set a `defaultValue`, or let the importer supply the value.                                          |

## Share a template

To let other people import your template, follow these steps:

1. Host the template JSON file at a public HTTPS URL, such as a raw GitHub file URL.
2. URL-encode the file URL.
3. Share the import link `https://use.runtype.com/now?from=ENCODED_TEMPLATE_URL`. The link opens the dashboard import page with a preview of the template.
4. Optional: Add a **Deploy to Runtype** button to your repository. For the Markdown snippet, see [Create a repository button](/developer-guides/guides/importing-products#create-a-repository-button).

Replace `ENCODED_TEMPLATE_URL` with the URL-encoded address of your template file.

## Create a product from a template

To create a product from a template with the API, follow these steps:

1. Preview the template with `POST /v1/quick-start/imports/preview`. Send the template as a hosted file with `source.kind: "url"` or inline with `source.kind: "json"`.
2. Collect values for the variables that the preview response lists without defaults.
3. Create the product with `POST /v1/quick-start/create`. Send the values in `variables`, with the `importToken` from the preview response.

An API key must hold a scope for everything that the template creates, such as `SECRETS:WRITE` for a template that declares secrets. A Test key can't hold `SECRETS:WRITE`, so it can't create a template product that declares secrets. For the full scope list, see [Required API key scopes](/developer-guides/guides/importing-products#required-api-key-scopes) in the importing guide, which also has request and response examples.

## Next steps

Continue with these guides:

* [Quickstart](/developer-guides/getting-started/quickstart): Make your first call to the Runtype API.
* [Agent Skills](/user-guide/agents/agent-skills): Bind loadable context bundles to agents.