back to tutorials

How to Deploy Multi-Tenant AI Agents with Terraform

Learn how to deploy an isolated AI agent into each customer's own ChatBotKit sub-account using the Terraform Provider. This tutorial covers sub-accounts (partner users), the run_as attribute, provider aliases, and shared vs. bespoke per-customer agents.

If you are building a SaaS or white-label product, every customer usually needs their own agent in their own isolated environment - separate bots, conversations, datasets, and settings. ChatBotKit models this with sub-accounts (partner users), and the Terraform Provider lets you deploy into each one from a single, version-controlled configuration.

This tutorial shows you how to do that cleanly: one master token, the provider's run_as attribute, and provider aliases - the standard Terraform multi-account pattern, the same way the AWS provider uses assume_role.

What You'll Learn

By the end of this tutorial, you will be able to:

  • Understand ChatBotKit sub-accounts (partner users) and how they isolate customers
  • Use one partner/master token plus run_as to operate on any sub-account - no per-customer tokens
  • Target each customer with a provider alias
  • Deploy the same agent to every customer from one reusable module
  • Or deploy a bespoke agent per customer
  • Add a new customer without touching shared code

Prerequisites

Before starting, make sure you have:

  • A ChatBotKit account with a partner/master API key
  • Terraform version 1.0 or higher installed
  • One sub-account per customer. Create these in the dashboard, or via the partner API (partner/user/create). You only need each sub-account's ID (not a separate token)
  • Familiarity with Terraform providers, resources, and modules

Estimated time: 25-35 minutes

How Isolation Works

The provider authenticates with a single api_key, and the account is whichever account that key belongs to - there is no per-resource account setting. To deploy into a specific sub-account, you set the provider's run_as attribute to that sub-account's ID. The provider then sends the X-RunAs-UserId header, and everything it creates lands in that customer's isolated environment.

The key benefit: you hold one partner/master token and select the account by ID. Account IDs are not secret, so there are no per-customer tokens to mint, store, or rotate.

The run_as attribute is available in the ChatBotKit Terraform Provider 1.5.0 and later.

Step 1: Set Up the Project

Create a project and set your partner/master token (used for every sub-account):

mkdir multi-tenant-agents
cd multi-tenant-agents
export CHATBOTKIT_API_KEY="your-partner-master-token"

Step 2: Configure a Provider Alias per Customer

In main.tf, declare one provider alias per customer. Each alias shares the same master token (from the environment) and differs only by run_as:

terraform {
  required_providers {
    chatbotkit = {
      source  = "chatbotkit/chatbotkit"
      version = "~> 1.5"
    }
  }
}

variable "acme_account_id" {
  description = "Acme's sub-account (partner user) ID"
  type        = string
}

variable "globex_account_id" {
  description = "Globex's sub-account (partner user) ID"
  type        = string
}

# api_key comes from CHATBOTKIT_API_KEY; run_as selects the sub-account.
provider "chatbotkit" {
  alias  = "acme"
  run_as = var.acme_account_id
}

provider "chatbotkit" {
  alias  = "globex"
  run_as = var.globex_account_id
}

Supply the account IDs in terraform.tfvars:

acme_account_id   = "user_...acme..."
globex_account_id = "user_...globex..."

Step 3: Package the Agent as a Module

So every customer gets the same agent, define it once as a module. Create modules/agent/main.tf:

terraform {
  required_providers {
    chatbotkit = {
      source = "chatbotkit/chatbotkit"
    }
  }
}

variable "customer_name" {
  description = "Display name of the customer"
  type        = string
}

resource "chatbotkit_skillset" "abilities" {
  name        = "${var.customer_name} Abilities"
  description = "Toolset for ${var.customer_name}'s agent"
}

resource "chatbotkit_skillset_ability" "web_search" {
  skillset_id = chatbotkit_skillset.abilities.id
  name        = "search_web"
  description = "Search the web for current information"
  instruction = <<-EOT
    ```search
    query: $[query! ys|the search query to find information on the web]
    ```
  EOT
}

resource "chatbotkit_bot" "agent" {
  name        = "${var.customer_name} Assistant"
  description = "Dedicated assistant for ${var.customer_name}"
  model       = "claude-4.5-sonnet"
  backstory   = "You are the dedicated AI assistant for ${var.customer_name}. Be helpful, concise, and professional."
  skillset_id = chatbotkit_skillset.abilities.id
}

output "bot_id" {
  value = chatbotkit_bot.agent.id
}

Step 4: Deploy the Same Agent to Every Customer

Back in main.tf, call the module once per customer, wiring each call to that customer's provider alias:

module "acme" {
  source    = "./modules/agent"
  providers = { chatbotkit = chatbotkit.acme }

  customer_name = "Acme Corporation"
}

module "globex" {
  source    = "./modules/agent"
  providers = { chatbotkit = chatbotkit.globex }

  customer_name = "Globex Inc"
}

output "bots" {
  value = {
    acme   = module.acme.bot_id
    globex = module.globex.bot_id
  }
}

There is no for_each here - one provider alias and one module call per customer. Deploy everything in a single run:

terraform init
terraform plan
terraform apply

Each customer now has their own agent in their own sub-account, all from one configuration and one state.

Step 5: Add a New Customer

Adding a customer is three small additions - a variable, a provider alias, and a module call:

variable "initech_account_id" {
  type = string
}

provider "chatbotkit" {
  alias  = "initech"
  run_as = var.initech_account_id
}

module "initech" {
  source        = "./modules/agent"
  providers     = { chatbotkit = chatbotkit.initech }
  customer_name = "Initech"
}

Run terraform apply again and only the new customer's resources are created.

Variation: A Different Agent per Customer

When customers genuinely differ - different skills, models, or channels - give each their own module instead of reusing one. Keep the same alias + run_as wiring; just point each module call at a customer-specific module (for example ./acme and ./globex), each defining its own bespoke agent. A single root still composes them all into one shared state and one apply.

This is the trade-off:

  • Shared module (Step 3-4): every customer gets the same agent; improve it once and redeploy to all.
  • Bespoke module per customer: full flexibility, but each agent is maintained on its own.

Important Notes

  • Account IDs vs. tokens. run_as takes a sub-account ID (not secret). Only the single master token in CHATBOTKIT_API_KEY is sensitive - keep it and your Terraform state out of version control and use an encrypted remote backend.
  • Creating sub-accounts. The provider operates within accounts; it does not create sub-accounts. Create them once in the dashboard or via the partner API (partner/user/create), optionally with per-customer limits to cap usage.
  • Cleanup. terraform destroy removes the agents. It does not delete the sub-accounts - remove those via the partner API (partner/user/{id}/delete).

Next Steps

By selecting accounts with run_as and composing modules with provider aliases, you can stand up - and tear down - an isolated agent for every customer from one reviewable, version-controlled configuration.