Skip to navigation

FPO templates

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:

KeyRequiredLimitPurpose
versionYesNoneSet the FPO version: 1.0, 1.1, or 2.0.
productYesNoneSet the product name and description. Both are required. icon and metadata are optional.
capabilitiesYes50Define the flows and agents that the product runs.
toolsYes100Define the tools that capabilities use. Send an empty array when the product has no tools.
surfacesYes50Define where users reach the capabilities, such as a chat surface.
_metaYesNoneDescribe how the FPO was produced.
recordsNo500Seed records that the import creates.
schedulesNo50Run capabilities on a schedule. See Add scheduled jobs.
secretsNo50Declare secrets that the product needs.
evalsNo50Add eval suites. See Add eval suites.
skillsNo20Add agent skills. See 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 does.

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

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
{
"name": "{{productName}}"
}

Variable manifest

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

FieldRequiredNotes
keyYesReference this key with {{key}} inside productObject. Each key must be unique.
labelYesUse this value as the form label.
descriptionNoShow this value in preview UIs.
inputTypeYesSet to text, textarea, url, secret, select, number, or boolean.
requiredYesSet whether the importer must supply a value.
defaultValueNoSet a string, number, or boolean. Secret variables can’t have a default.
placeholderNoProvide a hint for the input field.
optionsWhen selectRequired, 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.

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. 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 tool with auth.setupRequired: true and one or more auth.secrets entries.
  • 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.
  • 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.

The following fields define a tool’s auth object:

FieldRequiredNotes
typeYesSet to oauth, api_key, none, or user_provided.
setupRequiredYesSet to true so the import lists the tool’s secrets.
secretsYesList 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.
setupInstructionsYesSet 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
{
"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.

Example template

Use this complete template as a starting point:

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:

FieldRequiredNotes
idYesSet a template-local schedule ID.
capabilityIdYesReference the capabilities[].id to run.
triggerTypeYesSet to cron or one_time.
cronFor cronSet the cron expression, such as 0 9 * * MON.
runAtFor one_timeSet the run time as an ISO 8601 timestamp with an offset, such as 2027-01-19T09:00:00Z.
nameNoSet the schedule display name.
timezoneNoSet the time zone for the cron expression, such as UTC.
enabledNoSet whether the schedule runs.
recordTypeNoRun the capability once for each record of this type.
recordFilterNoRun the capability once for each record that matches this filter. It takes priority over recordType.
messagesNoSend these role and content messages to each run.
executionOptionsNoSet options for each scheduled run.

Use the following structure to define a scheduled capability:

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:

FieldRequiredNotes
idYesSet a template-local suite ID for duplicate detection.
nameYesSet the suite display name.
capabilityIdYesReference a capabilities[].id for the flow or agent that the suite tests.
gradersYesAdd 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".
descriptionNoDescribe what the suite tests.
casesNoAdd up to 200 example cases. Each case can include a name, input.variables, input.messages, input.endUser, input.tenant, an expected block, notes, and enabled.
recordedToolModeNoSet how a captured case replays recorded tool calls: next_step or continue.
recordedToolUnmatchedPolicyNoSet what happens when a replayed run makes an unrecorded tool call: fail or stub.
identityDefaultsNoSet 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.

Use the following structure to add an eval suite:

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
}
]
}
]
}
}

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?. Use 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:

FieldRequiredNotes
idYesSet a template-local skill ID for duplicate detection.
nameYesSet the skill display name.
descriptionYesDescribe when to load the skill.
contentYesProvide the SKILL.md markdown body that loads into the agent’s context when the skill fires.
slugNoUse a lowercase identifier that starts with a letter and contains letters, digits, underscores, or hyphens. Limit it to 64 characters.
trustLevelNoSet to org, imported, or community.
capabilitiesNoBind FPO capabilities with capabilityRefs, saved tools with toolIds, or inline tools with inlineTools.
bindToNoList the agent-backed capabilities that the skill binds to. See 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
{
"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:

"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.

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 as customer-support-fpo-template.json, then validate it with one of the following samples.

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.

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

FieldDescription
validtrue when the template has no errors.
errorsStructural and variable errors. Each entry has a code, message, path, and severity.
warningsIssues that don’t block import, such as unused variables.
recommendationsSuggested improvements.
normalizedVariablesThe parsed variable manifest.
referencedVariableKeysThe variable keys that the FPO references.
defaultsSufficienttrue when the template resolves to a valid FPO with defaults alone, without extra input.
defaultsValidationThe 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:

CodeSeverityCauseFix
DUPLICATE_TEMPLATE_VARIABLEErrorTwo manifest entries share a key.Rename or remove one entry.
INLINE_TEMPLATE_DEFAULT_NOT_ALLOWEDErrorA reference adds an inline default after a pipe character.Remove the inline default and set defaultValue in the manifest.
UNDECLARED_TEMPLATE_VARIABLEErrorThe FPO references a variable that the manifest doesn’t declare.Declare the variable, or wrap a runtime token as described in Resolution rules.
UNUSED_TEMPLATE_VARIABLEWarningThe manifest declares a variable that the FPO doesn’t use.Reference the variable or remove it.
TEMPLATE_VARIABLE_SECRET_IS_UNSAFEWarningA variable uses inputType: "secret".Move the credential to a pending secret. See Collect credentials.
MISSING_TEMPLATE_VARIABLEError in defaultsValidationA 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.

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 in the importing guide, which also has request and response examples.

Next steps

Continue with these guides: