Governing OpenRouter at Scale with Terraform: Workspaces, Guardrails, and Spend Limits as Code
Created: June 2, 2026 Updated: June 2, 2026
Introduction
OpenRouter has moved from a useful integration point into shared AI infrastructure. When one team uses it, the dashboard can be enough. When multiple product teams depend on it, dashboard-only administration becomes operational risk.
The problems are familiar to any platform team:
- API keys are created by hand and become hard to trace.
- Spend controls live outside the review process.
- Guardrails are easy to forget or apply inconsistently.
- Workspace conventions drift across teams and environments.
- Existing state is difficult to inspect from the same workflow that provisions new access.
The cloudopsworks/openrouter Terraform/OpenTofu provider exists to close that gap. It treats OpenRouter as a governed management plane: workspaces, guardrails, spend-capped API keys, and inventory data sources can all move through infrastructure-as-code review instead of one-off dashboard changes.
This article is the practical follow-up to the provider's earlier release story. If you want the engineering history first, read Terraform Provider OpenRouter: From First Commit to Verified v0.2.10.
Why key-only IaC is not enough
Managing an API key as code is useful, but it is not the same as governing the platform.
A key-only workflow answers one question: "Can this service authenticate?"
A governed workflow answers better questions:
- Which team or workspace owns the access?
- What spend limit applies?
- Does the key expire?
- Which providers or models are allowed through the guardrail?
- Can we inspect existing workspaces, keys, guardrails, organization members, and provider metadata from code?
- Can the same pattern be reproduced across development, staging, and production?
That is the reason the provider focuses on OpenRouter's management surface instead of stopping at API-key CRUD.
The target workflow
The operating pattern is straightforward:
- Create or resolve a workspace for the team.
- Attach a guardrail to that workspace.
- Issue a scoped API key with spend and reset controls.
- Use data sources to inspect current OpenRouter state.
- Review all changes through the normal pull-request and release path.
The result is not just a key. It is a small governance contract that can be reviewed, versioned, reproduced, and audited.
Install the provider
Start with the provider declaration and a management key. The provider accepts the key directly or through OPENROUTER_API_KEY.
terraform {
required_providers {
openrouter = {
source = "cloudopsworks/openrouter"
version = "~> 0.2"
}
}
}
variable "openrouter_management_key" {
type = string
sensitive = true
}
provider "openrouter" {
api_key = var.openrouter_management_key
}
For CI/CD, prefer injecting OPENROUTER_API_KEY from your secret store instead of committing credentials into variable files.
Create a governed workspace
A workspace is the first boundary. It gives a team or workload an explicit home instead of mixing every key into one default space.
resource "openrouter_workspace" "platform_ai" {
name = "Platform AI"
slug = "platform-ai"
description = "Shared OpenRouter workspace for platform services"
default_text_model = "openai/gpt-4o"
default_provider_sort = "price"
io_logging_sampling_rate = 0
is_data_discount_logging_enabled = false
is_observability_broadcast_enabled = true
is_observability_io_logging_enabled = true
}
The exact defaults should match your organization's privacy, observability, and cost posture. The important point is that those choices now sit in a reviewed Terraform file.
Add a guardrail
Guardrails are where the workflow moves from provisioning into governance. In this example, the production guardrail is tied to the workspace, sets a monthly USD limit, restricts providers, and enforces zero-data-retention behavior.
resource "openrouter_guardrail" "platform_production" {
name = "platform-production"
workspace_id = openrouter_workspace.platform_ai.id
description = "Production policy for platform AI services"
limit_usd = 500
reset_interval = "monthly"
allowed_providers = ["openai", "anthropic"]
enforce_zdr = true
}
A team can start with broad defaults and tighten the guardrail as its model-routing policy matures. The benefit of keeping it in Terraform is that every change leaves a trail.
Issue a scoped, spend-capped key
With the workspace in place, issue an API key for the workload. This example gives a backend service a monthly spend cap and includes BYOK usage in the limit calculation.
resource "openrouter_api_key" "backend_prod" {
name = "platform-backend-prod"
workspace_id = openrouter_workspace.platform_ai.id
limit = 200
limit_reset = "monthly"
include_byok_in_limit = true
expires_at = "2026-12-31T23:59:59Z"
}
One operational caveat matters: the raw OpenRouter key secret is only returned when the key is created. The provider marks it sensitive, but Terraform state can still contain sensitive values. Use a remote state backend with encryption and restricted access, and move the generated key into your runtime secret manager as part of the platform workflow.
Inspect existing state with data sources
Governance also depends on visibility. The provider includes data sources that let you inspect workspaces, keys, guardrails, organization membership, and provider metadata.
data "openrouter_workspaces" "all" {}
data "openrouter_api_keys" "platform_ai" {
workspace_id = openrouter_workspace.platform_ai.id
include_disabled = false
}
data "openrouter_guardrails" "platform_ai" {
workspace_id = openrouter_workspace.platform_ai.id
}
data "openrouter_organization" "current" {}
data "openrouter_providers" "all" {}
These data sources are useful for dashboards, validation checks, policy reports, and drift investigations. They also make it easier to migrate from dashboard-managed state into a code-managed model.
Scale the pattern across teams
For a single team, direct resources are clear enough. For many teams, use a map-driven pattern so every workspace follows the same contract.
variable "teams" {
type = map(object({
display_name = string
slug = string
monthly_limit = number
allowed_models = optional(list(string))
allowed_providers = optional(list(string), ["openai", "anthropic"])
}))
}
resource "openrouter_workspace" "team" {
for_each = var.teams
name = each.value.display_name
slug = each.value.slug
description = "OpenRouter workspace for ${each.value.display_name}"
}
resource "openrouter_guardrail" "team" {
for_each = var.teams
name = "${each.value.slug}-production"
workspace_id = openrouter_workspace.team[each.key].id
description = "Production policy for ${each.value.display_name}"
limit_usd = each.value.monthly_limit
reset_interval = "monthly"
allowed_models = try(each.value.allowed_models, null)
allowed_providers = each.value.allowed_providers
enforce_zdr = true
}
resource "openrouter_api_key" "team_backend" {
for_each = var.teams
name = "${each.value.slug}-backend-prod"
workspace_id = openrouter_workspace.team[each.key].id
limit = each.value.monthly_limit
limit_reset = "monthly"
include_byok_in_limit = true
}
This gives platform teams one place to encode the rule and each product team one small configuration entry to request access.
Store generated keys with the companion module
For teams that already use AWS Secrets Manager, the companion terraform-module-openrouter-mgmt-api-keys module wraps API-key creation and persists the generated secret immediately.
The module is designed for existing workspaces. A common pattern is:
- Use the provider to create or manage workspaces and guardrails.
- Pass each workspace slug or ID into the module.
- Let the module create workload keys and store them in AWS Secrets Manager using your naming convention.
module "openrouter_api_keys" {
source = "git::https://github.com/cloudopsworks/terraform-module-openrouter-mgmt-api-keys.git?ref=v0.1.1"
org = {
organization_name = "acme"
organization_unit = "platform"
environment_type = "production"
environment_name = "prod"
}
settings = {
workspaces = {
for team_key, team in var.teams : team_key => {
workspace_slug = team.slug
api_keys = {
backend = {
name_prefix = "backend"
limit = team.monthly_limit
limit_reset = "monthly"
include_byok_in_limit = true
secret = {
name_prefix = "${team.slug}-openrouter"
description = "OpenRouter key for ${team.display_name} backend"
}
}
}
}
}
}
}
Pin the module to a release tag instead of master. That keeps downstream environments reproducible and makes upgrades a deliberate change.
Terraform and OpenTofu
The same provider source address works for Terraform and OpenTofu:
source = "cloudopsworks/openrouter"
In most repositories, the workflow change is operational rather than structural: run terraform init/plan/apply or tofu init/plan/apply according to the toolchain your team has standardized. Keep provider versions pinned, keep lock files under review, and validate changes in the same branch strategy used for the rest of your infrastructure.
Production checklist
Before putting the pattern into production, make the implicit operating rules explicit:
- Use a management key with the minimum access your process supports.
- Keep Terraform or OpenTofu state encrypted and access-controlled.
- Decide whether generated API keys are copied into a dedicated secret manager.
- Define per-team monthly limits and reset intervals before rollout.
- Use guardrails for production workspaces, not just API keys.
- Keep imports and drift checks in the migration plan for existing dashboard-managed keys.
- Review provider and module upgrades through pull requests.
Where to start
Use the provider when you need direct control over OpenRouter workspaces, guardrails, and API keys:
- Terraform Registry:
cloudopsworks/openrouter - GitHub repository:
cloudopsworks/terraform-provider-openrouter - Open issues and feature requests
Use the companion module when your repeatable pattern is "create keys for known workspaces and persist them into AWS Secrets Manager":
And if you want to see how the provider reached its first verified public release, read the earlier article: Terraform Provider OpenRouter: From First Commit to Verified v0.2.10.