Export and Import an Instance
On this page
Polyant can serialise an entire instance into a JSON bundle and re-create it elsewhere - useful for backup, promoting from staging to production, or sharing a template between teams.
The bundle is safe to share over normal channels: credential VALUES are never included. The list of which secret keys are configured travels with the export, and so does the value of a key a tool or hook explicitly declares non-sensitive (a base URL, a choice from a list), which is configuration rather than a credential. Channel and skill configuration travels with every credential-like key removed - recursively, so a credential nested inside a structured sub-config does not leak either.
This guide covers the full round trip and the manual steps you must run after import.
Prerequisites
- An authenticated admin session (cookie or Bearer token).
- The
slugof the source instance (e.g.my-assistant). - For “overwrite mode”, the destination instance must already exist.
Step 1 - Export the bundle
curl -s http://localhost:4000/api/instances/my-assistant/export \
-H "Cookie: authjs.session-token=$TOKEN" \
-o my-assistant.bundle.jsonThe export endpoint returns indented JSON with a Content-Disposition header so browsers download it as a file. curl -o works fine; piping through jq also works.
What the bundle contains (assembled in export.service.ts):
- Instance metadata -
slug,name,description,status,provider,model,icon,sttProvider,langsmithProject, plus the togglesmemoryEnabled,knowledgeEnabled,langsmithEnabled,authEnabled,a2aEnabled. - Behaviour configuration -
thinkingEnabledand itsthinkingLevel,temperature,stateInPromptEnabled,datetimeInjectionEnabled,cacheEnabled,cacheTtl,toolResultsInHistoryEnabled,debugEnabled,governanceHistoryTurns, the six Behaviour overrides (time zone, locale, memory dedup threshold, the three message timings), the Web/API context field mapping (webContextFieldMapping), and the embedder (embeddingProvider,embeddingDim). - Opt-out configuration - enabled flag, stop/resume keywords, closing and resume messages, prompt-hint flag. See Privacy.
- Prompts - every section, full text.
- Skills - assignments only (slug + enabled + autoLoad + pinnedVersion). The skill DEFINITIONS live in the organization’s catalogue; if the destination organization lacks a skill, the import warns. Note that an assignment that imports cleanly can still be inert on the destination if that agent’s workspace has not been given the skill - see What a workspace may draw on.
- Manual tools - the list of tool names that were explicitly added on top of the skill-derived defaults.
- Secrets -
[{ key, configured, value? }]. Credential values are stripped.valueis present only for a key a tool or hook declares non-sensitive. On import such a value is written only if the destination engine also declares that key non-sensitive; otherwise it is dropped and reported as asecret_requiredwarning like any credential. - Channels -
[{ channelType, enabled, config }], whereconfigholds the non-secret settings only (allowedUserIds,whatsappNumber, an HTTP channel’s field mapping, …). Credential-like keys are removed recursively, including from nested sub-configs. The list always carries awebrow, so the agent’s Web/API switch travels with it, off included. - Skill env -
[{ skillSlug, key, encrypted, value? }]. Encrypted values are stripped; only non-encrypted ones travel. - Hooks -
event,actionType,actionConfig,enabled,positionandtimeoutMsfor each lifecycle hook. - Room config - prompt, outbound channel/target, eval interval.
- Event sources - with their definitions (matching prompt, interpretation prompt, routing).
- Scheduled tasks - schedule, prompt, outbound channel/target, retention flags, retries, and each task’s run deadline (
maxRunMs). - Governance (managed services) - the agent’s policy assignments with their enforcement and overrides, its retention policy window, and its model card. An assignment travels by the policy’s stable id and is resolved against what the destination organization can see; a policy that does not exist there is reported with a
governance_policy_missingwarning, and that gate is not active on the imported agent. - MCP servers - their configuration, with credential-like values stripped the same way channel configs are.
What the bundle does NOT contain:
- Conversation history, conversation state, memories, audit logs, pipeline traces - operational data, not configuration.
- Any credential value, and therefore no channel credential.
- The knowledge base. It has its own endpoints (
GET /api/instances/:slug/knowledge/exportandPOST .../knowledge/import), so moving it is a deliberate, separate step rather than something that rides along with a config bundle. - The opt-out contact list (the configuration travels; the per-contact statuses do not), and the retention scheduler’s own state (its last and next run), which belongs to the source deployment’s clock.
The bundle format is versioned, and the two builds number their versions separately:
| Build | Exports | Imports |
|---|---|---|
| Open-source build | 1.3 | 1.0 to 1.3 |
| Managed-services build | 1.5 | 1.0 to 1.5 |
The current version of each build is the one that carries the web channel row: 1.3 on the open-source build, 1.5 with managed services. A bundle older than that has no such row, because every agent’s HTTP endpoints were online before 1.2.0, and it imports with Web on so the agent keeps answering as it did. Its authentication setting comes from the bundle as usual.
An older bundle imports with the fields added later taking their defaults, so moving an agent from the open-source build to the managed-services build needs nothing. The other direction does. On the managed-services build 1.3 adds the governance block (policy assignments, retention window, model card), 1.4 adds pluginDependencies (for each marketplace plugin the agent’s tools and hooks come from, the plugin, the exact release and the tools and hooks the agent uses from it), and 1.5 the web row. On import, a release that is unknown, revoked, blocked or incompatible with the engine refuses the bundle, and one that is not installed in the destination organization answers a plugin_install_missing warning. An open-source engine has nowhere to put the governance or plugin blocks, and it refuses a 1.4 or 1.5 bundle at validation. To move such an agent to the open-source build anyway, set the bundle’s top-level version to 1.2 before importing: the engine then ignores those fields, and the agent arrives without its gates, retention window, model card and plugin release pins. A bundle that already carries a web row keeps its Web state; one without it arrives with Web on.
The two
1.3formats are different. The open-source engine reads any1.3bundle as its own, so a managed-services1.3bundle (exported before 1.2.0) passes validation there, loses its governance block, and, having nowebrow, arrives with Web off. Set itsversionto1.2before importing it into the open-source build.
Step 2a - Import as a new instance
This is the safest mode. The bundle is parsed through instanceBundleSchema and the slug is taken from the bundle itself; if it collides with an existing instance, the engine auto-resolves to a unique slug by appending -imported (my-assistant-imported, then -imported-2, -imported-3, …).
curl -s -X POST http://localhost:4000/api/instances/import \
-H "Content-Type: application/json" \
-H "Cookie: authjs.session-token=$TOKEN" \
--data-binary @my-assistant.bundle.jsonIf you want to control the destination slug, edit the bundle’s instance.slug field before posting (the slug lives under instance, not at the top level - the bundle’s top-level keys are version, exportedAt, type, instance):
jq '.instance.slug = "my-assistant-staging"' my-assistant.bundle.json > staging.bundle.json
curl -s -X POST http://localhost:4000/api/instances/import \
-H "Content-Type: application/json" \
--data-binary @staging.bundle.jsonThe response includes the created instance and a warnings array. Each warning is an object with type and message; the types are secret_required, missing_skill, missing_tool, channel_credentials, channel_invalid, channel_config_invalid, skill_env_required, event_source_credentials, plugin_not_granted, plugin_install_missing, mcp_server_credentials, mcp_server_invalid, governance_policy_missing, section_not_permitted, tool_recompute_failed, provider_role_required (managed services, with an extra role field), and hook_invalid - the last of which means the hook was not created, rather than created and flagged:
{
"warnings": [
{ "type": "secret_required", "message": "Secret \"openai_api_key\" needs to be configured" },
{ "type": "missing_skill", "message": "Skill 'foo' not found in catalog - assignment skipped" },
{ "type": "missing_tool", "message": "Manual tool 'bar' not registered on this engine" },
{ "type": "channel_credentials", "message": "Channel \"telegram\" imported disabled - configure credentials to enable" }
]
}Read the warnings before testing: an agent imported with a disabled channel and unconfigured secrets looks broken for entirely expected reasons.
Managed services. A bundle carries no provider key and no role binding, because a provider connection belongs to the organization that prepared it. The import answers one
provider_role_requiredwarning for each role the agent uses with nothing bound, and nosecret_requiredwarning for provider keys. The warning names the role in arolefield, so a script can bind it without parsing the message:{ "type": "provider_role_required", "role": "chat", "message": "Bind the agent's \"chat\" role to a provider connection: PUT /api/instances/<slug>/provider-roles/chat" }. The agent answers nothing until itschatrole is bound.
Step 2b - Import as overwrite
Use this to update an existing instance in-place. The target slug is taken from the URL.
curl -s -X POST http://localhost:4000/api/instances/my-assistant/import \
-H "Content-Type: application/json" \
-H "Cookie: authjs.session-token=$TOKEN" \
--data-binary @my-assistant.bundle.jsonOverwrite replaces prompts, skill assignments, manual tools, hooks, room config, event sources, scheduled tasks, channels, MCP servers and governance assignments; a retention policy or model card in the bundle replaces the destination’s. Existing credentials and channel credentials are preserved - the bundle has nothing to overwrite them with anyway, since credential values never travel. A non-sensitive key value in the bundle does overwrite the destination’s.
One field is deliberately not applied on overwrite: the embedder. Changing it on a live agent wipes its memories and knowledge base, so an overwrite import leaves embeddingProvider and embeddingDim untouched. They are applied on import-as-new only. To change an existing agent’s embedder, do it explicitly in Model → Embeddings and accept the wipe.
Step 3 - Re-enter secrets
Because the bundle ships no credential values, you must add each one back on the destination engine (the secret_required warnings list them). With managed services, bind the agent’s roles to connections first (the provider_role_required warnings list them; see Binding an agent’s roles); the secret route refuses provider keys, so the example below applies to the open-source build:
curl -s -X PUT http://localhost:4000/api/instances/my-assistant-staging/secrets \
-H "Content-Type: application/json" \
-d '{
"secrets": [
{ "key": "openai_api_key", "value": "sk-..." },
{ "key": "langsmith_api_key", "value": "ls_..." }
]
}'See Manage secrets for the full reference.
Step 4 - Reconnect channels
Channel rows are created on import, carrying the non-secret configuration so you only have to fill in what was stripped. But a channel is re-enabled only if its non-secret config alone satisfies the channel’s validation schema - true today only for the credential-less agent channel. Telegram, Slack, WhatsApp, and a credentialed HTTP outbound therefore arrive disabled, each with a channel_credentials warning.
Add the missing credentials from the channel’s card in the admin panel, or via the channel endpoint:
curl -s -X PUT http://localhost:4000/api/instances/my-assistant-staging/channels/telegram \
-H "Content-Type: application/json" \
-d '{ "config": { "botToken": "12345:ABC..." }, "enabled": true }'See Connect a channel for the per-channel payload.
Bonus - Move the knowledge base
The knowledge base is not part of the instance bundle. Move it explicitly, after the instance exists and its embedder is set:
curl -s http://localhost:4000/api/instances/my-assistant/knowledge/export -o knowledge.bundle.json
curl -s -X POST http://localhost:4000/api/instances/my-assistant-staging/knowledge/import \
-H "Content-Type: application/json" \
--data-binary @knowledge.bundle.jsonImport re-embeds every document with the destination agent’s current embedder, so the two agents do not need the same one. The destination must have knowledgeEnabled on and its embedder credentials configured (with managed services, a usable connection bound to its embedding role), or the request is rejected before anything is written. The whole bundle is validated up front - the import is all-or-nothing - and a filename collision is resolved by appending a suffix (manuale.txt → manuale (1).txt), never by overwriting.
Import is rate-limited to 5 requests/minute; export is not throttled. They require agent.knowledge:read and agent.knowledge:write respectively.
Bonus - Export and import an organization’s skill catalogue
The catalogue belongs to an organization and is independent of any single agent. Use these endpoints before importing instances that depend on custom skills (note the catalog/ segment - easy to miss):
# Export
curl -s http://localhost:4000/api/organizations/$ORG/skills/catalog/export -o skills.bundle.json
# Import
curl -s -X POST http://localhost:4000/api/organizations/$ORG/skills/catalog/import \
-H "Content-Type: application/json" \
--data-binary @skills.bundle.jsonVerification
After Step 2, before Steps 3 and 4:
curl -s http://localhost:4000/api/instances/my-assistant-staging/secrets | jq '.secrets[] | select(.configured == false)'- All non-default secret keys appear with
configured: false- they need values. GET /api/instances/my-assistant-staging/channelslists the imported channels with their non-secret config, credentialed onesenabled: false- add credentials to enable them.- The prompts, skill assignments, hooks, room config, event sources, scheduled tasks and (managed services) governance assignments match the source - the configuration round-tripped cleanly.
- The knowledge base is empty unless you moved it with the knowledge export/import endpoints.
Once Steps 3 and 4 are done, a test message via the Playground (or POST /v1/chat/completions) should succeed end-to-end.